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 axios from "axios";
import io from "socket.io-client"; import io from "socket.io-client";
import JSON5 from "json5"; import JSON5 from "json5";
import config from "../pynecone.json"
const UPLOAD = config.uploadUrl;
// Global variable to hold the token. // Global variable to hold the token.
let token; let token;
@ -106,6 +108,24 @@ export const applyEvent = async (event, router, socket) => {
return false; 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. * Process an event off the event queue.
* @param state The state with the event queue. * @param state The state with the event queue.
@ -132,19 +152,29 @@ export const updateState = async (
setResult({ ...result, processing: true }); setResult({ ...result, processing: true });
// Pop the next event off the queue and apply it. // 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. // Set new events to avoid reprocessing the same event.
setState({ ...state, events: state.events }); setState({ ...state, events: state.events });
// Apply the event. // Process events with handlers via REST and all others via websockets.
const eventSent = await applyEvent(event, router, socket); if (queue_event.handler) {
await applyRestEvent(queue_event, state, setResult)
}
else {
const eventSent = await applyEvent(queue_event, router, socket);
if (!eventSent) { if (!eventSent) {
// If no event was sent, set processing to false and return. // If no event was sent, set processing to false and return.
setResult({ ...state, processing: false }); setResult({ ...state, processing: false });
} }
}
}; };
/** /**
* Connect to a websocket and set the handlers. * Connect to a websocket and set the handlers.
* @param socket The socket object to connect. * @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 state The state to apply the delta to.
* @param setResult The function to set the result. * @param setResult The function to set the result.
* @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 (
state, state,
result,
setResult, setResult,
files,
handler, handler,
endpoint endpoint
) => { ) => {
// If we are already processing an event, or there are no upload files, return. const files = state.files
if (result.processing || files.length == 0) {
return;
}
// Set processing to true to block other events from being processed. // return if there's no file to upload
setResult({ ...result, processing: true }); if (files.length == 0) {
return
}
const headers = { const headers = {
"Content-Type": files[0].type, "Content-Type": files[0].type,
@ -246,10 +271,12 @@ export const uploadFiles = async (
* Create an event object. * Create an event object.
* @param name The name of the event. * @param name The name of the event.
* @param payload The payload 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. * @returns The event object.
*/ */
export const E = (name, payload) => { export const E = (name, payload = {}, handler = null) => {
return { name, payload }; return { name, payload, handler };
}; };

View File

@ -77,10 +77,7 @@ class Tag(Base):
elif isinstance(prop, EventChain): elif isinstance(prop, EventChain):
local_args = ",".join(([str(a) for a in prop.events[0].local_args])) local_args = ",".join(([str(a) for a in prop.events[0].local_args]))
if len(prop.events) == 1 and prop.events[0].upload: if prop.full_control:
# Special case for upload events.
event = format.format_upload_event(prop.events[0])
elif prop.full_control:
# Full control component events. # Full control component events.
event = format.format_full_control_event(prop) event = format.format_full_control_event(prop)
else: else:

View File

@ -61,7 +61,10 @@ class EventHandler(Base):
for arg in args: for arg in args:
# Special case for file uploads. # Special case for file uploads.
if isinstance(arg, FileUpload): if isinstance(arg, FileUpload):
return EventSpec(handler=self, upload=True) return EventSpec(
handler=self,
client_handler_name="uploadFiles",
)
# Otherwise, convert to JSON. # Otherwise, convert to JSON.
try: try:
@ -86,15 +89,15 @@ class EventSpec(Base):
# The event handler. # The event handler.
handler: EventHandler handler: EventHandler
# The handler on the client to process event.
client_handler_name: str = ""
# The local arguments on the frontend. # The local arguments on the frontend.
local_args: Tuple[Var, ...] = () local_args: Tuple[Var, ...] = ()
# The arguments to pass to the function. # The arguments to pass to the function.
args: Tuple[Tuple[Var, Var], ...] = () args: Tuple[Tuple[Var, Var], ...] = ()
# Whether to upload files.
upload: bool = False
class Config: class Config:
"""The Pydantic config.""" """The Pydantic config."""

View File

@ -83,6 +83,9 @@ def run(
frontend_port = get_config().port if port is None else port 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 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 --no-frontend-only and no --backend-only, then turn on frontend and backend both
if not frontend and not backend: if not frontend and not backend:
frontend = True frontend = True

View File

@ -20,13 +20,33 @@ if TYPE_CHECKING:
from pynecone.app import App 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(): def set_pynecone_project_hash():
"""Write the hash of the Pynecone project to a PCVERSION_APP_FILE.""" """Write the hash of the Pynecone project to a PCVERSION_APP_FILE."""
with open(constants.PCVERSION_APP_FILE) as f: # type: ignore update_json_file(
pynecone_json = json.load(f) constants.PCVERSION_APP_FILE, "project_hash", random.getrandbits(128)
pynecone_json["project_hash"] = random.getrandbits(128) )
with open(constants.PCVERSION_APP_FILE, "w") as f:
json.dump(pynecone_json, f, ensure_ascii=False)
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): 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 for name, val in event_spec.args
] ]
) )
return f"E(\"{format_event_handler(event_spec.handler)}\", {wrap(args, '{')})" 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_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)'
def format_full_control_event(event_chain: EventChain) -> str: 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}}'), ({"a": 1, "b": 2, "c": 3}, '{{"a": 1, "b": 2, "c": 3}}'),
( (
EventChain(events=[EventSpec(handler=EventHandler(fn=mock_event))]), EventChain(events=[EventSpec(handler=EventHandler(fn=mock_event))]),
'{() => Event([E("mock_event", {})])}', '{() => Event([E("mock_event", {}, )])}',
), ),
( (
EventChain( 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"}}'), ({"a": "red", "b": "blue"}, '{{"a": "red", "b": "blue"}}'),
(BaseVar(name="var", type_="int"), "{var}"), (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.handler == handler
assert event_spec.local_args == () assert event_spec.local_args == ()
assert event_spec.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) handler = EventHandler(fn=test_fn_with_args)
event_spec = handler(make_var("first"), make_var("second")) 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 event_spec.args == (("arg1", "first"), ("arg2", "second"))
assert ( assert (
format.format_event(event_spec) 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. # Passing args as strings should format differently.
event_spec = handler("first", "second") # type: ignore event_spec = handler("first", "second") # type: ignore
assert ( assert (
format.format_event(event_spec) 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" first, second = 123, "456"
@ -73,7 +73,7 @@ def test_call_event_handler():
event_spec = handler(first, second) # type: ignore event_spec = handler(first, second) # type: ignore
assert ( assert (
format.format_event(event_spec) 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 assert event_spec.handler == handler
@ -94,9 +94,9 @@ def test_event_redirect():
assert isinstance(spec, EventSpec) assert isinstance(spec, EventSpec)
assert spec.handler.fn.__qualname__ == "_redirect" assert spec.handler.fn.__qualname__ == "_redirect"
assert spec.args == (("path", "/path"),) 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")) 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(): def test_event_console_log():
@ -105,9 +105,9 @@ def test_event_console_log():
assert isinstance(spec, EventSpec) assert isinstance(spec, EventSpec)
assert spec.handler.fn.__qualname__ == "_console" assert spec.handler.fn.__qualname__ == "_console"
assert spec.args == (("message", "message"),) 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")) 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(): def test_event_window_alert():
@ -116,9 +116,9 @@ def test_event_window_alert():
assert isinstance(spec, EventSpec) assert isinstance(spec, EventSpec)
assert spec.handler.fn.__qualname__ == "_alert" assert spec.handler.fn.__qualname__ == "_alert"
assert spec.args == (("message", "message"),) 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")) 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(): def test_set_value():
@ -130,8 +130,8 @@ def test_set_value():
("ref", Var.create_safe("ref_input1")), ("ref", Var.create_safe("ref_input1")),
("value", ""), ("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")) spec = event.set_value("input1", Var.create_safe("message"))
assert ( 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 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( @pytest.mark.parametrize(
"app_name,expected_config_name", "app_name,expected_config_name",
[ [