exec.py 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804
  1. """Everything regarding execution of the built app."""
  2. from __future__ import annotations
  3. import hashlib
  4. import importlib.util
  5. import json
  6. import os
  7. import platform
  8. import re
  9. import subprocess
  10. import sys
  11. from collections.abc import Sequence
  12. from pathlib import Path
  13. from typing import NamedTuple, TypedDict
  14. from urllib.parse import urljoin
  15. import psutil
  16. from reflex import constants
  17. from reflex.config import get_config
  18. from reflex.constants.base import LogLevel
  19. from reflex.environment import environment
  20. from reflex.utils import console, path_ops
  21. from reflex.utils.decorator import once
  22. from reflex.utils.prerequisites import get_web_dir
  23. # For uvicorn windows bug fix (#2335)
  24. frontend_process = None
  25. def get_package_json_and_hash(package_json_path: Path) -> tuple[PackageJson, str]:
  26. """Get the content of package.json and its hash.
  27. Args:
  28. package_json_path: The path to the package.json file.
  29. Returns:
  30. A tuple containing the content of package.json as a dictionary and its SHA-256 hash.
  31. """
  32. with package_json_path.open("r") as file:
  33. json_data = json.load(file)
  34. # Calculate the hash
  35. json_string = json.dumps(json_data, sort_keys=True)
  36. hash_object = hashlib.sha256(json_string.encode())
  37. return (json_data, hash_object.hexdigest())
  38. class PackageJson(TypedDict):
  39. """package.json content."""
  40. dependencies: dict[str, str]
  41. devDependencies: dict[str, str]
  42. class Change(NamedTuple):
  43. """A named tuple to represent a change in package dependencies."""
  44. added: set[str]
  45. removed: set[str]
  46. def format_change(name: str, change: Change) -> str:
  47. """Format the change for display.
  48. Args:
  49. name: The name of the change (e.g., "dependencies" or "devDependencies").
  50. change: The Change named tuple containing added and removed packages.
  51. Returns:
  52. A formatted string representing the changes.
  53. """
  54. if not change.added and not change.removed:
  55. return ""
  56. added_str = ", ".join(sorted(change.added))
  57. removed_str = ", ".join(sorted(change.removed))
  58. change_str = f"{name}:\n"
  59. if change.added:
  60. change_str += f" Added: {added_str}\n"
  61. if change.removed:
  62. change_str += f" Removed: {removed_str}\n"
  63. return change_str.strip()
  64. def get_different_packages(
  65. old_package_json_content: PackageJson,
  66. new_package_json_content: PackageJson,
  67. ) -> tuple[Change, Change]:
  68. """Get the packages that are different between two package JSON contents.
  69. Args:
  70. old_package_json_content: The content of the old package JSON.
  71. new_package_json_content: The content of the new package JSON.
  72. Returns:
  73. A tuple containing two `Change` named tuples:
  74. - The first `Change` contains the changes in the `dependencies` section.
  75. - The second `Change` contains the changes in the `devDependencies` section.
  76. """
  77. def get_changes(old: dict[str, str], new: dict[str, str]) -> Change:
  78. """Get the changes between two dictionaries.
  79. Args:
  80. old: The old dictionary of packages.
  81. new: The new dictionary of packages.
  82. Returns:
  83. A `Change` named tuple containing the added and removed packages.
  84. """
  85. old_keys = set(old.keys())
  86. new_keys = set(new.keys())
  87. added = new_keys - old_keys
  88. removed = old_keys - new_keys
  89. return Change(added=added, removed=removed)
  90. dependencies_change = get_changes(
  91. old_package_json_content.get("dependencies", {}),
  92. new_package_json_content.get("dependencies", {}),
  93. )
  94. dev_dependencies_change = get_changes(
  95. old_package_json_content.get("devDependencies", {}),
  96. new_package_json_content.get("devDependencies", {}),
  97. )
  98. return dependencies_change, dev_dependencies_change
  99. def kill(proc_pid: int):
  100. """Kills a process and all its child processes.
  101. Args:
  102. proc_pid (int): The process ID of the process to be killed.
  103. Example:
  104. >>> kill(1234)
  105. """
  106. process = psutil.Process(proc_pid)
  107. for proc in process.children(recursive=True):
  108. proc.kill()
  109. process.kill()
  110. def notify_backend():
  111. """Output a string notifying where the backend is running."""
  112. console.print(
  113. f"Backend running at: [bold green]http://0.0.0.0:{get_config().backend_port}[/bold green]"
  114. )
  115. # run_process_and_launch_url is assumed to be used
  116. # only to launch the frontend
  117. # If this is not the case, might have to change the logic
  118. def run_process_and_launch_url(
  119. run_command: list[str | None], backend_present: bool = True
  120. ):
  121. """Run the process and launch the URL.
  122. Args:
  123. run_command: The command to run.
  124. backend_present: Whether the backend is present.
  125. """
  126. from reflex.utils import processes
  127. json_file_path = get_web_dir() / constants.PackageJson.PATH
  128. last_content, last_hash = get_package_json_and_hash(json_file_path)
  129. process = None
  130. first_run = True
  131. while True:
  132. if process is None:
  133. kwargs = {}
  134. if constants.IS_WINDOWS and backend_present:
  135. kwargs["creationflags"] = subprocess.CREATE_NEW_PROCESS_GROUP # pyright: ignore [reportAttributeAccessIssue]
  136. process = processes.new_process(
  137. run_command,
  138. cwd=get_web_dir(),
  139. shell=constants.IS_WINDOWS,
  140. **kwargs,
  141. )
  142. global frontend_process
  143. frontend_process = process
  144. if process.stdout:
  145. for line in processes.stream_logs("Starting frontend", process):
  146. new_content, new_hash = get_package_json_and_hash(json_file_path)
  147. if new_hash != last_hash:
  148. dependencies_change, dev_dependencies_change = (
  149. get_different_packages(last_content, new_content)
  150. )
  151. last_content, last_hash = new_content, new_hash
  152. console.info(
  153. "Detected changes in package.json.\n"
  154. + format_change("Dependencies", dependencies_change)
  155. + format_change("Dev Dependencies", dev_dependencies_change)
  156. )
  157. match = re.search(constants.Next.FRONTEND_LISTENING_REGEX, line)
  158. if match:
  159. if first_run:
  160. url = match.group(1)
  161. if get_config().frontend_path != "":
  162. url = urljoin(url, get_config().frontend_path)
  163. console.print(
  164. f"App running at: [bold green]{url}[/bold green]{' (Frontend-only mode)' if not backend_present else ''}"
  165. )
  166. if backend_present:
  167. notify_backend()
  168. first_run = False
  169. else:
  170. console.print("Frontend is restarting...")
  171. if process is not None:
  172. break # while True
  173. def run_frontend(root: Path, port: str, backend_present: bool = True):
  174. """Run the frontend.
  175. Args:
  176. root: The root path of the project.
  177. port: The port to run the frontend on.
  178. backend_present: Whether the backend is present.
  179. """
  180. from reflex.utils import prerequisites
  181. # validate dependencies before run
  182. prerequisites.validate_frontend_dependencies(init=False)
  183. # Run the frontend in development mode.
  184. console.rule("[bold green]App Running")
  185. os.environ["PORT"] = str(get_config().frontend_port if port is None else port)
  186. run_process_and_launch_url(
  187. [
  188. *prerequisites.get_js_package_executor(raise_on_none=True)[0],
  189. "run",
  190. "dev",
  191. ],
  192. backend_present,
  193. )
  194. def run_frontend_prod(root: Path, port: str, backend_present: bool = True):
  195. """Run the frontend.
  196. Args:
  197. root: The root path of the project (to keep same API as run_frontend).
  198. port: The port to run the frontend on.
  199. backend_present: Whether the backend is present.
  200. """
  201. from reflex.utils import prerequisites
  202. # Set the port.
  203. os.environ["PORT"] = str(get_config().frontend_port if port is None else port)
  204. # validate dependencies before run
  205. prerequisites.validate_frontend_dependencies(init=False)
  206. # Run the frontend in production mode.
  207. console.rule("[bold green]App Running")
  208. run_process_and_launch_url(
  209. [*prerequisites.get_js_package_executor(raise_on_none=True)[0], "run", "prod"],
  210. backend_present,
  211. )
  212. @once
  213. def _warn_user_about_uvicorn():
  214. console.warn(
  215. "Using Uvicorn for backend as it is installed. This behavior will change in 0.8.0 to use Granian by default."
  216. )
  217. def should_use_granian():
  218. """Whether to use Granian for backend.
  219. Returns:
  220. True if Granian should be used.
  221. """
  222. if environment.REFLEX_USE_GRANIAN.is_set():
  223. return environment.REFLEX_USE_GRANIAN.get()
  224. if (
  225. importlib.util.find_spec("uvicorn") is None
  226. or importlib.util.find_spec("gunicorn") is None
  227. ):
  228. return True
  229. _warn_user_about_uvicorn()
  230. return False
  231. def get_app_module():
  232. """Get the app module for the backend.
  233. Returns:
  234. The app module for the backend.
  235. """
  236. return get_config().module
  237. def get_app_instance():
  238. """Get the app module for the backend.
  239. Returns:
  240. The app module for the backend.
  241. """
  242. return f"{get_app_module()}:{constants.CompileVars.APP}"
  243. def get_app_file() -> Path:
  244. """Get the app file for the backend.
  245. Returns:
  246. The app file for the backend.
  247. Raises:
  248. ImportError: If the app module is not found.
  249. """
  250. current_working_dir = str(Path.cwd())
  251. if current_working_dir not in sys.path:
  252. # Add the current working directory to sys.path
  253. sys.path.insert(0, current_working_dir)
  254. module_spec = importlib.util.find_spec(get_app_module())
  255. if module_spec is None:
  256. msg = f"Module {get_app_module()} not found. Make sure the module is installed."
  257. raise ImportError(msg)
  258. file_name = module_spec.origin
  259. if file_name is None:
  260. msg = f"Module {get_app_module()} not found. Make sure the module is installed."
  261. raise ImportError(msg)
  262. return Path(file_name).resolve()
  263. def get_app_instance_from_file() -> str:
  264. """Get the app module for the backend.
  265. Returns:
  266. The app module for the backend.
  267. """
  268. return f"{get_app_file()}:{constants.CompileVars.APP}"
  269. def run_backend(
  270. host: str,
  271. port: int,
  272. loglevel: constants.LogLevel = constants.LogLevel.ERROR,
  273. frontend_present: bool = False,
  274. ):
  275. """Run the backend.
  276. Args:
  277. host: The app host
  278. port: The app port
  279. loglevel: The log level.
  280. frontend_present: Whether the frontend is present.
  281. """
  282. web_dir = get_web_dir()
  283. # Create a .nocompile file to skip compile for backend.
  284. if web_dir.exists():
  285. (web_dir / constants.NOCOMPILE_FILE).touch()
  286. if not frontend_present:
  287. notify_backend()
  288. # Run the backend in development mode.
  289. if should_use_granian():
  290. run_granian_backend(host, port, loglevel)
  291. else:
  292. run_uvicorn_backend(host, port, loglevel)
  293. def get_reload_paths() -> Sequence[Path]:
  294. """Get the reload paths for the backend.
  295. Returns:
  296. The reload paths for the backend.
  297. """
  298. config = get_config()
  299. reload_paths = [Path.cwd()]
  300. if (spec := importlib.util.find_spec(config.module)) is not None and spec.origin:
  301. module_path = Path(spec.origin).resolve().parent
  302. while module_path.parent.name and any(
  303. sibling_file.name == "__init__.py" for sibling_file in module_path.iterdir()
  304. ):
  305. # go up a level to find dir without `__init__.py`
  306. module_path = module_path.parent
  307. reload_paths = [module_path]
  308. include_dirs = tuple(
  309. map(Path.absolute, environment.REFLEX_HOT_RELOAD_INCLUDE_PATHS.get())
  310. )
  311. exclude_dirs = tuple(
  312. map(Path.absolute, environment.REFLEX_HOT_RELOAD_EXCLUDE_PATHS.get())
  313. )
  314. def is_excluded_by_default(path: Path) -> bool:
  315. if path.is_dir():
  316. if path.name.startswith("."):
  317. # exclude hidden directories
  318. return True
  319. if path.name.startswith("__"):
  320. # ignore things like __pycache__
  321. return True
  322. return path.name in (".gitignore", "uploaded_files")
  323. reload_paths = (
  324. tuple(
  325. path.absolute()
  326. for dir in reload_paths
  327. for path in dir.iterdir()
  328. if not is_excluded_by_default(path)
  329. )
  330. + include_dirs
  331. )
  332. if exclude_dirs:
  333. reload_paths = tuple(
  334. path
  335. for path in reload_paths
  336. if all(not path.samefile(exclude) for exclude in exclude_dirs)
  337. )
  338. console.debug(f"Reload paths: {list(map(str, reload_paths))}")
  339. return reload_paths
  340. def run_uvicorn_backend(host: str, port: int, loglevel: LogLevel):
  341. """Run the backend in development mode using Uvicorn.
  342. Args:
  343. host: The app host
  344. port: The app port
  345. loglevel: The log level.
  346. """
  347. import uvicorn
  348. uvicorn.run(
  349. app=f"{get_app_instance()}",
  350. factory=True,
  351. host=host,
  352. port=port,
  353. log_level=loglevel.value,
  354. reload=True,
  355. reload_dirs=list(map(str, get_reload_paths())),
  356. reload_delay=0.1,
  357. )
  358. HOTRELOAD_IGNORE_EXTENSIONS = (
  359. "txt",
  360. "toml",
  361. "sqlite",
  362. "yaml",
  363. "yml",
  364. "json",
  365. "sh",
  366. "bash",
  367. "log",
  368. )
  369. HOTRELOAD_IGNORE_PATTERNS = (
  370. *[rf"^.*\.{ext}$" for ext in HOTRELOAD_IGNORE_EXTENSIONS],
  371. r"^[^\.]*$", # Ignore files without an extension
  372. )
  373. def run_granian_backend(host: str, port: int, loglevel: LogLevel):
  374. """Run the backend in development mode using Granian.
  375. Args:
  376. host: The app host
  377. port: The app port
  378. loglevel: The log level.
  379. """
  380. console.debug("Using Granian for backend")
  381. if environment.REFLEX_STRICT_HOT_RELOAD.get():
  382. import multiprocessing
  383. multiprocessing.set_start_method("spawn", force=True)
  384. from granian.constants import Interfaces
  385. from granian.log import LogLevels
  386. from granian.server import MPServer as Granian
  387. Granian(
  388. target=get_app_instance_from_file(),
  389. factory=True,
  390. address=host,
  391. port=port,
  392. interface=Interfaces.ASGI,
  393. log_level=LogLevels(loglevel.value),
  394. reload=True,
  395. reload_paths=get_reload_paths(),
  396. reload_ignore_worker_failure=True,
  397. reload_ignore_patterns=HOTRELOAD_IGNORE_PATTERNS,
  398. reload_tick=100,
  399. workers_kill_timeout=2,
  400. ).serve()
  401. def _deprecate_asgi_config(
  402. config_name: str,
  403. reason: str = "",
  404. ):
  405. console.deprecate(
  406. f"config.{config_name}",
  407. reason=reason,
  408. deprecation_version="0.7.9",
  409. removal_version="0.8.0",
  410. )
  411. @once
  412. def _get_backend_workers():
  413. from reflex.utils import processes
  414. config = get_config()
  415. gunicorn_workers = config.gunicorn_workers or 0
  416. if config.gunicorn_workers is not None:
  417. _deprecate_asgi_config(
  418. "gunicorn_workers",
  419. "If you're using Granian, use GRANIAN_WORKERS instead.",
  420. )
  421. return gunicorn_workers if gunicorn_workers else processes.get_num_workers()
  422. @once
  423. def _get_backend_timeout():
  424. config = get_config()
  425. timeout = config.timeout or 120
  426. if config.timeout is not None:
  427. _deprecate_asgi_config(
  428. "timeout",
  429. "If you're using Granian, use GRANIAN_WORKERS_LIFETIME instead.",
  430. )
  431. return timeout
  432. @once
  433. def _get_backend_max_requests():
  434. config = get_config()
  435. gunicorn_max_requests = config.gunicorn_max_requests or 120
  436. if config.gunicorn_max_requests is not None:
  437. _deprecate_asgi_config("gunicorn_max_requests")
  438. return gunicorn_max_requests
  439. @once
  440. def _get_backend_max_requests_jitter():
  441. config = get_config()
  442. gunicorn_max_requests_jitter = config.gunicorn_max_requests_jitter or 25
  443. if config.gunicorn_max_requests_jitter is not None:
  444. _deprecate_asgi_config("gunicorn_max_requests_jitter")
  445. return gunicorn_max_requests_jitter
  446. def run_backend_prod(
  447. host: str,
  448. port: int,
  449. loglevel: constants.LogLevel = constants.LogLevel.ERROR,
  450. frontend_present: bool = False,
  451. ):
  452. """Run the backend.
  453. Args:
  454. host: The app host
  455. port: The app port
  456. loglevel: The log level.
  457. frontend_present: Whether the frontend is present.
  458. """
  459. if not frontend_present:
  460. notify_backend()
  461. if should_use_granian():
  462. run_granian_backend_prod(host, port, loglevel)
  463. else:
  464. run_uvicorn_backend_prod(host, port, loglevel)
  465. def run_uvicorn_backend_prod(host: str, port: int, loglevel: LogLevel):
  466. """Run the backend in production mode using Uvicorn.
  467. Args:
  468. host: The app host
  469. port: The app port
  470. loglevel: The log level.
  471. """
  472. from reflex.utils import processes
  473. config = get_config()
  474. app_module = get_app_instance()
  475. command = (
  476. [
  477. "uvicorn",
  478. *(
  479. (
  480. "--limit-max-requests",
  481. str(max_requessts),
  482. )
  483. if (
  484. (max_requessts := _get_backend_max_requests()) is not None
  485. and max_requessts > 0
  486. )
  487. else ()
  488. ),
  489. *(
  490. ("--timeout-keep-alive", str(timeout))
  491. if (timeout := _get_backend_timeout()) is not None
  492. else ()
  493. ),
  494. *("--host", host),
  495. *("--port", str(port)),
  496. *("--workers", str(_get_backend_workers())),
  497. "--factory",
  498. app_module,
  499. ]
  500. if constants.IS_WINDOWS
  501. else [
  502. "gunicorn",
  503. *("--worker-class", config.gunicorn_worker_class),
  504. *(
  505. (
  506. "--max-requests",
  507. str(max_requessts),
  508. )
  509. if (
  510. (max_requessts := _get_backend_max_requests()) is not None
  511. and max_requessts > 0
  512. )
  513. else ()
  514. ),
  515. *(
  516. (
  517. "--max-requests-jitter",
  518. str(max_requessts_jitter),
  519. )
  520. if (
  521. (max_requessts_jitter := _get_backend_max_requests_jitter())
  522. is not None
  523. and max_requessts_jitter > 0
  524. )
  525. else ()
  526. ),
  527. "--preload",
  528. *(
  529. ("--timeout", str(timeout))
  530. if (timeout := _get_backend_timeout()) is not None
  531. else ()
  532. ),
  533. *("--bind", f"{host}:{port}"),
  534. *("--threads", str(_get_backend_workers())),
  535. f"{app_module}()",
  536. ]
  537. )
  538. command += [
  539. *("--log-level", loglevel.value),
  540. ]
  541. processes.new_process(
  542. command,
  543. run=True,
  544. show_logs=True,
  545. env={
  546. environment.REFLEX_SKIP_COMPILE.name: "true"
  547. }, # skip compile for prod backend
  548. )
  549. def run_granian_backend_prod(host: str, port: int, loglevel: LogLevel):
  550. """Run the backend in production mode using Granian.
  551. Args:
  552. host: The app host
  553. port: The app port
  554. loglevel: The log level.
  555. """
  556. from reflex.utils import processes
  557. try:
  558. from granian.constants import Interfaces
  559. command = [
  560. "granian",
  561. *("--workers", str(_get_backend_workers())),
  562. *("--log-level", "critical"),
  563. *("--host", host),
  564. *("--port", str(port)),
  565. *("--interface", str(Interfaces.ASGI)),
  566. *("--factory", get_app_instance_from_file()),
  567. ]
  568. processes.new_process(
  569. command,
  570. run=True,
  571. show_logs=True,
  572. env={
  573. environment.REFLEX_SKIP_COMPILE.name: "true"
  574. }, # skip compile for prod backend
  575. )
  576. except ImportError:
  577. console.error(
  578. 'InstallError: REFLEX_USE_GRANIAN is set but `granian` is not installed. (run `pip install "granian[reload]>=1.6.0"`)'
  579. )
  580. def output_system_info():
  581. """Show system information if the loglevel is in DEBUG."""
  582. if console._LOG_LEVEL > constants.LogLevel.DEBUG:
  583. return
  584. from reflex.utils import prerequisites
  585. config = get_config()
  586. try:
  587. config_file = sys.modules[config.__module__].__file__
  588. except Exception:
  589. config_file = None
  590. console.rule("System Info")
  591. console.debug(f"Config file: {config_file!r}")
  592. console.debug(f"Config: {config}")
  593. dependencies = [
  594. f"[Reflex {constants.Reflex.VERSION} with Python {platform.python_version()} (PATH: {sys.executable})]",
  595. f"[Node {prerequisites.get_node_version()} (Minimum: {constants.Node.MIN_VERSION}) (PATH:{path_ops.get_node_path()})]",
  596. ]
  597. system = platform.system()
  598. dependencies.append(
  599. f"[Bun {prerequisites.get_bun_version()} (Minimum: {constants.Bun.MIN_VERSION}) (PATH: {path_ops.get_bun_path()})]"
  600. )
  601. if system == "Linux":
  602. os_version = platform.freedesktop_os_release().get("PRETTY_NAME", "Unknown")
  603. else:
  604. os_version = platform.version()
  605. dependencies.append(f"[OS {platform.system()} {os_version}]")
  606. for dep in dependencies:
  607. console.debug(f"{dep}")
  608. console.debug(
  609. f"Using package installer at: {prerequisites.get_nodejs_compatible_package_managers(raise_on_none=False)}"
  610. )
  611. console.debug(
  612. f"Using package executer at: {prerequisites.get_js_package_executor(raise_on_none=False)}"
  613. )
  614. if system != "Windows":
  615. console.debug(f"Unzip path: {path_ops.which('unzip')}")
  616. def is_testing_env() -> bool:
  617. """Whether the app is running in a testing environment.
  618. Returns:
  619. True if the app is running in under pytest.
  620. """
  621. return constants.PYTEST_CURRENT_TEST in os.environ
  622. def is_in_app_harness() -> bool:
  623. """Whether the app is running in the app harness.
  624. Returns:
  625. True if the app is running in the app harness.
  626. """
  627. return constants.APP_HARNESS_FLAG in os.environ
  628. def is_prod_mode() -> bool:
  629. """Check if the app is running in production mode.
  630. Returns:
  631. True if the app is running in production mode or False if running in dev mode.
  632. """
  633. current_mode = environment.REFLEX_ENV_MODE.get()
  634. return current_mode == constants.Env.PROD
  635. def get_compile_context() -> constants.CompileContext:
  636. """Check if the app is compiled for deploy.
  637. Returns:
  638. Whether the app is being compiled for deploy.
  639. """
  640. return environment.REFLEX_COMPILE_CONTEXT.get()