config.py 37 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172
  1. """The Reflex config."""
  2. from __future__ import annotations
  3. import concurrent.futures
  4. import dataclasses
  5. import enum
  6. import importlib
  7. import inspect
  8. import multiprocessing
  9. import os
  10. import platform
  11. import sys
  12. import threading
  13. import urllib.parse
  14. from collections.abc import Callable
  15. from functools import lru_cache
  16. from importlib.util import find_spec
  17. from pathlib import Path
  18. from types import ModuleType
  19. from typing import (
  20. TYPE_CHECKING,
  21. Annotated,
  22. Any,
  23. ClassVar,
  24. Generic,
  25. TypeVar,
  26. get_args,
  27. get_origin,
  28. get_type_hints,
  29. )
  30. import pydantic.v1 as pydantic
  31. from reflex import constants
  32. from reflex.base import Base
  33. from reflex.constants.base import LogLevel
  34. from reflex.plugins import Plugin, TailwindV3Plugin, TailwindV4Plugin
  35. from reflex.utils import console
  36. from reflex.utils.exceptions import ConfigError, EnvironmentVarValueError
  37. from reflex.utils.types import (
  38. GenericType,
  39. is_union,
  40. true_type_for_pydantic_field,
  41. value_inside_optional,
  42. )
  43. try:
  44. from dotenv import load_dotenv
  45. except ImportError:
  46. load_dotenv = None
  47. def _load_dotenv_from_str(env_files: str) -> None:
  48. if not env_files:
  49. return
  50. if load_dotenv is None:
  51. console.error(
  52. """The `python-dotenv` package is required to load environment variables from a file. Run `pip install "python-dotenv>=1.1.0"`."""
  53. )
  54. return
  55. # load env files in reverse order if they exist
  56. for env_file_path in [
  57. Path(p) for s in reversed(env_files.split(os.pathsep)) if (p := s.strip())
  58. ]:
  59. if env_file_path.exists():
  60. load_dotenv(env_file_path, override=True)
  61. def _load_dotenv_from_env():
  62. """Load environment variables from paths specified in REFLEX_ENV_FILE."""
  63. show_deprecation = False
  64. env_env_file = os.environ.get("REFLEX_ENV_FILE")
  65. if not env_env_file:
  66. env_env_file = os.environ.get("ENV_FILE")
  67. if env_env_file:
  68. show_deprecation = True
  69. if show_deprecation:
  70. console.deprecate(
  71. "Usage of deprecated ENV_FILE env var detected.",
  72. reason="Prefer `REFLEX_` prefix when setting env vars.",
  73. deprecation_version="0.7.13",
  74. removal_version="0.8.0",
  75. )
  76. if env_env_file:
  77. _load_dotenv_from_str(env_env_file)
  78. # Load the env files at import time if they are set in the ENV_FILE environment variable.
  79. _load_dotenv_from_env()
  80. class DBConfig(Base):
  81. """Database config."""
  82. engine: str
  83. username: str | None = ""
  84. password: str | None = ""
  85. host: str | None = ""
  86. port: int | None = None
  87. database: str
  88. @classmethod
  89. def postgresql(
  90. cls,
  91. database: str,
  92. username: str,
  93. password: str | None = None,
  94. host: str | None = None,
  95. port: int | None = 5432,
  96. ) -> DBConfig:
  97. """Create an instance with postgresql engine.
  98. Args:
  99. database: Database name.
  100. username: Database username.
  101. password: Database password.
  102. host: Database host.
  103. port: Database port.
  104. Returns:
  105. DBConfig instance.
  106. """
  107. return cls(
  108. engine="postgresql",
  109. username=username,
  110. password=password,
  111. host=host,
  112. port=port,
  113. database=database,
  114. )
  115. @classmethod
  116. def postgresql_psycopg(
  117. cls,
  118. database: str,
  119. username: str,
  120. password: str | None = None,
  121. host: str | None = None,
  122. port: int | None = 5432,
  123. ) -> DBConfig:
  124. """Create an instance with postgresql+psycopg engine.
  125. Args:
  126. database: Database name.
  127. username: Database username.
  128. password: Database password.
  129. host: Database host.
  130. port: Database port.
  131. Returns:
  132. DBConfig instance.
  133. """
  134. return cls(
  135. engine="postgresql+psycopg",
  136. username=username,
  137. password=password,
  138. host=host,
  139. port=port,
  140. database=database,
  141. )
  142. @classmethod
  143. def sqlite(
  144. cls,
  145. database: str,
  146. ) -> DBConfig:
  147. """Create an instance with sqlite engine.
  148. Args:
  149. database: Database name.
  150. Returns:
  151. DBConfig instance.
  152. """
  153. return cls(
  154. engine="sqlite",
  155. database=database,
  156. )
  157. def get_url(self) -> str:
  158. """Get database URL.
  159. Returns:
  160. The database URL.
  161. """
  162. host = (
  163. f"{self.host}:{self.port}" if self.host and self.port else self.host or ""
  164. )
  165. username = urllib.parse.quote_plus(self.username) if self.username else ""
  166. password = urllib.parse.quote_plus(self.password) if self.password else ""
  167. if username:
  168. path = f"{username}:{password}@{host}" if password else f"{username}@{host}"
  169. else:
  170. path = f"{host}"
  171. return f"{self.engine}://{path}/{self.database}"
  172. def get_default_value_for_field(field: dataclasses.Field) -> Any:
  173. """Get the default value for a field.
  174. Args:
  175. field: The field.
  176. Returns:
  177. The default value.
  178. Raises:
  179. ValueError: If no default value is found.
  180. """
  181. if field.default != dataclasses.MISSING:
  182. return field.default
  183. elif field.default_factory != dataclasses.MISSING:
  184. return field.default_factory()
  185. else:
  186. raise ValueError(
  187. f"Missing value for environment variable {field.name} and no default value found"
  188. )
  189. # TODO: Change all interpret_.* signatures to value: str, field: dataclasses.Field once we migrate rx.Config to dataclasses
  190. def interpret_boolean_env(value: str, field_name: str) -> bool:
  191. """Interpret a boolean environment variable value.
  192. Args:
  193. value: The environment variable value.
  194. field_name: The field name.
  195. Returns:
  196. The interpreted value.
  197. Raises:
  198. EnvironmentVarValueError: If the value is invalid.
  199. """
  200. true_values = ["true", "1", "yes", "y"]
  201. false_values = ["false", "0", "no", "n"]
  202. if value.lower() in true_values:
  203. return True
  204. elif value.lower() in false_values:
  205. return False
  206. raise EnvironmentVarValueError(f"Invalid boolean value: {value} for {field_name}")
  207. def interpret_int_env(value: str, field_name: str) -> int:
  208. """Interpret an integer environment variable value.
  209. Args:
  210. value: The environment variable value.
  211. field_name: The field name.
  212. Returns:
  213. The interpreted value.
  214. Raises:
  215. EnvironmentVarValueError: If the value is invalid.
  216. """
  217. try:
  218. return int(value)
  219. except ValueError as ve:
  220. raise EnvironmentVarValueError(
  221. f"Invalid integer value: {value} for {field_name}"
  222. ) from ve
  223. def interpret_existing_path_env(value: str, field_name: str) -> ExistingPath:
  224. """Interpret a path environment variable value as an existing path.
  225. Args:
  226. value: The environment variable value.
  227. field_name: The field name.
  228. Returns:
  229. The interpreted value.
  230. Raises:
  231. EnvironmentVarValueError: If the path does not exist.
  232. """
  233. path = Path(value)
  234. if not path.exists():
  235. raise EnvironmentVarValueError(f"Path does not exist: {path} for {field_name}")
  236. return path
  237. def interpret_path_env(value: str, field_name: str) -> Path:
  238. """Interpret a path environment variable value.
  239. Args:
  240. value: The environment variable value.
  241. field_name: The field name.
  242. Returns:
  243. The interpreted value.
  244. """
  245. return Path(value)
  246. def interpret_enum_env(value: str, field_type: GenericType, field_name: str) -> Any:
  247. """Interpret an enum environment variable value.
  248. Args:
  249. value: The environment variable value.
  250. field_type: The field type.
  251. field_name: The field name.
  252. Returns:
  253. The interpreted value.
  254. Raises:
  255. EnvironmentVarValueError: If the value is invalid.
  256. """
  257. try:
  258. return field_type(value)
  259. except ValueError as ve:
  260. raise EnvironmentVarValueError(
  261. f"Invalid enum value: {value} for {field_name}"
  262. ) from ve
  263. def interpret_env_var_value(
  264. value: str, field_type: GenericType, field_name: str
  265. ) -> Any:
  266. """Interpret an environment variable value based on the field type.
  267. Args:
  268. value: The environment variable value.
  269. field_type: The field type.
  270. field_name: The field name.
  271. Returns:
  272. The interpreted value.
  273. Raises:
  274. ValueError: If the value is invalid.
  275. """
  276. field_type = value_inside_optional(field_type)
  277. if is_union(field_type):
  278. raise ValueError(
  279. f"Union types are not supported for environment variables: {field_name}."
  280. )
  281. if field_type is bool:
  282. return interpret_boolean_env(value, field_name)
  283. elif field_type is str:
  284. return value
  285. elif field_type is int:
  286. return interpret_int_env(value, field_name)
  287. elif field_type is Path:
  288. return interpret_path_env(value, field_name)
  289. elif field_type is ExistingPath:
  290. return interpret_existing_path_env(value, field_name)
  291. elif get_origin(field_type) is list:
  292. return [
  293. interpret_env_var_value(
  294. v,
  295. get_args(field_type)[0],
  296. f"{field_name}[{i}]",
  297. )
  298. for i, v in enumerate(value.split(":"))
  299. ]
  300. elif inspect.isclass(field_type) and issubclass(field_type, enum.Enum):
  301. return interpret_enum_env(value, field_type, field_name)
  302. else:
  303. raise ValueError(
  304. f"Invalid type for environment variable {field_name}: {field_type}. This is probably an issue in Reflex."
  305. )
  306. T = TypeVar("T")
  307. class EnvVar(Generic[T]):
  308. """Environment variable."""
  309. name: str
  310. default: Any
  311. type_: T
  312. def __init__(self, name: str, default: Any, type_: T) -> None:
  313. """Initialize the environment variable.
  314. Args:
  315. name: The environment variable name.
  316. default: The default value.
  317. type_: The type of the value.
  318. """
  319. self.name = name
  320. self.default = default
  321. self.type_ = type_
  322. def interpret(self, value: str) -> T:
  323. """Interpret the environment variable value.
  324. Args:
  325. value: The environment variable value.
  326. Returns:
  327. The interpreted value.
  328. """
  329. return interpret_env_var_value(value, self.type_, self.name)
  330. def getenv(self) -> T | None:
  331. """Get the interpreted environment variable value.
  332. Returns:
  333. The environment variable value.
  334. """
  335. env_value = os.getenv(self.name, None)
  336. if env_value and env_value.strip():
  337. return self.interpret(env_value)
  338. return None
  339. def is_set(self) -> bool:
  340. """Check if the environment variable is set.
  341. Returns:
  342. True if the environment variable is set.
  343. """
  344. return bool(os.getenv(self.name, "").strip())
  345. def get(self) -> T:
  346. """Get the interpreted environment variable value or the default value if not set.
  347. Returns:
  348. The interpreted value.
  349. """
  350. env_value = self.getenv()
  351. if env_value is not None:
  352. return env_value
  353. return self.default
  354. def set(self, value: T | None) -> None:
  355. """Set the environment variable. None unsets the variable.
  356. Args:
  357. value: The value to set.
  358. """
  359. if value is None:
  360. _ = os.environ.pop(self.name, None)
  361. else:
  362. if isinstance(value, enum.Enum):
  363. value = value.value
  364. if isinstance(value, list):
  365. str_value = ":".join(str(v) for v in value)
  366. else:
  367. str_value = str(value)
  368. os.environ[self.name] = str_value
  369. @lru_cache
  370. def get_type_hints_environment(cls: type) -> dict[str, Any]:
  371. """Get the type hints for the environment variables.
  372. Args:
  373. cls: The class.
  374. Returns:
  375. The type hints.
  376. """
  377. return get_type_hints(cls)
  378. class env_var: # noqa: N801 # pyright: ignore [reportRedeclaration]
  379. """Descriptor for environment variables."""
  380. name: str
  381. default: Any
  382. internal: bool = False
  383. def __init__(self, default: Any, internal: bool = False) -> None:
  384. """Initialize the descriptor.
  385. Args:
  386. default: The default value.
  387. internal: Whether the environment variable is reflex internal.
  388. """
  389. self.default = default
  390. self.internal = internal
  391. def __set_name__(self, owner: Any, name: str):
  392. """Set the name of the descriptor.
  393. Args:
  394. owner: The owner class.
  395. name: The name of the descriptor.
  396. """
  397. self.name = name
  398. def __get__(
  399. self, instance: EnvironmentVariables, owner: type[EnvironmentVariables]
  400. ):
  401. """Get the EnvVar instance.
  402. Args:
  403. instance: The instance.
  404. owner: The owner class.
  405. Returns:
  406. The EnvVar instance.
  407. """
  408. type_ = get_args(get_type_hints_environment(owner)[self.name])[0]
  409. env_name = self.name
  410. if self.internal:
  411. env_name = f"__{env_name}"
  412. return EnvVar(name=env_name, default=self.default, type_=type_)
  413. if TYPE_CHECKING:
  414. def env_var(default: Any, internal: bool = False) -> EnvVar:
  415. """Typing helper for the env_var descriptor.
  416. Args:
  417. default: The default value.
  418. internal: Whether the environment variable is reflex internal.
  419. Returns:
  420. The EnvVar instance.
  421. """
  422. return default
  423. class PathExistsFlag:
  424. """Flag to indicate that a path must exist."""
  425. ExistingPath = Annotated[Path, PathExistsFlag]
  426. class PerformanceMode(enum.Enum):
  427. """Performance mode for the app."""
  428. WARN = "warn"
  429. RAISE = "raise"
  430. OFF = "off"
  431. class ExecutorType(enum.Enum):
  432. """Executor for compiling the frontend."""
  433. THREAD = "thread"
  434. PROCESS = "process"
  435. MAIN_THREAD = "main_thread"
  436. @classmethod
  437. def get_executor_from_environment(cls):
  438. """Get the executor based on the environment variables.
  439. Returns:
  440. The executor.
  441. """
  442. executor_type = environment.REFLEX_COMPILE_EXECUTOR.get()
  443. reflex_compile_processes = environment.REFLEX_COMPILE_PROCESSES.get()
  444. reflex_compile_threads = environment.REFLEX_COMPILE_THREADS.get()
  445. # By default, use the main thread. Unless the user has specified a different executor.
  446. # Using a process pool is much faster, but not supported on all platforms. It's gated behind a flag.
  447. if executor_type is None:
  448. if (
  449. platform.system() not in ("Linux", "Darwin")
  450. and reflex_compile_processes is not None
  451. ):
  452. console.warn("Multiprocessing is only supported on Linux and MacOS.")
  453. if (
  454. platform.system() in ("Linux", "Darwin")
  455. and reflex_compile_processes is not None
  456. ):
  457. if reflex_compile_processes == 0:
  458. console.warn(
  459. "Number of processes must be greater than 0. If you want to use the default number of processes, set REFLEX_COMPILE_EXECUTOR to 'process'. Defaulting to None."
  460. )
  461. reflex_compile_processes = None
  462. elif reflex_compile_processes < 0:
  463. console.warn(
  464. "Number of processes must be greater than 0. Defaulting to None."
  465. )
  466. reflex_compile_processes = None
  467. executor_type = ExecutorType.PROCESS
  468. elif reflex_compile_threads is not None:
  469. if reflex_compile_threads == 0:
  470. console.warn(
  471. "Number of threads must be greater than 0. If you want to use the default number of threads, set REFLEX_COMPILE_EXECUTOR to 'thread'. Defaulting to None."
  472. )
  473. reflex_compile_threads = None
  474. elif reflex_compile_threads < 0:
  475. console.warn(
  476. "Number of threads must be greater than 0. Defaulting to None."
  477. )
  478. reflex_compile_threads = None
  479. executor_type = ExecutorType.THREAD
  480. else:
  481. executor_type = ExecutorType.MAIN_THREAD
  482. match executor_type:
  483. case ExecutorType.PROCESS:
  484. executor = concurrent.futures.ProcessPoolExecutor(
  485. max_workers=reflex_compile_processes,
  486. mp_context=multiprocessing.get_context("fork"),
  487. )
  488. case ExecutorType.THREAD:
  489. executor = concurrent.futures.ThreadPoolExecutor(
  490. max_workers=reflex_compile_threads
  491. )
  492. case ExecutorType.MAIN_THREAD:
  493. FUTURE_RESULT_TYPE = TypeVar("FUTURE_RESULT_TYPE")
  494. class MainThreadExecutor:
  495. def __enter__(self):
  496. return self
  497. def __exit__(self, *args):
  498. pass
  499. def submit(
  500. self, fn: Callable[..., FUTURE_RESULT_TYPE], *args, **kwargs
  501. ) -> concurrent.futures.Future[FUTURE_RESULT_TYPE]:
  502. future_job = concurrent.futures.Future()
  503. future_job.set_result(fn(*args, **kwargs))
  504. return future_job
  505. executor = MainThreadExecutor()
  506. return executor
  507. class EnvironmentVariables:
  508. """Environment variables class to instantiate environment variables."""
  509. # Indicate the current command that was invoked in the reflex CLI.
  510. REFLEX_COMPILE_CONTEXT: EnvVar[constants.CompileContext] = env_var(
  511. constants.CompileContext.UNDEFINED, internal=True
  512. )
  513. # Whether to use npm over bun to install and run the frontend.
  514. REFLEX_USE_NPM: EnvVar[bool] = env_var(False)
  515. # The npm registry to use.
  516. NPM_CONFIG_REGISTRY: EnvVar[str | None] = env_var(None)
  517. # Whether to use Granian for the backend. By default, the backend uses Uvicorn if available.
  518. REFLEX_USE_GRANIAN: EnvVar[bool] = env_var(False)
  519. # Whether to use the system installed bun. If set to false, bun will be bundled with the app.
  520. REFLEX_USE_SYSTEM_BUN: EnvVar[bool] = env_var(False)
  521. # The working directory for the next.js commands.
  522. REFLEX_WEB_WORKDIR: EnvVar[Path] = env_var(Path(constants.Dirs.WEB))
  523. # The working directory for the states directory.
  524. REFLEX_STATES_WORKDIR: EnvVar[Path] = env_var(Path(constants.Dirs.STATES))
  525. # Path to the alembic config file
  526. ALEMBIC_CONFIG: EnvVar[ExistingPath] = env_var(Path(constants.ALEMBIC_CONFIG))
  527. # Disable SSL verification for HTTPX requests.
  528. SSL_NO_VERIFY: EnvVar[bool] = env_var(False)
  529. # The directory to store uploaded files.
  530. REFLEX_UPLOADED_FILES_DIR: EnvVar[Path] = env_var(
  531. Path(constants.Dirs.UPLOADED_FILES)
  532. )
  533. REFLEX_COMPILE_EXECUTOR: EnvVar[ExecutorType | None] = env_var(None)
  534. # Whether to use separate processes to compile the frontend and how many. If not set, defaults to thread executor.
  535. REFLEX_COMPILE_PROCESSES: EnvVar[int | None] = env_var(None)
  536. # Whether to use separate threads to compile the frontend and how many. Defaults to `min(32, os.cpu_count() + 4)`.
  537. REFLEX_COMPILE_THREADS: EnvVar[int | None] = env_var(None)
  538. # The directory to store reflex dependencies.
  539. REFLEX_DIR: EnvVar[Path] = env_var(constants.Reflex.DIR)
  540. # Whether to print the SQL queries if the log level is INFO or lower.
  541. SQLALCHEMY_ECHO: EnvVar[bool] = env_var(False)
  542. # Whether to check db connections before using them.
  543. SQLALCHEMY_POOL_PRE_PING: EnvVar[bool] = env_var(True)
  544. # Whether to ignore the redis config error. Some redis servers only allow out-of-band configuration.
  545. REFLEX_IGNORE_REDIS_CONFIG_ERROR: EnvVar[bool] = env_var(False)
  546. # Whether to skip purging the web directory in dev mode.
  547. REFLEX_PERSIST_WEB_DIR: EnvVar[bool] = env_var(False)
  548. # The reflex.build frontend host.
  549. REFLEX_BUILD_FRONTEND: EnvVar[str] = env_var(
  550. constants.Templates.REFLEX_BUILD_FRONTEND
  551. )
  552. # The reflex.build backend host.
  553. REFLEX_BUILD_BACKEND: EnvVar[str] = env_var(
  554. constants.Templates.REFLEX_BUILD_BACKEND
  555. )
  556. # This env var stores the execution mode of the app
  557. REFLEX_ENV_MODE: EnvVar[constants.Env] = env_var(constants.Env.DEV)
  558. # Whether to run the backend only. Exclusive with REFLEX_FRONTEND_ONLY.
  559. REFLEX_BACKEND_ONLY: EnvVar[bool] = env_var(False)
  560. # Whether to run the frontend only. Exclusive with REFLEX_BACKEND_ONLY.
  561. REFLEX_FRONTEND_ONLY: EnvVar[bool] = env_var(False)
  562. # The port to run the frontend on.
  563. REFLEX_FRONTEND_PORT: EnvVar[int | None] = env_var(None)
  564. # The port to run the backend on.
  565. REFLEX_BACKEND_PORT: EnvVar[int | None] = env_var(None)
  566. # If this env var is set to "yes", App.compile will be a no-op
  567. REFLEX_SKIP_COMPILE: EnvVar[bool] = env_var(False, internal=True)
  568. # Whether to run app harness tests in headless mode.
  569. APP_HARNESS_HEADLESS: EnvVar[bool] = env_var(False)
  570. # Which app harness driver to use.
  571. APP_HARNESS_DRIVER: EnvVar[str] = env_var("Chrome")
  572. # Arguments to pass to the app harness driver.
  573. APP_HARNESS_DRIVER_ARGS: EnvVar[str] = env_var("")
  574. # Whether to check for outdated package versions.
  575. REFLEX_CHECK_LATEST_VERSION: EnvVar[bool] = env_var(True)
  576. # In which performance mode to run the app.
  577. REFLEX_PERF_MODE: EnvVar[PerformanceMode] = env_var(PerformanceMode.WARN)
  578. # The maximum size of the reflex state in kilobytes.
  579. REFLEX_STATE_SIZE_LIMIT: EnvVar[int] = env_var(1000)
  580. # Whether to use the turbopack bundler.
  581. REFLEX_USE_TURBOPACK: EnvVar[bool] = env_var(False)
  582. # Additional paths to include in the hot reload. Separated by a colon.
  583. REFLEX_HOT_RELOAD_INCLUDE_PATHS: EnvVar[list[Path]] = env_var([])
  584. # Paths to exclude from the hot reload. Takes precedence over include paths. Separated by a colon.
  585. REFLEX_HOT_RELOAD_EXCLUDE_PATHS: EnvVar[list[Path]] = env_var([])
  586. # Enables different behavior for when the backend would do a cold start if it was inactive.
  587. REFLEX_DOES_BACKEND_COLD_START: EnvVar[bool] = env_var(False)
  588. # The timeout for the backend to do a cold start in seconds.
  589. REFLEX_BACKEND_COLD_START_TIMEOUT: EnvVar[int] = env_var(10)
  590. # Used by flexgen to enumerate the pages.
  591. REFLEX_ADD_ALL_ROUTES_ENDPOINT: EnvVar[bool] = env_var(False)
  592. # The address to bind the HTTP client to. You can set this to "::" to enable IPv6.
  593. REFLEX_HTTP_CLIENT_BIND_ADDRESS: EnvVar[str | None] = env_var(None)
  594. # Maximum size of the message in the websocket server in bytes.
  595. REFLEX_SOCKET_MAX_HTTP_BUFFER_SIZE: EnvVar[int] = env_var(
  596. constants.POLLING_MAX_HTTP_BUFFER_SIZE
  597. )
  598. # The interval to send a ping to the websocket server in seconds.
  599. REFLEX_SOCKET_INTERVAL: EnvVar[int] = env_var(constants.Ping.INTERVAL)
  600. # The timeout to wait for a pong from the websocket server in seconds.
  601. REFLEX_SOCKET_TIMEOUT: EnvVar[int] = env_var(constants.Ping.TIMEOUT)
  602. # Whether to run Granian in a spawn process. This enables Reflex to pick up on environment variable changes between hot reloads.
  603. REFLEX_STRICT_HOT_RELOAD: EnvVar[bool] = env_var(False)
  604. environment = EnvironmentVariables()
  605. # These vars are not logged because they may contain sensitive information.
  606. _sensitive_env_vars = {"DB_URL", "ASYNC_DB_URL", "REDIS_URL"}
  607. class Config(Base):
  608. """The config defines runtime settings for the app.
  609. By default, the config is defined in an `rxconfig.py` file in the root of the app.
  610. ```python
  611. # rxconfig.py
  612. import reflex as rx
  613. config = rx.Config(
  614. app_name="myapp",
  615. api_url="http://localhost:8000",
  616. )
  617. ```
  618. Every config value can be overridden by an environment variable with the same name in uppercase.
  619. For example, `db_url` can be overridden by setting the `DB_URL` environment variable.
  620. See the [configuration](https://reflex.dev/docs/getting-started/configuration/) docs for more info.
  621. """
  622. class Config: # pyright: ignore [reportIncompatibleVariableOverride]
  623. """Pydantic config for the config."""
  624. validate_assignment = True
  625. use_enum_values = False
  626. # The name of the app (should match the name of the app directory).
  627. app_name: str
  628. # The path to the app module.
  629. app_module_import: str | None = None
  630. # The log level to use.
  631. loglevel: constants.LogLevel = constants.LogLevel.DEFAULT
  632. # The port to run the frontend on. NOTE: When running in dev mode, the next available port will be used if this is taken.
  633. frontend_port: int | None = None
  634. # The path to run the frontend on. For example, "/app" will run the frontend on http://localhost:3000/app
  635. frontend_path: str = ""
  636. # The port to run the backend on. NOTE: When running in dev mode, the next available port will be used if this is taken.
  637. backend_port: int | None = None
  638. # The backend url the frontend will connect to. This must be updated if the backend is hosted elsewhere, or in production.
  639. api_url: str = f"http://localhost:{constants.DefaultPorts.BACKEND_PORT}"
  640. # The url the frontend will be hosted on.
  641. deploy_url: str | None = f"http://localhost:{constants.DefaultPorts.FRONTEND_PORT}"
  642. # The url the backend will be hosted on.
  643. backend_host: str = "0.0.0.0"
  644. # The database url used by rx.Model.
  645. db_url: str | None = "sqlite:///reflex.db"
  646. # The async database url used by rx.Model.
  647. async_db_url: str | None = None
  648. # The redis url
  649. redis_url: str | None = None
  650. # Telemetry opt-in.
  651. telemetry_enabled: bool = True
  652. # The bun path
  653. bun_path: ExistingPath = constants.Bun.DEFAULT_PATH
  654. # Timeout to do a production build of a frontend page.
  655. static_page_generation_timeout: int = 60
  656. # List of origins that are allowed to connect to the backend API.
  657. cors_allowed_origins: list[str] = ["*"]
  658. # Tailwind config.
  659. tailwind: dict[str, Any] | None = {"plugins": ["@tailwindcss/typography"]}
  660. # DEPRECATED. Timeout when launching the gunicorn server.
  661. timeout: int | None = None
  662. # Whether to enable or disable nextJS gzip compression.
  663. next_compression: bool = True
  664. # Whether to enable or disable NextJS dev indicator.
  665. next_dev_indicators: bool = False
  666. # Whether to use React strict mode in nextJS
  667. react_strict_mode: bool = True
  668. # Additional frontend packages to install.
  669. frontend_packages: list[str] = []
  670. # DEPRECATED. The worker class used in production mode
  671. gunicorn_worker_class: str = "uvicorn.workers.UvicornH11Worker"
  672. # DEPRECATED. Number of gunicorn workers from user
  673. gunicorn_workers: int | None = None
  674. # DEPRECATED. Number of requests before a worker is restarted; set to 0 to disable
  675. gunicorn_max_requests: int | None = None
  676. # DEPRECATED. Variance limit for max requests; gunicorn only
  677. gunicorn_max_requests_jitter: int | None = None
  678. # Indicate which type of state manager to use
  679. state_manager_mode: constants.StateManagerMode = constants.StateManagerMode.DISK
  680. # Maximum expiration lock time for redis state manager
  681. redis_lock_expiration: int = constants.Expiration.LOCK
  682. # Maximum lock time before warning for redis state manager.
  683. redis_lock_warning_threshold: int = constants.Expiration.LOCK_WARNING_THRESHOLD
  684. # Token expiration time for redis state manager
  685. redis_token_expiration: int = constants.Expiration.TOKEN
  686. # Attributes that were explicitly set by the user.
  687. _non_default_attributes: set[str] = pydantic.PrivateAttr(set())
  688. # Path to file containing key-values pairs to override in the environment; Dotenv format.
  689. env_file: str | None = None
  690. # Whether to automatically create setters for state base vars
  691. state_auto_setters: bool = True
  692. # Whether to display the sticky "Built with Reflex" badge on all pages.
  693. show_built_with_reflex: bool | None = None
  694. # Whether the app is running in the reflex cloud environment.
  695. is_reflex_cloud: bool = False
  696. # Extra overlay function to run after the app is built. Formatted such that `from path_0.path_1... import path[-1]`, and calling it with no arguments would work. For example, "reflex.components.moment.moment".
  697. extra_overlay_function: str | None = None
  698. # List of plugins to use in the app.
  699. plugins: list[Plugin] = []
  700. _prefixes: ClassVar[list[str]] = ["REFLEX_"]
  701. def __init__(self, *args, **kwargs):
  702. """Initialize the config values.
  703. Args:
  704. *args: The args to pass to the Pydantic init method.
  705. **kwargs: The kwargs to pass to the Pydantic init method.
  706. Raises:
  707. ConfigError: If some values in the config are invalid.
  708. """
  709. super().__init__(*args, **kwargs)
  710. # Clean up this code when we remove plain envvar in 0.8.0
  711. show_deprecation = False
  712. env_loglevel = os.environ.get("REFLEX_LOGLEVEL")
  713. if not env_loglevel:
  714. env_loglevel = os.environ.get("LOGLEVEL")
  715. if env_loglevel:
  716. show_deprecation = True
  717. if env_loglevel is not None:
  718. env_loglevel = LogLevel(env_loglevel.lower())
  719. if env_loglevel or self.loglevel != LogLevel.DEFAULT:
  720. console.set_log_level(env_loglevel or self.loglevel)
  721. if show_deprecation:
  722. console.deprecate(
  723. "Usage of deprecated LOGLEVEL env var detected.",
  724. reason="Prefer `REFLEX_` prefix when setting env vars.",
  725. deprecation_version="0.7.13",
  726. removal_version="0.8.0",
  727. )
  728. # Update the config from environment variables.
  729. env_kwargs = self.update_from_env()
  730. for key, env_value in env_kwargs.items():
  731. setattr(self, key, env_value)
  732. # Update default URLs if ports were set
  733. kwargs.update(env_kwargs)
  734. self._non_default_attributes.update(kwargs)
  735. self._replace_defaults(**kwargs)
  736. if self.tailwind is not None and not any(
  737. isinstance(plugin, (TailwindV3Plugin, TailwindV4Plugin))
  738. for plugin in self.plugins
  739. ):
  740. console.deprecate(
  741. "Inferring tailwind usage",
  742. reason="""
  743. If you are using tailwind, add `rx.plugins.TailwindV3Plugin()` to the `plugins=[]` in rxconfig.py.
  744. If you are not using tailwind, set `tailwind` to `None` in rxconfig.py.""",
  745. deprecation_version="0.7.13",
  746. removal_version="0.8.0",
  747. dedupe=True,
  748. )
  749. self.plugins.append(TailwindV3Plugin())
  750. if (
  751. self.state_manager_mode == constants.StateManagerMode.REDIS
  752. and not self.redis_url
  753. ):
  754. raise ConfigError(
  755. f"{self._prefixes[0]}REDIS_URL is required when using the redis state manager."
  756. )
  757. @property
  758. def app_module(self) -> ModuleType | None:
  759. """Return the app module if `app_module_import` is set.
  760. Returns:
  761. The app module.
  762. """
  763. return (
  764. importlib.import_module(self.app_module_import)
  765. if self.app_module_import
  766. else None
  767. )
  768. @property
  769. def module(self) -> str:
  770. """Get the module name of the app.
  771. Returns:
  772. The module name.
  773. """
  774. if self.app_module_import is not None:
  775. return self.app_module_import
  776. return ".".join([self.app_name, self.app_name])
  777. def update_from_env(self) -> dict[str, Any]:
  778. """Update the config values based on set environment variables.
  779. If there is a set env_file, it is loaded first.
  780. Returns:
  781. The updated config values.
  782. """
  783. if self.env_file:
  784. _load_dotenv_from_str(self.env_file)
  785. updated_values = {}
  786. # Iterate over the fields.
  787. for key, field in self.__fields__.items():
  788. # The env var name is the key in uppercase.
  789. for prefix in self._prefixes:
  790. if env_var := os.environ.get(f"{prefix}{key.upper()}"):
  791. break
  792. else:
  793. # Default to non-prefixed env var if other are not found.
  794. if env_var := os.environ.get(key.upper()):
  795. console.deprecate(
  796. f"Usage of deprecated {key.upper()} env var detected.",
  797. reason=f"Prefer `{self._prefixes[0]}` prefix when setting env vars.",
  798. deprecation_version="0.7.13",
  799. removal_version="0.8.0",
  800. )
  801. # If the env var is set, override the config value.
  802. if env_var and env_var.strip():
  803. # Interpret the value.
  804. value = interpret_env_var_value(
  805. env_var, true_type_for_pydantic_field(field), field.name
  806. )
  807. # Set the value.
  808. updated_values[key] = value
  809. if key.upper() in _sensitive_env_vars:
  810. env_var = "***"
  811. if value != getattr(self, key):
  812. console.debug(
  813. f"Overriding config value {key} with env var {key.upper()}={env_var}",
  814. dedupe=True,
  815. )
  816. return updated_values
  817. def get_event_namespace(self) -> str:
  818. """Get the path that the backend Websocket server lists on.
  819. Returns:
  820. The namespace for websocket.
  821. """
  822. event_url = constants.Endpoint.EVENT.get_url()
  823. return urllib.parse.urlsplit(event_url).path
  824. def _replace_defaults(self, **kwargs):
  825. """Replace formatted defaults when the caller provides updates.
  826. Args:
  827. **kwargs: The kwargs passed to the config or from the env.
  828. """
  829. if "api_url" not in self._non_default_attributes and "backend_port" in kwargs:
  830. self.api_url = f"http://localhost:{kwargs['backend_port']}"
  831. if (
  832. "deploy_url" not in self._non_default_attributes
  833. and "frontend_port" in kwargs
  834. ):
  835. self.deploy_url = f"http://localhost:{kwargs['frontend_port']}"
  836. if "api_url" not in self._non_default_attributes:
  837. # If running in Github Codespaces, override API_URL
  838. codespace_name = os.getenv("CODESPACE_NAME")
  839. github_codespaces_port_forwarding_domain = os.getenv(
  840. "GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN"
  841. )
  842. # If running on Replit.com interactively, override API_URL to ensure we maintain the backend_port
  843. replit_dev_domain = os.getenv("REPLIT_DEV_DOMAIN")
  844. backend_port = kwargs.get("backend_port", self.backend_port)
  845. if codespace_name and github_codespaces_port_forwarding_domain:
  846. self.api_url = (
  847. f"https://{codespace_name}-{kwargs.get('backend_port', self.backend_port)}"
  848. f".{github_codespaces_port_forwarding_domain}"
  849. )
  850. elif replit_dev_domain and backend_port:
  851. self.api_url = f"https://{replit_dev_domain}:{backend_port}"
  852. def _set_persistent(self, **kwargs):
  853. """Set values in this config and in the environment so they persist into subprocess.
  854. Args:
  855. **kwargs: The kwargs passed to the config.
  856. """
  857. for key, value in kwargs.items():
  858. if value is not None:
  859. os.environ[self._prefixes[0] + key.upper()] = str(value)
  860. setattr(self, key, value)
  861. self._non_default_attributes.update(kwargs)
  862. self._replace_defaults(**kwargs)
  863. def _get_config() -> Config:
  864. """Get the app config.
  865. Returns:
  866. The app config.
  867. """
  868. # only import the module if it exists. If a module spec exists then
  869. # the module exists.
  870. spec = find_spec(constants.Config.MODULE)
  871. if not spec:
  872. # we need this condition to ensure that a ModuleNotFound error is not thrown when
  873. # running unit/integration tests or during `reflex init`.
  874. return Config(app_name="")
  875. rxconfig = importlib.import_module(constants.Config.MODULE)
  876. return rxconfig.config
  877. # Protect sys.path from concurrent modification
  878. _config_lock = threading.RLock()
  879. def get_config(reload: bool = False) -> Config:
  880. """Get the app config.
  881. Args:
  882. reload: Re-import the rxconfig module from disk
  883. Returns:
  884. The app config.
  885. """
  886. cached_rxconfig = sys.modules.get(constants.Config.MODULE, None)
  887. if cached_rxconfig is not None:
  888. if reload:
  889. # Remove any cached module when `reload` is requested.
  890. del sys.modules[constants.Config.MODULE]
  891. else:
  892. return cached_rxconfig.config
  893. with _config_lock:
  894. orig_sys_path = sys.path.copy()
  895. sys.path.clear()
  896. sys.path.append(str(Path.cwd()))
  897. try:
  898. # Try to import the module with only the current directory in the path.
  899. return _get_config()
  900. except Exception:
  901. # If the module import fails, try to import with the original sys.path.
  902. sys.path.extend(orig_sys_path)
  903. return _get_config()
  904. finally:
  905. # Find any entries added to sys.path by rxconfig.py itself.
  906. extra_paths = [
  907. p for p in sys.path if p not in orig_sys_path and p != str(Path.cwd())
  908. ]
  909. # Restore the original sys.path.
  910. sys.path.clear()
  911. sys.path.extend(extra_paths)
  912. sys.path.extend(orig_sys_path)