We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Too much data for declared Content-Length when passing string with non-ascii characters via files parameter
### Checklist

<!-- Please make sure you check all these items before submitting your bug report. -->

- [x ] The bug is reproducible against the latest release and/or `master`.
- [ x] There are no similar issues or pull requests to fix it yet.

### Describe the bug

`h11._util.LocalProtocolError: Too much data for declared Content-Length` is raised when passing string with non-ascii characters via files parameter.

### To reproduce

```
import httpx

response = httpx.post(
    "https://httpbin.org/post",
    files={
        'upload-file': ('example.txt', '\u00E9', 'text/plain; charset=utf-8')
    }
)
response.raise_for_status()
print(response.read())
```
-->

### Expected behavior

An exception not be raised and the content length properly computed

### Actual behavior

An exception is raised.

### Debugging material

```
Traceback (most recent call last):
  File "/***redacted***/httpx_bug.py", line 5, in <module>
    response = httpx.post(
  File "/***redacted***/.venv/lib/python3.9/site-packages/httpx/_api.py", line 296, in post
    return request(
  File "/***redacted***/.venv/lib/python3.9/site-packages/httpx/_api.py", line 93, in request
    return client.request(
  File "/***redacted***/.venv/lib/python3.9/site-packages/httpx/_client.py", line 733, in request
    return self.send(
  File "/***redacted***/.venv/lib/python3.9/site-packages/httpx/_client.py", line 767, in send
    response = self._send_handling_auth(
  File "/***redacted***/.venv/lib/python3.9/site-packages/httpx/_client.py", line 805, in _send_handling_auth
    response = self._send_handling_redirects(
  File "/***redacted***/.venv/lib/python3.9/site-packages/httpx/_client.py", line 837, in _send_handling_redirects
    response = self._send_single_request(request, timeout)
  File "/***redacted***/.venv/lib/python3.9/site-packages/httpx/_client.py", line 861, in _send_single_request
    (status_code, headers, stream, ext) = transport.request(
  File "/***redacted***/.venv/lib/python3.9/site-packages/httpcore/_sync/connection_pool.py", line 218, in request
    response = connection.request(
  File "/***redacted***/.venv/lib/python3.9/site-packages/httpcore/_sync/connection.py", line 106, in request
    return self.connection.request(method, url, headers, stream, ext)
  File "/***redacted***/.venv/lib/python3.9/site-packages/httpcore/_sync/http11.py", line 66, in request
    self._send_request_body(stream, timeout)
  File "/***redacted***/.venv/lib/python3.9/site-packages/httpcore/_sync/http11.py", line 112, in _send_request_body
    self._send_event(event, timeout)
  File "/***redacted***/.venv/lib/python3.9/site-packages/httpcore/_sync/http11.py", line 123, in _send_event
    bytes_to_send = self.h11_state.send(event)
  File "/***redacted***/.venv/lib/python3.9/site-packages/h11/_connection.py", line 468, in send
    data_list = self.send_with_data_passthrough(event)
  File "/***redacted***/.venv/lib/python3.9/site-packages/h11/_connection.py", line 501, in send_with_data_passthrough
    writer(event, data_list.append)
  File "/***redacted***/.venv/lib/python3.9/site-packages/h11/_writers.py", line 58, in __call__
    self.send_data(event.data, write)
  File "/***redacted***/.venv/lib/python3.9/site-packages/h11/_writers.py", line 78, in send_data
    raise LocalProtocolError("Too much data for declared Content-Length")
h11._util.LocalProtocolError: Too much data for declared Content-Length
```
-->

### Environment

- OS: macOS
- Python version: `Python 3.9.1`
- HTTPX version: `0.16.1`
- Async environment: n/a
- HTTP proxy: no
- Custom certificates: no

### Additional context
n/a


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `httpx/_multipart.py`
Content:
```
1 import binascii
2 import os
3 import typing
4 from pathlib import Path
5 
6 from ._types import FileContent, FileTypes, RequestFiles
7 from ._utils import (
8     format_form_param,
9     guess_content_type,
10     peek_filelike_length,
11     to_bytes,
12 )
13 
14 
15 class DataField:
16     """
17     A single form field item, within a multipart form field.
18     """
19 
20     def __init__(self, name: str, value: typing.Union[str, bytes]) -> None:
21         if not isinstance(name, str):
22             raise TypeError(
23                 f"Invalid type for name. Expected str, got {type(name)}: {name!r}"
24             )
25         if not isinstance(value, (str, bytes)):
26             raise TypeError(
27                 f"Invalid type for value. Expected str or bytes, got {type(value)}: {value!r}"
28             )
29         self.name = name
30         self.value = value
31 
32     def render_headers(self) -> bytes:
33         if not hasattr(self, "_headers"):
34             name = format_form_param("name", self.name)
35             self._headers = b"".join(
36                 [b"Content-Disposition: form-data; ", name, b"\r\n\r\n"]
37             )
38 
39         return self._headers
40 
41     def render_data(self) -> bytes:
42         if not hasattr(self, "_data"):
43             self._data = (
44                 self.value
45                 if isinstance(self.value, bytes)
46                 else self.value.encode("utf-8")
47             )
48 
49         return self._data
50 
51     def get_length(self) -> int:
52         headers = self.render_headers()
53         data = self.render_data()
54         return len(headers) + len(data)
55 
56     def render(self) -> typing.Iterator[bytes]:
57         yield self.render_headers()
58         yield self.render_data()
59 
60 
61 class FileField:
62     """
63     A single file field item, within a multipart form field.
64     """
65 
66     def __init__(self, name: str, value: FileTypes) -> None:
67         self.name = name
68 
69         fileobj: FileContent
70 
71         if isinstance(value, tuple):
72             try:
73                 filename, fileobj, content_type = value  # type: ignore
74             except ValueError:
75                 filename, fileobj = value  # type: ignore
76                 content_type = guess_content_type(filename)
77         else:
78             filename = Path(str(getattr(value, "name", "upload"))).name
79             fileobj = value
80             content_type = guess_content_type(filename)
81 
82         self.filename = filename
83         self.file = fileobj
84         self.content_type = content_type
85         self._consumed = False
86 
87     def get_length(self) -> int:
88         headers = self.render_headers()
89 
90         if isinstance(self.file, (str, bytes)):
91             return len(headers) + len(self.file)
92 
93         # Let's do our best not to read `file` into memory.
94         try:
95             file_length = peek_filelike_length(self.file)
96         except OSError:
97             # As a last resort, read file and cache contents for later.
98             assert not hasattr(self, "_data")
99             self._data = to_bytes(self.file.read())
100             file_length = len(self._data)
101 
102         return len(headers) + file_length
103 
104     def render_headers(self) -> bytes:
105         if not hasattr(self, "_headers"):
106             parts = [
107                 b"Content-Disposition: form-data; ",
108                 format_form_param("name", self.name),
109             ]
110             if self.filename:
111                 filename = format_form_param("filename", self.filename)
112                 parts.extend([b"; ", filename])
113             if self.content_type is not None:
114                 content_type = self.content_type.encode()
115                 parts.extend([b"\r\nContent-Type: ", content_type])
116             parts.append(b"\r\n\r\n")
117             self._headers = b"".join(parts)
118 
119         return self._headers
120 
121     def render_data(self) -> typing.Iterator[bytes]:
122         if isinstance(self.file, (str, bytes)):
123             yield to_bytes(self.file)
124             return
125 
126         if hasattr(self, "_data"):
127             # Already rendered.
128             yield self._data
129             return
130 
131         if self._consumed:  # pragma: nocover
132             self.file.seek(0)
133         self._consumed = True
134 
135         for chunk in self.file:
136             yield to_bytes(chunk)
137 
138     def render(self) -> typing.Iterator[bytes]:
139         yield self.render_headers()
140         yield from self.render_data()
141 
142 
143 class MultipartStream:
144     """
145     Request content as streaming multipart encoded form data.
146     """
147 
148     def __init__(self, data: dict, files: RequestFiles, boundary: bytes = None) -> None:
149         if boundary is None:
150             boundary = binascii.hexlify(os.urandom(16))
151 
152         self.boundary = boundary
153         self.content_type = "multipart/form-data; boundary=%s" % boundary.decode(
154             "ascii"
155         )
156         self.fields = list(self._iter_fields(data, files))
157 
158     def _iter_fields(
159         self, data: dict, files: RequestFiles
160     ) -> typing.Iterator[typing.Union[FileField, DataField]]:
161         for name, value in data.items():
162             if isinstance(value, list):
163                 for item in value:
164                     yield DataField(name=name, value=item)
165             else:
166                 yield DataField(name=name, value=value)
167 
168         file_items = files.items() if isinstance(files, typing.Mapping) else files
169         for name, value in file_items:
170             yield FileField(name=name, value=value)
171 
172     def iter_chunks(self) -> typing.Iterator[bytes]:
173         for field in self.fields:
174             yield b"--%s\r\n" % self.boundary
175             yield from field.render()
176             yield b"\r\n"
177         yield b"--%s--\r\n" % self.boundary
178 
179     def iter_chunks_lengths(self) -> typing.Iterator[int]:
180         boundary_length = len(self.boundary)
181         # Follow closely what `.iter_chunks()` does.
182         for field in self.fields:
183             yield 2 + boundary_length + 2
184             yield field.get_length()
185             yield 2
186         yield 2 + boundary_length + 4
187 
188     def get_content_length(self) -> int:
189         return sum(self.iter_chunks_lengths())
190 
191     # Content stream interface.
192 
193     def get_headers(self) -> typing.Dict[str, str]:
194         content_length = str(self.get_content_length())
195         content_type = self.content_type
196         return {"Content-Length": content_length, "Content-Type": content_type}
197 
198     def __iter__(self) -> typing.Iterator[bytes]:
199         for chunk in self.iter_chunks():
200             yield chunk
201 
202     async def __aiter__(self) -> typing.AsyncIterator[bytes]:
203         for chunk in self.iter_chunks():
204             yield chunk
205
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/httpx/_multipart.py b/httpx/_multipart.py
--- a/httpx/_multipart.py
+++ b/httpx/_multipart.py
@@ -40,11 +40,7 @@
 
     def render_data(self) -> bytes:
         if not hasattr(self, "_data"):
-            self._data = (
-                self.value
-                if isinstance(self.value, bytes)
-                else self.value.encode("utf-8")
-            )
+            self._data = to_bytes(self.value)
 
         return self._data
 
@@ -88,7 +84,7 @@
         headers = self.render_headers()
 
         if isinstance(self.file, (str, bytes)):
-            return len(headers) + len(self.file)
+            return len(headers) + len(to_bytes(self.file))
 
         # Let's do our best not to read `file` into memory.
         try:

{"golden_diff": "diff --git a/httpx/_multipart.py b/httpx/_multipart.py\n--- a/httpx/_multipart.py\n+++ b/httpx/_multipart.py\n@@ -40,11 +40,7 @@\n \n     def render_data(self) -> bytes:\n         if not hasattr(self, \"_data\"):\n-            self._data = (\n-                self.value\n-                if isinstance(self.value, bytes)\n-                else self.value.encode(\"utf-8\")\n-            )\n+            self._data = to_bytes(self.value)\n \n         return self._data\n \n@@ -88,7 +84,7 @@\n         headers = self.render_headers()\n \n         if isinstance(self.file, (str, bytes)):\n-            return len(headers) + len(self.file)\n+            return len(headers) + len(to_bytes(self.file))\n \n         # Let's do our best not to read `file` into memory.\n         try:\n", "issue": "Too much data for declared Content-Length when passing string with non-ascii characters via files parameter\n### Checklist\r\n\r\n<!-- Please make sure you check all these items before submitting your bug report. -->\r\n\r\n- [x ] The bug is reproducible against the latest release and/or `master`.\r\n- [ x] There are no similar issues or pull requests to fix it yet.\r\n\r\n### Describe the bug\r\n\r\n`h11._util.LocalProtocolError: Too much data for declared Content-Length` is raised when passing string with non-ascii characters via files parameter.\r\n\r\n### To reproduce\r\n\r\n```\r\nimport httpx\r\n\r\nresponse = httpx.post(\r\n    \"https://httpbin.org/post\",\r\n    files={\r\n        'upload-file': ('example.txt', '\\u00E9', 'text/plain; charset=utf-8')\r\n    }\r\n)\r\nresponse.raise_for_status()\r\nprint(response.read())\r\n```\r\n-->\r\n\r\n### Expected behavior\r\n\r\nAn exception not be raised and the content length properly computed\r\n\r\n### Actual behavior\r\n\r\nAn exception is raised.\r\n\r\n### Debugging material\r\n\r\n```\r\nTraceback (most recent call last):\r\n  File \"/***redacted***/httpx_bug.py\", line 5, in <module>\r\n    response = httpx.post(\r\n  File \"/***redacted***/.venv/lib/python3.9/site-packages/httpx/_api.py\", line 296, in post\r\n    return request(\r\n  File \"/***redacted***/.venv/lib/python3.9/site-packages/httpx/_api.py\", line 93, in request\r\n    return client.request(\r\n  File \"/***redacted***/.venv/lib/python3.9/site-packages/httpx/_client.py\", line 733, in request\r\n    return self.send(\r\n  File \"/***redacted***/.venv/lib/python3.9/site-packages/httpx/_client.py\", line 767, in send\r\n    response = self._send_handling_auth(\r\n  File \"/***redacted***/.venv/lib/python3.9/site-packages/httpx/_client.py\", line 805, in _send_handling_auth\r\n    response = self._send_handling_redirects(\r\n  File \"/***redacted***/.venv/lib/python3.9/site-packages/httpx/_client.py\", line 837, in _send_handling_redirects\r\n    response = self._send_single_request(request, timeout)\r\n  File \"/***redacted***/.venv/lib/python3.9/site-packages/httpx/_client.py\", line 861, in _send_single_request\r\n    (status_code, headers, stream, ext) = transport.request(\r\n  File \"/***redacted***/.venv/lib/python3.9/site-packages/httpcore/_sync/connection_pool.py\", line 218, in request\r\n    response = connection.request(\r\n  File \"/***redacted***/.venv/lib/python3.9/site-packages/httpcore/_sync/connection.py\", line 106, in request\r\n    return self.connection.request(method, url, headers, stream, ext)\r\n  File \"/***redacted***/.venv/lib/python3.9/site-packages/httpcore/_sync/http11.py\", line 66, in request\r\n    self._send_request_body(stream, timeout)\r\n  File \"/***redacted***/.venv/lib/python3.9/site-packages/httpcore/_sync/http11.py\", line 112, in _send_request_body\r\n    self._send_event(event, timeout)\r\n  File \"/***redacted***/.venv/lib/python3.9/site-packages/httpcore/_sync/http11.py\", line 123, in _send_event\r\n    bytes_to_send = self.h11_state.send(event)\r\n  File \"/***redacted***/.venv/lib/python3.9/site-packages/h11/_connection.py\", line 468, in send\r\n    data_list = self.send_with_data_passthrough(event)\r\n  File \"/***redacted***/.venv/lib/python3.9/site-packages/h11/_connection.py\", line 501, in send_with_data_passthrough\r\n    writer(event, data_list.append)\r\n  File \"/***redacted***/.venv/lib/python3.9/site-packages/h11/_writers.py\", line 58, in __call__\r\n    self.send_data(event.data, write)\r\n  File \"/***redacted***/.venv/lib/python3.9/site-packages/h11/_writers.py\", line 78, in send_data\r\n    raise LocalProtocolError(\"Too much data for declared Content-Length\")\r\nh11._util.LocalProtocolError: Too much data for declared Content-Length\r\n```\r\n-->\r\n\r\n### Environment\r\n\r\n- OS: macOS\r\n- Python version: `Python 3.9.1`\r\n- HTTPX version: `0.16.1`\r\n- Async environment: n/a\r\n- HTTP proxy: no\r\n- Custom certificates: no\r\n\r\n### Additional context\r\nn/a\r\n\n", "before_files": [{"content": "import binascii\nimport os\nimport typing\nfrom pathlib import Path\n\nfrom ._types import FileContent, FileTypes, RequestFiles\nfrom ._utils import (\n    format_form_param,\n    guess_content_type,\n    peek_filelike_length,\n    to_bytes,\n)\n\n\nclass DataField:\n    \"\"\"\n    A single form field item, within a multipart form field.\n    \"\"\"\n\n    def __init__(self, name: str, value: typing.Union[str, bytes]) -> None:\n        if not isinstance(name, str):\n            raise TypeError(\n                f\"Invalid type for name. Expected str, got {type(name)}: {name!r}\"\n            )\n        if not isinstance(value, (str, bytes)):\n            raise TypeError(\n                f\"Invalid type for value. Expected str or bytes, got {type(value)}: {value!r}\"\n            )\n        self.name = name\n        self.value = value\n\n    def render_headers(self) -> bytes:\n        if not hasattr(self, \"_headers\"):\n            name = format_form_param(\"name\", self.name)\n            self._headers = b\"\".join(\n                [b\"Content-Disposition: form-data; \", name, b\"\\r\\n\\r\\n\"]\n            )\n\n        return self._headers\n\n    def render_data(self) -> bytes:\n        if not hasattr(self, \"_data\"):\n            self._data = (\n                self.value\n                if isinstance(self.value, bytes)\n                else self.value.encode(\"utf-8\")\n            )\n\n        return self._data\n\n    def get_length(self) -> int:\n        headers = self.render_headers()\n        data = self.render_data()\n        return len(headers) + len(data)\n\n    def render(self) -> typing.Iterator[bytes]:\n        yield self.render_headers()\n        yield self.render_data()\n\n\nclass FileField:\n    \"\"\"\n    A single file field item, within a multipart form field.\n    \"\"\"\n\n    def __init__(self, name: str, value: FileTypes) -> None:\n        self.name = name\n\n        fileobj: FileContent\n\n        if isinstance(value, tuple):\n            try:\n                filename, fileobj, content_type = value  # type: ignore\n            except ValueError:\n                filename, fileobj = value  # type: ignore\n                content_type = guess_content_type(filename)\n        else:\n            filename = Path(str(getattr(value, \"name\", \"upload\"))).name\n            fileobj = value\n            content_type = guess_content_type(filename)\n\n        self.filename = filename\n        self.file = fileobj\n        self.content_type = content_type\n        self._consumed = False\n\n    def get_length(self) -> int:\n        headers = self.render_headers()\n\n        if isinstance(self.file, (str, bytes)):\n            return len(headers) + len(self.file)\n\n        # Let's do our best not to read `file` into memory.\n        try:\n            file_length = peek_filelike_length(self.file)\n        except OSError:\n            # As a last resort, read file and cache contents for later.\n            assert not hasattr(self, \"_data\")\n            self._data = to_bytes(self.file.read())\n            file_length = len(self._data)\n\n        return len(headers) + file_length\n\n    def render_headers(self) -> bytes:\n        if not hasattr(self, \"_headers\"):\n            parts = [\n                b\"Content-Disposition: form-data; \",\n                format_form_param(\"name\", self.name),\n            ]\n            if self.filename:\n                filename = format_form_param(\"filename\", self.filename)\n                parts.extend([b\"; \", filename])\n            if self.content_type is not None:\n                content_type = self.content_type.encode()\n                parts.extend([b\"\\r\\nContent-Type: \", content_type])\n            parts.append(b\"\\r\\n\\r\\n\")\n            self._headers = b\"\".join(parts)\n\n        return self._headers\n\n    def render_data(self) -> typing.Iterator[bytes]:\n        if isinstance(self.file, (str, bytes)):\n            yield to_bytes(self.file)\n            return\n\n        if hasattr(self, \"_data\"):\n            # Already rendered.\n            yield self._data\n            return\n\n        if self._consumed:  # pragma: nocover\n            self.file.seek(0)\n        self._consumed = True\n\n        for chunk in self.file:\n            yield to_bytes(chunk)\n\n    def render(self) -> typing.Iterator[bytes]:\n        yield self.render_headers()\n        yield from self.render_data()\n\n\nclass MultipartStream:\n    \"\"\"\n    Request content as streaming multipart encoded form data.\n    \"\"\"\n\n    def __init__(self, data: dict, files: RequestFiles, boundary: bytes = None) -> None:\n        if boundary is None:\n            boundary = binascii.hexlify(os.urandom(16))\n\n        self.boundary = boundary\n        self.content_type = \"multipart/form-data; boundary=%s\" % boundary.decode(\n            \"ascii\"\n        )\n        self.fields = list(self._iter_fields(data, files))\n\n    def _iter_fields(\n        self, data: dict, files: RequestFiles\n    ) -> typing.Iterator[typing.Union[FileField, DataField]]:\n        for name, value in data.items():\n            if isinstance(value, list):\n                for item in value:\n                    yield DataField(name=name, value=item)\n            else:\n                yield DataField(name=name, value=value)\n\n        file_items = files.items() if isinstance(files, typing.Mapping) else files\n        for name, value in file_items:\n            yield FileField(name=name, value=value)\n\n    def iter_chunks(self) -> typing.Iterator[bytes]:\n        for field in self.fields:\n            yield b\"--%s\\r\\n\" % self.boundary\n            yield from field.render()\n            yield b\"\\r\\n\"\n        yield b\"--%s--\\r\\n\" % self.boundary\n\n    def iter_chunks_lengths(self) -> typing.Iterator[int]:\n        boundary_length = len(self.boundary)\n        # Follow closely what `.iter_chunks()` does.\n        for field in self.fields:\n            yield 2 + boundary_length + 2\n            yield field.get_length()\n            yield 2\n        yield 2 + boundary_length + 4\n\n    def get_content_length(self) -> int:\n        return sum(self.iter_chunks_lengths())\n\n    # Content stream interface.\n\n    def get_headers(self) -> typing.Dict[str, str]:\n        content_length = str(self.get_content_length())\n        content_type = self.content_type\n        return {\"Content-Length\": content_length, \"Content-Type\": content_type}\n\n    def __iter__(self) -> typing.Iterator[bytes]:\n        for chunk in self.iter_chunks():\n            yield chunk\n\n    async def __aiter__(self) -> typing.AsyncIterator[bytes]:\n        for chunk in self.iter_chunks():\n            yield chunk\n", "path": "httpx/_multipart.py"}], "after_files": [{"content": "import binascii\nimport os\nimport typing\nfrom pathlib import Path\n\nfrom ._types import FileContent, FileTypes, RequestFiles\nfrom ._utils import (\n    format_form_param,\n    guess_content_type,\n    peek_filelike_length,\n    to_bytes,\n)\n\n\nclass DataField:\n    \"\"\"\n    A single form field item, within a multipart form field.\n    \"\"\"\n\n    def __init__(self, name: str, value: typing.Union[str, bytes]) -> None:\n        if not isinstance(name, str):\n            raise TypeError(\n                f\"Invalid type for name. Expected str, got {type(name)}: {name!r}\"\n            )\n        if not isinstance(value, (str, bytes)):\n            raise TypeError(\n                f\"Invalid type for value. Expected str or bytes, got {type(value)}: {value!r}\"\n            )\n        self.name = name\n        self.value = value\n\n    def render_headers(self) -> bytes:\n        if not hasattr(self, \"_headers\"):\n            name = format_form_param(\"name\", self.name)\n            self._headers = b\"\".join(\n                [b\"Content-Disposition: form-data; \", name, b\"\\r\\n\\r\\n\"]\n            )\n\n        return self._headers\n\n    def render_data(self) -> bytes:\n        if not hasattr(self, \"_data\"):\n            self._data = to_bytes(self.value)\n\n        return self._data\n\n    def get_length(self) -> int:\n        headers = self.render_headers()\n        data = self.render_data()\n        return len(headers) + len(data)\n\n    def render(self) -> typing.Iterator[bytes]:\n        yield self.render_headers()\n        yield self.render_data()\n\n\nclass FileField:\n    \"\"\"\n    A single file field item, within a multipart form field.\n    \"\"\"\n\n    def __init__(self, name: str, value: FileTypes) -> None:\n        self.name = name\n\n        fileobj: FileContent\n\n        if isinstance(value, tuple):\n            try:\n                filename, fileobj, content_type = value  # type: ignore\n            except ValueError:\n                filename, fileobj = value  # type: ignore\n                content_type = guess_content_type(filename)\n        else:\n            filename = Path(str(getattr(value, \"name\", \"upload\"))).name\n            fileobj = value\n            content_type = guess_content_type(filename)\n\n        self.filename = filename\n        self.file = fileobj\n        self.content_type = content_type\n        self._consumed = False\n\n    def get_length(self) -> int:\n        headers = self.render_headers()\n\n        if isinstance(self.file, (str, bytes)):\n            return len(headers) + len(to_bytes(self.file))\n\n        # Let's do our best not to read `file` into memory.\n        try:\n            file_length = peek_filelike_length(self.file)\n        except OSError:\n            # As a last resort, read file and cache contents for later.\n            assert not hasattr(self, \"_data\")\n            self._data = to_bytes(self.file.read())\n            file_length = len(self._data)\n\n        return len(headers) + file_length\n\n    def render_headers(self) -> bytes:\n        if not hasattr(self, \"_headers\"):\n            parts = [\n                b\"Content-Disposition: form-data; \",\n                format_form_param(\"name\", self.name),\n            ]\n            if self.filename:\n                filename = format_form_param(\"filename\", self.filename)\n                parts.extend([b\"; \", filename])\n            if self.content_type is not None:\n                content_type = self.content_type.encode()\n                parts.extend([b\"\\r\\nContent-Type: \", content_type])\n            parts.append(b\"\\r\\n\\r\\n\")\n            self._headers = b\"\".join(parts)\n\n        return self._headers\n\n    def render_data(self) -> typing.Iterator[bytes]:\n        if isinstance(self.file, (str, bytes)):\n            yield to_bytes(self.file)\n            return\n\n        if hasattr(self, \"_data\"):\n            # Already rendered.\n            yield self._data\n            return\n\n        if self._consumed:  # pragma: nocover\n            self.file.seek(0)\n        self._consumed = True\n\n        for chunk in self.file:\n            yield to_bytes(chunk)\n\n    def render(self) -> typing.Iterator[bytes]:\n        yield self.render_headers()\n        yield from self.render_data()\n\n\nclass MultipartStream:\n    \"\"\"\n    Request content as streaming multipart encoded form data.\n    \"\"\"\n\n    def __init__(self, data: dict, files: RequestFiles, boundary: bytes = None) -> None:\n        if boundary is None:\n            boundary = binascii.hexlify(os.urandom(16))\n\n        self.boundary = boundary\n        self.content_type = \"multipart/form-data; boundary=%s\" % boundary.decode(\n            \"ascii\"\n        )\n        self.fields = list(self._iter_fields(data, files))\n\n    def _iter_fields(\n        self, data: dict, files: RequestFiles\n    ) -> typing.Iterator[typing.Union[FileField, DataField]]:\n        for name, value in data.items():\n            if isinstance(value, list):\n                for item in value:\n                    yield DataField(name=name, value=item)\n            else:\n                yield DataField(name=name, value=value)\n\n        file_items = files.items() if isinstance(files, typing.Mapping) else files\n        for name, value in file_items:\n            yield FileField(name=name, value=value)\n\n    def iter_chunks(self) -> typing.Iterator[bytes]:\n        for field in self.fields:\n            yield b\"--%s\\r\\n\" % self.boundary\n            yield from field.render()\n            yield b\"\\r\\n\"\n        yield b\"--%s--\\r\\n\" % self.boundary\n\n    def iter_chunks_lengths(self) -> typing.Iterator[int]:\n        boundary_length = len(self.boundary)\n        # Follow closely what `.iter_chunks()` does.\n        for field in self.fields:\n            yield 2 + boundary_length + 2\n            yield field.get_length()\n            yield 2\n        yield 2 + boundary_length + 4\n\n    def get_content_length(self) -> int:\n        return sum(self.iter_chunks_lengths())\n\n    # Content stream interface.\n\n    def get_headers(self) -> typing.Dict[str, str]:\n        content_length = str(self.get_content_length())\n        content_type = self.content_type\n        return {\"Content-Length\": content_length, \"Content-Type\": content_type}\n\n    def __iter__(self) -> typing.Iterator[bytes]:\n        for chunk in self.iter_chunks():\n            yield chunk\n\n    async def __aiter__(self) -> typing.AsyncIterator[bytes]:\n        for chunk in self.iter_chunks():\n            yield chunk\n", "path": "httpx/_multipart.py"}]}