Update upload event to use queue (#1005)

This commit is contained in:
Elijah Ahianyo 2023-05-12 23:44:16 +00:00 committed by GitHub
parent 7e8a4930ba
commit 139e4cf05b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 100 additions and 90 deletions

View File

@ -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
}

View File

@ -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:

View File

@ -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."""

View File

@ -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

View File

@ -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):

View File

@ -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:

View File

@ -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}"),

View File

@ -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}, )'
)

View File

@ -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",
[