From 4505279d5d857769f0876e2728f6e3f4f0e4f3a4 Mon Sep 17 00:00:00 2001 From: Elijah Ahianyo Date: Thu, 20 Jul 2023 01:05:42 +0000 Subject: [PATCH] Synchronize Event Namespace (#1370) --- reflex/app.py | 9 ++++++++- reflex/config.py | 15 +++++++++++++++ reflex/constants.py | 3 ++- tests/test_config.py | 27 ++++++++++++++++++++++++++- 4 files changed, 51 insertions(+), 3 deletions(-) diff --git a/reflex/app.py b/reflex/app.py index c0850c40c..235c0d87a 100644 --- a/reflex/app.py +++ b/reflex/app.py @@ -97,6 +97,9 @@ class App(Base): Args: *args: Args to initialize the app with. **kwargs: Kwargs to initialize the app with. + + Raises: + ValueError: If the event namespace is not provided in the config. """ super().__init__(*args, **kwargs) @@ -128,9 +131,13 @@ class App(Base): # Create the socket app. Note event endpoint constant replaces the default 'socket.io' path. self.socket_app = ASGIApp(self.sio, socketio_path="") + namespace = config.get_event_namespace() + + if not namespace: + raise ValueError("event namespace must be provided in the config.") # Create the event namespace and attach the main app. Not related to any paths. - self.event_namespace = EventNamespace("/event", self) + self.event_namespace = EventNamespace(namespace, self) # Register the event namespace with the socket. self.sio.register_namespace(self.event_namespace) diff --git a/reflex/config.py b/reflex/config.py index 4e2345b23..727b801fd 100644 --- a/reflex/config.py +++ b/reflex/config.py @@ -206,6 +206,9 @@ class Config(Base): # Whether to enable or disable nextJS gzip compression. next_compression: bool = True + # The event namespace for ws connection + event_namespace: Optional[str] = constants.EVENT_NAMESPACE + def __init__(self, *args, **kwargs): """Initialize the config values. @@ -251,6 +254,18 @@ class Config(Base): except AttributeError: pass + def get_event_namespace(self) -> Optional[str]: + """Get the websocket event namespace. + + Returns: + The namespace for websocket. + """ + if self.event_namespace: + return f'/{self.event_namespace.strip("/")}' + + event_url = constants.Endpoint.EVENT.get_url() + return urllib.parse.urlsplit(event_url).path + def get_config() -> Config: """Get the app config. diff --git a/reflex/constants.py b/reflex/constants.py index d64138dfa..f8adf5744 100644 --- a/reflex/constants.py +++ b/reflex/constants.py @@ -197,7 +197,8 @@ OLD_CONFIG_FILE = f"pcconfig{PY_EXT}" PRODUCTION_BACKEND_URL = "https://{username}-{app_name}.api.pynecone.app" # Token expiration time in seconds. TOKEN_EXPIRATION = 60 * 60 - +# The event namespace for websocket +EVENT_NAMESPACE = get_value("EVENT_NAMESPACE") # Env modes class Env(str, Enum): diff --git a/tests/test_config.py b/tests/test_config.py index 291608046..c2a390b46 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -5,7 +5,7 @@ import pytest import reflex as rx from reflex import constants -from reflex.config import DBConfig +from reflex.config import DBConfig, get_config from reflex.constants import get_value @@ -133,3 +133,28 @@ def test_get_value(monkeypatch, key, value, expected_value_type_in_config): casted_value = get_value(key, type_=expected_value_type_in_config) assert isinstance(casted_value, expected_value_type_in_config) + + +@pytest.mark.parametrize( + "kwargs, expected", + [ + ({"app_name": "test_app", "api_url": "http://example.com"}, "/event"), + ({"app_name": "test_app", "api_url": "http://example.com/api"}, "/api/event"), + ({"app_name": "test_app", "event_namespace": "/event"}, "/event"), + ({"app_name": "test_app", "event_namespace": "event"}, "/event"), + ({"app_name": "test_app", "event_namespace": "event/"}, "/event"), + ], +) +def test_event_namespace(mocker, kwargs, expected): + """Test the event namespace. + + Args: + mocker: The pytest mock object. + kwargs: The Config kwargs. + expected: Expected namespace + """ + conf = rx.Config(**kwargs) + mocker.patch("reflex.config.get_config", return_value=conf) + + config = get_config() + assert config.get_event_namespace() == expected