From 1cf0d76ade96529c12b9f442e260e38ae56e18e7 Mon Sep 17 00:00:00 2001 From: Hemanth Thirthahalli Date: Thu, 13 Nov 2025 16:16:59 +0530 Subject: [PATCH 1/5] Add read_text method to UploadFile for async text reading --- fastapi/datastructures.py | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/fastapi/datastructures.py b/fastapi/datastructures.py index 8ad9aa11a..d7c49f1b7 100644 --- a/fastapi/datastructures.py +++ b/fastapi/datastructures.py @@ -136,6 +136,33 @@ class UploadFile(StarletteUploadFile): To be awaitable, compatible with async, this is run in threadpool. """ return await super().close() + + async def read_text( + self, + encoding: Annotated[ + str, + Doc("The text encoding to use when decoding bytes. Defaults to 'utf-8'."), + ] = "utf-8", + ) -> str: + + """ + Read the entire file as a text string. + This is a convenience wrapper around `await self.read()` + that decodes the bytes using the given encoding. + ## Example + ```python + @app.post("/upload-text/") + async def upload_text(file: UploadFile): + text = await file.read_text() + return {"length": len(text)} + ``` + Args: + encoding: The text encoding to use (default: 'utf-8'). + Returns: + The decoded file content as a string. + """ + data = await self.read() + return data.decode(encoding) @classmethod def __get_validators__(cls: Type["UploadFile"]) -> Iterable[Callable[..., Any]]: From ae3636e1ea8fb2573eebeaa1c9fafd7509adc6f4 Mon Sep 17 00:00:00 2001 From: Hemanth Thirthahalli Date: Thu, 13 Nov 2025 16:17:51 +0530 Subject: [PATCH 2/5] ruff fix --- fastapi/datastructures.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fastapi/datastructures.py b/fastapi/datastructures.py index d7c49f1b7..b6ff24044 100644 --- a/fastapi/datastructures.py +++ b/fastapi/datastructures.py @@ -136,7 +136,7 @@ class UploadFile(StarletteUploadFile): To be awaitable, compatible with async, this is run in threadpool. """ return await super().close() - + async def read_text( self, encoding: Annotated[ From 42887de02ae8fefb4186aa69e6bbaa02f5794037 Mon Sep 17 00:00:00 2001 From: Hemanth Thirthahalli Date: Thu, 13 Nov 2025 16:18:59 +0530 Subject: [PATCH 3/5] dependancy added --- pyproject.toml | 3 +++ requirements-tests.txt | 1 + 2 files changed, 4 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 7d2be0074..eac1304a6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -172,6 +172,9 @@ addopts = [ ] xfail_strict = true junit_family = "xunit2" +markers = [ + "asyncio: mark test to run with asyncio event loop", +] filterwarnings = [ "error", 'ignore:starlette.middleware.wsgi is deprecated and will be removed in a future release\..*:DeprecationWarning:starlette', diff --git a/requirements-tests.txt b/requirements-tests.txt index c5de4157e..5e68223ed 100644 --- a/requirements-tests.txt +++ b/requirements-tests.txt @@ -1,6 +1,7 @@ -e .[all] -r requirements-docs-tests.txt pytest >=7.1.3,<9.0.0 +pytest-asyncio >=0.21.0,<0.25.0 coverage[toml] >= 6.5.0,< 8.0 mypy ==1.14.1 dirty-equals ==0.9.0 From 3f5ad3689c7a71f0c72c1f95c881285ca2ef9d9a Mon Sep 17 00:00:00 2001 From: Hemanth Thirthahalli Date: Thu, 13 Nov 2025 16:20:07 +0530 Subject: [PATCH 4/5] added testcases --- tests/test_datastructures.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/tests/test_datastructures.py b/tests/test_datastructures.py index 7e57d525c..f49fb431f 100644 --- a/tests/test_datastructures.py +++ b/tests/test_datastructures.py @@ -70,3 +70,16 @@ async def test_upload_file(): await file.seek(0) assert await file.read() == b"data and more data!" await file.close() + + +@pytest.mark.asyncio +async def test_uploadfile_read_text(tmp_path): + + file_path = tmp_path / "sample.txt" + file_path.write_text("Hello FastAPI!") + + with open(file_path, "rb") as f: + upload = UploadFile(filename="sample.txt", file=f) + content = await upload.read_text() + assert content == "Hello FastAPI!" + assert upload.filename == "sample.txt" # make sure .read_text() doesn't modify filename or headers \ No newline at end of file From 5a38a03bf84a4e738d1e0c38a56def60e38aa1f4 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 13 Nov 2025 10:56:19 +0000 Subject: [PATCH 5/5] =?UTF-8?q?=F0=9F=8E=A8=20[pre-commit.ci]=20Auto=20for?= =?UTF-8?q?mat=20from=20pre-commit.com=20hooks?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- fastapi/datastructures.py | 31 +++++++++++++++---------------- tests/test_datastructures.py | 5 +++-- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/fastapi/datastructures.py b/fastapi/datastructures.py index b6ff24044..b8592b5f8 100644 --- a/fastapi/datastructures.py +++ b/fastapi/datastructures.py @@ -143,23 +143,22 @@ class UploadFile(StarletteUploadFile): str, Doc("The text encoding to use when decoding bytes. Defaults to 'utf-8'."), ] = "utf-8", - ) -> str: - + ) -> str: """ - Read the entire file as a text string. - This is a convenience wrapper around `await self.read()` - that decodes the bytes using the given encoding. - ## Example - ```python - @app.post("/upload-text/") - async def upload_text(file: UploadFile): - text = await file.read_text() - return {"length": len(text)} - ``` - Args: - encoding: The text encoding to use (default: 'utf-8'). - Returns: - The decoded file content as a string. + Read the entire file as a text string. + This is a convenience wrapper around `await self.read()` + that decodes the bytes using the given encoding. + ## Example + ```python + @app.post("/upload-text/") + async def upload_text(file: UploadFile): + text = await file.read_text() + return {"length": len(text)} + ``` + Args: + encoding: The text encoding to use (default: 'utf-8'). + Returns: + The decoded file content as a string. """ data = await self.read() return data.decode(encoding) diff --git a/tests/test_datastructures.py b/tests/test_datastructures.py index f49fb431f..df965fd55 100644 --- a/tests/test_datastructures.py +++ b/tests/test_datastructures.py @@ -74,7 +74,6 @@ async def test_upload_file(): @pytest.mark.asyncio async def test_uploadfile_read_text(tmp_path): - file_path = tmp_path / "sample.txt" file_path.write_text("Hello FastAPI!") @@ -82,4 +81,6 @@ async def test_uploadfile_read_text(tmp_path): upload = UploadFile(filename="sample.txt", file=f) content = await upload.read_text() assert content == "Hello FastAPI!" - assert upload.filename == "sample.txt" # make sure .read_text() doesn't modify filename or headers \ No newline at end of file + assert ( + upload.filename == "sample.txt" + ) # make sure .read_text() doesn't modify filename or headers