diff --git a/reflex/.templates/web/utils/state.js b/reflex/.templates/web/utils/state.js index 9afb618db..299aab047 100644 --- a/reflex/.templates/web/utils/state.js +++ b/reflex/.templates/web/utils/state.js @@ -323,21 +323,26 @@ export const uploadFiles = async (state, setResult, handler) => { } // Send the file to the server. - await axios.post(UPLOADURL, formdata, headers).then((response) => { - // Apply the delta and set the result. - const update = response.data; - applyDelta(state, update.delta); - - // Set processing to false and return. - setResult({ - state: state, - events: update.events, - final: true, - processing: false, - }); - }); - - return true; + await axios.post(UPLOADURL, formdata, headers) + .then(() => { return true; }) + .catch( + error => { + if (error.response) { + // The request was made and the server responded with a status code + // that falls out of the range of 2xx + console.log(error.response.data); + } else if (error.request) { + // The request was made but no response was received + // `error.request` is an instance of XMLHttpRequest in the browser and an instance of + // http.ClientRequest in node.js + console.log(error.request); + } else { + // Something happened in setting up the request that triggered an Error + console.log(error.message); + } + return false; + } + ) }; /** diff --git a/reflex/app.py b/reflex/app.py index 490f868d8..c0850c40c 100644 --- a/reflex/app.py +++ b/reflex/app.py @@ -88,6 +88,9 @@ class App(Base): # The component to render if there is a connection error to the server. connect_error_component: Optional[Component] = None + # The async server name space + event_namespace: Optional[AsyncNamespace] = None + def __init__(self, *args, **kwargs): """Initialize the app. @@ -127,11 +130,10 @@ class App(Base): self.socket_app = ASGIApp(self.sio, socketio_path="") # Create the event namespace and attach the main app. Not related to any paths. - event_namespace = EventNamespace("/event", self) + self.event_namespace = EventNamespace("/event", self) # Register the event namespace with the socket. - self.sio.register_namespace(event_namespace) - + self.sio.register_namespace(self.event_namespace) # Mount the socket app with the API. self.api.mount(str(constants.Endpoint.EVENT), self.socket_app) @@ -592,9 +594,6 @@ def upload(app: App): Args: files: The file(s) to upload. - Returns: - The state update after processing the event. - Raises: ValueError: if there are no args with supported annotation. """ @@ -603,10 +602,10 @@ def upload(app: App): for file in files: assert file.filename is not None file.filename = file.filename.split(":")[-1] - # Get the state for the session. state = app.state_manager.get_state(token) - + # get the current session ID + sid = state.get_sid() # get the current state(parent state/substate) path = handler.split(".")[:-1] current_state = state.get_substate(path) @@ -636,12 +635,17 @@ def upload(app: App): name=handler, payload={handler_upload_param[0]: files}, ) - # TODO: refactor this to handle yields. - update = await state._process(event).__anext__() - + async for update in state._process(event): + # Postprocess the event. + update = await app.postprocess(state, event, update) + # Send update to client + await asyncio.create_task( + app.event_namespace.emit( # type: ignore + str(constants.SocketEvent.EVENT), update.json(), to=sid + ) + ) # Set the state for the session. app.state_manager.set_state(event.token, state) - return update return upload_file diff --git a/tests/test_app.py b/tests/test_app.py index 1586a2ed7..bd85ffe7d 100644 --- a/tests/test_app.py +++ b/tests/test_app.py @@ -602,7 +602,7 @@ async def test_dict_mutation_detection__plain_list( @pytest.mark.asyncio @pytest.mark.parametrize( - "fixture, expected", + "fixture, delta", [ ( "upload_state", @@ -626,22 +626,23 @@ async def test_dict_mutation_detection__plain_list( ), ], ) -async def test_upload_file(fixture, request, expected): +async def test_upload_file(fixture, request, delta): """Test that file upload works correctly. Args: fixture: The state. request: Fixture request. - expected: Expected delta + delta: Expected delta """ + app = App(state=request.getfixturevalue(fixture)) + app.event_namespace.emit = AsyncMock() # type: ignore + current_state = app.state_manager.get_state("token") data = b"This is binary data" # Create a binary IO object and write data to it bio = io.BytesIO() bio.write(data) - app = App(state=request.getfixturevalue(fixture)) - file1 = UploadFile( filename="token:file_upload_state.multi_handle_upload:True:image1.jpg", file=bio, @@ -650,10 +651,17 @@ async def test_upload_file(fixture, request, expected): filename="token:file_upload_state.multi_handle_upload:True:image2.jpg", file=bio, ) - fn = upload(app) - result = await fn([file1, file2]) # type: ignore - assert isinstance(result, StateUpdate) - assert result.delta == expected + upload_fn = upload(app) + await upload_fn([file1, file2]) + state_update = StateUpdate(delta=delta, events=[], final=True) + + app.event_namespace.emit.assert_called_with( # type: ignore + "event", state_update.json(), to=current_state.get_sid() + ) + assert app.state_manager.get_state("token").dict()["img_list"] == [ + "image1.jpg", + "image2.jpg", + ] @pytest.mark.asyncio