1
0
Khaleel Al-Adhami 1 долоо хоног өмнө
parent
commit
09c03867a6

+ 8 - 4
reflex/app.py

@@ -1157,11 +1157,12 @@ class App(MiddlewareMixin, LifespanMixin):
         for substate in state.class_subclasses:
             self._validate_var_dependencies(substate)
 
-    def _compile(self, export: bool = False):
+    def _compile(self, export: bool = False, dry_run: bool = False):
         """Compile the app and output it to the pages folder.
 
         Args:
             export: Whether to compile the app for export.
+            dry_run: Whether to compile the app without saving it.
 
         Raises:
             ReflexRuntimeError: When any page uses state, but no rx.State subclass is defined.
@@ -1175,7 +1176,7 @@ class App(MiddlewareMixin, LifespanMixin):
 
         should_compile = self._should_compile()
         backend_dir = prerequisites.get_backend_dir()
-        if not should_compile and backend_dir.exists():
+        if not dry_run and not should_compile and backend_dir.exists():
             stateful_pages_marker = backend_dir / constants.Dirs.STATEFUL_PAGES
             if stateful_pages_marker.exists():
                 with stateful_pages_marker.open("r") as f:
@@ -1210,7 +1211,7 @@ class App(MiddlewareMixin, LifespanMixin):
         if config.react_strict_mode:
             app_wrappers[(200, "StrictMode")] = StrictMode.create()
 
-        if not should_compile:
+        if not should_compile and not dry_run:
             with console.timing("Evaluate Pages (Backend)"):
                 for route in self._unevaluated_pages:
                     console.debug(f"Evaluating page: {route}")
@@ -1363,7 +1364,7 @@ class App(MiddlewareMixin, LifespanMixin):
 
         # Copy the assets.
         assets_src = Path.cwd() / constants.Dirs.APP_ASSETS
-        if assets_src.is_dir():
+        if assets_src.is_dir() and not dry_run:
             with console.timing("Copy assets"):
                 path_ops.update_directory_tree(
                     src=assets_src,
@@ -1449,6 +1450,9 @@ class App(MiddlewareMixin, LifespanMixin):
         progress.advance(task)
         progress.stop()
 
+        if dry_run:
+            return
+
         # Install frontend packages.
         with console.timing("Install Frontend Packages"):
             self._get_frontend_packages(all_imports)

+ 24 - 0
reflex/reflex.py

@@ -351,6 +351,30 @@ def run(
     )
 
 
+@cli.command()
+@loglevel_option
+@click.option(
+    "--dry",
+    is_flag=True,
+    default=False,
+    help="Run the command without making any changes.",
+)
+def compile(dry: bool):
+    """Compile the app in the current directory."""
+    import time
+
+    from reflex.utils import prerequisites
+
+    # Check the app.
+    if prerequisites.needs_reinit():
+        _init(name=get_config().app_name)
+    get_config(reload=True)
+    starting_time = time.monotonic()
+    prerequisites.compile_app(dry_run=dry)
+    elapsed_time = time.monotonic() - starting_time
+    console.success(f"App compiled successfully in {elapsed_time:.3f} seconds.")
+
+
 @cli.command()
 @loglevel_option
 @click.option(

+ 10 - 4
reflex/utils/prerequisites.py

@@ -431,12 +431,15 @@ def validate_app(reload: bool = False) -> None:
     get_and_validate_app(reload=reload)
 
 
-def get_compiled_app(reload: bool = False, export: bool = False) -> ModuleType:
+def get_compiled_app(
+    reload: bool = False, export: bool = False, dry_run: 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
         export: Compile the app for export
+        dry_run: If True, do not write the compiled app to disk.
 
     Returns:
         The compiled app based on the default config.
@@ -445,18 +448,21 @@ def get_compiled_app(reload: bool = False, export: bool = False) -> ModuleType:
     # For py3.9 compatibility when redis is used, we MUST add any decorator pages
     # before compiling the app in a thread to avoid event loop error (REF-2172).
     app._apply_decorated_pages()
-    app._compile(export=export)
+    app._compile(export=export, dry_run=dry_run)
     return app_module
 
 
-def compile_app(reload: bool = False, export: bool = False) -> None:
+def compile_app(
+    reload: bool = False, export: bool = False, dry_run: bool = False
+) -> None:
     """Compile the app module based on the default config.
 
     Args:
         reload: Re-import the app module from disk
         export: Compile the app for export
+        dry_run: If True, do not write the compiled app to disk.
     """
-    get_compiled_app(reload=reload, export=export)
+    get_compiled_app(reload=reload, export=export, dry_run=dry_run)
 
 
 def _can_colorize() -> bool:

+ 6 - 4
reflex/utils/types.py

@@ -29,11 +29,7 @@ from typing import (  # noqa: UP035
 from typing import get_origin as get_origin_og
 from typing import get_type_hints as get_type_hints_og
 
-import sqlalchemy
 from pydantic.v1.fields import ModelField
-from sqlalchemy.ext.associationproxy import AssociationProxyInstance
-from sqlalchemy.ext.hybrid import hybrid_property
-from sqlalchemy.orm import DeclarativeBase, Mapped, QueryableAttribute, Relationship
 from typing_extensions import Self as Self
 from typing_extensions import override as override
 
@@ -331,6 +327,8 @@ def get_property_hint(attr: Any | None) -> GenericType | None:
     Returns:
         The type hint of the property, if it is a property, else None.
     """
+    from sqlalchemy.ext.hybrid import hybrid_property
+
     if not isinstance(attr, (property, hybrid_property)):
         return None
     hints = get_type_hints(attr.fget)
@@ -349,6 +347,10 @@ def get_attribute_access_type(cls: GenericType, name: str) -> GenericType | None
     Returns:
         The type of the attribute, if accessible, or None
     """
+    import sqlalchemy
+    from sqlalchemy.ext.associationproxy import AssociationProxyInstance
+    from sqlalchemy.orm import DeclarativeBase, Mapped, QueryableAttribute, Relationship
+
     from reflex.model import Model
 
     try:

+ 5 - 7
reflex/vars/base.py

@@ -40,7 +40,6 @@ from typing import (  # noqa: UP035
 )
 
 from rich.markup import escape
-from sqlalchemy.orm import DeclarativeBase
 from typing_extensions import deprecated, override
 
 from reflex import constants
@@ -3330,18 +3329,17 @@ def dispatch(
     ).guess_type()
 
 
-V = TypeVar("V")
-
-BASE_TYPE = TypeVar("BASE_TYPE", bound=Base | None)
-SQLA_TYPE = TypeVar("SQLA_TYPE", bound=DeclarativeBase | None)
-
 if TYPE_CHECKING:
     from _typeshed import DataclassInstance
+    from sqlalchemy.orm import DeclarativeBase
 
+    SQLA_TYPE = TypeVar("SQLA_TYPE", bound=DeclarativeBase | None)
+    BASE_TYPE = TypeVar("BASE_TYPE", bound=Base | None)
     DATACLASS_TYPE = TypeVar("DATACLASS_TYPE", bound=DataclassInstance | None)
+    MAPPING_TYPE = TypeVar("MAPPING_TYPE", bound=Mapping | None)
+    V = TypeVar("V")
 
 FIELD_TYPE = TypeVar("FIELD_TYPE")
-MAPPING_TYPE = TypeVar("MAPPING_TYPE", bound=Mapping | None)
 
 
 class Field(Generic[FIELD_TYPE]):