Răsfoiți Sursa

Handle upload bugfix (#886)

Elijah Ahianyo 2 ani în urmă
părinte
comite
5ad3882898
3 a modificat fișierele cu 169 adăugiri și 13 ștergeri
  1. 5 1
      pynecone/app.py
  2. 123 0
      tests/conftest.py
  3. 41 12
      tests/test_app.py

+ 5 - 1
pynecone/app.py

@@ -504,10 +504,14 @@ def upload(app: App):
 
         # Get the state for the session.
         state = app.state_manager.get_state(token)
+
+        # get the current state(parent state/substate)
+        path = handler.split(".")[:-1]
+        current_state = state.get_substate(path)
         handler_upload_param: Tuple = ()
 
         # get handler function
-        func = getattr(state, handler.split(".")[-1])
+        func = getattr(current_state, handler.split(".")[-1])
 
         # check if there exists any handler args with annotation, List[UploadFile]
         for k, v in inspect.getfullargspec(

+ 123 - 0
tests/conftest.py

@@ -244,6 +244,129 @@ def upload_state(tmp_path):
     return FileUploadState
 
 
+@pytest.fixture
+def upload_sub_state(tmp_path):
+    """Create upload substate.
+
+    Args:
+        tmp_path: pytest tmp_path
+
+    Returns:
+        The state
+
+    """
+
+    class FileState(pc.State):
+        """The base state."""
+
+        pass
+
+    class FileUploadState(FileState):
+        """The substate for uploading a file."""
+
+        img_list: List[str]
+
+        async def handle_upload2(self, files):
+            """Handle the upload of a file.
+
+            Args:
+                files: The uploaded files.
+            """
+            for file in files:
+                upload_data = await file.read()
+                outfile = f"{tmp_path}/{file.filename}"
+
+                # Save the file.
+                with open(outfile, "wb") as file_object:
+                    file_object.write(upload_data)
+
+                # Update the img var.
+                self.img_list.append(file.filename)
+
+        async def multi_handle_upload(self, files: List[pc.UploadFile]):
+            """Handle the upload of a file.
+
+            Args:
+                files: The uploaded files.
+            """
+            for file in files:
+                upload_data = await file.read()
+                outfile = f"{tmp_path}/{file.filename}"
+
+                # Save the file.
+                with open(outfile, "wb") as file_object:
+                    file_object.write(upload_data)
+
+                # Update the img var.
+                self.img_list.append(file.filename)
+
+    return FileUploadState
+
+
+@pytest.fixture
+def upload_grand_sub_state(tmp_path):
+    """Create upload grand-state.
+
+    Args:
+        tmp_path: pytest tmp_path
+
+    Returns:
+        The state
+
+    """
+
+    class BaseFileState(pc.State):
+        """The base state."""
+
+        pass
+
+    class FileSubState(BaseFileState):
+        """The substate."""
+
+        pass
+
+    class FileUploadState(FileSubState):
+        """The grand-substate for uploading a file."""
+
+        img_list: List[str]
+
+        async def handle_upload2(self, files):
+            """Handle the upload of a file.
+
+            Args:
+                files: The uploaded files.
+            """
+            for file in files:
+                upload_data = await file.read()
+                outfile = f"{tmp_path}/{file.filename}"
+
+                # Save the file.
+                with open(outfile, "wb") as file_object:
+                    file_object.write(upload_data)
+
+                # Update the img var.
+                self.img_list.append(file.filename)
+
+        async def multi_handle_upload(self, files: List[pc.UploadFile]):
+            """Handle the upload of a file.
+
+            Args:
+                files: The uploaded files.
+            """
+            for file in files:
+                upload_data = await file.read()
+                outfile = f"{tmp_path}/{file.filename}"
+
+                # Save the file.
+                with open(outfile, "wb") as file_object:
+                    file_object.write(upload_data)
+
+                # Update the img var.
+                self.img_list.append(file.filename)
+
+    return FileUploadState
+
+
 @pytest.fixture
 def base_config_values() -> Dict:
     """Get base config values.

+ 41 - 12
tests/test_app.py

@@ -412,11 +412,38 @@ async def test_dict_mutation_detection__plain_list(
 
 
 @pytest.mark.asyncio
-async def test_upload_file(upload_state):
+@pytest.mark.parametrize(
+    "fixture, expected",
+    [
+        (
+            "upload_state",
+            {"file_upload_state": {"img_list": ["image1.jpg", "image2.jpg"]}},
+        ),
+        (
+            "upload_sub_state",
+            {
+                "file_state.file_upload_state": {
+                    "img_list": ["image1.jpg", "image2.jpg"]
+                }
+            },
+        ),
+        (
+            "upload_grand_sub_state",
+            {
+                "base_file_state.file_sub_state.file_upload_state": {
+                    "img_list": ["image1.jpg", "image2.jpg"]
+                }
+            },
+        ),
+    ],
+)
+async def test_upload_file(fixture, request, expected):
     """Test that file upload works correctly.
 
     Args:
-        upload_state: the state
+        fixture: The state.
+        request: Fixture request.
+        expected: Expected delta
     """
     data = b"This is binary data"
 
@@ -424,7 +451,7 @@ async def test_upload_file(upload_state):
     bio = io.BytesIO()
     bio.write(data)
 
-    app = App(state=upload_state)
+    app = App(state=request.getfixturevalue(fixture))
 
     file1 = UploadFile(
         filename="token:file_upload_state.multi_handle_upload:True:image1.jpg",
@@ -439,17 +466,19 @@ async def test_upload_file(upload_state):
     fn = upload(app)
     result = await fn([file1, file2])  # type: ignore
     assert isinstance(result, StateUpdate)
-    assert result.delta == {
-        "file_upload_state": {"img_list": ["image1.jpg", "image2.jpg"]}
-    }
+    assert result.delta == expected
 
 
 @pytest.mark.asyncio
-async def test_upload_file_without_annotation(upload_state):
+@pytest.mark.parametrize(
+    "fixture", ["upload_state", "upload_sub_state", "upload_grand_sub_state"]
+)
+async def test_upload_file_without_annotation(fixture, request):
     """Test that an error is thrown when there's no param annotated with pc.UploadFile or List[UploadFile].
 
     Args:
-        upload_state: the state
+        fixture: The state.
+        request: Fixture request.
     """
     data = b"This is binary data"
 
@@ -457,15 +486,15 @@ async def test_upload_file_without_annotation(upload_state):
     bio = io.BytesIO()
     bio.write(data)
 
-    app = App(state=upload_state)
+    app = App(state=request.getfixturevalue(fixture))
 
     file1 = UploadFile(
-        filename="token:upload_state.handle_upload2:True:image1.jpg",
+        filename="token:file_upload_state.handle_upload2:True:image1.jpg",
         file=bio,
         content_type="image/jpeg",
     )
     file2 = UploadFile(
-        filename="token:upload_state.handle_upload2:True:image2.jpg",
+        filename="token:file_upload_state.handle_upload2:True:image2.jpg",
         file=bio,
         content_type="image/jpeg",
     )
@@ -474,5 +503,5 @@ async def test_upload_file_without_annotation(upload_state):
         await fn([file1, file2])
     assert (
         err.value.args[0]
-        == "`upload_state.handle_upload2` handler should have a parameter annotated as List[pc.UploadFile]"
+        == "`file_upload_state.handle_upload2` handler should have a parameter annotated as List[pc.UploadFile]"
     )