utils.py 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305
  1. """Common utility functions used in the compiler."""
  2. from __future__ import annotations
  3. import inspect
  4. import json
  5. import os
  6. from typing import TYPE_CHECKING, Any, Dict, List, Set, Type
  7. from pynecone import constants, utils
  8. from pynecone.compiler import templates
  9. from pynecone.components.base import (
  10. Body,
  11. Description,
  12. DocumentHead,
  13. Head,
  14. Html,
  15. Image,
  16. Link,
  17. Main,
  18. Script,
  19. Title,
  20. )
  21. from pynecone.components.component import ImportDict
  22. from pynecone.state import State
  23. from pynecone.style import Style
  24. from pynecone.var import BaseVar, Var
  25. if TYPE_CHECKING:
  26. from pynecone.components.component import Component, CustomComponent
  27. # To re-export this function.
  28. merge_imports = utils.merge_imports
  29. def compile_import_statement(lib: str, fields: Set[str]) -> str:
  30. """Compile an import statement.
  31. Args:
  32. lib: The library to import from.
  33. fields: The set of fields to import from the library.
  34. Returns:
  35. The compiled import statement.
  36. """
  37. # Check for default imports.
  38. defaults = {
  39. field
  40. for field in fields
  41. if field.lower() == lib.lower().replace("-", "").replace("/", "")
  42. }
  43. assert len(defaults) < 2
  44. # Get the default import, and the specific imports.
  45. default = next(iter(defaults), "")
  46. rest = fields - defaults
  47. return templates.format_import(lib=lib, default=default, rest=rest)
  48. def compile_imports(imports: ImportDict) -> str:
  49. """Compile an import dict.
  50. Args:
  51. imports: The import dict to compile.
  52. Returns:
  53. The compiled import dict.
  54. """
  55. return templates.join(
  56. [compile_import_statement(lib, fields) for lib, fields in imports.items()]
  57. )
  58. def compile_constant_declaration(name: str, value: str) -> str:
  59. """Compile a constant declaration.
  60. Args:
  61. name: The name of the constant.
  62. value: The value of the constant.
  63. Returns:
  64. The compiled constant declaration.
  65. """
  66. return templates.CONST(name=name, value=json.dumps(value))
  67. def compile_constants() -> str:
  68. """Compile all the necessary constants.
  69. Returns:
  70. A string of all the compiled constants.
  71. """
  72. endpoint = constants.Endpoint.EVENT
  73. return templates.join(
  74. [compile_constant_declaration(name=endpoint.name, value=endpoint.get_url())]
  75. )
  76. def compile_state(state: Type[State]) -> str:
  77. """Compile the state of the app.
  78. Args:
  79. state: The app state object.
  80. Returns:
  81. A string of the compiled state.
  82. """
  83. initial_state = state().dict()
  84. initial_state.update(
  85. {
  86. "events": [{"name": utils.get_hydrate_event(state)}],
  87. }
  88. )
  89. initial_state = utils.format_state(initial_state)
  90. synced_state = templates.format_state(
  91. state=state.get_name(), initial_state=json.dumps(initial_state)
  92. )
  93. initial_result = {
  94. constants.STATE: None,
  95. constants.EVENTS: [],
  96. constants.PROCESSING: False,
  97. }
  98. result = templates.format_state(
  99. state="result",
  100. initial_state=json.dumps(initial_result),
  101. )
  102. router = templates.ROUTER
  103. socket = templates.SOCKET
  104. return templates.join([synced_state, result, router, socket])
  105. def compile_events(state: Type[State]) -> str:
  106. """Compile all the events for a given component.
  107. Args:
  108. state: The state class for the component.
  109. Returns:
  110. A string of the compiled events for the component.
  111. """
  112. state_name = state.get_name()
  113. state_setter = templates.format_state_setter(state_name)
  114. return templates.EVENT_FN(state=state_name, set_state=state_setter)
  115. def compile_effects(state: Type[State]) -> str:
  116. """Compile all the effects for a given component.
  117. Args:
  118. state: The state class for the component.
  119. Returns:
  120. A string of the compiled effects for the component.
  121. """
  122. state_name = state.get_name()
  123. set_state = templates.format_state_setter(state_name)
  124. return templates.USE_EFFECT(state=state_name, set_state=set_state)
  125. def compile_render(component: Component) -> str:
  126. """Compile the component's render method.
  127. Args:
  128. component: The component to compile the render method for.
  129. Returns:
  130. A string of the compiled render method.
  131. """
  132. return component.render()
  133. def compile_custom_component(component: CustomComponent) -> tuple[str, ImportDict]:
  134. """Compile a custom component.
  135. Args:
  136. component: The custom component to compile.
  137. Returns:
  138. A tuple of the compiled component and the imports required by the component.
  139. """
  140. props = [
  141. BaseVar(
  142. name=name,
  143. type_=prop.type_ if utils._isinstance(prop, Var) else type(prop),
  144. is_local=True,
  145. )
  146. for name, prop in component.props.items()
  147. ]
  148. # Compile the component.
  149. render = component.component_fn(*props)
  150. # Concatenate the props.
  151. props = ", ".join([prop.name for prop in props])
  152. # Compile the component.
  153. return (
  154. templates.COMPONENT(
  155. name=component.tag,
  156. props=props,
  157. render=render,
  158. ),
  159. render.get_imports(),
  160. )
  161. def create_document_root(stylesheets: List[str]) -> Component:
  162. """Create the document root.
  163. Args:
  164. stylesheets: The list of stylesheets to include in the document root.
  165. Returns:
  166. The document root.
  167. """
  168. sheets = [Link.create(rel="stylesheet", href=href) for href in stylesheets]
  169. return Html.create(
  170. DocumentHead.create(*sheets),
  171. Body.create(
  172. Main.create(),
  173. Script.create(),
  174. ),
  175. )
  176. def create_theme(style: Style) -> Dict:
  177. """Create the base style for the app.
  178. Args:
  179. style: The style dict for the app.
  180. Returns:
  181. The base style for the app.
  182. """
  183. return {
  184. "styles": {
  185. "global": Style({k: v for k, v in style.items() if not isinstance(k, type)})
  186. },
  187. }
  188. def get_page_path(path: str) -> str:
  189. """Get the path of the compiled JS file for the given page.
  190. Args:
  191. path: The path of the page.
  192. Returns:
  193. The path of the compiled JS file.
  194. """
  195. return os.path.join(constants.WEB_PAGES_DIR, path + constants.JS_EXT)
  196. def get_theme_path() -> str:
  197. """Get the path of the base theme style.
  198. Returns:
  199. The path of the theme style.
  200. """
  201. return os.path.join(constants.WEB_UTILS_DIR, constants.THEME + constants.JS_EXT)
  202. def get_components_path() -> str:
  203. """Get the path of the compiled components.
  204. Returns:
  205. The path of the compiled components.
  206. """
  207. return os.path.join(constants.WEB_UTILS_DIR, "components" + constants.JS_EXT)
  208. def add_meta(page: Component, title: str, image: str, description: str) -> Component:
  209. """Add metadata to a page.
  210. Args:
  211. page: The component for the page.
  212. title: The title of the page.
  213. image: The image for the page.
  214. description: The description of the page.
  215. Returns:
  216. The component with the metadata added.
  217. """
  218. page.children.append(
  219. Head.create(
  220. Title.create(title),
  221. Description.create(content=description),
  222. Image.create(content=image),
  223. )
  224. )
  225. return page
  226. def write_page(path: str, code: str):
  227. """Write the given code to the given path.
  228. Args:
  229. path: The path to write the code to.
  230. code: The code to write.
  231. """
  232. utils.mkdir(os.path.dirname(path))
  233. with open(path, "w") as f:
  234. f.write(code)