compiler.py 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928
  1. """Compiler for the reflex apps."""
  2. from __future__ import annotations
  3. from collections.abc import Iterable, Sequence
  4. from datetime import datetime
  5. from inspect import getmodule
  6. from pathlib import Path
  7. from typing import TYPE_CHECKING
  8. from reflex import constants
  9. from reflex.compiler import templates, utils
  10. from reflex.components.base.fragment import Fragment
  11. from reflex.components.component import (
  12. BaseComponent,
  13. Component,
  14. ComponentStyle,
  15. CustomComponent,
  16. StatefulComponent,
  17. )
  18. from reflex.config import environment, get_config
  19. from reflex.constants.compiler import PageNames
  20. from reflex.state import BaseState
  21. from reflex.style import SYSTEM_COLOR_MODE
  22. from reflex.utils import console, path_ops
  23. from reflex.utils.exceptions import ReflexError
  24. from reflex.utils.exec import is_prod_mode
  25. from reflex.utils.imports import ImportVar
  26. from reflex.utils.prerequisites import get_web_dir
  27. from reflex.vars.base import LiteralVar, Var
  28. def _apply_common_imports(
  29. imports: dict[str, list[ImportVar]],
  30. ):
  31. imports.setdefault("@emotion/react", []).append(ImportVar("jsx"))
  32. imports.setdefault("react", []).append(ImportVar("Fragment"))
  33. def _compile_document_root(root: Component) -> str:
  34. """Compile the document root.
  35. Args:
  36. root: The document root to compile.
  37. Returns:
  38. The compiled document root.
  39. """
  40. document_root_imports = root._get_all_imports()
  41. _apply_common_imports(document_root_imports)
  42. return templates.DOCUMENT_ROOT.render(
  43. imports=utils.compile_imports(document_root_imports),
  44. document=root.render(),
  45. )
  46. def _normalize_library_name(lib: str) -> str:
  47. """Normalize the library name.
  48. Args:
  49. lib: The library name to normalize.
  50. Returns:
  51. The normalized library name.
  52. """
  53. if lib == "react":
  54. return "React"
  55. return lib.replace("$/", "").replace("@", "").replace("/", "_").replace("-", "_")
  56. def _compile_app(app_root: Component) -> str:
  57. """Compile the app template component.
  58. Args:
  59. app_root: The app root to compile.
  60. Returns:
  61. The compiled app.
  62. """
  63. from reflex.components.dynamic import bundled_libraries
  64. window_libraries = [
  65. (_normalize_library_name(name), name) for name in bundled_libraries
  66. ]
  67. app_root_imports = app_root._get_all_imports()
  68. _apply_common_imports(app_root_imports)
  69. return templates.APP_ROOT.render(
  70. imports=utils.compile_imports(app_root_imports),
  71. custom_codes=app_root._get_all_custom_code(),
  72. hooks=app_root._get_all_hooks(),
  73. window_libraries=window_libraries,
  74. render=app_root.render(),
  75. dynamic_imports=app_root._get_all_dynamic_imports(),
  76. )
  77. def _compile_theme(theme: str) -> str:
  78. """Compile the theme.
  79. Args:
  80. theme: The theme to compile.
  81. Returns:
  82. The compiled theme.
  83. """
  84. return templates.THEME.render(theme=theme)
  85. def _compile_contexts(state: type[BaseState] | None, theme: Component | None) -> str:
  86. """Compile the initial state and contexts.
  87. Args:
  88. state: The app state.
  89. theme: The top-level app theme.
  90. Returns:
  91. The compiled context file.
  92. """
  93. appearance = getattr(theme, "appearance", None)
  94. if appearance is None or str(LiteralVar.create(appearance)) == '"inherit"':
  95. appearance = LiteralVar.create(SYSTEM_COLOR_MODE)
  96. last_compiled_time = str(datetime.now())
  97. return (
  98. templates.CONTEXT.render(
  99. initial_state=utils.compile_state(state),
  100. state_name=state.get_name(),
  101. client_storage=utils.compile_client_storage(state),
  102. is_dev_mode=not is_prod_mode(),
  103. last_compiled_time=last_compiled_time,
  104. default_color_mode=appearance,
  105. )
  106. if state
  107. else templates.CONTEXT.render(
  108. is_dev_mode=not is_prod_mode(),
  109. default_color_mode=appearance,
  110. last_compiled_time=last_compiled_time,
  111. )
  112. )
  113. def _compile_page(
  114. component: BaseComponent,
  115. state: type[BaseState] | None,
  116. ) -> str:
  117. """Compile the component given the app state.
  118. Args:
  119. component: The component to compile.
  120. state: The app state.
  121. Returns:
  122. The compiled component.
  123. """
  124. imports = component._get_all_imports()
  125. _apply_common_imports(imports)
  126. imports = utils.compile_imports(imports)
  127. # Compile the code to render the component.
  128. kwargs = {"state_name": state.get_name()} if state is not None else {}
  129. return templates.PAGE.render(
  130. imports=imports,
  131. dynamic_imports=component._get_all_dynamic_imports(),
  132. custom_codes=component._get_all_custom_code(),
  133. hooks=component._get_all_hooks(),
  134. render=component.render(),
  135. **kwargs,
  136. )
  137. def compile_root_stylesheet(stylesheets: list[str]) -> tuple[str, str]:
  138. """Compile the root stylesheet.
  139. Args:
  140. stylesheets: The stylesheets to include in the root stylesheet.
  141. Returns:
  142. The path and code of the compiled root stylesheet.
  143. """
  144. output_path = utils.get_root_stylesheet_path()
  145. code = _compile_root_stylesheet(stylesheets)
  146. return output_path, code
  147. def _validate_stylesheet(stylesheet_full_path: Path, assets_app_path: Path) -> None:
  148. """Validate the stylesheet.
  149. Args:
  150. stylesheet_full_path: The stylesheet to validate.
  151. assets_app_path: The path to the assets directory.
  152. Raises:
  153. ValueError: If the stylesheet is not supported.
  154. FileNotFoundError: If the stylesheet is not found.
  155. """
  156. suffix = stylesheet_full_path.suffix[1:] if stylesheet_full_path.suffix else ""
  157. if suffix not in constants.Reflex.STYLESHEETS_SUPPORTED:
  158. raise ValueError(f"Stylesheet file {stylesheet_full_path} is not supported.")
  159. if not stylesheet_full_path.absolute().is_relative_to(assets_app_path.absolute()):
  160. raise FileNotFoundError(
  161. f"Cannot include stylesheets from outside the assets directory: {stylesheet_full_path}"
  162. )
  163. if not stylesheet_full_path.name:
  164. raise ValueError(
  165. f"Stylesheet file name cannot be empty: {stylesheet_full_path}"
  166. )
  167. if (
  168. len(
  169. stylesheet_full_path.absolute()
  170. .relative_to(assets_app_path.absolute())
  171. .parts
  172. )
  173. == 1
  174. and stylesheet_full_path.stem == PageNames.STYLESHEET_ROOT
  175. ):
  176. raise ValueError(
  177. f"Stylesheet file name cannot be '{PageNames.STYLESHEET_ROOT}': {stylesheet_full_path}"
  178. )
  179. def _compile_root_stylesheet(stylesheets: list[str]) -> str:
  180. """Compile the root stylesheet.
  181. Args:
  182. stylesheets: The stylesheets to include in the root stylesheet.
  183. Returns:
  184. The compiled root stylesheet.
  185. Raises:
  186. FileNotFoundError: If a specified stylesheet in assets directory does not exist.
  187. """
  188. # Add tailwind css if enabled.
  189. sheets = (
  190. [constants.Tailwind.ROOT_STYLE_PATH]
  191. if get_config().tailwind is not None
  192. else []
  193. )
  194. failed_to_import_sass = False
  195. assets_app_path = Path.cwd() / constants.Dirs.APP_ASSETS
  196. stylesheets_files: list[Path] = []
  197. stylesheets_urls = []
  198. for stylesheet in stylesheets:
  199. if not utils.is_valid_url(stylesheet):
  200. # check if stylesheet provided exists.
  201. stylesheet_full_path = assets_app_path / stylesheet.strip("/")
  202. if not stylesheet_full_path.exists():
  203. raise FileNotFoundError(
  204. f"The stylesheet file {stylesheet_full_path} does not exist."
  205. )
  206. if stylesheet_full_path.is_dir():
  207. all_files = (
  208. file
  209. for ext in constants.Reflex.STYLESHEETS_SUPPORTED
  210. for file in stylesheet_full_path.rglob("*." + ext)
  211. )
  212. for file in all_files:
  213. if file.is_dir():
  214. continue
  215. # Validate the stylesheet.
  216. _validate_stylesheet(file, assets_app_path)
  217. stylesheets_files.append(file)
  218. else:
  219. # Validate the stylesheet.
  220. _validate_stylesheet(stylesheet_full_path, assets_app_path)
  221. stylesheets_files.append(stylesheet_full_path)
  222. else:
  223. stylesheets_urls.append(stylesheet)
  224. sheets.extend(dict.fromkeys(stylesheets_urls))
  225. for stylesheet in stylesheets_files:
  226. target_path = stylesheet.relative_to(assets_app_path).with_suffix(".css")
  227. target = get_web_dir() / constants.Dirs.STYLES / target_path
  228. target.parent.mkdir(parents=True, exist_ok=True)
  229. if stylesheet.suffix == ".css":
  230. path_ops.cp(src=stylesheet, dest=target, overwrite=True)
  231. else:
  232. try:
  233. from sass import compile as sass_compile
  234. target.write_text(
  235. data=sass_compile(
  236. filename=str(stylesheet),
  237. output_style="compressed",
  238. ),
  239. encoding="utf8",
  240. )
  241. except ImportError:
  242. failed_to_import_sass = True
  243. str_target_path = "./" + str(target_path)
  244. sheets.append(str_target_path) if str_target_path not in sheets else None
  245. if failed_to_import_sass:
  246. console.error(
  247. 'The `libsass` package is required to compile sass/scss stylesheet files. Run `pip install "libsass>=0.23.0"`.'
  248. )
  249. return templates.STYLE.render(stylesheets=sheets)
  250. def _compile_component(component: Component | StatefulComponent) -> str:
  251. """Compile a single component.
  252. Args:
  253. component: The component to compile.
  254. Returns:
  255. The compiled component.
  256. """
  257. return templates.COMPONENT.render(component=component)
  258. def _compile_components(
  259. components: set[CustomComponent],
  260. ) -> tuple[str, dict[str, list[ImportVar]]]:
  261. """Compile the components.
  262. Args:
  263. components: The components to compile.
  264. Returns:
  265. The compiled components.
  266. """
  267. imports = {
  268. "react": [ImportVar(tag="memo")],
  269. f"$/{constants.Dirs.STATE_PATH}": [ImportVar(tag="isTrue")],
  270. }
  271. component_renders = []
  272. # Compile each component.
  273. for component in components:
  274. component_render, component_imports = utils.compile_custom_component(component)
  275. component_renders.append(component_render)
  276. imports = utils.merge_imports(imports, component_imports)
  277. _apply_common_imports(imports)
  278. dynamic_imports = {
  279. comp_import: None
  280. for comp_render in component_renders
  281. if "dynamic_imports" in comp_render
  282. for comp_import in comp_render["dynamic_imports"]
  283. }
  284. custom_codes = {
  285. comp_custom_code: None
  286. for comp_render in component_renders
  287. for comp_custom_code in comp_render.get("custom_code", [])
  288. }
  289. # Compile the components page.
  290. return (
  291. templates.COMPONENTS.render(
  292. imports=utils.compile_imports(imports),
  293. components=component_renders,
  294. dynamic_imports=dynamic_imports,
  295. custom_codes=custom_codes,
  296. ),
  297. imports,
  298. )
  299. def _compile_stateful_components(
  300. page_components: list[BaseComponent],
  301. ) -> str:
  302. """Walk the page components and extract shared stateful components.
  303. Any StatefulComponent that is shared by more than one page will be rendered
  304. to a separate module and marked rendered_as_shared so subsequent
  305. renderings will import the component from the shared module instead of
  306. directly including the code for it.
  307. Args:
  308. page_components: The Components or StatefulComponents to compile.
  309. Returns:
  310. The rendered stateful components code.
  311. """
  312. all_import_dicts = []
  313. rendered_components = {}
  314. def get_shared_components_recursive(component: BaseComponent):
  315. """Get the shared components for a component and its children.
  316. A shared component is a StatefulComponent that appears in 2 or more
  317. pages and is a candidate for writing to a common file and importing
  318. into each page where it is used.
  319. Args:
  320. component: The component to collect shared StatefulComponents for.
  321. """
  322. for child in component.children:
  323. # Depth-first traversal.
  324. get_shared_components_recursive(child)
  325. # When the component is referenced by more than one page, render it
  326. # to be included in the STATEFUL_COMPONENTS module.
  327. # Skip this step in dev mode, thereby avoiding potential hot reload errors for larger apps
  328. if (
  329. isinstance(component, StatefulComponent)
  330. and component.references > 1
  331. and is_prod_mode()
  332. ):
  333. # Reset this flag to render the actual component.
  334. component.rendered_as_shared = False
  335. # Include dynamic imports in the shared component.
  336. if dynamic_imports := component._get_all_dynamic_imports():
  337. rendered_components.update(dict.fromkeys(dynamic_imports))
  338. # Include custom code in the shared component.
  339. rendered_components.update(
  340. dict.fromkeys(component._get_all_custom_code()),
  341. )
  342. # Include all imports in the shared component.
  343. all_import_dicts.append(component._get_all_imports())
  344. # Indicate that this component now imports from the shared file.
  345. component.rendered_as_shared = True
  346. for page_component in page_components:
  347. get_shared_components_recursive(page_component)
  348. # Don't import from the file that we're about to create.
  349. all_imports = utils.merge_imports(*all_import_dicts)
  350. all_imports.pop(
  351. f"$/{constants.Dirs.UTILS}/{constants.PageNames.STATEFUL_COMPONENTS}", None
  352. )
  353. if rendered_components:
  354. _apply_common_imports(all_imports)
  355. return templates.STATEFUL_COMPONENTS.render(
  356. imports=utils.compile_imports(all_imports),
  357. memoized_code="\n".join(rendered_components),
  358. )
  359. def _compile_tailwind(
  360. config: dict,
  361. ) -> str:
  362. """Compile the Tailwind config.
  363. Args:
  364. config: The Tailwind config.
  365. Returns:
  366. The compiled Tailwind config.
  367. """
  368. return templates.TAILWIND_CONFIG.render(
  369. **config,
  370. )
  371. def compile_document_root(
  372. head_components: list[Component],
  373. html_lang: str | None = None,
  374. html_custom_attrs: dict[str, Var | str] | None = None,
  375. ) -> tuple[str, str]:
  376. """Compile the document root.
  377. Args:
  378. head_components: The components to include in the head.
  379. html_lang: The language of the document, will be added to the html root element.
  380. html_custom_attrs: custom attributes added to the html root element.
  381. Returns:
  382. The path and code of the compiled document root.
  383. """
  384. # Get the path for the output file.
  385. output_path = utils.get_page_path(constants.PageNames.DOCUMENT_ROOT)
  386. # Create the document root.
  387. document_root = utils.create_document_root(
  388. head_components, html_lang=html_lang, html_custom_attrs=html_custom_attrs
  389. )
  390. # Compile the document root.
  391. code = _compile_document_root(document_root)
  392. return output_path, code
  393. def compile_app(app_root: Component) -> tuple[str, str]:
  394. """Compile the app root.
  395. Args:
  396. app_root: The app root component to compile.
  397. Returns:
  398. The path and code of the compiled app wrapper.
  399. """
  400. # Get the path for the output file.
  401. output_path = utils.get_page_path(constants.PageNames.APP_ROOT)
  402. # Compile the document root.
  403. code = _compile_app(app_root)
  404. return output_path, code
  405. def compile_theme(style: ComponentStyle) -> tuple[str, str]:
  406. """Compile the theme.
  407. Args:
  408. style: The style to compile.
  409. Returns:
  410. The path and code of the compiled theme.
  411. """
  412. output_path = utils.get_theme_path()
  413. # Create the theme.
  414. theme = utils.create_theme(style)
  415. # Compile the theme.
  416. code = _compile_theme(str(LiteralVar.create(theme)))
  417. return output_path, code
  418. def compile_contexts(
  419. state: type[BaseState] | None,
  420. theme: Component | None,
  421. ) -> tuple[str, str]:
  422. """Compile the initial state / context.
  423. Args:
  424. state: The app state.
  425. theme: The top-level app theme.
  426. Returns:
  427. The path and code of the compiled context.
  428. """
  429. # Get the path for the output file.
  430. output_path = utils.get_context_path()
  431. return output_path, _compile_contexts(state, theme)
  432. def compile_page(
  433. path: str, component: BaseComponent, state: type[BaseState] | None
  434. ) -> tuple[str, str]:
  435. """Compile a single page.
  436. Args:
  437. path: The path to compile the page to.
  438. component: The component to compile.
  439. state: The app state.
  440. Returns:
  441. The path and code of the compiled page.
  442. """
  443. # Get the path for the output file.
  444. output_path = utils.get_page_path(path)
  445. # Add the style to the component.
  446. code = _compile_page(component, state)
  447. return output_path, code
  448. def compile_components(
  449. components: set[CustomComponent],
  450. ) -> tuple[str, str, dict[str, list[ImportVar]]]:
  451. """Compile the custom components.
  452. Args:
  453. components: The custom components to compile.
  454. Returns:
  455. The path and code of the compiled components.
  456. """
  457. # Get the path for the output file.
  458. output_path = utils.get_components_path()
  459. # Compile the components.
  460. code, imports = _compile_components(components)
  461. return output_path, code, imports
  462. def compile_stateful_components(
  463. pages: Iterable[Component],
  464. ) -> tuple[str, str, list[BaseComponent]]:
  465. """Separately compile components that depend on State vars.
  466. StatefulComponents are compiled as their own component functions with their own
  467. useContext declarations, which allows page components to be stateless and avoid
  468. re-rendering along with parts of the page that actually depend on state.
  469. Args:
  470. pages: The pages to extract stateful components from.
  471. Returns:
  472. The path and code of the compiled stateful components.
  473. """
  474. output_path = utils.get_stateful_components_path()
  475. # Compile the stateful components.
  476. page_components = [StatefulComponent.compile_from(page) or page for page in pages]
  477. code = _compile_stateful_components(page_components)
  478. return output_path, code, page_components
  479. def compile_tailwind(
  480. config: dict,
  481. ):
  482. """Compile the Tailwind config.
  483. Args:
  484. config: The Tailwind config.
  485. Returns:
  486. The compiled Tailwind config.
  487. """
  488. # Get the path for the output file.
  489. output_path = str((get_web_dir() / constants.Tailwind.CONFIG).absolute())
  490. # Compile the config.
  491. code = _compile_tailwind(config)
  492. return output_path, code
  493. def remove_tailwind_from_postcss() -> tuple[str, str]:
  494. """If tailwind is not to be used, remove it from postcss.config.js.
  495. Returns:
  496. The path and code of the compiled postcss.config.js.
  497. """
  498. # Get the path for the output file.
  499. output_path = str(get_web_dir() / constants.Dirs.POSTCSS_JS)
  500. code = [
  501. line
  502. for line in Path(output_path).read_text().splitlines(keepends=True)
  503. if "tailwindcss: " not in line
  504. ]
  505. # Compile the config.
  506. return output_path, "".join(code)
  507. def purge_web_pages_dir():
  508. """Empty out .web/pages directory."""
  509. if not is_prod_mode() and environment.REFLEX_PERSIST_WEB_DIR.get():
  510. # Skip purging the web directory in dev mode if REFLEX_PERSIST_WEB_DIR is set.
  511. return
  512. # Empty out the web pages directory.
  513. utils.empty_dir(get_web_dir() / constants.Dirs.PAGES, keep_files=["_app.js"])
  514. if TYPE_CHECKING:
  515. from reflex.app import ComponentCallable, UnevaluatedPage
  516. def _into_component_once(
  517. component: Component
  518. | ComponentCallable
  519. | tuple[Component, ...]
  520. | str
  521. | Var
  522. | int
  523. | float,
  524. ) -> Component | None:
  525. """Convert a component to a Component.
  526. Args:
  527. component: The component to convert.
  528. Returns:
  529. The converted component.
  530. """
  531. if isinstance(component, Component):
  532. return component
  533. if isinstance(component, (Var, int, float, str)):
  534. return Fragment.create(component)
  535. if isinstance(component, Sequence):
  536. return Fragment.create(*component)
  537. return None
  538. def readable_name_from_component(
  539. component: Component | ComponentCallable,
  540. ) -> str | None:
  541. """Get the readable name of a component.
  542. Args:
  543. component: The component to get the name of.
  544. Returns:
  545. The readable name of the component.
  546. """
  547. if isinstance(component, Component):
  548. return type(component).__name__
  549. if isinstance(component, (Var, int, float, str)):
  550. return str(component)
  551. if isinstance(component, Sequence):
  552. return ", ".join(str(c) for c in component)
  553. if callable(component):
  554. module_name = getattr(component, "__module__", None)
  555. if module_name is not None:
  556. module = getmodule(component)
  557. if module is not None:
  558. module_name = module.__name__
  559. if module_name is not None:
  560. return f"{module_name}.{component.__name__}"
  561. return component.__name__
  562. return None
  563. def into_component(component: Component | ComponentCallable) -> Component:
  564. """Convert a component to a Component.
  565. Args:
  566. component: The component to convert.
  567. Returns:
  568. The converted component.
  569. Raises:
  570. TypeError: If the component is not a Component.
  571. # noqa: DAR401
  572. """
  573. if (converted := _into_component_once(component)) is not None:
  574. return converted
  575. try:
  576. if (
  577. callable(component)
  578. and (converted := _into_component_once(component())) is not None
  579. ):
  580. return converted
  581. except KeyError as e:
  582. if isinstance(e, ReflexError):
  583. raise
  584. key = e.args[0] if e.args else None
  585. if key is not None and isinstance(key, Var):
  586. raise TypeError(
  587. "Cannot access a primitive map with a Var. Consider calling rx.Var.create() on the map."
  588. ).with_traceback(e.__traceback__) from None
  589. raise
  590. except TypeError as e:
  591. if isinstance(e, ReflexError):
  592. raise
  593. message = e.args[0] if e.args else None
  594. if message and isinstance(message, str):
  595. if message.endswith("has no len()") and (
  596. "ArrayCastedVar" in message
  597. or "ObjectCastedVar" in message
  598. or "StringCastedVar" in message
  599. ):
  600. raise TypeError(
  601. "Cannot pass a Var to a built-in function. Consider using .length() for accessing the length of an iterable Var."
  602. ).with_traceback(e.__traceback__) from None
  603. if message.endswith(
  604. "indices must be integers or slices, not NumberCastedVar"
  605. ) or message.endswith(
  606. "indices must be integers or slices, not BooleanCastedVar"
  607. ):
  608. raise TypeError(
  609. "Cannot index into a primitive sequence with a Var. Consider calling rx.Var.create() on the sequence."
  610. ).with_traceback(e.__traceback__) from None
  611. if "CastedVar" in str(e):
  612. raise TypeError(
  613. "Cannot pass a Var to a built-in function. Consider moving the operation to the backend, using existing Var operations, or defining a custom Var operation."
  614. ).with_traceback(e.__traceback__) from None
  615. raise
  616. raise TypeError(f"Expected a Component, got {type(component)}")
  617. def compile_unevaluated_page(
  618. route: str,
  619. page: UnevaluatedPage,
  620. state: type[BaseState] | None = None,
  621. style: ComponentStyle | None = None,
  622. theme: Component | None = None,
  623. ) -> tuple[Component, bool]:
  624. """Compiles an uncompiled page into a component and adds meta information.
  625. Args:
  626. route: The route of the page.
  627. page: The uncompiled page object.
  628. state: The state of the app.
  629. style: The style of the page.
  630. theme: The theme of the page.
  631. Returns:
  632. The compiled component and whether state should be enabled.
  633. """
  634. # Generate the component if it is a callable.
  635. component = into_component(page.component)
  636. component._add_style_recursive(style or {}, theme)
  637. enable_state = False
  638. # Ensure state is enabled if this page uses state.
  639. if state is None:
  640. if page.on_load or component._has_stateful_event_triggers():
  641. enable_state = True
  642. else:
  643. for var in component._get_vars(include_children=True):
  644. var_data = var._get_all_var_data()
  645. if not var_data:
  646. continue
  647. if not var_data.state:
  648. continue
  649. enable_state = True
  650. break
  651. from reflex.app import OverlayFragment
  652. from reflex.utils.format import make_default_page_title
  653. component = OverlayFragment.create(component)
  654. meta_args = {
  655. "title": (
  656. page.title
  657. if page.title is not None
  658. else make_default_page_title(get_config().app_name, route)
  659. ),
  660. "image": page.image,
  661. "meta": page.meta,
  662. }
  663. if page.description is not None:
  664. meta_args["description"] = page.description
  665. # Add meta information to the component.
  666. utils.add_meta(
  667. component,
  668. **meta_args,
  669. )
  670. return component, enable_state
  671. class ExecutorSafeFunctions:
  672. """Helper class to allow parallelisation of parts of the compilation process.
  673. This class (and its class attributes) are available at global scope.
  674. In a multiprocessing context (like when using a ProcessPoolExecutor), the content of this
  675. global class is logically replicated to any FORKED process.
  676. How it works:
  677. * Before the child process is forked, ensure that we stash any input data required by any future
  678. function call in the child process.
  679. * After the child process is forked, the child process will have a copy of the global class, which
  680. includes the previously stashed input data.
  681. * Any task submitted to the child process simply needs a way to communicate which input data the
  682. requested function call requires.
  683. Why do we need this? Passing input data directly to child process often not possible because the input data is not picklable.
  684. The mechanic described here removes the need to pickle the input data at all.
  685. Limitations:
  686. * This can never support returning unpicklable OUTPUT data.
  687. * Any object mutations done by the child process will not propagate back to the parent process (fork goes one way!).
  688. """
  689. COMPONENTS: dict[str, BaseComponent] = {}
  690. UNCOMPILED_PAGES: dict[str, UnevaluatedPage] = {}
  691. STATE: type[BaseState] | None = None
  692. @classmethod
  693. def compile_page(cls, route: str) -> tuple[str, str]:
  694. """Compile a page.
  695. Args:
  696. route: The route of the page to compile.
  697. Returns:
  698. The path and code of the compiled page.
  699. """
  700. return compile_page(route, cls.COMPONENTS[route], cls.STATE)
  701. @classmethod
  702. def compile_unevaluated_page(
  703. cls,
  704. route: str,
  705. style: ComponentStyle,
  706. theme: Component | None,
  707. ) -> tuple[str, Component, tuple[str, str]]:
  708. """Compile an unevaluated page.
  709. Args:
  710. route: The route of the page to compile.
  711. style: The style of the page.
  712. theme: The theme of the page.
  713. Returns:
  714. The route, compiled component, and compiled page.
  715. """
  716. component, enable_state = compile_unevaluated_page(
  717. route, cls.UNCOMPILED_PAGES[route], cls.STATE, style, theme
  718. )
  719. return route, component, compile_page(route, component, cls.STATE)
  720. @classmethod
  721. def compile_theme(cls, style: ComponentStyle | None) -> tuple[str, str]:
  722. """Compile the theme.
  723. Args:
  724. style: The style to compile.
  725. Returns:
  726. The path and code of the compiled theme.
  727. Raises:
  728. ValueError: If the style is not set.
  729. """
  730. if style is None:
  731. raise ValueError("STYLE should be set")
  732. return compile_theme(style)