Update upload event to use queue (#1005)
This commit is contained in:
parent
7e8a4930ba
commit
139e4cf05b
@ -2,7 +2,9 @@
|
||||
import axios from "axios";
|
||||
import io from "socket.io-client";
|
||||
import JSON5 from "json5";
|
||||
import config from "../pynecone.json"
|
||||
|
||||
const UPLOAD = config.uploadUrl;
|
||||
// Global variable to hold the token.
|
||||
let token;
|
||||
|
||||
@ -92,7 +94,7 @@ export const applyEvent = async (event, router, socket) => {
|
||||
|
||||
if (event.name == "_set_value") {
|
||||
event.payload.ref.current.value = event.payload.value;
|
||||
return false;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Send the event to the server.
|
||||
@ -106,6 +108,24 @@ export const applyEvent = async (event, router, socket) => {
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Process an event off the event queue.
|
||||
* @param queue_event The current event
|
||||
* @param state The state with the event queue.
|
||||
* @param setResult The function to set the result.
|
||||
*/
|
||||
export const applyRestEvent = async (
|
||||
queue_event,
|
||||
state,
|
||||
setResult,
|
||||
) => {
|
||||
if (queue_event.handler == "uploadFiles") {
|
||||
await uploadFiles(state, setResult, queue_event.name, UPLOAD)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Process an event off the event queue.
|
||||
* @param state The state with the event queue.
|
||||
@ -132,19 +152,29 @@ export const updateState = async (
|
||||
setResult({ ...result, processing: true });
|
||||
|
||||
// Pop the next event off the queue and apply it.
|
||||
const event = state.events.shift();
|
||||
|
||||
const queue_event = state.events.shift();
|
||||
// Set new events to avoid reprocessing the same event.
|
||||
setState({ ...state, events: state.events });
|
||||
|
||||
// Apply the event.
|
||||
const eventSent = await applyEvent(event, router, socket);
|
||||
if (!eventSent) {
|
||||
// If no event was sent, set processing to false and return.
|
||||
setResult({ ...state, processing: false });
|
||||
// Process events with handlers via REST and all others via websockets.
|
||||
if (queue_event.handler) {
|
||||
|
||||
await applyRestEvent(queue_event, state, setResult)
|
||||
|
||||
|
||||
}
|
||||
else {
|
||||
const eventSent = await applyEvent(queue_event, router, socket);
|
||||
if (!eventSent) {
|
||||
// If no event was sent, set processing to false and return.
|
||||
setResult({ ...state, processing: false });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Connect to a websocket and set the handlers.
|
||||
* @param socket The socket object to connect.
|
||||
@ -196,26 +226,21 @@ export const connect = async (
|
||||
*
|
||||
* @param state The state to apply the delta to.
|
||||
* @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 (
|
||||
state,
|
||||
result,
|
||||
setResult,
|
||||
files,
|
||||
handler,
|
||||
endpoint
|
||||
) => {
|
||||
// If we are already processing an event, or there are no upload files, return.
|
||||
if (result.processing || files.length == 0) {
|
||||
return;
|
||||
}
|
||||
const files = state.files
|
||||
|
||||
// Set processing to true to block other events from being processed.
|
||||
setResult({ ...result, processing: true });
|
||||
// return if there's no file to upload
|
||||
if (files.length == 0) {
|
||||
return
|
||||
}
|
||||
|
||||
const headers = {
|
||||
"Content-Type": files[0].type,
|
||||
@ -246,10 +271,12 @@ export const uploadFiles = async (
|
||||
* Create an event object.
|
||||
* @param name The name of the event.
|
||||
* @param payload The payload of the event.
|
||||
* @param use_websocket Whether the event uses websocket.
|
||||
* @param handler The client handler to process event.
|
||||
* @returns The event object.
|
||||
*/
|
||||
export const E = (name, payload) => {
|
||||
return { name, payload };
|
||||
export const E = (name, payload = {}, handler = null) => {
|
||||
return { name, payload, handler };
|
||||
};
|
||||
|
||||
|
||||
@ -259,5 +286,5 @@ export const E = (name, payload) => {
|
||||
* @returns True if the value is truthy, false otherwise.
|
||||
*/
|
||||
export const isTrue = (val) => {
|
||||
return Array.isArray(val) ? val.length > 0 : !!val
|
||||
return Array.isArray(val) ? val.length > 0 : !!val
|
||||
}
|
||||
|
@ -77,10 +77,7 @@ class Tag(Base):
|
||||
elif isinstance(prop, EventChain):
|
||||
local_args = ",".join(([str(a) for a in prop.events[0].local_args]))
|
||||
|
||||
if len(prop.events) == 1 and prop.events[0].upload:
|
||||
# Special case for upload events.
|
||||
event = format.format_upload_event(prop.events[0])
|
||||
elif prop.full_control:
|
||||
if prop.full_control:
|
||||
# Full control component events.
|
||||
event = format.format_full_control_event(prop)
|
||||
else:
|
||||
|
@ -61,7 +61,10 @@ class EventHandler(Base):
|
||||
for arg in args:
|
||||
# Special case for file uploads.
|
||||
if isinstance(arg, FileUpload):
|
||||
return EventSpec(handler=self, upload=True)
|
||||
return EventSpec(
|
||||
handler=self,
|
||||
client_handler_name="uploadFiles",
|
||||
)
|
||||
|
||||
# Otherwise, convert to JSON.
|
||||
try:
|
||||
@ -86,15 +89,15 @@ class EventSpec(Base):
|
||||
# The event handler.
|
||||
handler: EventHandler
|
||||
|
||||
# The handler on the client to process event.
|
||||
client_handler_name: str = ""
|
||||
|
||||
# The local arguments on the frontend.
|
||||
local_args: Tuple[Var, ...] = ()
|
||||
|
||||
# The arguments to pass to the function.
|
||||
args: Tuple[Tuple[Var, Var], ...] = ()
|
||||
|
||||
# Whether to upload files.
|
||||
upload: bool = False
|
||||
|
||||
class Config:
|
||||
"""The Pydantic config."""
|
||||
|
||||
|
@ -83,6 +83,9 @@ def run(
|
||||
frontend_port = get_config().port if port is None else port
|
||||
backend_port = get_config().backend_port if backend_port is None else backend_port
|
||||
|
||||
# set the upload url in pynecone.json file
|
||||
build.set_pynecone_upload_endpoint()
|
||||
|
||||
# If --no-frontend-only and no --backend-only, then turn on frontend and backend both
|
||||
if not frontend and not backend:
|
||||
frontend = True
|
||||
|
@ -20,13 +20,33 @@ if TYPE_CHECKING:
|
||||
from pynecone.app import App
|
||||
|
||||
|
||||
def update_json_file(file_path, key, value):
|
||||
"""Update the contents of a json file.
|
||||
|
||||
Args:
|
||||
file_path: the path to the JSON file.
|
||||
key: object key to update.
|
||||
value: value of key.
|
||||
"""
|
||||
with open(file_path) as f: # type: ignore
|
||||
json_object = json.load(f)
|
||||
json_object[key] = value
|
||||
with open(file_path, "w") as f:
|
||||
json.dump(json_object, f, ensure_ascii=False)
|
||||
|
||||
|
||||
def set_pynecone_project_hash():
|
||||
"""Write the hash of the Pynecone project to a PCVERSION_APP_FILE."""
|
||||
with open(constants.PCVERSION_APP_FILE) as f: # type: ignore
|
||||
pynecone_json = json.load(f)
|
||||
pynecone_json["project_hash"] = random.getrandbits(128)
|
||||
with open(constants.PCVERSION_APP_FILE, "w") as f:
|
||||
json.dump(pynecone_json, f, ensure_ascii=False)
|
||||
update_json_file(
|
||||
constants.PCVERSION_APP_FILE, "project_hash", random.getrandbits(128)
|
||||
)
|
||||
|
||||
|
||||
def set_pynecone_upload_endpoint():
|
||||
"""Write the upload url to a PCVERSION_APP_FILE."""
|
||||
update_json_file(
|
||||
constants.PCVERSION_APP_FILE, "uploadUrl", constants.Endpoint.UPLOAD.get_url()
|
||||
)
|
||||
|
||||
|
||||
def generate_sitemap(deploy_url: str):
|
||||
|
@ -294,21 +294,8 @@ def format_event(event_spec: EventSpec) -> str:
|
||||
for name, val in event_spec.args
|
||||
]
|
||||
)
|
||||
return f"E(\"{format_event_handler(event_spec.handler)}\", {wrap(args, '{')})"
|
||||
|
||||
|
||||
def format_upload_event(event_spec: EventSpec) -> str:
|
||||
"""Format an upload event.
|
||||
|
||||
Args:
|
||||
event_spec: The event to format.
|
||||
|
||||
Returns:
|
||||
The compiled event.
|
||||
"""
|
||||
state, name = get_event_handler_parts(event_spec.handler)
|
||||
parent_state = state.split(".")[0]
|
||||
return f'uploadFiles({parent_state}, {constants.RESULT}, set{constants.RESULT.capitalize()}, {parent_state}.files, "{state}.{name}",UPLOAD)'
|
||||
quote = '"'
|
||||
return f"E(\"{format_event_handler(event_spec.handler)}\", {wrap(args, '{')}, {wrap(event_spec.client_handler_name, quote) if event_spec.client_handler_name else ''})"
|
||||
|
||||
|
||||
def format_full_control_event(event_chain: EventChain) -> str:
|
||||
|
@ -25,7 +25,7 @@ def mock_event(arg):
|
||||
({"a": 1, "b": 2, "c": 3}, '{{"a": 1, "b": 2, "c": 3}}'),
|
||||
(
|
||||
EventChain(events=[EventSpec(handler=EventHandler(fn=mock_event))]),
|
||||
'{() => Event([E("mock_event", {})])}',
|
||||
'{() => Event([E("mock_event", {}, )])}',
|
||||
),
|
||||
(
|
||||
EventChain(
|
||||
@ -37,7 +37,7 @@ def mock_event(arg):
|
||||
)
|
||||
]
|
||||
),
|
||||
'{(_e) => Event([E("mock_event", {arg:_e.target.value})])}',
|
||||
'{(_e) => Event([E("mock_event", {arg:_e.target.value}, )])}',
|
||||
),
|
||||
({"a": "red", "b": "blue"}, '{{"a": "red", "b": "blue"}}'),
|
||||
(BaseVar(name="var", type_="int"), "{var}"),
|
||||
|
@ -47,7 +47,7 @@ def test_call_event_handler():
|
||||
assert event_spec.handler == handler
|
||||
assert event_spec.local_args == ()
|
||||
assert event_spec.args == ()
|
||||
assert format.format_event(event_spec) == 'E("test_fn", {})'
|
||||
assert format.format_event(event_spec) == 'E("test_fn", {}, )'
|
||||
|
||||
handler = EventHandler(fn=test_fn_with_args)
|
||||
event_spec = handler(make_var("first"), make_var("second"))
|
||||
@ -58,14 +58,14 @@ def test_call_event_handler():
|
||||
assert event_spec.args == (("arg1", "first"), ("arg2", "second"))
|
||||
assert (
|
||||
format.format_event(event_spec)
|
||||
== 'E("test_fn_with_args", {arg1:first,arg2:second})'
|
||||
== 'E("test_fn_with_args", {arg1:first,arg2:second}, )'
|
||||
)
|
||||
|
||||
# Passing args as strings should format differently.
|
||||
event_spec = handler("first", "second") # type: ignore
|
||||
assert (
|
||||
format.format_event(event_spec)
|
||||
== 'E("test_fn_with_args", {arg1:"first",arg2:"second"})'
|
||||
== 'E("test_fn_with_args", {arg1:"first",arg2:"second"}, )'
|
||||
)
|
||||
|
||||
first, second = 123, "456"
|
||||
@ -73,7 +73,7 @@ def test_call_event_handler():
|
||||
event_spec = handler(first, second) # type: ignore
|
||||
assert (
|
||||
format.format_event(event_spec)
|
||||
== 'E("test_fn_with_args", {arg1:123,arg2:"456"})'
|
||||
== 'E("test_fn_with_args", {arg1:123,arg2:"456"}, )'
|
||||
)
|
||||
|
||||
assert event_spec.handler == handler
|
||||
@ -94,9 +94,9 @@ def test_event_redirect():
|
||||
assert isinstance(spec, EventSpec)
|
||||
assert spec.handler.fn.__qualname__ == "_redirect"
|
||||
assert spec.args == (("path", "/path"),)
|
||||
assert format.format_event(spec) == 'E("_redirect", {path:"/path"})'
|
||||
assert format.format_event(spec) == 'E("_redirect", {path:"/path"}, )'
|
||||
spec = event.redirect(Var.create_safe("path"))
|
||||
assert format.format_event(spec) == 'E("_redirect", {path:path})'
|
||||
assert format.format_event(spec) == 'E("_redirect", {path:path}, )'
|
||||
|
||||
|
||||
def test_event_console_log():
|
||||
@ -105,9 +105,9 @@ def test_event_console_log():
|
||||
assert isinstance(spec, EventSpec)
|
||||
assert spec.handler.fn.__qualname__ == "_console"
|
||||
assert spec.args == (("message", "message"),)
|
||||
assert format.format_event(spec) == 'E("_console", {message:"message"})'
|
||||
assert format.format_event(spec) == 'E("_console", {message:"message"}, )'
|
||||
spec = event.console_log(Var.create_safe("message"))
|
||||
assert format.format_event(spec) == 'E("_console", {message:message})'
|
||||
assert format.format_event(spec) == 'E("_console", {message:message}, )'
|
||||
|
||||
|
||||
def test_event_window_alert():
|
||||
@ -116,9 +116,9 @@ def test_event_window_alert():
|
||||
assert isinstance(spec, EventSpec)
|
||||
assert spec.handler.fn.__qualname__ == "_alert"
|
||||
assert spec.args == (("message", "message"),)
|
||||
assert format.format_event(spec) == 'E("_alert", {message:"message"})'
|
||||
assert format.format_event(spec) == 'E("_alert", {message:"message"}, )'
|
||||
spec = event.window_alert(Var.create_safe("message"))
|
||||
assert format.format_event(spec) == 'E("_alert", {message:message})'
|
||||
assert format.format_event(spec) == 'E("_alert", {message:message}, )'
|
||||
|
||||
|
||||
def test_set_value():
|
||||
@ -130,8 +130,8 @@ def test_set_value():
|
||||
("ref", Var.create_safe("ref_input1")),
|
||||
("value", ""),
|
||||
)
|
||||
assert format.format_event(spec) == 'E("_set_value", {ref:ref_input1,value:""})'
|
||||
assert format.format_event(spec) == 'E("_set_value", {ref:ref_input1,value:""}, )'
|
||||
spec = event.set_value("input1", Var.create_safe("message"))
|
||||
assert (
|
||||
format.format_event(spec) == 'E("_set_value", {ref:ref_input1,value:message})'
|
||||
format.format_event(spec) == 'E("_set_value", {ref:ref_input1,value:message}, )'
|
||||
)
|
||||
|
@ -366,33 +366,6 @@ def test_issubclass(cls: type, cls_check: type, expected: bool):
|
||||
assert types._issubclass(cls, cls_check) == expected
|
||||
|
||||
|
||||
def test_format_sub_state_event(upload_sub_state_event_spec):
|
||||
"""Test formatting an upload event spec of substate.
|
||||
|
||||
Args:
|
||||
upload_sub_state_event_spec: The event spec fixture.
|
||||
"""
|
||||
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)'
|
||||
)
|
||||
|
||||
|
||||
def test_format_upload_event(upload_event_spec):
|
||||
"""Test formatting an upload event spec.
|
||||
|
||||
Args:
|
||||
upload_event_spec: The event spec fixture.
|
||||
"""
|
||||
assert (
|
||||
format.format_upload_event(upload_event_spec)
|
||||
== "uploadFiles(upload_state, result, setResult, "
|
||||
'upload_state.files, "upload_state.handle_upload1",'
|
||||
"UPLOAD)"
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"app_name,expected_config_name",
|
||||
[
|
||||
|
Loading…
Reference in New Issue
Block a user