Skip to content

Commit 09c0386

Browse files
authored
add reflex compile cli (#5201)
1 parent f7122fc commit 09c0386

File tree

5 files changed

+53
-19
lines changed

5 files changed

+53
-19
lines changed

reflex/app.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1157,11 +1157,12 @@ def _validate_var_dependencies(self, state: type[BaseState] | None = None) -> No
11571157
for substate in state.class_subclasses:
11581158
self._validate_var_dependencies(substate)
11591159

1160-
def _compile(self, export: bool = False):
1160+
def _compile(self, export: bool = False, dry_run: bool = False):
11611161
"""Compile the app and output it to the pages folder.
11621162
11631163
Args:
11641164
export: Whether to compile the app for export.
1165+
dry_run: Whether to compile the app without saving it.
11651166
11661167
Raises:
11671168
ReflexRuntimeError: When any page uses state, but no rx.State subclass is defined.
@@ -1175,7 +1176,7 @@ def get_compilation_time() -> str:
11751176

11761177
should_compile = self._should_compile()
11771178
backend_dir = prerequisites.get_backend_dir()
1178-
if not should_compile and backend_dir.exists():
1179+
if not dry_run and not should_compile and backend_dir.exists():
11791180
stateful_pages_marker = backend_dir / constants.Dirs.STATEFUL_PAGES
11801181
if stateful_pages_marker.exists():
11811182
with stateful_pages_marker.open("r") as f:
@@ -1210,7 +1211,7 @@ def get_compilation_time() -> str:
12101211
if config.react_strict_mode:
12111212
app_wrappers[(200, "StrictMode")] = StrictMode.create()
12121213

1213-
if not should_compile:
1214+
if not should_compile and not dry_run:
12141215
with console.timing("Evaluate Pages (Backend)"):
12151216
for route in self._unevaluated_pages:
12161217
console.debug(f"Evaluating page: {route}")
@@ -1363,7 +1364,7 @@ def memoized_toast_provider():
13631364

13641365
# Copy the assets.
13651366
assets_src = Path.cwd() / constants.Dirs.APP_ASSETS
1366-
if assets_src.is_dir():
1367+
if assets_src.is_dir() and not dry_run:
13671368
with console.timing("Copy assets"):
13681369
path_ops.update_directory_tree(
13691370
src=assets_src,
@@ -1449,6 +1450,9 @@ def _submit_work(fn: Callable[..., tuple[str, str]], *args, **kwargs):
14491450
progress.advance(task)
14501451
progress.stop()
14511452

1453+
if dry_run:
1454+
return
1455+
14521456
# Install frontend packages.
14531457
with console.timing("Install Frontend Packages"):
14541458
self._get_frontend_packages(all_imports)

reflex/reflex.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -351,6 +351,30 @@ def run(
351351
)
352352

353353

354+
@cli.command()
355+
@loglevel_option
356+
@click.option(
357+
"--dry",
358+
is_flag=True,
359+
default=False,
360+
help="Run the command without making any changes.",
361+
)
362+
def compile(dry: bool):
363+
"""Compile the app in the current directory."""
364+
import time
365+
366+
from reflex.utils import prerequisites
367+
368+
# Check the app.
369+
if prerequisites.needs_reinit():
370+
_init(name=get_config().app_name)
371+
get_config(reload=True)
372+
starting_time = time.monotonic()
373+
prerequisites.compile_app(dry_run=dry)
374+
elapsed_time = time.monotonic() - starting_time
375+
console.success(f"App compiled successfully in {elapsed_time:.3f} seconds.")
376+
377+
354378
@cli.command()
355379
@loglevel_option
356380
@click.option(

reflex/utils/prerequisites.py

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -431,12 +431,15 @@ def validate_app(reload: bool = False) -> None:
431431
get_and_validate_app(reload=reload)
432432

433433

434-
def get_compiled_app(reload: bool = False, export: bool = False) -> ModuleType:
434+
def get_compiled_app(
435+
reload: bool = False, export: bool = False, dry_run: bool = False
436+
) -> ModuleType:
435437
"""Get the app module based on the default config after first compiling it.
436438
437439
Args:
438440
reload: Re-import the app module from disk
439441
export: Compile the app for export
442+
dry_run: If True, do not write the compiled app to disk.
440443
441444
Returns:
442445
The compiled app based on the default config.
@@ -445,18 +448,21 @@ def get_compiled_app(reload: bool = False, export: bool = False) -> ModuleType:
445448
# For py3.9 compatibility when redis is used, we MUST add any decorator pages
446449
# before compiling the app in a thread to avoid event loop error (REF-2172).
447450
app._apply_decorated_pages()
448-
app._compile(export=export)
451+
app._compile(export=export, dry_run=dry_run)
449452
return app_module
450453

451454

452-
def compile_app(reload: bool = False, export: bool = False) -> None:
455+
def compile_app(
456+
reload: bool = False, export: bool = False, dry_run: bool = False
457+
) -> None:
453458
"""Compile the app module based on the default config.
454459
455460
Args:
456461
reload: Re-import the app module from disk
457462
export: Compile the app for export
463+
dry_run: If True, do not write the compiled app to disk.
458464
"""
459-
get_compiled_app(reload=reload, export=export)
465+
get_compiled_app(reload=reload, export=export, dry_run=dry_run)
460466

461467

462468
def _can_colorize() -> bool:

reflex/utils/types.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,7 @@
2929
from typing import get_origin as get_origin_og
3030
from typing import get_type_hints as get_type_hints_og
3131

32-
import sqlalchemy
3332
from pydantic.v1.fields import ModelField
34-
from sqlalchemy.ext.associationproxy import AssociationProxyInstance
35-
from sqlalchemy.ext.hybrid import hybrid_property
36-
from sqlalchemy.orm import DeclarativeBase, Mapped, QueryableAttribute, Relationship
3733
from typing_extensions import Self as Self
3834
from typing_extensions import override as override
3935

@@ -331,6 +327,8 @@ def get_property_hint(attr: Any | None) -> GenericType | None:
331327
Returns:
332328
The type hint of the property, if it is a property, else None.
333329
"""
330+
from sqlalchemy.ext.hybrid import hybrid_property
331+
334332
if not isinstance(attr, (property, hybrid_property)):
335333
return None
336334
hints = get_type_hints(attr.fget)
@@ -349,6 +347,10 @@ def get_attribute_access_type(cls: GenericType, name: str) -> GenericType | None
349347
Returns:
350348
The type of the attribute, if accessible, or None
351349
"""
350+
import sqlalchemy
351+
from sqlalchemy.ext.associationproxy import AssociationProxyInstance
352+
from sqlalchemy.orm import DeclarativeBase, Mapped, QueryableAttribute, Relationship
353+
352354
from reflex.model import Model
353355

354356
try:

reflex/vars/base.py

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,6 @@
4040
)
4141

4242
from rich.markup import escape
43-
from sqlalchemy.orm import DeclarativeBase
4443
from typing_extensions import deprecated, override
4544

4645
from reflex import constants
@@ -3330,18 +3329,17 @@ def dispatch(
33303329
).guess_type()
33313330

33323331

3333-
V = TypeVar("V")
3334-
3335-
BASE_TYPE = TypeVar("BASE_TYPE", bound=Base | None)
3336-
SQLA_TYPE = TypeVar("SQLA_TYPE", bound=DeclarativeBase | None)
3337-
33383332
if TYPE_CHECKING:
33393333
from _typeshed import DataclassInstance
3334+
from sqlalchemy.orm import DeclarativeBase
33403335

3336+
SQLA_TYPE = TypeVar("SQLA_TYPE", bound=DeclarativeBase | None)
3337+
BASE_TYPE = TypeVar("BASE_TYPE", bound=Base | None)
33413338
DATACLASS_TYPE = TypeVar("DATACLASS_TYPE", bound=DataclassInstance | None)
3339+
MAPPING_TYPE = TypeVar("MAPPING_TYPE", bound=Mapping | None)
3340+
V = TypeVar("V")
33423341

33433342
FIELD_TYPE = TypeVar("FIELD_TYPE")
3344-
MAPPING_TYPE = TypeVar("MAPPING_TYPE", bound=Mapping | None)
33453343

33463344

33473345
class Field(Generic[FIELD_TYPE]):

0 commit comments

Comments
 (0)