fix public API for some attributes of App()
This commit is contained in:
parent
83c1ee35bc
commit
4dfe739e45
@ -274,7 +274,7 @@ class App(MiddlewareMixin, LifespanMixin):
|
|||||||
)
|
)
|
||||||
|
|
||||||
# Admin dashboard to view and manage the database.
|
# Admin dashboard to view and manage the database.
|
||||||
_admin_dash: Optional[AdminDash] = None
|
admin_dash: Optional[AdminDash] = None
|
||||||
|
|
||||||
# The async server name space. PRIVATE.
|
# The async server name space. PRIVATE.
|
||||||
_event_namespace: Optional[EventNamespace] = None
|
_event_namespace: Optional[EventNamespace] = None
|
||||||
@ -292,6 +292,24 @@ class App(MiddlewareMixin, LifespanMixin):
|
|||||||
[Exception], Union[EventSpec, List[EventSpec], None]
|
[Exception], Union[EventSpec, List[EventSpec], None]
|
||||||
] = default_backend_exception_handler
|
] = default_backend_exception_handler
|
||||||
|
|
||||||
|
@property
|
||||||
|
def api(self) -> FastAPI | None:
|
||||||
|
"""Get the backend api.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The backend api.
|
||||||
|
"""
|
||||||
|
return self._api
|
||||||
|
|
||||||
|
@property
|
||||||
|
def event_namespace(self) -> EventNamespace | None:
|
||||||
|
"""Get the event namespace.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The event namespace.
|
||||||
|
"""
|
||||||
|
return self._event_namespace
|
||||||
|
|
||||||
def __post_init__(self):
|
def __post_init__(self):
|
||||||
"""Initialize the app.
|
"""Initialize the app.
|
||||||
|
|
||||||
@ -384,10 +402,10 @@ class App(MiddlewareMixin, LifespanMixin):
|
|||||||
self._event_namespace = EventNamespace(namespace, self)
|
self._event_namespace = EventNamespace(namespace, self)
|
||||||
|
|
||||||
# Register the event namespace with the socket.
|
# Register the event namespace with the socket.
|
||||||
self.sio.register_namespace(self._event_namespace)
|
self.sio.register_namespace(self.event_namespace)
|
||||||
# Mount the socket app with the API.
|
# Mount the socket app with the API.
|
||||||
if self._api:
|
if self.api:
|
||||||
self._api.mount(str(constants.Endpoint.EVENT), socket_app)
|
self.api.mount(str(constants.Endpoint.EVENT), socket_app)
|
||||||
|
|
||||||
# Check the exception handlers
|
# Check the exception handlers
|
||||||
self._validate_exception_handlers()
|
self._validate_exception_handlers()
|
||||||
@ -409,44 +427,44 @@ class App(MiddlewareMixin, LifespanMixin):
|
|||||||
Returns:
|
Returns:
|
||||||
The backend api.
|
The backend api.
|
||||||
"""
|
"""
|
||||||
if not self._api:
|
if not self.api:
|
||||||
raise ValueError("The app has not been initialized.")
|
raise ValueError("The app has not been initialized.")
|
||||||
return self._api
|
return self.api
|
||||||
|
|
||||||
def _add_default_endpoints(self):
|
def _add_default_endpoints(self):
|
||||||
"""Add default api endpoints (ping)."""
|
"""Add default api endpoints (ping)."""
|
||||||
# To test the server.
|
# To test the server.
|
||||||
if not self._api:
|
if not self.api:
|
||||||
return
|
return
|
||||||
|
|
||||||
self._api.get(str(constants.Endpoint.PING))(ping)
|
self.api.get(str(constants.Endpoint.PING))(ping)
|
||||||
self._api.get(str(constants.Endpoint.HEALTH))(health)
|
self.api.get(str(constants.Endpoint.HEALTH))(health)
|
||||||
|
|
||||||
def _add_optional_endpoints(self):
|
def _add_optional_endpoints(self):
|
||||||
"""Add optional api endpoints (_upload)."""
|
"""Add optional api endpoints (_upload)."""
|
||||||
if not self._api:
|
if not self.api:
|
||||||
return
|
return
|
||||||
|
|
||||||
if Upload.is_used:
|
if Upload.is_used:
|
||||||
# To upload files.
|
# To upload files.
|
||||||
self._api.post(str(constants.Endpoint.UPLOAD))(upload(self))
|
self.api.post(str(constants.Endpoint.UPLOAD))(upload(self))
|
||||||
|
|
||||||
# To access uploaded files.
|
# To access uploaded files.
|
||||||
self._api.mount(
|
self.api.mount(
|
||||||
str(constants.Endpoint.UPLOAD),
|
str(constants.Endpoint.UPLOAD),
|
||||||
StaticFiles(directory=get_upload_dir()),
|
StaticFiles(directory=get_upload_dir()),
|
||||||
name="uploaded_files",
|
name="uploaded_files",
|
||||||
)
|
)
|
||||||
if codespaces.is_running_in_codespaces():
|
if codespaces.is_running_in_codespaces():
|
||||||
self._api.get(str(constants.Endpoint.AUTH_CODESPACE))(
|
self.api.get(str(constants.Endpoint.AUTH_CODESPACE))(
|
||||||
codespaces.auth_codespace
|
codespaces.auth_codespace
|
||||||
)
|
)
|
||||||
|
|
||||||
def _add_cors(self):
|
def _add_cors(self):
|
||||||
"""Add CORS middleware to the app."""
|
"""Add CORS middleware to the app."""
|
||||||
if not self._api:
|
if not self.api:
|
||||||
return
|
return
|
||||||
self._api.add_middleware(
|
self.api.add_middleware(
|
||||||
cors.CORSMiddleware,
|
cors.CORSMiddleware,
|
||||||
allow_credentials=True,
|
allow_credentials=True,
|
||||||
allow_methods=["*"],
|
allow_methods=["*"],
|
||||||
@ -689,10 +707,10 @@ class App(MiddlewareMixin, LifespanMixin):
|
|||||||
def _setup_admin_dash(self):
|
def _setup_admin_dash(self):
|
||||||
"""Setup the admin dash."""
|
"""Setup the admin dash."""
|
||||||
# Get the admin dash.
|
# Get the admin dash.
|
||||||
if not self._api:
|
if not self.api:
|
||||||
return
|
return
|
||||||
|
|
||||||
admin_dash = self._admin_dash
|
admin_dash = self.admin_dash
|
||||||
|
|
||||||
if admin_dash and admin_dash.models:
|
if admin_dash and admin_dash.models:
|
||||||
# Build the admin dashboard
|
# Build the admin dashboard
|
||||||
@ -710,7 +728,7 @@ class App(MiddlewareMixin, LifespanMixin):
|
|||||||
view = admin_dash.view_overrides.get(model, ModelView)
|
view = admin_dash.view_overrides.get(model, ModelView)
|
||||||
admin.add_view(view(model))
|
admin.add_view(view(model))
|
||||||
|
|
||||||
admin.mount_to(self._api)
|
admin.mount_to(self.api)
|
||||||
|
|
||||||
def _get_frontend_packages(self, imports: Dict[str, set[ImportVar]]):
|
def _get_frontend_packages(self, imports: Dict[str, set[ImportVar]]):
|
||||||
"""Gets the frontend packages to be installed and filters out the unnecessary ones.
|
"""Gets the frontend packages to be installed and filters out the unnecessary ones.
|
||||||
@ -1113,7 +1131,7 @@ class App(MiddlewareMixin, LifespanMixin):
|
|||||||
Raises:
|
Raises:
|
||||||
RuntimeError: If the app has not been initialized yet.
|
RuntimeError: If the app has not been initialized yet.
|
||||||
"""
|
"""
|
||||||
if self._event_namespace is None:
|
if self.event_namespace is None:
|
||||||
raise RuntimeError("App has not been initialized yet.")
|
raise RuntimeError("App has not been initialized yet.")
|
||||||
|
|
||||||
# Get exclusive access to the state.
|
# Get exclusive access to the state.
|
||||||
@ -1124,7 +1142,7 @@ class App(MiddlewareMixin, LifespanMixin):
|
|||||||
if delta:
|
if delta:
|
||||||
# When the state is modified reset dirty status and emit the delta to the frontend.
|
# When the state is modified reset dirty status and emit the delta to the frontend.
|
||||||
state._clean()
|
state._clean()
|
||||||
await self._event_namespace.emit_update(
|
await self.event_namespace.emit_update(
|
||||||
update=StateUpdate(delta=delta),
|
update=StateUpdate(delta=delta),
|
||||||
sid=state.router.session.session_id,
|
sid=state.router.session.session_id,
|
||||||
)
|
)
|
||||||
@ -1152,7 +1170,7 @@ class App(MiddlewareMixin, LifespanMixin):
|
|||||||
Raises:
|
Raises:
|
||||||
RuntimeError: If the app has not been initialized yet.
|
RuntimeError: If the app has not been initialized yet.
|
||||||
"""
|
"""
|
||||||
if self._event_namespace is None:
|
if self.event_namespace is None:
|
||||||
raise RuntimeError("App has not been initialized yet.")
|
raise RuntimeError("App has not been initialized yet.")
|
||||||
|
|
||||||
# Process the event.
|
# Process the event.
|
||||||
@ -1163,7 +1181,7 @@ class App(MiddlewareMixin, LifespanMixin):
|
|||||||
update = await self._postprocess(state, event, update)
|
update = await self._postprocess(state, event, update)
|
||||||
|
|
||||||
# Send the update to the client.
|
# Send the update to the client.
|
||||||
await self._event_namespace.emit_update(
|
await self.event_namespace.emit_update(
|
||||||
update=update,
|
update=update,
|
||||||
sid=state.router.session.session_id,
|
sid=state.router.session.session_id,
|
||||||
)
|
)
|
||||||
@ -1308,10 +1326,10 @@ async def process(
|
|||||||
if (
|
if (
|
||||||
not state.router_data
|
not state.router_data
|
||||||
and event.name != get_hydrate_event(state)
|
and event.name != get_hydrate_event(state)
|
||||||
and app._event_namespace is not None
|
and app.event_namespace is not None
|
||||||
):
|
):
|
||||||
await asyncio.create_task(
|
await asyncio.create_task(
|
||||||
app._event_namespace.emit(
|
app.event_namespace.emit(
|
||||||
"reload",
|
"reload",
|
||||||
data=event,
|
data=event,
|
||||||
to=sid,
|
to=sid,
|
||||||
|
@ -36,7 +36,7 @@ class CompileVars(SimpleNamespace):
|
|||||||
# The expected variable name where the rx.App is stored.
|
# The expected variable name where the rx.App is stored.
|
||||||
APP = "app"
|
APP = "app"
|
||||||
# The expected variable name where the API object is stored for deployment.
|
# The expected variable name where the API object is stored for deployment.
|
||||||
API = "_api"
|
API = "api"
|
||||||
# The name of the router variable.
|
# The name of the router variable.
|
||||||
ROUTER = "router"
|
ROUTER = "router"
|
||||||
# The name of the socket variable.
|
# The name of the socket variable.
|
||||||
|
@ -323,11 +323,11 @@ class AppHarness:
|
|||||||
return _shutdown_redis
|
return _shutdown_redis
|
||||||
|
|
||||||
def _start_backend(self, port=0):
|
def _start_backend(self, port=0):
|
||||||
if self.app_instance is None or self.app_instance._api is None:
|
if self.app_instance is None or self.app_instance.api is None:
|
||||||
raise RuntimeError("App was not initialized.")
|
raise RuntimeError("App was not initialized.")
|
||||||
self.backend = uvicorn.Server(
|
self.backend = uvicorn.Server(
|
||||||
uvicorn.Config(
|
uvicorn.Config(
|
||||||
app=self.app_instance._api,
|
app=self.app_instance.api,
|
||||||
host="127.0.0.1",
|
host="127.0.0.1",
|
||||||
port=port,
|
port=port,
|
||||||
)
|
)
|
||||||
|
@ -212,7 +212,7 @@ def test_default_app(app: App):
|
|||||||
"""
|
"""
|
||||||
assert app.middleware == [HydrateMiddleware()]
|
assert app.middleware == [HydrateMiddleware()]
|
||||||
assert app.style == Style()
|
assert app.style == Style()
|
||||||
assert app._admin_dash is None
|
assert app.admin_dash is None
|
||||||
|
|
||||||
|
|
||||||
def test_multiple_states_error(monkeypatch, test_state, redundant_test_state):
|
def test_multiple_states_error(monkeypatch, test_state, redundant_test_state):
|
||||||
@ -357,10 +357,10 @@ def test_initialize_with_admin_dashboard(test_model):
|
|||||||
Args:
|
Args:
|
||||||
test_model: The default model.
|
test_model: The default model.
|
||||||
"""
|
"""
|
||||||
app = App(_admin_dash=AdminDash(models=[test_model]))
|
app = App(admin_dash=AdminDash(models=[test_model]))
|
||||||
assert app._admin_dash is not None
|
assert app.admin_dash is not None
|
||||||
assert len(app._admin_dash.models) > 0
|
assert len(app.admin_dash.models) > 0
|
||||||
assert app._admin_dash.models[0] == test_model
|
assert app.admin_dash.models[0] == test_model
|
||||||
|
|
||||||
|
|
||||||
def test_initialize_with_custom_admin_dashboard(
|
def test_initialize_with_custom_admin_dashboard(
|
||||||
@ -377,12 +377,12 @@ def test_initialize_with_custom_admin_dashboard(
|
|||||||
"""
|
"""
|
||||||
custom_auth_provider = test_custom_auth_admin()
|
custom_auth_provider = test_custom_auth_admin()
|
||||||
custom_admin = Admin(engine=test_get_engine, auth_provider=custom_auth_provider)
|
custom_admin = Admin(engine=test_get_engine, auth_provider=custom_auth_provider)
|
||||||
app = App(_admin_dash=AdminDash(models=[test_model_auth], admin=custom_admin))
|
app = App(admin_dash=AdminDash(models=[test_model_auth], admin=custom_admin))
|
||||||
assert app._admin_dash is not None
|
assert app.admin_dash is not None
|
||||||
assert app._admin_dash.admin is not None
|
assert app.admin_dash.admin is not None
|
||||||
assert len(app._admin_dash.models) > 0
|
assert len(app.admin_dash.models) > 0
|
||||||
assert app._admin_dash.models[0] == test_model_auth
|
assert app.admin_dash.models[0] == test_model_auth
|
||||||
assert app._admin_dash.admin.auth_provider == custom_auth_provider
|
assert app.admin_dash.admin.auth_provider == custom_auth_provider
|
||||||
|
|
||||||
|
|
||||||
def test_initialize_admin_dashboard_with_view_overrides(test_model):
|
def test_initialize_admin_dashboard_with_view_overrides(test_model):
|
||||||
@ -396,13 +396,13 @@ def test_initialize_admin_dashboard_with_view_overrides(test_model):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
app = App(
|
app = App(
|
||||||
_admin_dash=AdminDash(
|
admin_dash=AdminDash(
|
||||||
models=[test_model], view_overrides={test_model: TestModelView}
|
models=[test_model], view_overrides={test_model: TestModelView}
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
assert app._admin_dash is not None
|
assert app.admin_dash is not None
|
||||||
assert app._admin_dash.models == [test_model]
|
assert app.admin_dash.models == [test_model]
|
||||||
assert app._admin_dash.view_overrides[test_model] == TestModelView
|
assert app.admin_dash.view_overrides[test_model] == TestModelView
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
@ -772,7 +772,7 @@ async def test_upload_file(tmp_path, state, delta, token: str, mocker):
|
|||||||
# The App state must be the "root" of the state tree
|
# The App state must be the "root" of the state tree
|
||||||
app = App()
|
app = App()
|
||||||
app._enable_state()
|
app._enable_state()
|
||||||
app._event_namespace.emit = AsyncMock() # type: ignore
|
app.event_namespace.emit = AsyncMock() # type: ignore
|
||||||
current_state = await app.state_manager.get_state(_substate_key(token, state))
|
current_state = await app.state_manager.get_state(_substate_key(token, state))
|
||||||
data = b"This is binary data"
|
data = b"This is binary data"
|
||||||
|
|
||||||
|
@ -1913,7 +1913,7 @@ def mock_app_simple(monkeypatch) -> rx.App:
|
|||||||
|
|
||||||
setattr(app_module, CompileVars.APP, app)
|
setattr(app_module, CompileVars.APP, app)
|
||||||
app._state = TestState
|
app._state = TestState
|
||||||
app._event_namespace.emit = CopyingAsyncMock() # type: ignore
|
app.event_namespace.emit = CopyingAsyncMock() # type: ignore
|
||||||
|
|
||||||
def _mock_get_app(*args, **kwargs):
|
def _mock_get_app(*args, **kwargs):
|
||||||
return app_module
|
return app_module
|
||||||
@ -2021,9 +2021,9 @@ async def test_state_proxy(grandchild_state: GrandchildState, mock_app: rx.App):
|
|||||||
assert gotten_grandchild_state.value2 == "42"
|
assert gotten_grandchild_state.value2 == "42"
|
||||||
|
|
||||||
# ensure state update was emitted
|
# ensure state update was emitted
|
||||||
assert mock_app._event_namespace is not None
|
assert mock_app.event_namespace is not None
|
||||||
mock_app._event_namespace.emit.assert_called_once()
|
mock_app.event_namespace.emit.assert_called_once()
|
||||||
mcall = mock_app._event_namespace.emit.mock_calls[0]
|
mcall = mock_app.event_namespace.emit.mock_calls[0]
|
||||||
assert mcall.args[0] == str(SocketEvent.EVENT)
|
assert mcall.args[0] == str(SocketEvent.EVENT)
|
||||||
assert mcall.args[1] == StateUpdate(
|
assert mcall.args[1] == StateUpdate(
|
||||||
delta={
|
delta={
|
||||||
@ -2225,8 +2225,8 @@ async def test_background_task_no_block(mock_app: rx.App, token: str):
|
|||||||
)
|
)
|
||||||
).order == exp_order
|
).order == exp_order
|
||||||
|
|
||||||
assert mock_app._event_namespace is not None
|
assert mock_app.event_namespace is not None
|
||||||
emit_mock = mock_app._event_namespace.emit
|
emit_mock = mock_app.event_namespace.emit
|
||||||
|
|
||||||
first_ws_message = emit_mock.mock_calls[0].args[1]
|
first_ws_message = emit_mock.mock_calls[0].args[1]
|
||||||
assert (
|
assert (
|
||||||
|
Loading…
Reference in New Issue
Block a user