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 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;
|
||||||
|
|
||||||
@ -92,7 +94,7 @@ export const applyEvent = async (event, router, socket) => {
|
|||||||
|
|
||||||
if (event.name == "_set_value") {
|
if (event.name == "_set_value") {
|
||||||
event.payload.ref.current.value = event.payload.value;
|
event.payload.ref.current.value = event.payload.value;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send the event to the server.
|
// Send the event to the server.
|
||||||
@ -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) {
|
||||||
if (!eventSent) {
|
|
||||||
// If no event was sent, set processing to false and return.
|
await applyRestEvent(queue_event, state, setResult)
|
||||||
setResult({ ...state, processing: false });
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
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.
|
* 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 };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
@ -259,5 +286,5 @@ export const E = (name, payload) => {
|
|||||||
* @returns True if the value is truthy, false otherwise.
|
* @returns True if the value is truthy, false otherwise.
|
||||||
*/
|
*/
|
||||||
export const isTrue = (val) => {
|
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):
|
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:
|
||||||
|
@ -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."""
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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):
|
||||||
|
@ -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:
|
||||||
|
@ -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}"),
|
||||||
|
@ -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}, )'
|
||||||
)
|
)
|
||||||
|
@ -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",
|
||||||
[
|
[
|
||||||
|
Loading…
Reference in New Issue
Block a user