Sfoglia il codice sorgente

Update upload event to use queue (#1005)

Elijah Ahianyo 2 anni fa
parent
commit
139e4cf05b

+ 48 - 21
pynecone/.templates/web/utils/state.js

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

+ 1 - 4
pynecone/components/tags/tag.py

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

+ 7 - 4
pynecone/event.py

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

+ 3 - 0
pynecone/pc.py

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

+ 25 - 5
pynecone/utils/build.py

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

+ 2 - 15
pynecone/utils/format.py

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

+ 2 - 2
tests/components/test_tag.py

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

+ 12 - 12
tests/test_event.py

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

+ 0 - 27
tests/test_utils.py

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