Repository: streamlit/streamlit

Issue:
File_uploader() - ValueError: I/O operation on closed file.
# Summary

I upgraded Streamlit and file_uploader() haven't the expected behavior. I'm trying to load a file, then use a dropdown menu to select the right column to analyse. I adapted the proposed solution [here ](https://github.com/streamlit/streamlit/issues/2266), but I still have  "ValueError: I/O operation on closed file." when I select a value in the dropdown menu.

# Steps to reproduce

What are the steps we should take to reproduce the bug:

1. Select a file to upload
2. Select a value in the dropdown menu

[Screencast](https://drive.google.com/file/d/1Mw33AsLPKugGS4k_dg1JlE55b4_Yy9uK/view?usp=sharing)

## Expected behavior:

Explain what you expect to happen when you go through the steps above, assuming there were no bugs.

## Is this a regression?

That is, did this use to work the way you expected in the past?
yes 

# Debug info

streamlit==0.70.0
streamlit-nightly==0.70.1.dev20201101

# Additional information

Here is my code : 

```
def upload_file():
    file = st.sidebar.file_uploader("Choisir un fichier", type=["csv"])
    if file is None:
        return None,None,None,None,None,None
    file.seek(0)
    df = pd.read_csv(file, sep=';',index_col=None, encoding = "utf-8")
    menu_value = st.sidebar.selectbox('Quelle colonne correspond au texte à analyser ?',df.columns)
    language,remove_hashtags,remove_usernames,ncomponents=sidebar()
    return(df,menu_value,language,remove_hashtags,remove_usernames,ncomponents)

def sidebar():
    language = st.sidebar.selectbox('Quelle est la langue du corpus ?',['french','english'])
    remove_hashtags=st.sidebar.checkbox('Supprimer les hashtags',value=False)
    remove_usernames=st.sidebar.checkbox('Supprimer les mentions',value=False)
    ncomponents=st.sidebar.slider("Combien de topics à détecter", min_value=0, max_value=100, value=20, step=1, format=None, key=None)
    return language,remove_hashtags,remove_usernames,ncomponents
    
def main():
    st.set_page_config(
        page_title="Topic Modeling",
        page_icon="🧊",
        layout="wide",
        initial_sidebar_state="expanded",
    )
    st.set_option('deprecation.showfileUploaderEncoding', False)

    st.sidebar.title('Paramètres')
    st.title('Topic Modelling')
    projet=st.sidebar.text_input("Donner un nom de projet", value='monProjet', max_chars=None, key=None, type='default')
    
    
    df,menu_value,language,remove_hashtags,remove_usernames,ncomponents=upload_file()
   
    if st.sidebar.button('Valider'):
        analyse(projet,language,remove_hashtags,remove_usernames,ncomponents,df_input,menu_value)
        list_results(path="./"+projet+"/")

```


Discussion:
I have the similar requirement and I am facing the same issue as mentioned above. Is there a workaround?
Closing as a duplicate #2235. Also resolved in #2279
It looks like this is still happening intermittently in v0.71.0. Reopening
From Thiago:
> My 100% blind guess is we're creating a single file-like-object on upload and reusing it on every run — so if the file is closed and then gets reused it will brerak. To fix, we'd create a new file-like object each time.
Yup, the only thing is we aren't closing the file. My guess is the memory is being reclaimed and the file is getting closed for us. However, I was testing with a 189B file and though I wasn't able to repro consistently, I was able to do so and my machine should not be complaining about 189B. Definitely need to do more research to confirm. For sure we'll add better error handling, to recreate if possible but that to me is a bandaid. 

Additionally, re-creating a new file-like object each run when we have the file seems very suboptimal to me and a waste of memory... 

Resolving patch:
diff --git a/lib/streamlit/elements/file_uploader.py b/lib/streamlit/elements/file_uploader.py
--- a/lib/streamlit/elements/file_uploader.py
+++ b/lib/streamlit/elements/file_uploader.py
@@ -1,10 +1,10 @@
 from streamlit import config
 
+from streamlit.errors import StreamlitDeprecationWarning
 from streamlit.proto.FileUploader_pb2 import FileUploader as FileUploaderProto
 from streamlit.report_thread import get_report_ctx
-from streamlit.file_util import get_encoded_file_data
-from streamlit.errors import StreamlitDeprecationWarning
 from .utils import NoValue, _set_widget_id
+from ..uploaded_file_manager import UploadedFile
 
 
 class FileUploaderMixin:
@@ -46,8 +46,7 @@ def file_uploader(
 
             The UploadedFile class is a subclass of BytesIO, and therefore
             it is "file-like". This means you can pass them anywhere where
-            a file is expected. As a subclass of BytesIO, make sure to
-            reset the buffer after reading it with `UploadedFile.seek(0)`.
+            a file is expected.
 
         Examples
         --------
@@ -108,19 +107,17 @@ def file_uploader(
         file_uploader_proto.multiple_files = accept_multiple_files
         _set_widget_id("file_uploader", file_uploader_proto, user_key=key)
 
-        files = None
+        file_recs = None
         ctx = get_report_ctx()
         if ctx is not None:
-            files = ctx.uploaded_file_mgr.get_files(
+            file_recs = ctx.uploaded_file_mgr.get_files(
                 session_id=ctx.session_id, widget_id=file_uploader_proto.id
             )
 
-        if files is None or len(files) == 0:
+        if file_recs is None or len(file_recs) == 0:
             return_value = [] if accept_multiple_files else NoValue
         else:
-            for file in files:
-                if file.tell() > 0:
-                    file.seek(0)
+            files = [UploadedFile(rec) for rec in file_recs]
             return_value = files if accept_multiple_files else files[0]
 
         return dg._enqueue("file_uploader", file_uploader_proto, return_value)  # type: ignore
diff --git a/lib/streamlit/server/upload_file_request_handler.py b/lib/streamlit/server/upload_file_request_handler.py
--- a/lib/streamlit/server/upload_file_request_handler.py
+++ b/lib/streamlit/server/upload_file_request_handler.py
@@ -18,7 +18,7 @@
 import tornado.httputil
 import tornado.web
 
-from streamlit.uploaded_file_manager import UploadedFile
+from streamlit.uploaded_file_manager import UploadedFileRec
 from streamlit import config
 from streamlit.logger import get_logger
 from streamlit.report import Report
@@ -97,8 +97,8 @@ def _require_arg(args, name):
         return arg[0].decode("utf-8")
 
     def post(self, **kwargs):
-        args = {}  # type: Dict[str, List[bytes]]
-        files = {}  # type: Dict[str, List[Any]]
+        args: Dict[str, List[bytes]] = {}
+        files: Dict[str, List[Any]] = {}
 
         tornado.httputil.parse_body_arguments(
             content_type=self.request.headers["Content-Type"],
@@ -119,11 +119,11 @@ def post(self, **kwargs):
         )
 
         # Create an UploadedFile object for each file.
-        uploaded_files = []
+        uploaded_files: List[UploadedFileRec] = []
         for id, flist in files.items():
             for file in flist:
                 uploaded_files.append(
-                    UploadedFile(
+                    UploadedFileRec(
                         id=id,
                         name=file["filename"],
                         type=file["content_type"],
diff --git a/lib/streamlit/uploaded_file_manager.py b/lib/streamlit/uploaded_file_manager.py
--- a/lib/streamlit/uploaded_file_manager.py
+++ b/lib/streamlit/uploaded_file_manager.py
@@ -14,20 +14,36 @@
 
 import io
 import threading
-from typing import Dict
-from typing import List
-from typing import NamedTuple
-from typing import Tuple
+from typing import Dict, NamedTuple, Optional, List, Tuple
 from blinker import Signal
 
 
+class UploadedFileRec(NamedTuple):
+    """Metadata and raw bytes for an uploaded file. Immutable."""
+
+    id: str
+    name: str
+    type: str
+    data: bytes
+
+
 class UploadedFile(io.BytesIO):
-    def __init__(self, id, name, type, data, **kwargs):
-        super(UploadedFile, self).__init__(data)
-        self.id = id
-        self.name = name
-        self.type = type
-        self.size = self.getbuffer().nbytes
+    """A mutable uploaded file.
+
+    This class extends BytesIO, which has copy-on-write semantics when
+    initialized with `bytes`.
+    """
+
+    def __init__(self, record: UploadedFileRec):
+        # BytesIO's copy-on-write semantics doesn't seem to be mentioned in
+        # the Python docs - possibly because it's a CPython-only optimization
+        # and not guaranteed to be in other Python runtimes. But it's detailed
+        # here: https://hg.python.org/cpython/rev/79a5fbe2c78f
+        super(UploadedFile, self).__init__(record.data)
+        self.id = record.id
+        self.name = record.name
+        self.type = record.type
+        self.size = len(record.data)
 
 
 class UploadedFileManager(object):
@@ -36,7 +52,7 @@ class UploadedFileManager(object):
     """
 
     def __init__(self):
-        self._files_by_id = {}  # type: Dict[Tuple[str, str], List[UploadedFile] ]
+        self._files_by_id: Dict[Tuple[str, str], List[UploadedFileRec]] = {}
         self._file_counts_by_id: Dict[Tuple[str, str], int] = {}
         # Prevents concurrent access to the _files_by_id dict.
         # In remove_session_files(), we iterate over the dict's keys. It's
@@ -47,8 +63,8 @@ def __init__(self):
 
             Parameters
             ----------
-            files : UploadedFileList
-                The file list that was added or updated.
+            session_id : str
+                The session_id for the session whose files were updated.
             """
         )
 
@@ -70,7 +86,7 @@ def _add_files(
         self,
         session_id: str,
         widget_id: str,
-        files: List[UploadedFile],
+        files: List[UploadedFileRec],
     ):
         """
         Add a list of files to the FileManager. Does not emit any signals
@@ -86,8 +102,8 @@ def add_files(
         self,
         session_id: str,
         widget_id: str,
-        files: List[UploadedFile],
-    ):
+        files: List[UploadedFileRec],
+    ) -> None:
         """Add a list of files to the FileManager.
 
         The "on_file_added" Signal will be emitted after the list is added.
@@ -98,14 +114,16 @@ def add_files(
             The session ID of the report that owns the files.
         widget_id : str
             The widget ID of the FileUploader that created the files.
-        files : List[UploadedFile]
-            The files to add.
+        files : List[UploadedFileRec]
+            The file records to add.
         """
 
         self._add_files(session_id, widget_id, files)
         self._on_files_updated(session_id, widget_id)
 
-    def get_files(self, session_id: str, widget_id: str):
+    def get_files(
+        self, session_id: str, widget_id: str
+    ) -> Optional[List[UploadedFileRec]]:
         """Return the file list with the given ID, or None if the ID doesn't exist.
 
         Parameters
@@ -117,14 +135,13 @@ def get_files(self, session_id: str, widget_id: str):
 
         Returns
         -------
-        list of UploadedFile or None
+        list of UploadedFileRec or None
         """
         files_by_widget = session_id, widget_id
         with self._files_lock:
-            file_list = self._files_by_id.get(files_by_widget, None)
-        return file_list
+            return self._files_by_id.get(files_by_widget, None)
 
-    def remove_file(self, session_id: str, widget_id: str, file_id: str):
+    def remove_file(self, session_id: str, widget_id: str, file_id: str) -> None:
         """Remove the file list with the given ID, if it exists."""
         files_by_widget = session_id, widget_id
         with self._files_lock:
@@ -135,7 +152,7 @@ def remove_file(self, session_id: str, widget_id: str, file_id: str):
             if len(file_list) != len(self._files_by_id[files_by_widget]):
                 self._on_files_updated(session_id, widget_id)
 
-    def _remove_files(self, session_id: str, widget_id: str):
+    def _remove_files(self, session_id: str, widget_id: str) -> None:
         """Remove the file list for the provided widget in the
         provided session, if it exists.
 
@@ -146,7 +163,7 @@ def _remove_files(self, session_id: str, widget_id: str):
         with self._files_lock:
             self._files_by_id.pop(files_by_widget, None)
 
-    def remove_files(self, session_id: str, widget_id: str):
+    def remove_files(self, session_id: str, widget_id: str) -> None:
         """Remove the file list for the provided widget in the
         provided session, if it exists.
 
@@ -160,7 +177,7 @@ def remove_files(self, session_id: str, widget_id: str):
         self._remove_files(session_id, widget_id)
         self._on_files_updated(session_id, widget_id)
 
-    def remove_session_files(self, session_id: str):
+    def remove_session_files(self, session_id: str) -> None:
         """Remove all files that belong to the given report.
 
         Parameters
@@ -181,8 +198,8 @@ def replace_files(
         self,
         session_id: str,
         widget_id: str,
-        files: List[UploadedFile],
-    ):
+        files: List[UploadedFileRec],
+    ) -> None:
         """Removes the file list for the provided widget in the
         provided session, if it exists and add the provided files
         to the widget in the session
@@ -193,7 +210,7 @@ def replace_files(
             The session ID of the report that owns the file.
         widget_id : str
             The widget ID of the FileUploader that created the file.
-        files : List[UploadedFile]
+        files : List[UploadedFileRec]
             The files to add.
         """
         self._remove_files(session_id, widget_id)
@@ -205,7 +222,7 @@ def update_file_count(
         session_id: str,
         widget_id: str,
         file_count: int,
-    ):
+    ) -> None:
         files_by_widget = session_id, widget_id
         self._file_counts_by_id[files_by_widget] = file_count
         self._on_files_updated(session_id, widget_id)


Test patch:
diff --git a/lib/tests/streamlit/file_uploader_test.py b/lib/tests/streamlit/file_uploader_test.py
--- a/lib/tests/streamlit/file_uploader_test.py
+++ b/lib/tests/streamlit/file_uploader_test.py
@@ -18,8 +18,7 @@
 
 import streamlit as st
 from streamlit import config
-from streamlit.uploaded_file_manager import UploadedFile
-from streamlit.file_util import get_encoded_file_data
+from streamlit.uploaded_file_manager import UploadedFileRec, UploadedFile
 from tests import testutil
 
 
@@ -48,12 +47,12 @@ def test_multiple_types(self):
     @patch("streamlit.uploaded_file_manager.UploadedFileManager.get_files")
     def test_multiple_files(self, get_files_patch):
         """Test the accept_multiple_files flag"""
-        files = [
-            UploadedFile("id1", "file1", "type", b"123"),
-            UploadedFile("id2", "file2", "type", b"456"),
+        file_recs = [
+            UploadedFileRec("id1", "file1", "type", b"123"),
+            UploadedFileRec("id2", "file2", "type", b"456"),
         ]
 
-        get_files_patch.return_value = files
+        get_files_patch.return_value = file_recs
 
         for accept_multiple in [True, False]:
             return_val = st.file_uploader(
@@ -62,12 +61,28 @@ def test_multiple_files(self, get_files_patch):
             c = self.get_delta_from_queue().new_element.file_uploader
             self.assertEqual(accept_multiple, c.multiple_files)
 
-            # If "accept_multiple_files" is True, then we should get a list of values
-            # back. Otherwise, we should just get a single value.
+            # If "accept_multiple_files" is True, then we should get a list of
+            # values back. Otherwise, we should just get a single value.
+
+            # Because file_uploader returns unique UploadedFile instances
+            # each time it's called, we convert the return value back
+            # from UploadedFile -> UploadedFileRec (which implements
+            # equals()) to test equality.
+
             if accept_multiple:
-                self.assertEqual(files, return_val)
+                results = [
+                    UploadedFileRec(file.id, file.name, file.type, file.getvalue())
+                    for file in return_val
+                ]
+                self.assertEqual(file_recs, results)
             else:
-                self.assertEqual(files[0], return_val)
+                results = UploadedFileRec(
+                    return_val.id,
+                    return_val.name,
+                    return_val.type,
+                    return_val.getvalue(),
+                )
+                self.assertEqual(file_recs[0], results)
 
     def test_max_upload_size_mb(self):
         """Test that the max upload size is the configuration value."""
@@ -77,3 +92,27 @@ def test_max_upload_size_mb(self):
         self.assertEqual(
             c.max_upload_size_mb, config.get_option("server.maxUploadSize")
         )
+
+    @patch("streamlit.uploaded_file_manager.UploadedFileManager.get_files")
+    def test_unique_uploaded_file_instance(self, get_files_patch):
+        """We should get a unique UploadedFile instance each time we access
+        the file_uploader widget."""
+        file_recs = [
+            UploadedFileRec("id1", "file1", "type", b"123"),
+            UploadedFileRec("id2", "file2", "type", b"456"),
+        ]
+
+        get_files_patch.return_value = file_recs
+
+        # These file_uploaders have different labels so that we don't cause
+        # a DuplicateKey  error - but because we're patching the get_files
+        # function, both file_uploaders will refer to the same files.
+        file1: UploadedFile = st.file_uploader("a", accept_multiple_files=False)
+        file2: UploadedFile = st.file_uploader("b", accept_multiple_files=False)
+
+        self.assertNotEqual(file1, file2)
+
+        # Seeking in one instance should not impact the position in the other.
+        file1.seek(2)
+        self.assertEqual(b"3", file1.read())
+        self.assertEqual(b"123", file2.read())
diff --git a/lib/tests/streamlit/hashing_test.py b/lib/tests/streamlit/hashing_test.py
--- a/lib/tests/streamlit/hashing_test.py
+++ b/lib/tests/streamlit/hashing_test.py
@@ -23,7 +23,6 @@
 import tempfile
 import time
 import types
-import torch
 import torchvision
 import unittest
 import urllib
@@ -34,7 +33,6 @@
 import altair.vegalite.v3
 import numpy as np
 import pandas as pd
-import pytest
 import sqlalchemy as db
 import torch
 from parameterized import parameterized
@@ -56,7 +54,7 @@
 from streamlit.hashing import _NP_SIZE_LARGE
 from streamlit.hashing import _PANDAS_ROWS_LARGE
 from streamlit.type_util import is_type, get_fqn_type
-from streamlit.uploaded_file_manager import UploadedFile
+from streamlit.uploaded_file_manager import UploadedFile, UploadedFileRec
 import streamlit as st
 
 from tests import testutil
@@ -319,20 +317,20 @@ def test_numpy(self):
 
     @parameterized.expand(
         [
-            (BytesIO, {b"123"}, {b"456"}, {b"123"}),
-            (StringIO, {"123"}, {"456"}, {"123"}),
+            (BytesIO, b"123", b"456", b"123"),
+            (StringIO, "123", "456", "123"),
             (
                 UploadedFile,
-                ["id", "name", "type", b"123"],
-                ["id", "name", "type", b"456"],
-                ["id", "name", "type", b"123"],
+                UploadedFileRec("id", "name", "type", b"123"),
+                UploadedFileRec("id", "name", "type", b"456"),
+                UploadedFileRec("id", "name", "type", b"123"),
             ),
         ]
     )
     def test_io(self, io_type, io_data1, io_data2, io_data3):
-        io1 = io_type(*io_data1)
-        io2 = io_type(*io_data2)
-        io3 = io_type(*io_data3)
+        io1 = io_type(io_data1)
+        io2 = io_type(io_data2)
+        io3 = io_type(io_data3)
 
         self.assertEqual(get_hash(io1), get_hash(io3))
         self.assertNotEqual(get_hash(io1), get_hash(io2))
diff --git a/lib/tests/streamlit/server_test.py b/lib/tests/streamlit/server_test.py
--- a/lib/tests/streamlit/server_test.py
+++ b/lib/tests/streamlit/server_test.py
@@ -29,7 +29,7 @@
 from streamlit import config, RootContainer
 from streamlit.cursor import make_delta_path
 from streamlit.report_session import ReportSession
-from streamlit.uploaded_file_manager import UploadedFile
+from streamlit.uploaded_file_manager import UploadedFileRec
 from streamlit.server.server import MAX_PORT_SEARCH_RETRIES
 from streamlit.forward_msg_cache import ForwardMsgCache
 from streamlit.forward_msg_cache import populate_hash_if_needed
@@ -326,7 +326,7 @@ def test_uploaded_file_triggers_rerun(self):
             session_info1 = list(self.server._session_info_by_id.values())[0]
             session_info2 = list(self.server._session_info_by_id.values())[1]
 
-            file = UploadedFile("id", "file.txt", "type", b"123")
+            file = UploadedFileRec("id", "file.txt", "type", b"123")
 
             # "Upload a file" for Session1
             self.server._uploaded_file_mgr.update_file_count(
@@ -364,7 +364,7 @@ def test_multiple_uploaded_file_triggers_one_rerun(self):
             session_info1 = list(self.server._session_info_by_id.values())[0]
             session_info2 = list(self.server._session_info_by_id.values())[1]
 
-            file = UploadedFile("id", "file.txt", "type", b"123")
+            file = UploadedFileRec("id", "file.txt", "type", b"123")
 
             # "Upload 2 files" for Session1
             self.server._uploaded_file_mgr.update_file_count(
@@ -401,7 +401,7 @@ def test_orphaned_upload_file_deletion(self):
             self.server._uploaded_file_mgr.add_files(
                 session_id="no_such_session",
                 widget_id="widget_id",
-                files=[UploadedFile("id", "file.txt", "type", b"123")],
+                files=[UploadedFileRec("id", "file.txt", "type", b"123")],
             )
 
             self.assertIsNone(
diff --git a/lib/tests/streamlit/upload_file_request_handler_test.py b/lib/tests/streamlit/upload_file_request_handler_test.py
--- a/lib/tests/streamlit/upload_file_request_handler_test.py
+++ b/lib/tests/streamlit/upload_file_request_handler_test.py
@@ -14,22 +14,28 @@
 
 """UploadFileHandler.py unit tests"""
 
+from typing import NamedTuple
+
 import requests
 import tornado.gen
 import tornado.testing
 import tornado.web
 import tornado.websocket
-
 from parameterized import parameterized
 
-from streamlit.uploaded_file_manager import UploadedFile
-from streamlit.uploaded_file_manager import UploadedFileManager
 from streamlit.logger import get_logger
 from streamlit.server.upload_file_request_handler import UploadFileRequestHandler
+from streamlit.uploaded_file_manager import UploadedFileManager
+from streamlit.uploaded_file_manager import UploadedFileRec
 
 LOGGER = get_logger(__name__)
 
 
+class MockFile(NamedTuple):
+    name: str
+    data: bytes
+
+
 def _get_filename(file):
     """Sort key for lists of UploadedFiles"""
     return file.name
@@ -70,9 +76,9 @@ def _upload_files(self, params):
 
     def test_upload_one_file(self):
         """Uploading a file should populate our file_mgr."""
-        file = UploadedFile("id", "image.png", "type", b"123")
+        file = MockFile("filename", b"123")
         params = {
-            file.name: file,
+            file.name: file.data,
             "sessionId": (None, "fooReport"),
             "widgetId": (None, "barWidget"),
             "totalFiles": (None, "1"),
@@ -80,22 +86,22 @@ def test_upload_one_file(self):
         response = self._upload_files(params)
         self.assertEqual(200, response.code)
         self.assertEqual(
-            [file.getvalue()],
+            [(file.name, file.data)],
             [
-                file.getvalue()
-                for file in self.file_mgr.get_files("fooReport", "barWidget")
+                (rec.name, rec.data)
+                for rec in self.file_mgr.get_files("fooReport", "barWidget")
             ],
         )
 
     def test_upload_multiple_files(self):
-        file1 = UploadedFile("id1", "image1.png", "type", b"123")
-        file2 = UploadedFile("id2", "image2.png", "type", b"456")
-        file3 = UploadedFile("id3", "image3.png", "type", b"789")
+        file_1 = MockFile("file1", b"123")
+        file_2 = MockFile("file2", b"456")
+        file_3 = MockFile("file3", b"789")
 
         params = {
-            file1.name: file1,
-            file2.name: file2,
-            file3.name: file3,
+            file_1.name: file_1.data,
+            file_2.name: file_2.data,
+            file_3.name: file_3.data,
             "sessionId": (None, "fooReport"),
             "widgetId": (None, "barWidget"),
             "totalFiles": (None, "1"),
@@ -103,11 +109,11 @@ def test_upload_multiple_files(self):
         response = self._upload_files(params)
         self.assertEqual(200, response.code)
         self.assertEqual(
-            sorted([file1.name, file2.name, file3.name]),
+            sorted([file_1, file_2, file_3]),
             sorted(
                 [
-                    file.name
-                    for file in self.file_mgr.get_files("fooReport", "barWidget")
+                    (rec.name, rec.data)
+                    for rec in self.file_mgr.get_files("fooReport", "barWidget")
                 ]
             ),
         )
@@ -139,8 +145,8 @@ def test_upload_missing_file(self):
 
     def test_delete_file(self):
         """File should be able to be deleted successfully"""
-        file1 = UploadedFile("1234", "name", "type", b"1234")
-        file2 = UploadedFile("4567", "name", "type", b"1234")
+        file1 = UploadedFileRec("1234", "name", "type", b"1234")
+        file2 = UploadedFileRec("4567", "name", "type", b"1234")
 
         self.file_mgr.add_files("session1", "widget1", [file1])
         self.file_mgr.add_files("session2", "widget2", [file2])
@@ -152,8 +158,8 @@ def test_delete_file(self):
 
     def test_delete_file_across_sessions(self):
         """Deleting file param mismatch should fail with 404 status."""
-        file1 = UploadedFile("1234", "name", "type", b"1234")
-        file2 = UploadedFile("4567", "name", "type", b"1234")
+        file1 = UploadedFileRec("1234", "name", "type", b"1234")
+        file2 = UploadedFileRec("4567", "name", "type", b"1234")
 
         self.file_mgr.add_files("session1", "widget1", [file1])
         self.file_mgr.add_files("session2", "widget2", [file2])
diff --git a/lib/tests/streamlit/uploaded_file_manager_test.py b/lib/tests/streamlit/uploaded_file_manager_test.py
--- a/lib/tests/streamlit/uploaded_file_manager_test.py
+++ b/lib/tests/streamlit/uploaded_file_manager_test.py
@@ -16,11 +16,11 @@
 
 import unittest
 
-from streamlit.uploaded_file_manager import UploadedFile
 from streamlit.uploaded_file_manager import UploadedFileManager
+from streamlit.uploaded_file_manager import UploadedFileRec
 
-file1 = UploadedFile(id="id1", name="file1", type="type", data=b"file1")
-file2 = UploadedFile(id="id2", name="file2", type="type", data=b"file2")
+file1 = UploadedFileRec(id="id1", name="file1", type="type", data=b"file1")
+file2 = UploadedFileRec(id="id2", name="file2", type="type", data=b"file2")
 
 
 class UploadedFileManagerTest(unittest.TestCase):