Skip to content

Commit

Permalink
Add size attribute to UploadFile
Browse files Browse the repository at this point in the history
  • Loading branch information
rafalp committed Jan 11, 2022
1 parent 3c93a19 commit 48166ac
Show file tree
Hide file tree
Showing 3 changed files with 17 additions and 0 deletions.
1 change: 1 addition & 0 deletions docs/requests.md
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ multidict, containing both file uploads and text input. File upload items are re
* `content_type`: A `str` with the content type (MIME type / media type) (e.g. `image/jpeg`).
* `file`: A <a href="https://docs.python.org/3/library/tempfile.html#tempfile.SpooledTemporaryFile" target="_blank">`SpooledTemporaryFile`</a> (a <a href="https://docs.python.org/3/glossary.html#term-file-like-object" target="_blank">file-like</a> object). This is the actual Python file that you can pass directly to other functions or libraries that expect a "file-like" object.
* `headers`: A `Headers` object. Often this will only be the `Content-Type` header, but if additional headers were included in the multipart field they will be included here. Note that these headers have no relationship with the headers in `Request.headers`.
* `size`: An `int` with file's size in bytes.

`UploadFile` has the following `async` methods. They all call the corresponding file methods underneath (using the internal `SpooledTemporaryFile`).

Expand Down
13 changes: 13 additions & 0 deletions starlette/datastructures.py
Original file line number Diff line number Diff line change
Expand Up @@ -416,7 +416,10 @@ class UploadFile:

spool_max_size = 1024 * 1024
file: typing.BinaryIO
filename: str
content_type: str
headers: "Headers"
size: int

def __init__(
self,
Expand All @@ -430,16 +433,26 @@ def __init__(
self.content_type = content_type
if file is None:
self.file = tempfile.SpooledTemporaryFile(max_size=self.spool_max_size) # type: ignore # noqa: E501
self.size = 0
else:
self.file = file
self.size = self._get_file_size(file)
self.headers = headers or Headers()

def _get_file_size(self, file: typing.BinaryIO):
self.file.seek(0, 2) # Seek end of file
size = self.file.tell()
self.file.seek(0, 0)
return size

@property
def _in_memory(self) -> bool:
rolled_to_disk = getattr(self.file, "_rolled", True)
return not rolled_to_disk

async def write(self, data: bytes) -> None:
self.size += len(data)

if self._in_memory:
self.file.write(data) # type: ignore
else:
Expand Down
3 changes: 3 additions & 0 deletions tests/test_datastructures.py
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,7 @@ async def test_upload_file():
await big_file.write(b"big-data")
await big_file.seek(0)
assert await big_file.read(1024) == b"big-data" * 128
assert big_file.size == 4104
await big_file.close()


Expand All @@ -232,9 +233,11 @@ async def test_upload_file_file_input():
"""Test passing file/stream into the UploadFile constructor"""
stream = io.BytesIO(b"data")
file = UploadFile(filename="file", file=stream)
assert file.size == 4
assert await file.read() == b"data"
await file.write(b" and more data!")
assert await file.read() == b""
assert file.size == 19
await file.seek(0)
assert await file.read() == b"data and more data!"

Expand Down

0 comments on commit 48166ac

Please sign in to comment.