config.py 25 KB

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