diff --git a/README.md b/README.md index 0b301ea0d..874c2de92 100644 --- a/README.md +++ b/README.md @@ -123,7 +123,6 @@ def index(): # Add state and page to the app. app = rx.App() app.add_page(index, title="reflex:DALL·E") -app.compile() ``` ## Let's break this down. diff --git a/docs/es/README.md b/docs/es/README.md index dd82b7461..74c3e24a7 100644 --- a/docs/es/README.md +++ b/docs/es/README.md @@ -121,7 +121,6 @@ def index(): # Add state and page to the app. app = rx.App() app.add_page(index, title="reflex:DALL·E") -app.compile() ``` ## Vamos a desglosarlo. @@ -191,7 +190,6 @@ Añadimos una página desde la raíz (root) de la aplicación al componente de ```python app.add_page(index, title="DALL-E") -app.compile() ``` Puedes crear una aplicación multipágina añadiendo más páginas. diff --git a/docs/in/README.md b/docs/in/README.md index 220504ef4..12e662f11 100644 --- a/docs/in/README.md +++ b/docs/in/README.md @@ -120,7 +120,6 @@ def index(): # Add state and page to the app. app = rx.App() app.add_page(index, title="reflex:DALL·E") -app.compile() ``` ## चलो इसे विस्तार से देखते हैं। @@ -190,7 +189,6 @@ app = rx.App() ```python app.add_page(index, title="DALL-E") -app.compile() ``` आप और पेज जोड़कर एक मल्टी-पेज एप्लिकेशन बना सकते हैं। diff --git a/docs/it/README.md b/docs/it/README.md index 04e3f1e9b..77274ae03 100644 --- a/docs/it/README.md +++ b/docs/it/README.md @@ -121,7 +121,6 @@ def index(): # Aggiungi stato e pagina all'app. app = rx.App() app.add_page(index, title="reflex:DALL·E") -app.compile() ``` ## Analizziamolo @@ -191,7 +190,6 @@ Possiamo aggiungere una pagina dalla radice dell'app al componente dell'indice.A ```python app.add_page(index, title="DALL-E") -app.compile() ``` Puoi creare un'app multi-pagina aggiungendo altre pagine. diff --git a/docs/kr/README.md b/docs/kr/README.md index b671458d6..2e443c855 100644 --- a/docs/kr/README.md +++ b/docs/kr/README.md @@ -121,7 +121,6 @@ def index(): # Add state and page to the app. app = rx.App() app.add_page(index, title="reflex:DALL·E") -app.compile() ``` ## 하나씩 살펴보겠습니다. @@ -194,7 +193,6 @@ app = rx.App() ```python app.add_page(index, title="DALL-E") -app.compile() ``` 여러 페이지를 추가하여 멀티 페이지 앱을 만들 수 있습니다. diff --git a/docs/pt/pt_br/README.md b/docs/pt/pt_br/README.md index ac69f009f..96708a75b 100644 --- a/docs/pt/pt_br/README.md +++ b/docs/pt/pt_br/README.md @@ -121,7 +121,6 @@ def index(): # Adição do estado e da página no app. app = rx.App() app.add_page(index, title="reflex:DALL·E") -app.compile() ``` ## Vamos por partes. @@ -192,7 +191,6 @@ Adicionamos uma página na raíz do app, apontando para o componente index. Tamb ```python app.add_page(index, title="DALL-E") -app.compile() ``` Você pode criar mais páginas e adicioná-las ao seu app. diff --git a/docs/tr/README.md b/docs/tr/README.md index 970161adf..9b95a6fc4 100644 --- a/docs/tr/README.md +++ b/docs/tr/README.md @@ -124,7 +124,6 @@ def index(): # Sayfa ve durumu uygulamaya ekleyin. app = rx.App() app.add_page(index, title="reflex:DALL·E") -app.compile() ``` ## Daha Detaylı İceleyelim @@ -194,7 +193,6 @@ Uygulamamızın kök dizinine index bileşeninden bir sayfa ekliyoruz. Ayrıca s ```python app.add_page(index, title="DALL-E") -app.compile() ``` Daha fazla sayfa ekleyerek çok sayfalı bir uygulama oluşturabilirsiniz. diff --git a/docs/zh/zh_cn/README.md b/docs/zh/zh_cn/README.md index bf6dbe179..6e6da1282 100644 --- a/docs/zh/zh_cn/README.md +++ b/docs/zh/zh_cn/README.md @@ -122,7 +122,6 @@ def index(): # Add state and page to the app. app = rx.App() app.add_page(index, title="reflex:DALL·E") -app.compile() ``` ## 让我们分解以上步骤. @@ -194,7 +193,6 @@ app = rx.App() ```python app.add_page(index, title="DALL-E") -app.compile() ``` 您可以通过增加更多页面来创建一个多页面的应用. diff --git a/docs/zh/zh_tw/README.md b/docs/zh/zh_tw/README.md index 098f661a6..ced699f47 100644 --- a/docs/zh/zh_tw/README.md +++ b/docs/zh/zh_tw/README.md @@ -121,7 +121,6 @@ def index(): # 把狀態跟頁面添加到應用程式。 app = rx.App() app.add_page(index, title="reflex:DALL·E") -app.compile() ``` ## 讓我們來拆解一下。 @@ -192,7 +191,6 @@ app = rx.App() ```python app.add_page(index, title="DALL-E") -app.compile() ``` 你可以添加更多頁面至路由藉此來建立多頁面應用程式(multi-page app) diff --git a/integration/test_background_task.py b/integration/test_background_task.py index bc70ff01f..799d68c2f 100644 --- a/integration/test_background_task.py +++ b/integration/test_background_task.py @@ -95,7 +95,6 @@ def BackgroundTask(): app = rx.App(state=rx.State) app.add_page(index) - app.compile() @pytest.fixture(scope="session") diff --git a/integration/test_call_script.py b/integration/test_call_script.py index 95bdf37c9..c3a364306 100644 --- a/integration/test_call_script.py +++ b/integration/test_call_script.py @@ -227,8 +227,6 @@ def CallScript(): rx.button("Reset", id="reset", on_click=CallScriptState.reset_), ) - app.compile() - @pytest.fixture(scope="session") def call_script(tmp_path_factory) -> Generator[AppHarness, None, None]: diff --git a/integration/test_client_storage.py b/integration/test_client_storage.py index a9a311d8c..2678f3d52 100644 --- a/integration/test_client_storage.py +++ b/integration/test_client_storage.py @@ -100,7 +100,6 @@ def ClientSide(): app = rx.App(state=rx.State) app.add_page(index) app.add_page(index, route="/foo") - app.compile() @pytest.fixture(scope="session") diff --git a/integration/test_connection_banner.py b/integration/test_connection_banner.py index 0468e6b53..293d2e412 100644 --- a/integration/test_connection_banner.py +++ b/integration/test_connection_banner.py @@ -21,7 +21,6 @@ def ConnectionBanner(): app = rx.App(state=rx.State) app.add_page(index) - app.compile() @pytest.fixture() diff --git a/integration/test_dynamic_routes.py b/integration/test_dynamic_routes.py index cafb9c61b..7cb64754f 100644 --- a/integration/test_dynamic_routes.py +++ b/integration/test_dynamic_routes.py @@ -66,7 +66,6 @@ def DynamicRoute(): app.add_page(index, route="/page/[page_id]", on_load=DynamicState.on_load) # type: ignore app.add_page(index, route="/static/x", on_load=DynamicState.on_load) # type: ignore app.add_custom_404_page(on_load=DynamicState.on_load) # type: ignore - app.compile() @pytest.fixture(scope="session") diff --git a/integration/test_event_actions.py b/integration/test_event_actions.py index da444a5cf..89c8ece22 100644 --- a/integration/test_event_actions.py +++ b/integration/test_event_actions.py @@ -132,7 +132,6 @@ def TestEventAction(): app = rx.App(state=rx.State) app.add_page(index) - app.compile() @pytest.fixture(scope="session") diff --git a/integration/test_event_chain.py b/integration/test_event_chain.py index 7d003635e..ec3f125b4 100644 --- a/integration/test_event_chain.py +++ b/integration/test_event_chain.py @@ -242,8 +242,6 @@ def EventChain(): app.add_page(on_mount_return_chain) app.add_page(on_mount_yield_chain) - app.compile() - @pytest.fixture(scope="session") def event_chain(tmp_path_factory) -> Generator[AppHarness, None, None]: diff --git a/integration/test_form_submit.py b/integration/test_form_submit.py index 8d36cc01d..b64846c1b 100644 --- a/integration/test_form_submit.py +++ b/integration/test_form_submit.py @@ -62,8 +62,6 @@ def FormSubmit(): height="100vh", ) - app.compile() - def FormSubmitName(): """App with a form using on_submit.""" @@ -124,8 +122,6 @@ def FormSubmitName(): height="100vh", ) - app.compile() - @pytest.fixture( scope="session", params=[FormSubmit, FormSubmitName], ids=["id", "name"] diff --git a/integration/test_input.py b/integration/test_input.py index 2ca15424e..111124349 100644 --- a/integration/test_input.py +++ b/integration/test_input.py @@ -40,8 +40,6 @@ def FullyControlledInput(): rx.button("CLEAR", on_click=rx.set_value("on_change_input", "")), ) - app.compile() - @pytest.fixture() def fully_controlled_input(tmp_path) -> Generator[AppHarness, None, None]: diff --git a/integration/test_login_flow.py b/integration/test_login_flow.py index f53635743..a4272fe39 100644 --- a/integration/test_login_flow.py +++ b/integration/test_login_flow.py @@ -45,7 +45,6 @@ def LoginSample(): app = rx.App(state=rx.State) app.add_page(index) app.add_page(login) - app.compile() @pytest.fixture(scope="session") diff --git a/integration/test_server_side_event.py b/integration/test_server_side_event.py index 31a38ba36..b8e8d21c0 100644 --- a/integration/test_server_side_event.py +++ b/integration/test_server_side_event.py @@ -75,8 +75,6 @@ def ServerSideEvent(): ), ) - app.compile() - @pytest.fixture(scope="session") def server_side_event(tmp_path_factory) -> Generator[AppHarness, None, None]: diff --git a/integration/test_table.py b/integration/test_table.py index 00e6a8b22..560d70b94 100644 --- a/integration/test_table.py +++ b/integration/test_table.py @@ -88,8 +88,6 @@ def Table(): ) ) - app.compile() - @pytest.fixture() def table(tmp_path_factory) -> Generator[AppHarness, None, None]: diff --git a/integration/test_upload.py b/integration/test_upload.py index 13fb28d36..c832f5acf 100644 --- a/integration/test_upload.py +++ b/integration/test_upload.py @@ -115,7 +115,6 @@ def UploadFile(): app = rx.App(state=rx.State) app.add_page(index) - app.compile() @pytest.fixture(scope="session") diff --git a/integration/test_var_operations.py b/integration/test_var_operations.py index a0f245fe4..4c56c01f5 100644 --- a/integration/test_var_operations.py +++ b/integration/test_var_operations.py @@ -565,8 +565,6 @@ def VarOperations(): ), ) - app.compile() - @pytest.fixture(scope="session") def var_operations(tmp_path_factory) -> Generator[AppHarness, None, None]: diff --git a/reflex/app.py b/reflex/app.py index 63930acea..948d3622a 100644 --- a/reflex/app.py +++ b/reflex/app.py @@ -619,6 +619,19 @@ class App(Base): return True def compile(self): + """compile_() is the new function for performing compilation. + Reflex framework will call it automatically as needed. + """ + console.deprecate( + feature_name="app.compile()", + reason="Explicit calls to app.compile() are not needed." + " Method will be removed in 0.4.0", + deprecation_version="0.3.8", + removal_version="0.4.0", + ) + return + + def compile_(self): """Compile the app and output it to the pages folder.""" # add the pages before the compile check so App know onload methods for render, kwargs in DECORATED_PAGES: diff --git a/reflex/app.pyi b/reflex/app.pyi index b3b77bd20..38175ca28 100644 --- a/reflex/app.pyi +++ b/reflex/app.pyi @@ -120,6 +120,7 @@ class App(Base): def setup_admin_dash(self) -> None: ... def get_frontend_packages(self, imports: Dict[str, str]): ... def compile(self) -> None: ... + def compile_(self) -> None: ... def modify_state(self, token: str) -> AsyncContextManager[State]: ... def _process_background( self, state: State, event: Event diff --git a/reflex/app_module_for_backend.py b/reflex/app_module_for_backend.py new file mode 100644 index 000000000..4a5b10719 --- /dev/null +++ b/reflex/app_module_for_backend.py @@ -0,0 +1,13 @@ +"""Shims the real reflex app module for running backend server (uvicorn or gunicorn). +Only the app attribute is explicitly exposed. +""" +from reflex import constants +from reflex.utils.prerequisites import get_compiled_app + +if "app" != constants.CompileVars.APP: + raise AssertionError("unexpected variable name for 'app'") +app = getattr(get_compiled_app(), constants.CompileVars.APP) + +# ensure only "app" is exposed. +del get_compiled_app +del constants diff --git a/reflex/reflex.py b/reflex/reflex.py index d558b2e17..dc0fb58ea 100644 --- a/reflex/reflex.py +++ b/reflex/reflex.py @@ -178,7 +178,7 @@ def _run( if frontend: prerequisites.update_next_config() # Get the app module. - prerequisites.get_app() + prerequisites.get_compiled_app() # Warn if schema is not up to date. prerequisites.check_schema_up_to_date() @@ -362,7 +362,7 @@ def db_init(): # Initialize the database. _skip_compile() - prerequisites.get_app() + prerequisites.get_compiled_app() model.Model.alembic_init() model.Model.migrate(autogenerate=True) @@ -373,8 +373,9 @@ def migrate(): from reflex import model from reflex.utils import prerequisites + # TODO see if we can use `get_app()` instead (no compile). Would _skip_compile still be needed then? _skip_compile() - prerequisites.get_app() + prerequisites.get_compiled_app() if not prerequisites.check_db_initialized(): return model.Model.migrate() @@ -393,8 +394,9 @@ def makemigrations( from reflex import model from reflex.utils import prerequisites + # TODO see if we can use `get_app()` instead (no compile). Would _skip_compile still be needed then? _skip_compile() - prerequisites.get_app() + prerequisites.get_compiled_app() if not prerequisites.check_db_initialized(): return with model.Model.get_db_engine().connect() as connection: diff --git a/reflex/state.py b/reflex/state.py index db756d5c2..04e2554a5 100644 --- a/reflex/state.py +++ b/reflex/state.py @@ -1316,6 +1316,7 @@ class State(BaseState): Returns: The list of events to queue for on load handling. """ + # Do not app.compile_()! It should be already compiled by now. app = getattr(prerequisites.get_app(), constants.CompileVars.APP) load_events = app.get_load_events(self.router.page.path) if not load_events and self.is_hydrated: @@ -1364,6 +1365,7 @@ class StateProxy(wrapt.ObjectProxy): state_instance: The state instance to proxy. """ super().__init__(state_instance) + # compile is not relevant to backend logic self._self_app = getattr(prerequisites.get_app(), constants.CompileVars.APP) self._self_substate_path = state_instance.get_full_name().split(".") self._self_actx = None diff --git a/reflex/testing.py b/reflex/testing.py index 4e32f52b3..63a56ff06 100644 --- a/reflex/testing.py +++ b/reflex/testing.py @@ -165,7 +165,7 @@ class AppHarness: # reset rx.State subclasses State.class_subclasses.clear() # self.app_module.app. - self.app_module = reflex.utils.prerequisites.get_app(reload=True) + self.app_module = reflex.utils.prerequisites.get_compiled_app(reload=True) self.app_instance = self.app_module.app if isinstance(self.app_instance.state_manager, StateManagerRedis): # Create our own redis connection for testing. diff --git a/reflex/utils/exec.py b/reflex/utils/exec.py index 04b225a18..a992534b4 100644 --- a/reflex/utils/exec.py +++ b/reflex/utils/exec.py @@ -160,7 +160,7 @@ def run_backend( import uvicorn config = get_config() - app_module = f"{config.app_name}.{config.app_name}:{constants.CompileVars.APP}" + app_module = f"reflex.app_module_for_backend:{constants.CompileVars.APP}" # Create a .nocompile file to skip compile for backend. if os.path.exists(constants.Dirs.WEB): @@ -196,7 +196,7 @@ def run_backend_prod( config = get_config() RUN_BACKEND_PROD = f"gunicorn --worker-class {config.gunicorn_worker_class} --preload --timeout {config.timeout} --log-level critical".split() RUN_BACKEND_PROD_WINDOWS = f"uvicorn --timeout-keep-alive {config.timeout}".split() - app_module = f"{config.app_name}.{config.app_name}:{constants.CompileVars.APP}" + app_module = f"reflex.app_module_for_backend:{constants.CompileVars.APP}" command = ( [ *RUN_BACKEND_PROD_WINDOWS, diff --git a/reflex/utils/export.py b/reflex/utils/export.py index f2b8e69a8..12a6933fb 100644 --- a/reflex/utils/export.py +++ b/reflex/utils/export.py @@ -56,7 +56,7 @@ def export( # Update some parameters for export prerequisites.update_next_config(export=True) # Ensure module can be imported and app.compile() is called. - prerequisites.get_app() + prerequisites.get_compiled_app() # Set up .web directory and install frontend dependencies. build.setup_frontend(Path.cwd()) diff --git a/reflex/utils/prerequisites.py b/reflex/utils/prerequisites.py index 2b9c6d8c3..58bb6bdd2 100644 --- a/reflex/utils/prerequisites.py +++ b/reflex/utils/prerequisites.py @@ -160,9 +160,24 @@ def get_app(reload: bool = False) -> ModuleType: app = __import__(module, fromlist=(constants.CompileVars.APP,)) if reload: importlib.reload(app) + return app +def get_compiled_app(reload: bool = False) -> ModuleType: + """Get the app module based on the default config after first compiling it. + + Args: + reload: Re-import the app module from disk + + Returns: + The compiled app based on the default config. + """ + app_module = get_app(reload=reload) + getattr(app_module, constants.CompileVars.APP).compile_() + return app_module + + def get_redis() -> Redis | None: """Get the redis client. diff --git a/tests/test_app.py b/tests/test_app.py index eb1b783ed..ac4028536 100644 --- a/tests/test_app.py +++ b/tests/test_app.py @@ -1213,7 +1213,7 @@ def test_app_wrap_compile_theme(compilable_app): """ app, web_dir = compilable_app app.theme = rdxt.theme(accent_color="plum") - app.compile() + app.compile_() app_js_contents = (web_dir / "pages" / "_app.js").read_text() app_js_lines = [ line.strip() for line in app_js_contents.splitlines() if line.strip() @@ -1263,7 +1263,7 @@ def test_app_wrap_priority(compilable_app): return Fragment1.create(Fragment3.create()) app.add_page(page) - app.compile() + app.compile_() app_js_contents = (web_dir / "pages" / "_app.js").read_text() app_js_lines = [ line.strip() for line in app_js_contents.splitlines() if line.strip() diff --git a/tests/test_state.py b/tests/test_state.py index de9187efc..423886655 100644 --- a/tests/test_state.py +++ b/tests/test_state.py @@ -1590,7 +1590,11 @@ def mock_app(monkeypatch, state_manager: StateManager) -> rx.App: app.state = TestState app._state_manager = state_manager app.event_namespace.emit = AsyncMock() # type: ignore - monkeypatch.setattr(prerequisites, "get_app", lambda: app_module) + + def _mock_get_app(*args, **kwargs): + return app_module + + monkeypatch.setattr(prerequisites, "get_app", _mock_get_app) return app diff --git a/tests/test_testing.py b/tests/test_testing.py index ff87534ba..10b3d9663 100644 --- a/tests/test_testing.py +++ b/tests/test_testing.py @@ -21,7 +21,7 @@ def test_app_harness(tmp_path): app = rx.App(state=State) app.add_page(lambda: rx.text("Basic App"), route="/", title="index") - app.compile() + app.compile_() with AppHarness.create( root=tmp_path,