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:
parent
25472d7d85
commit
a2f86f9fbb
@ -192,6 +192,7 @@ export const connect = async (
|
|||||||
* @param setResult The function to set the result.
|
* @param setResult The function to set the result.
|
||||||
* @param files The files to upload.
|
* @param files The files to upload.
|
||||||
* @param handler The handler to use.
|
* @param handler The handler to use.
|
||||||
|
* @param multiUpload Whether handler args on backend is multiupload
|
||||||
* @param endpoint The endpoint to upload to.
|
* @param endpoint The endpoint to upload to.
|
||||||
*/
|
*/
|
||||||
export const uploadFiles = async (
|
export const uploadFiles = async (
|
||||||
@ -200,6 +201,7 @@ export const uploadFiles = async (
|
|||||||
setResult,
|
setResult,
|
||||||
files,
|
files,
|
||||||
handler,
|
handler,
|
||||||
|
multiUpload,
|
||||||
endpoint
|
endpoint
|
||||||
) => {
|
) => {
|
||||||
// If we are already processing an event, or there are no upload files, return.
|
// 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.
|
// Set processing to true to block other events from being processed.
|
||||||
setResult({ ...result, processing: true });
|
setResult({ ...result, processing: true });
|
||||||
|
|
||||||
// Currently only supports uploading one file.
|
const name = multiUpload ? "files" : "file"
|
||||||
const file = files[0];
|
|
||||||
const headers = {
|
const headers = {
|
||||||
"Content-Type": file.type,
|
"Content-Type": files[0].type,
|
||||||
};
|
};
|
||||||
const formdata = new FormData();
|
const formdata = new FormData();
|
||||||
|
|
||||||
// Add the token and handler to the file name.
|
// 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.
|
// Send the file to the server.
|
||||||
await axios.post(endpoint, formdata, headers).then((response) => {
|
await axios.post(endpoint, formdata, headers).then((response) => {
|
||||||
|
@ -465,22 +465,27 @@ def upload(app: App):
|
|||||||
The upload function.
|
The upload function.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
async def upload_file(file: UploadFile):
|
async def upload_file(files: List[UploadFile]):
|
||||||
"""Upload a file.
|
"""Upload a file.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
file: The file to upload.
|
files: The file(s) to upload.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
The state update after processing the event.
|
The state update after processing the event.
|
||||||
"""
|
"""
|
||||||
# Get the token and filename.
|
token, handler, key = files[0].filename.split(":")[:3]
|
||||||
token, handler, filename = file.filename.split(":", 2)
|
for file in files:
|
||||||
file.filename = filename
|
file.filename = file.filename.split(":")[-1]
|
||||||
|
|
||||||
# Get the state for the session.
|
# Get the state for the session.
|
||||||
state = app.state_manager.get_state(token)
|
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)
|
update = await state.process(event)
|
||||||
return update
|
return update
|
||||||
|
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import inspect
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
@ -298,9 +299,15 @@ def format_upload_event(event_spec: EventSpec) -> str:
|
|||||||
"""
|
"""
|
||||||
from pynecone.compiler import templates
|
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)
|
state, name = get_event_handler_parts(event_spec.handler)
|
||||||
parent_state = state.split(".")[0]
|
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]:
|
def format_query_params(router_data: Dict[str, Any]) -> Dict[str, str]:
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
"""Test fixtures."""
|
"""Test fixtures."""
|
||||||
import platform
|
import platform
|
||||||
from typing import Generator
|
from typing import Generator, List
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
@ -141,6 +141,7 @@ class UploadState(pc.State):
|
|||||||
"""The base state for uploading a file."""
|
"""The base state for uploading a file."""
|
||||||
|
|
||||||
img: str
|
img: str
|
||||||
|
img_list: List[str]
|
||||||
|
|
||||||
async def handle_upload(self, file: pc.UploadFile):
|
async def handle_upload(self, file: pc.UploadFile):
|
||||||
"""Handle the upload of a file.
|
"""Handle the upload of a file.
|
||||||
@ -158,6 +159,23 @@ class UploadState(pc.State):
|
|||||||
# Update the img var.
|
# Update the img var.
|
||||||
self.img = file.filename
|
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):
|
class BaseState(pc.State):
|
||||||
"""The test base state."""
|
"""The test base state."""
|
||||||
@ -197,3 +215,13 @@ def upload_sub_state_event_spec():
|
|||||||
Event Spec.
|
Event Spec.
|
||||||
"""
|
"""
|
||||||
return EventSpec(handler=SubUploadState.handle_upload, upload=True) # type: ignore
|
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
|
||||||
|
@ -296,7 +296,7 @@ def test_format_upload_event(upload_event_spec):
|
|||||||
assert (
|
assert (
|
||||||
format.format_upload_event(upload_event_spec)
|
format.format_upload_event(upload_event_spec)
|
||||||
== "uploadFiles(upload_state, result, setResult, "
|
== "uploadFiles(upload_state, result, setResult, "
|
||||||
'upload_state.files, "upload_state.handle_upload", '
|
'upload_state.files, "upload_state.handle_upload",false ,'
|
||||||
"UPLOAD)"
|
"UPLOAD)"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -310,5 +310,19 @@ def test_format_sub_state_event(upload_sub_state_event_spec):
|
|||||||
assert (
|
assert (
|
||||||
format.format_upload_event(upload_sub_state_event_spec)
|
format.format_upload_event(upload_sub_state_event_spec)
|
||||||
== "uploadFiles(base_state, result, setResult, base_state.files, "
|
== "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)"
|
||||||
)
|
)
|
||||||
|
Loading…
Reference in New Issue
Block a user