config.py 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900
  1. """The Reflex config."""
  2. from __future__ import annotations
  3. import enum
  4. import importlib
  5. import inspect
  6. import os
  7. import sys
  8. import threading
  9. import urllib.parse
  10. from importlib.util import find_spec
  11. from pathlib import Path
  12. from typing import (
  13. TYPE_CHECKING,
  14. Any,
  15. Callable,
  16. Dict,
  17. Generic,
  18. List,
  19. Optional,
  20. Set,
  21. TypeVar,
  22. get_args,
  23. )
  24. from typing_extensions import Annotated, get_type_hints
  25. from reflex.utils.exceptions import ConfigError, EnvironmentVarValueError
  26. from reflex.utils.types import GenericType, is_union, value_inside_optional
  27. try:
  28. import pydantic.v1 as pydantic
  29. except ModuleNotFoundError:
  30. import pydantic
  31. from reflex_cli.constants.hosting import Hosting
  32. from reflex import constants
  33. from reflex.base import Base
  34. from reflex.utils import console
  35. class DBConfig(Base):
  36. """Database config."""
  37. engine: str
  38. username: Optional[str] = ""
  39. password: Optional[str] = ""
  40. host: Optional[str] = ""
  41. port: Optional[int] = None
  42. database: str
  43. @classmethod
  44. def postgresql(
  45. cls,
  46. database: str,
  47. username: str,
  48. password: str | None = None,
  49. host: str | None = None,
  50. port: int | None = 5432,
  51. ) -> DBConfig:
  52. """Create an instance with postgresql engine.
  53. Args:
  54. database: Database name.
  55. username: Database username.
  56. password: Database password.
  57. host: Database host.
  58. port: Database port.
  59. Returns:
  60. DBConfig instance.
  61. """
  62. return cls(
  63. engine="postgresql",
  64. username=username,
  65. password=password,
  66. host=host,
  67. port=port,
  68. database=database,
  69. )
  70. @classmethod
  71. def postgresql_psycopg2(
  72. cls,
  73. database: str,
  74. username: str,
  75. password: str | None = None,
  76. host: str | None = None,
  77. port: int | None = 5432,
  78. ) -> DBConfig:
  79. """Create an instance with postgresql+psycopg2 engine.
  80. Args:
  81. database: Database name.
  82. username: Database username.
  83. password: Database password.
  84. host: Database host.
  85. port: Database port.
  86. Returns:
  87. DBConfig instance.
  88. """
  89. return cls(
  90. engine="postgresql+psycopg2",
  91. username=username,
  92. password=password,
  93. host=host,
  94. port=port,
  95. database=database,
  96. )
  97. @classmethod
  98. def sqlite(
  99. cls,
  100. database: str,
  101. ) -> DBConfig:
  102. """Create an instance with sqlite engine.
  103. Args:
  104. database: Database name.
  105. Returns:
  106. DBConfig instance.
  107. """
  108. return cls(
  109. engine="sqlite",
  110. database=database,
  111. )
  112. def get_url(self) -> str:
  113. """Get database URL.
  114. Returns:
  115. The database URL.
  116. """
  117. host = (
  118. f"{self.host}:{self.port}" if self.host and self.port else self.host or ""
  119. )
  120. username = urllib.parse.quote_plus(self.username) if self.username else ""
  121. password = urllib.parse.quote_plus(self.password) if self.password else ""
  122. if username:
  123. path = f"{username}:{password}@{host}" if password else f"{username}@{host}"
  124. else:
  125. path = f"{host}"
  126. return f"{self.engine}://{path}/{self.database}"
  127. # TODO: Change all interpret_.* signatures to value: str, field: dataclasses.Field once we migrate rx.Config to dataclasses
  128. def interpret_boolean_env(value: str, field_name: str) -> bool:
  129. """Interpret a boolean environment variable value.
  130. Args:
  131. value: The environment variable value.
  132. field_name: The field name.
  133. Returns:
  134. The interpreted value.
  135. Raises:
  136. EnvironmentVarValueError: If the value is invalid.
  137. """
  138. true_values = ["true", "1", "yes", "y"]
  139. false_values = ["false", "0", "no", "n"]
  140. if value.lower() in true_values:
  141. return True
  142. elif value.lower() in false_values:
  143. return False
  144. raise EnvironmentVarValueError(f"Invalid boolean value: {value} for {field_name}")
  145. def interpret_int_env(value: str, field_name: str) -> int:
  146. """Interpret an integer environment variable value.
  147. Args:
  148. value: The environment variable value.
  149. field_name: The field name.
  150. Returns:
  151. The interpreted value.
  152. Raises:
  153. EnvironmentVarValueError: If the value is invalid.
  154. """
  155. try:
  156. return int(value)
  157. except ValueError as ve:
  158. raise EnvironmentVarValueError(
  159. f"Invalid integer value: {value} for {field_name}"
  160. ) from ve
  161. def interpret_existing_path_env(value: str, field_name: str) -> ExistingPath:
  162. """Interpret a path environment variable value as an existing path.
  163. Args:
  164. value: The environment variable value.
  165. field_name: The field name.
  166. Returns:
  167. The interpreted value.
  168. Raises:
  169. EnvironmentVarValueError: If the path does not exist.
  170. """
  171. path = Path(value)
  172. if not path.exists():
  173. raise EnvironmentVarValueError(f"Path does not exist: {path} for {field_name}")
  174. return path
  175. def interpret_path_env(value: str, field_name: str) -> Path:
  176. """Interpret a path environment variable value.
  177. Args:
  178. value: The environment variable value.
  179. field_name: The field name.
  180. Returns:
  181. The interpreted value.
  182. """
  183. return Path(value)
  184. def interpret_enum_env(value: str, field_type: GenericType, field_name: str) -> Any:
  185. """Interpret an enum environment variable value.
  186. Args:
  187. value: The environment variable value.
  188. field_type: The field type.
  189. field_name: The field name.
  190. Returns:
  191. The interpreted value.
  192. Raises:
  193. EnvironmentVarValueError: If the value is invalid.
  194. """
  195. try:
  196. return field_type(value)
  197. except ValueError as ve:
  198. raise EnvironmentVarValueError(
  199. f"Invalid enum value: {value} for {field_name}"
  200. ) from ve
  201. def interpret_env_var_value(
  202. value: str, field_type: GenericType, field_name: str
  203. ) -> Any:
  204. """Interpret an environment variable value based on the field type.
  205. Args:
  206. value: The environment variable value.
  207. field_type: The field type.
  208. field_name: The field name.
  209. Returns:
  210. The interpreted value.
  211. Raises:
  212. ValueError: If the value is invalid.
  213. """
  214. field_type = value_inside_optional(field_type)
  215. if is_union(field_type):
  216. raise ValueError(
  217. f"Union types are not supported for environment variables: {field_name}."
  218. )
  219. if field_type is bool:
  220. return interpret_boolean_env(value, field_name)
  221. elif field_type is str:
  222. return value
  223. elif field_type is int:
  224. return interpret_int_env(value, field_name)
  225. elif field_type is Path:
  226. return interpret_path_env(value, field_name)
  227. elif field_type is ExistingPath:
  228. return interpret_existing_path_env(value, field_name)
  229. elif inspect.isclass(field_type) and issubclass(field_type, enum.Enum):
  230. return interpret_enum_env(value, field_type, field_name)
  231. else:
  232. raise ValueError(
  233. f"Invalid type for environment variable {field_name}: {field_type}. This is probably an issue in Reflex."
  234. )
  235. T = TypeVar("T")
  236. ENV_VAR_DEFAULT_FACTORY = Callable[[], T]
  237. class EnvVar(Generic[T]):
  238. """Environment variable."""
  239. name: str
  240. default: Any
  241. default_factory: Optional[ENV_VAR_DEFAULT_FACTORY]
  242. type_: T
  243. def __init__(
  244. self,
  245. name: str,
  246. default: Any,
  247. default_factory: Optional[ENV_VAR_DEFAULT_FACTORY],
  248. type_: T,
  249. ) -> None:
  250. """Initialize the environment variable.
  251. Args:
  252. name: The environment variable name.
  253. default: The default value.
  254. default_factory: The default factory.
  255. type_: The type of the value.
  256. """
  257. self.name = name
  258. self.default = default
  259. self.default_factory = default_factory
  260. self.type_ = type_
  261. def get_default(self) -> T:
  262. """Get the default value.
  263. Returns:
  264. The default value.
  265. """
  266. if self.default_factory is not None:
  267. return self.default_factory()
  268. return self.default
  269. def interpret(self, value: str) -> T:
  270. """Interpret the environment variable value.
  271. Args:
  272. value: The environment variable value.
  273. Returns:
  274. The interpreted value.
  275. """
  276. return interpret_env_var_value(value, self.type_, self.name)
  277. def getenv(self) -> Optional[T]:
  278. """Get the interpreted environment variable value.
  279. Returns:
  280. The environment variable value.
  281. """
  282. env_value = os.getenv(self.name, None)
  283. if env_value is not None:
  284. return self.interpret(env_value)
  285. return None
  286. def is_set(self) -> bool:
  287. """Check if the environment variable is set.
  288. Returns:
  289. True if the environment variable is set.
  290. """
  291. return self.name in os.environ
  292. def get(self) -> T:
  293. """Get the interpreted environment variable value or the default value if not set.
  294. Returns:
  295. The interpreted value.
  296. """
  297. env_value = self.getenv()
  298. if env_value is not None:
  299. return env_value
  300. return self.get_default()
  301. def set(self, value: T | None) -> None:
  302. """Set the environment variable. None unsets the variable.
  303. Args:
  304. value: The value to set.
  305. """
  306. if value is None:
  307. _ = os.environ.pop(self.name, None)
  308. else:
  309. if isinstance(value, enum.Enum):
  310. value = value.value
  311. os.environ[self.name] = str(value)
  312. class env_var: # type: ignore
  313. """Descriptor for environment variables."""
  314. name: str
  315. default: Any
  316. default_factory: Optional[ENV_VAR_DEFAULT_FACTORY]
  317. internal: bool = False
  318. def __init__(
  319. self,
  320. default: Any = None,
  321. default_factory: Optional[ENV_VAR_DEFAULT_FACTORY] = None,
  322. internal: bool = False,
  323. ) -> None:
  324. """Initialize the descriptor.
  325. Args:
  326. default: The default value.
  327. default_factory: The default factory.
  328. internal: Whether the environment variable is reflex internal.
  329. """
  330. self.default = default
  331. self.default_factory = default_factory
  332. self.internal = internal
  333. def __set_name__(self, owner, name):
  334. """Set the name of the descriptor.
  335. Args:
  336. owner: The owner class.
  337. name: The name of the descriptor.
  338. """
  339. self.name = name
  340. def __get__(self, instance, owner):
  341. """Get the EnvVar instance.
  342. Args:
  343. instance: The instance.
  344. owner: The owner class.
  345. Returns:
  346. The EnvVar instance.
  347. """
  348. type_ = get_args(get_type_hints(owner)[self.name])[0]
  349. env_name = self.name
  350. if self.internal:
  351. env_name = f"__{env_name}"
  352. return EnvVar(
  353. name=env_name,
  354. default=self.default,
  355. type_=type_,
  356. default_factory=self.default_factory,
  357. )
  358. if TYPE_CHECKING:
  359. def __new__(
  360. cls,
  361. default: Optional[T] = None,
  362. default_factory: Optional[ENV_VAR_DEFAULT_FACTORY[T]] = None,
  363. internal: bool = False,
  364. ) -> EnvVar[T]:
  365. """Create a new EnvVar instance.
  366. Args:
  367. cls: The class.
  368. default: The default value.
  369. default_factory: The default factory.
  370. internal: Whether the environment variable is reflex internal.
  371. """
  372. ...
  373. class PathExistsFlag:
  374. """Flag to indicate that a path must exist."""
  375. ExistingPath = Annotated[Path, PathExistsFlag]
  376. class PerformanceMode(enum.Enum):
  377. """Performance mode for the app."""
  378. WARN = "warn"
  379. RAISE = "raise"
  380. OFF = "off"
  381. class EnvironmentVariables:
  382. """Environment variables class to instantiate environment variables."""
  383. def __init__(self):
  384. """Initialize the environment variables.
  385. Raises:
  386. NotImplementedError: Always.
  387. """
  388. raise NotImplementedError(
  389. f"{type(self).__name__} is a class singleton and not meant to be instantiated."
  390. )
  391. # Whether to use npm over bun to install frontend packages.
  392. REFLEX_USE_NPM: EnvVar[bool] = env_var(False)
  393. # The npm registry to use.
  394. NPM_CONFIG_REGISTRY: EnvVar[Optional[str]] = env_var(None)
  395. # Whether to use Granian for the backend. Otherwise, use Uvicorn.
  396. REFLEX_USE_GRANIAN: EnvVar[bool] = env_var(False)
  397. # The username to use for authentication on python package repository. Username and password must both be provided.
  398. TWINE_USERNAME: EnvVar[Optional[str]] = env_var(None)
  399. # The password to use for authentication on python package repository. Username and password must both be provided.
  400. TWINE_PASSWORD: EnvVar[Optional[str]] = env_var(None)
  401. # Whether to use the system installed bun. If set to false, bun will be bundled with the app.
  402. REFLEX_USE_SYSTEM_BUN: EnvVar[bool] = env_var(False)
  403. # Whether to use the system installed node and npm. If set to false, node and npm will be bundled with the app.
  404. REFLEX_USE_SYSTEM_NODE: EnvVar[bool] = env_var(False)
  405. # The working directory for the next.js commands.
  406. REFLEX_WEB_WORKDIR: EnvVar[Path] = env_var(Path(constants.Dirs.WEB))
  407. # Path to the alembic config file
  408. ALEMBIC_CONFIG: EnvVar[ExistingPath] = env_var(Path(constants.ALEMBIC_CONFIG))
  409. # Disable SSL verification for HTTPX requests.
  410. SSL_NO_VERIFY: EnvVar[bool] = env_var(False)
  411. # The directory to store uploaded files.
  412. REFLEX_UPLOADED_FILES_DIR: EnvVar[Path] = env_var(
  413. Path(constants.Dirs.UPLOADED_FILES)
  414. )
  415. # Whether to use separate processes to compile the frontend and how many. If not set, defaults to thread executor.
  416. REFLEX_COMPILE_PROCESSES: EnvVar[Optional[int]] = env_var(None)
  417. # Whether to use separate threads to compile the frontend and how many. Defaults to `min(32, os.cpu_count() + 4)`.
  418. REFLEX_COMPILE_THREADS: EnvVar[Optional[int]] = env_var(None)
  419. # The directory to store reflex dependencies.
  420. REFLEX_DIR: EnvVar[Path] = env_var(Path(constants.Reflex.DIR))
  421. # Whether to print the SQL queries if the log level is INFO or lower.
  422. SQLALCHEMY_ECHO: EnvVar[bool] = env_var(False)
  423. # Whether to ignore the redis config error. Some redis servers only allow out-of-band configuration.
  424. REFLEX_IGNORE_REDIS_CONFIG_ERROR: EnvVar[bool] = env_var(False)
  425. # Whether to skip purging the web directory in dev mode.
  426. REFLEX_PERSIST_WEB_DIR: EnvVar[bool] = env_var(False)
  427. # The reflex.build frontend host.
  428. REFLEX_BUILD_FRONTEND: EnvVar[str] = env_var(
  429. constants.Templates.REFLEX_BUILD_FRONTEND
  430. )
  431. # The reflex.build backend host.
  432. REFLEX_BUILD_BACKEND: EnvVar[str] = env_var(
  433. constants.Templates.REFLEX_BUILD_BACKEND
  434. )
  435. # This env var stores the execution mode of the app
  436. REFLEX_ENV_MODE: EnvVar[constants.Env] = env_var(constants.Env.DEV)
  437. # Whether to run the backend only. Exclusive with REFLEX_FRONTEND_ONLY.
  438. REFLEX_BACKEND_ONLY: EnvVar[bool] = env_var(False)
  439. # Whether to run the frontend only. Exclusive with REFLEX_BACKEND_ONLY.
  440. REFLEX_FRONTEND_ONLY: EnvVar[bool] = env_var(False)
  441. # Reflex internal env to reload the config.
  442. RELOAD_CONFIG: EnvVar[bool] = env_var(False, internal=True)
  443. # If this env var is set to "yes", App.compile will be a no-op
  444. REFLEX_SKIP_COMPILE: EnvVar[bool] = env_var(False, internal=True)
  445. # Whether to run app harness tests in headless mode.
  446. APP_HARNESS_HEADLESS: EnvVar[bool] = env_var(False)
  447. # Which app harness driver to use.
  448. APP_HARNESS_DRIVER: EnvVar[str] = env_var("Chrome")
  449. # Arguments to pass to the app harness driver.
  450. APP_HARNESS_DRIVER_ARGS: EnvVar[str] = env_var("")
  451. # Where to save screenshots when tests fail.
  452. SCREENSHOT_DIR: EnvVar[Optional[Path]] = env_var(None)
  453. # Whether to check for outdated package versions.
  454. REFLEX_CHECK_LATEST_VERSION: EnvVar[bool] = env_var(True)
  455. # In which performance mode to run the app.
  456. REFLEX_PERF_MODE: EnvVar[Optional[PerformanceMode]] = env_var(PerformanceMode.WARN)
  457. # The maximum size of the reflex state in kilobytes.
  458. REFLEX_STATE_SIZE_LIMIT: EnvVar[int] = env_var(1000)
  459. # Whether to minify state names. Default to true in prod mode and false otherwise.
  460. REFLEX_MINIFY_STATES: EnvVar[Optional[bool]] = env_var(
  461. default_factory=lambda: EnvironmentVariables.REFLEX_ENV_MODE.get()
  462. == constants.Env.PROD
  463. )
  464. class Config(Base):
  465. """The config defines runtime settings for the app.
  466. By default, the config is defined in an `rxconfig.py` file in the root of the app.
  467. ```python
  468. # rxconfig.py
  469. import reflex as rx
  470. config = rx.Config(
  471. app_name="myapp",
  472. api_url="http://localhost:8000",
  473. )
  474. ```
  475. Every config value can be overridden by an environment variable with the same name in uppercase.
  476. For example, `db_url` can be overridden by setting the `DB_URL` environment variable.
  477. See the [configuration](https://reflex.dev/docs/getting-started/configuration/) docs for more info.
  478. """
  479. class Config:
  480. """Pydantic config for the config."""
  481. validate_assignment = True
  482. # The name of the app (should match the name of the app directory).
  483. app_name: str
  484. # The log level to use.
  485. loglevel: constants.LogLevel = constants.LogLevel.DEFAULT
  486. # The port to run the frontend on. NOTE: When running in dev mode, the next available port will be used if this is taken.
  487. frontend_port: int = constants.DefaultPorts.FRONTEND_PORT
  488. # The path to run the frontend on. For example, "/app" will run the frontend on http://localhost:3000/app
  489. frontend_path: str = ""
  490. # The port to run the backend on. NOTE: When running in dev mode, the next available port will be used if this is taken.
  491. backend_port: int = constants.DefaultPorts.BACKEND_PORT
  492. # The backend url the frontend will connect to. This must be updated if the backend is hosted elsewhere, or in production.
  493. api_url: str = f"http://localhost:{backend_port}"
  494. # The url the frontend will be hosted on.
  495. deploy_url: Optional[str] = f"http://localhost:{frontend_port}"
  496. # The url the backend will be hosted on.
  497. backend_host: str = "0.0.0.0"
  498. # The database url used by rx.Model.
  499. db_url: Optional[str] = "sqlite:///reflex.db"
  500. # The redis url
  501. redis_url: Optional[str] = None
  502. # Telemetry opt-in.
  503. telemetry_enabled: bool = True
  504. # The bun path
  505. bun_path: ExistingPath = constants.Bun.DEFAULT_PATH
  506. # Timeout to do a production build of a frontend page.
  507. static_page_generation_timeout: int = 60
  508. # List of origins that are allowed to connect to the backend API.
  509. cors_allowed_origins: List[str] = ["*"]
  510. # Tailwind config.
  511. tailwind: Optional[Dict[str, Any]] = {"plugins": ["@tailwindcss/typography"]}
  512. # Timeout when launching the gunicorn server. TODO(rename this to backend_timeout?)
  513. timeout: int = 120
  514. # Whether to enable or disable nextJS gzip compression.
  515. next_compression: bool = True
  516. # Whether to use React strict mode in nextJS
  517. react_strict_mode: bool = True
  518. # Additional frontend packages to install.
  519. frontend_packages: List[str] = []
  520. # The hosting service backend URL.
  521. cp_backend_url: str = Hosting.CP_BACKEND_URL
  522. # The hosting service frontend URL.
  523. cp_web_url: str = Hosting.CP_WEB_URL
  524. # The worker class used in production mode
  525. gunicorn_worker_class: str = "uvicorn.workers.UvicornH11Worker"
  526. # Number of gunicorn workers from user
  527. gunicorn_workers: Optional[int] = None
  528. # Number of requests before a worker is restarted
  529. gunicorn_max_requests: int = 100
  530. # Variance limit for max requests; gunicorn only
  531. gunicorn_max_requests_jitter: int = 25
  532. # Indicate which type of state manager to use
  533. state_manager_mode: constants.StateManagerMode = constants.StateManagerMode.DISK
  534. # Maximum expiration lock time for redis state manager
  535. redis_lock_expiration: int = constants.Expiration.LOCK
  536. # Token expiration time for redis state manager
  537. redis_token_expiration: int = constants.Expiration.TOKEN
  538. # Attributes that were explicitly set by the user.
  539. _non_default_attributes: Set[str] = pydantic.PrivateAttr(set())
  540. # Path to file containing key-values pairs to override in the environment; Dotenv format.
  541. env_file: Optional[str] = None
  542. def __init__(self, *args, **kwargs):
  543. """Initialize the config values.
  544. Args:
  545. *args: The args to pass to the Pydantic init method.
  546. **kwargs: The kwargs to pass to the Pydantic init method.
  547. Raises:
  548. ConfigError: If some values in the config are invalid.
  549. """
  550. super().__init__(*args, **kwargs)
  551. # Update the config from environment variables.
  552. env_kwargs = self.update_from_env()
  553. for key, env_value in env_kwargs.items():
  554. setattr(self, key, env_value)
  555. # Update default URLs if ports were set
  556. kwargs.update(env_kwargs)
  557. self._non_default_attributes.update(kwargs)
  558. self._replace_defaults(**kwargs)
  559. if (
  560. self.state_manager_mode == constants.StateManagerMode.REDIS
  561. and not self.redis_url
  562. ):
  563. raise ConfigError(
  564. "REDIS_URL is required when using the redis state manager."
  565. )
  566. @property
  567. def module(self) -> str:
  568. """Get the module name of the app.
  569. Returns:
  570. The module name.
  571. """
  572. return ".".join([self.app_name, self.app_name])
  573. def update_from_env(self) -> dict[str, Any]:
  574. """Update the config values based on set environment variables.
  575. If there is a set env_file, it is loaded first.
  576. Returns:
  577. The updated config values.
  578. """
  579. if self.env_file:
  580. try:
  581. from dotenv import load_dotenv # type: ignore
  582. # load env file if exists
  583. load_dotenv(self.env_file, override=True)
  584. except ImportError:
  585. console.error(
  586. """The `python-dotenv` package is required to load environment variables from a file. Run `pip install "python-dotenv>=1.0.1"`."""
  587. )
  588. updated_values = {}
  589. # Iterate over the fields.
  590. for key, field in self.__fields__.items():
  591. # The env var name is the key in uppercase.
  592. env_var = os.environ.get(key.upper())
  593. # If the env var is set, override the config value.
  594. if env_var is not None:
  595. if key.upper() != "DB_URL":
  596. console.info(
  597. f"Overriding config value {key} with env var {key.upper()}={env_var}",
  598. dedupe=True,
  599. )
  600. # Interpret the value.
  601. value = interpret_env_var_value(env_var, field.outer_type_, field.name)
  602. # Set the value.
  603. updated_values[key] = value
  604. return updated_values
  605. def get_event_namespace(self) -> str:
  606. """Get the path that the backend Websocket server lists on.
  607. Returns:
  608. The namespace for websocket.
  609. """
  610. event_url = constants.Endpoint.EVENT.get_url()
  611. return urllib.parse.urlsplit(event_url).path
  612. def _replace_defaults(self, **kwargs):
  613. """Replace formatted defaults when the caller provides updates.
  614. Args:
  615. **kwargs: The kwargs passed to the config or from the env.
  616. """
  617. if "api_url" not in self._non_default_attributes and "backend_port" in kwargs:
  618. self.api_url = f"http://localhost:{kwargs['backend_port']}"
  619. if (
  620. "deploy_url" not in self._non_default_attributes
  621. and "frontend_port" in kwargs
  622. ):
  623. self.deploy_url = f"http://localhost:{kwargs['frontend_port']}"
  624. if "api_url" not in self._non_default_attributes:
  625. # If running in Github Codespaces, override API_URL
  626. codespace_name = os.getenv("CODESPACE_NAME")
  627. GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN = os.getenv(
  628. "GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN"
  629. )
  630. # If running on Replit.com interactively, override API_URL to ensure we maintain the backend_port
  631. replit_dev_domain = os.getenv("REPLIT_DEV_DOMAIN")
  632. backend_port = kwargs.get("backend_port", self.backend_port)
  633. if codespace_name and GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN:
  634. self.api_url = (
  635. f"https://{codespace_name}-{kwargs.get('backend_port', self.backend_port)}"
  636. f".{GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN}"
  637. )
  638. elif replit_dev_domain and backend_port:
  639. self.api_url = f"https://{replit_dev_domain}:{backend_port}"
  640. def _set_persistent(self, **kwargs):
  641. """Set values in this config and in the environment so they persist into subprocess.
  642. Args:
  643. **kwargs: The kwargs passed to the config.
  644. """
  645. for key, value in kwargs.items():
  646. if value is not None:
  647. os.environ[key.upper()] = str(value)
  648. setattr(self, key, value)
  649. self._non_default_attributes.update(kwargs)
  650. self._replace_defaults(**kwargs)
  651. def _get_config() -> Config:
  652. """Get the app config.
  653. Returns:
  654. The app config.
  655. """
  656. # only import the module if it exists. If a module spec exists then
  657. # the module exists.
  658. spec = find_spec(constants.Config.MODULE)
  659. if not spec:
  660. # we need this condition to ensure that a ModuleNotFound error is not thrown when
  661. # running unit/integration tests or during `reflex init`.
  662. return Config(app_name="")
  663. rxconfig = importlib.import_module(constants.Config.MODULE)
  664. return rxconfig.config
  665. # Protect sys.path from concurrent modification
  666. _config_lock = threading.RLock()
  667. def get_config(reload: bool = False) -> Config:
  668. """Get the app config.
  669. Args:
  670. reload: Re-import the rxconfig module from disk
  671. Returns:
  672. The app config.
  673. """
  674. cached_rxconfig = sys.modules.get(constants.Config.MODULE, None)
  675. if cached_rxconfig is not None:
  676. if reload:
  677. # Remove any cached module when `reload` is requested.
  678. del sys.modules[constants.Config.MODULE]
  679. else:
  680. return cached_rxconfig.config
  681. with _config_lock:
  682. sys_path = sys.path.copy()
  683. sys.path.clear()
  684. sys.path.append(os.getcwd())
  685. try:
  686. # Try to import the module with only the current directory in the path.
  687. return _get_config()
  688. except Exception:
  689. # If the module import fails, try to import with the original sys.path.
  690. sys.path.extend(sys_path)
  691. return _get_config()
  692. finally:
  693. # Restore the original sys.path.
  694. sys.path.clear()
  695. sys.path.extend(sys_path)