constants.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383
  1. """Constants used throughout the package."""
  2. from __future__ import annotations
  3. import os
  4. import platform
  5. import re
  6. from enum import Enum
  7. from types import SimpleNamespace
  8. from platformdirs import PlatformDirs
  9. # importlib is only available for Python 3.8+ so we need the backport for Python 3.7
  10. try:
  11. from importlib import metadata
  12. except ImportError:
  13. import importlib_metadata as metadata # pyright: ignore[reportMissingImports]
  14. IS_WINDOWS = platform.system() == "Windows"
  15. def get_fnm_name() -> str | None:
  16. """Get the appropriate fnm executable name based on the current platform.
  17. Returns:
  18. The fnm executable name for the current platform.
  19. """
  20. platform_os = platform.system()
  21. if platform_os == "Windows":
  22. return "fnm-windows"
  23. elif platform_os == "Darwin":
  24. return "fnm-macos"
  25. elif platform_os == "Linux":
  26. machine = platform.machine()
  27. if machine == "arm" or machine.startswith("armv7"):
  28. return "fnm-arm32"
  29. elif machine.startswith("aarch") or machine.startswith("armv8"):
  30. return "fnm-arm64"
  31. return "fnm-linux"
  32. return None
  33. # App names and versions.
  34. # The name of the Reflex package.
  35. MODULE_NAME = "reflex"
  36. # The current version of Reflex.
  37. VERSION = metadata.version(MODULE_NAME)
  38. # Files and directories used to init a new project.
  39. # The directory to store reflex dependencies.
  40. REFLEX_DIR = (
  41. # on windows, we use C:/Users/<username>/AppData/Local/reflex.
  42. # on macOS, we use ~/Library/Application Support/reflex.
  43. # on linux, we use ~/.local/share/reflex.
  44. PlatformDirs(MODULE_NAME, False).user_data_dir
  45. )
  46. # The root directory of the reflex library.
  47. ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
  48. # The name of the assets directory.
  49. APP_ASSETS_DIR = "assets"
  50. # The template directory used during reflex init.
  51. TEMPLATE_DIR = os.path.join(ROOT_DIR, MODULE_NAME, ".templates")
  52. # The web subdirectory of the template directory.
  53. WEB_TEMPLATE_DIR = os.path.join(TEMPLATE_DIR, "web")
  54. # The assets subdirectory of the template directory.
  55. ASSETS_TEMPLATE_DIR = os.path.join(TEMPLATE_DIR, APP_ASSETS_DIR)
  56. # The jinja template directory.
  57. JINJA_TEMPLATE_DIR = os.path.join(TEMPLATE_DIR, "jinja")
  58. # Bun config.
  59. # The Bun version.
  60. BUN_VERSION = "0.7.3"
  61. # Min Bun Version
  62. MIN_BUN_VERSION = "0.7.0"
  63. # The directory to store the bun.
  64. BUN_ROOT_PATH = os.path.join(REFLEX_DIR, "bun")
  65. # Default bun path.
  66. DEFAULT_BUN_PATH = os.path.join(BUN_ROOT_PATH, "bin", "bun")
  67. # URL to bun install script.
  68. BUN_INSTALL_URL = "https://bun.sh/install"
  69. # FNM / Node config.
  70. # The FNM version.
  71. FNM_VERSION = "1.35.1"
  72. # The Node version.
  73. NODE_VERSION = "18.17.0"
  74. # The minimum required node version.
  75. NODE_VERSION_MIN = "16.8.0"
  76. # The directory to store fnm.
  77. FNM_DIR = os.path.join(REFLEX_DIR, "fnm")
  78. FNM_FILENAME = get_fnm_name()
  79. # The fnm executable binary.
  80. FNM_EXE = os.path.join(FNM_DIR, "fnm.exe" if IS_WINDOWS else "fnm")
  81. # The node bin path.
  82. NODE_BIN_PATH = os.path.join(
  83. FNM_DIR,
  84. "node-versions",
  85. f"v{NODE_VERSION}",
  86. "installation",
  87. "bin" if not IS_WINDOWS else "",
  88. )
  89. # The default path where node is installed.
  90. NODE_PATH = os.path.join(NODE_BIN_PATH, "node.exe" if IS_WINDOWS else "node")
  91. # The default path where npm is installed.
  92. NPM_PATH = os.path.join(NODE_BIN_PATH, "npm")
  93. # The URL to the fnm release binary
  94. FNM_INSTALL_URL = (
  95. f"https://github.com/Schniz/fnm/releases/download/v{FNM_VERSION}/{FNM_FILENAME}.zip"
  96. )
  97. # The frontend directories in a project.
  98. # The web folder where the NextJS app is compiled to.
  99. WEB_DIR = ".web"
  100. # The name of the utils file.
  101. UTILS_DIR = "utils"
  102. # The name of the output static directory.
  103. STATIC_DIR = "_static"
  104. # The name of the state file.
  105. STATE_PATH = "/".join([UTILS_DIR, "state"])
  106. # The name of the components file.
  107. COMPONENTS_PATH = "/".join([UTILS_DIR, "components"])
  108. # The directory where the app pages are compiled to.
  109. WEB_PAGES_DIR = os.path.join(WEB_DIR, "pages")
  110. # The directory where the static build is located.
  111. WEB_STATIC_DIR = os.path.join(WEB_DIR, STATIC_DIR)
  112. # The directory where the utils file is located.
  113. WEB_UTILS_DIR = os.path.join(WEB_DIR, UTILS_DIR)
  114. # The directory where the assets are located.
  115. WEB_ASSETS_DIR = os.path.join(WEB_DIR, "public")
  116. # The directory where styles are located.
  117. STYLES_DIR = os.path.join(WEB_DIR, "styles")
  118. # The Tailwind config.
  119. TAILWIND_CONFIG = os.path.join(WEB_DIR, "tailwind.config.js")
  120. # Default Tailwind content paths
  121. TAILWIND_CONTENT = ["./pages/**/*.{js,ts,jsx,tsx}"]
  122. # Relative tailwind style path to root stylesheet in STYLES_DIR.
  123. TAILWIND_ROOT_STYLE_PATH = "./tailwind.css"
  124. # The Tailwindcss version
  125. TAILWIND_VERSION = "tailwindcss@^3.3.2"
  126. # The NextJS config file
  127. NEXT_CONFIG_FILE = "next.config.js"
  128. # The sitemap config file.
  129. SITEMAP_CONFIG_FILE = os.path.join(WEB_DIR, "next-sitemap.config.js")
  130. # The node modules directory.
  131. NODE_MODULES = "node_modules"
  132. # The package lock file.
  133. PACKAGE_LOCK = "package-lock.json"
  134. # The reflex json file.
  135. REFLEX_JSON = os.path.join(WEB_DIR, "reflex.json")
  136. # The env json file.
  137. ENV_JSON = os.path.join(WEB_DIR, "env.json")
  138. # Compiler variables.
  139. # The extension for compiled Javascript files.
  140. JS_EXT = ".js"
  141. # The extension for python files.
  142. PY_EXT = ".py"
  143. # The extension for css files.
  144. CSS_EXT = ".css"
  145. # The expected variable name where the rx.App is stored.
  146. APP_VAR = "app"
  147. # The expected variable name where the API object is stored for deployment.
  148. API_VAR = "api"
  149. # The name of the router variable.
  150. ROUTER = "router"
  151. # The name of the socket variable.
  152. SOCKET = "socket"
  153. # The name of the variable to hold API results.
  154. RESULT = "result"
  155. # The name of the final variable.
  156. FINAL = "final"
  157. # The name of the process variable.
  158. PROCESSING = "processing"
  159. # The name of the state variable.
  160. STATE = "state"
  161. # The name of the events variable.
  162. EVENTS = "events"
  163. # The name of the initial hydrate event.
  164. HYDRATE = "hydrate"
  165. # The name of the is_hydrated variable.
  166. IS_HYDRATED = "is_hydrated"
  167. # The name of the index page.
  168. INDEX_ROUTE = "index"
  169. # The name of the app root page.
  170. APP_ROOT = "_app"
  171. # The root stylesheet filename.
  172. STYLESHEET_ROOT = "styles"
  173. # The name of the document root page.
  174. DOCUMENT_ROOT = "_document"
  175. # The name of the theme page.
  176. THEME = "theme"
  177. # The prefix used to create setters for state vars.
  178. SETTER_PREFIX = "set_"
  179. # The name of the frontend zip during deployment.
  180. FRONTEND_ZIP = "frontend.zip"
  181. # The name of the backend zip during deployment.
  182. BACKEND_ZIP = "backend.zip"
  183. # The default title to show for Reflex apps.
  184. DEFAULT_TITLE = "Reflex App"
  185. # The default description to show for Reflex apps.
  186. DEFAULT_DESCRIPTION = "A Reflex app."
  187. # The default image to show for Reflex apps.
  188. DEFAULT_IMAGE = "favicon.ico"
  189. # The default meta list to show for Reflex apps.
  190. DEFAULT_META_LIST = []
  191. # The gitignore file.
  192. GITIGNORE_FILE = ".gitignore"
  193. # Files to gitignore.
  194. DEFAULT_GITIGNORE = {WEB_DIR, "*.db", "__pycache__/", "*.py[cod]"}
  195. # The name of the reflex config module.
  196. CONFIG_MODULE = "rxconfig"
  197. # The python config file.
  198. CONFIG_FILE = f"{CONFIG_MODULE}{PY_EXT}"
  199. # The previous config file.
  200. OLD_CONFIG_FILE = f"pcconfig{PY_EXT}"
  201. # The deployment URL.
  202. PRODUCTION_BACKEND_URL = "https://{username}-{app_name}.api.pynecone.app"
  203. # Token expiration time in seconds.
  204. TOKEN_EXPIRATION = 60 * 60
  205. # Testing variables.
  206. # Testing os env set by pytest when running a test case.
  207. PYTEST_CURRENT_TEST = "PYTEST_CURRENT_TEST"
  208. # Env modes
  209. class Env(str, Enum):
  210. """The environment modes."""
  211. DEV = "dev"
  212. PROD = "prod"
  213. # Log levels
  214. class LogLevel(str, Enum):
  215. """The log levels."""
  216. DEBUG = "debug"
  217. INFO = "info"
  218. WARNING = "warning"
  219. ERROR = "error"
  220. CRITICAL = "critical"
  221. def __le__(self, other: LogLevel) -> bool:
  222. """Compare log levels.
  223. Args:
  224. other: The other log level.
  225. Returns:
  226. True if the log level is less than or equal to the other log level.
  227. """
  228. levels = list(LogLevel)
  229. return levels.index(self) <= levels.index(other)
  230. # Templates
  231. class Template(str, Enum):
  232. """The templates to use for the app."""
  233. DEFAULT = "default"
  234. COUNTER = "counter"
  235. class Endpoint(Enum):
  236. """Endpoints for the reflex backend API."""
  237. PING = "ping"
  238. EVENT = "_event"
  239. UPLOAD = "_upload"
  240. def __str__(self) -> str:
  241. """Get the string representation of the endpoint.
  242. Returns:
  243. The path for the endpoint.
  244. """
  245. return f"/{self.value}"
  246. def get_url(self) -> str:
  247. """Get the URL for the endpoint.
  248. Returns:
  249. The full URL for the endpoint.
  250. """
  251. # Import here to avoid circular imports.
  252. from reflex.config import get_config
  253. # Get the API URL from the config.
  254. config = get_config()
  255. url = "".join([config.api_url, str(self)])
  256. # The event endpoint is a websocket.
  257. if self == Endpoint.EVENT:
  258. # Replace the protocol with ws.
  259. url = url.replace("https://", "wss://").replace("http://", "ws://")
  260. # Return the url.
  261. return url
  262. class SocketEvent(Enum):
  263. """Socket events sent by the reflex backend API."""
  264. PING = "ping"
  265. EVENT = "event"
  266. def __str__(self) -> str:
  267. """Get the string representation of the event name.
  268. Returns:
  269. The event name string.
  270. """
  271. return str(self.value)
  272. class RouteArgType(SimpleNamespace):
  273. """Type of dynamic route arg extracted from URI route."""
  274. # Typecast to str is needed for Enum to work.
  275. SINGLE = str("arg_single")
  276. LIST = str("arg_list")
  277. # the name of the backend var containing path and client information
  278. ROUTER_DATA = "router_data"
  279. class RouteVar(SimpleNamespace):
  280. """Names of variables used in the router_data dict stored in State."""
  281. CLIENT_IP = "ip"
  282. CLIENT_TOKEN = "token"
  283. HEADERS = "headers"
  284. PATH = "pathname"
  285. ORIGIN = "asPath"
  286. SESSION_ID = "sid"
  287. QUERY = "query"
  288. COOKIE = "cookie"
  289. class RouteRegex(SimpleNamespace):
  290. """Regex used for extracting route args in route."""
  291. ARG = re.compile(r"\[(?!\.)([^\[\]]+)\]")
  292. # group return the catchall pattern (i.e. "[[..slug]]")
  293. CATCHALL = re.compile(r"(\[?\[\.{3}(?![0-9]).*\]?\])")
  294. # group return the arg name (i.e. "slug")
  295. STRICT_CATCHALL = re.compile(r"\[\.{3}([a-zA-Z_][\w]*)\]")
  296. # group return the arg name (i.e. "slug")
  297. OPT_CATCHALL = re.compile(r"\[\[\.{3}([a-zA-Z_][\w]*)\]\]")
  298. # 404 variables
  299. SLUG_404 = "404"
  300. TITLE_404 = "404 - Not Found"
  301. FAVICON_404 = "favicon.ico"
  302. DESCRIPTION_404 = "The page was not found"
  303. ROUTE_NOT_FOUND = "routeNotFound"
  304. # Color mode variables
  305. USE_COLOR_MODE = "useColorMode"
  306. COLOR_MODE = "colorMode"
  307. TOGGLE_COLOR_MODE = "toggleColorMode"
  308. # Server socket configuration variables
  309. POLLING_MAX_HTTP_BUFFER_SIZE = 1000 * 1000
  310. PING_INTERVAL = 25
  311. PING_TIMEOUT = 120
  312. # Alembic migrations
  313. ALEMBIC_CONFIG = os.environ.get("ALEMBIC_CONFIG", "alembic.ini")
  314. # Keys in the client_side_storage dict
  315. COOKIES = "cookies"
  316. LOCAL_STORAGE = "local_storage"
  317. # Names of event handlers on all components mapped to useEffect
  318. ON_MOUNT = "on_mount"
  319. ON_UNMOUNT = "on_unmount"
  320. # If this env var is set to "yes", App.compile will be a no-op
  321. SKIP_COMPILE_ENV_VAR = "__REFLEX_SKIP_COMPILE"