Feature/Multi File upload (#712)

* This PR adds a feature to upload multiple files

* modified function to support python 3.8

* change code to use types instead of  arg name
This commit is contained in:
Elijah Ahianyo 2023-03-23 06:54:08 +00:00 committed by GitHub
parent 25472d7d85
commit a2f86f9fbb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 71 additions and 14 deletions

View File

@ -192,6 +192,7 @@ export const connect = async (
* @param setResult The function to set the result.
* @param files The files to upload.
* @param handler The handler to use.
* @param multiUpload Whether handler args on backend is multiupload
* @param endpoint The endpoint to upload to.
*/
export const uploadFiles = async (
@ -200,6 +201,7 @@ export const uploadFiles = async (
setResult,
files,
handler,
multiUpload,
endpoint
) => {
// If we are already processing an event, or there are no upload files, return.
@ -210,15 +212,16 @@ export const uploadFiles = async (
// Set processing to true to block other events from being processed.
setResult({ ...result, processing: true });
// Currently only supports uploading one file.
const file = files[0];
const name = multiUpload ? "files" : "file"
const headers = {
"Content-Type": file.type,
"Content-Type": files[0].type,
};
const formdata = new FormData();
// Add the token and handler to the file name.
formdata.append("file", file, getToken() + ":" + handler + ":" + file.name);
for (let i = 0; i < files.length; i++) {
formdata.append("files", files[i], getToken() + ":" + handler + ":" + name + ":" + files[i].name);
}
// Send the file to the server.
await axios.post(endpoint, formdata, headers).then((response) => {

View File

@ -465,22 +465,27 @@ def upload(app: App):
The upload function.
"""
async def upload_file(file: UploadFile):
async def upload_file(files: List[UploadFile]):
"""Upload a file.
Args:
file: The file to upload.
files: The file(s) to upload.
Returns:
The state update after processing the event.
"""
# Get the token and filename.
token, handler, filename = file.filename.split(":", 2)
file.filename = filename
token, handler, key = files[0].filename.split(":")[:3]
for file in files:
file.filename = file.filename.split(":")[-1]
# Get the state for the session.
state = app.state_manager.get_state(token)
event = Event(token=token, name=handler, payload={"file": file})
# Event payload should have `files` as key for multi-uploads and `file` otherwise
event = Event(
token=token,
name=handler,
payload={key: files[0] if key == "file" else files},
)
update = await state.process(event)
return update

View File

@ -2,6 +2,7 @@
from __future__ import annotations
import inspect
import json
import os
import re
@ -298,9 +299,15 @@ def format_upload_event(event_spec: EventSpec) -> str:
"""
from pynecone.compiler import templates
multi_upload = any(
types._issubclass(arg_type, List)
for arg_type in inspect.getfullargspec(
event_spec.handler.fn
).annotations.values()
)
state, name = get_event_handler_parts(event_spec.handler)
parent_state = state.split(".")[0]
return f'uploadFiles({parent_state}, {templates.RESULT}, {templates.SET_RESULT}, {parent_state}.files, "{state}.{name}", UPLOAD)'
return f'uploadFiles({parent_state}, {templates.RESULT}, {templates.SET_RESULT}, {parent_state}.files, "{state}.{name}",{str(multi_upload).lower()} ,UPLOAD)'
def format_query_params(router_data: Dict[str, Any]) -> Dict[str, str]:

View File

@ -1,6 +1,6 @@
"""Test fixtures."""
import platform
from typing import Generator
from typing import Generator, List
import pytest
@ -141,6 +141,7 @@ class UploadState(pc.State):
"""The base state for uploading a file."""
img: str
img_list: List[str]
async def handle_upload(self, file: pc.UploadFile):
"""Handle the upload of a file.
@ -158,6 +159,23 @@ class UploadState(pc.State):
# Update the img var.
self.img = 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".web/public/{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)
class BaseState(pc.State):
"""The test base state."""
@ -197,3 +215,13 @@ def upload_sub_state_event_spec():
Event Spec.
"""
return EventSpec(handler=SubUploadState.handle_upload, upload=True) # type: ignore
@pytest.fixture
def multi_upload_event_spec():
"""Create an event Spec for a multi-upload base state.
Returns:
Event Spec.
"""
return EventSpec(handler=UploadState.multi_handle_upload, upload=True) # type: ignore

View File

@ -296,7 +296,7 @@ def test_format_upload_event(upload_event_spec):
assert (
format.format_upload_event(upload_event_spec)
== "uploadFiles(upload_state, result, setResult, "
'upload_state.files, "upload_state.handle_upload", '
'upload_state.files, "upload_state.handle_upload",false ,'
"UPLOAD)"
)
@ -310,5 +310,19 @@ def test_format_sub_state_event(upload_sub_state_event_spec):
assert (
format.format_upload_event(upload_sub_state_event_spec)
== "uploadFiles(base_state, result, setResult, base_state.files, "
'"base_state.sub_upload_state.handle_upload", UPLOAD)'
'"base_state.sub_upload_state.handle_upload",false ,UPLOAD)'
)
def test_format_multi_upload_event(multi_upload_event_spec):
"""Test formatting an upload event spec.
Args:
multi_upload_event_spec: The event spec fixture.
"""
assert (
format.format_upload_event(multi_upload_event_spec)
== "uploadFiles(upload_state, result, setResult, "
'upload_state.files, "upload_state.multi_handle_upload",true ,'
"UPLOAD)"
)