config.py 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294
  1. """The Reflex config."""
  2. from __future__ import annotations
  3. import importlib
  4. import os
  5. import sys
  6. import urllib.parse
  7. from typing import Any, Dict, List, Optional
  8. from reflex import constants
  9. from reflex.base import Base
  10. from reflex.utils import console
  11. class DBConfig(Base):
  12. """Database config."""
  13. engine: str
  14. username: Optional[str] = ""
  15. password: Optional[str] = ""
  16. host: Optional[str] = ""
  17. port: Optional[int] = None
  18. database: str
  19. @classmethod
  20. def postgresql(
  21. cls,
  22. database: str,
  23. username: str,
  24. password: str | None = None,
  25. host: str | None = None,
  26. port: int | None = 5432,
  27. ) -> DBConfig:
  28. """Create an instance with postgresql engine.
  29. Args:
  30. database: Database name.
  31. username: Database username.
  32. password: Database password.
  33. host: Database host.
  34. port: Database port.
  35. Returns:
  36. DBConfig instance.
  37. """
  38. return cls(
  39. engine="postgresql",
  40. username=username,
  41. password=password,
  42. host=host,
  43. port=port,
  44. database=database,
  45. )
  46. @classmethod
  47. def postgresql_psycopg2(
  48. cls,
  49. database: str,
  50. username: str,
  51. password: str | None = None,
  52. host: str | None = None,
  53. port: int | None = 5432,
  54. ) -> DBConfig:
  55. """Create an instance with postgresql+psycopg2 engine.
  56. Args:
  57. database: Database name.
  58. username: Database username.
  59. password: Database password.
  60. host: Database host.
  61. port: Database port.
  62. Returns:
  63. DBConfig instance.
  64. """
  65. return cls(
  66. engine="postgresql+psycopg2",
  67. username=username,
  68. password=password,
  69. host=host,
  70. port=port,
  71. database=database,
  72. )
  73. @classmethod
  74. def sqlite(
  75. cls,
  76. database: str,
  77. ) -> DBConfig:
  78. """Create an instance with sqlite engine.
  79. Args:
  80. database: Database name.
  81. Returns:
  82. DBConfig instance.
  83. """
  84. return cls(
  85. engine="sqlite",
  86. database=database,
  87. )
  88. def get_url(self) -> str:
  89. """Get database URL.
  90. Returns:
  91. The database URL.
  92. """
  93. host = (
  94. f"{self.host}:{self.port}" if self.host and self.port else self.host or ""
  95. )
  96. username = urllib.parse.quote_plus(self.username) if self.username else ""
  97. password = urllib.parse.quote_plus(self.password) if self.password else ""
  98. if username:
  99. path = f"{username}:{password}@{host}" if password else f"{username}@{host}"
  100. else:
  101. path = f"{host}"
  102. return f"{self.engine}://{path}/{self.database}"
  103. class Config(Base):
  104. """A Reflex config."""
  105. class Config:
  106. """Pydantic config for the config."""
  107. validate_assignment = True
  108. # The name of the app.
  109. app_name: str
  110. # The log level to use.
  111. loglevel: constants.LogLevel = constants.LogLevel.INFO
  112. # The port to run the frontend on.
  113. frontend_port: int = 3000
  114. # The port to run the backend on.
  115. backend_port: int = 8000
  116. # The backend url the frontend will connect to.
  117. api_url: str = f"http://localhost:{backend_port}"
  118. # The url the frontend will be hosted on.
  119. deploy_url: Optional[str] = f"http://localhost:{frontend_port}"
  120. # The url the backend will be hosted on.
  121. backend_host: str = "0.0.0.0"
  122. # The database url.
  123. db_url: Optional[str] = "sqlite:///reflex.db"
  124. # The redis url.
  125. redis_url: Optional[str] = None
  126. # Telemetry opt-in.
  127. telemetry_enabled: bool = True
  128. # The bun path
  129. bun_path: str = constants.DEFAULT_BUN_PATH
  130. # List of origins that are allowed to connect to the backend API.
  131. cors_allowed_origins: List[str] = ["*"]
  132. # Tailwind config.
  133. tailwind: Optional[Dict[str, Any]] = None
  134. # Timeout when launching the gunicorn server. TODO(rename this to backend_timeout?)
  135. timeout: int = 120
  136. # Whether to enable or disable nextJS gzip compression.
  137. next_compression: bool = True
  138. # The event namespace for ws connection
  139. event_namespace: Optional[str] = None
  140. # Params to remove eventually.
  141. # Additional frontend packages to install. (TODO: these can be inferred from the imports)
  142. frontend_packages: List[str] = []
  143. # For rest are for deploy only.
  144. # The rxdeploy url.
  145. rxdeploy_url: Optional[str] = None
  146. # The username.
  147. username: Optional[str] = None
  148. def __init__(self, *args, **kwargs):
  149. """Initialize the config values.
  150. Args:
  151. *args: The args to pass to the Pydantic init method.
  152. **kwargs: The kwargs to pass to the Pydantic init method.
  153. """
  154. super().__init__(*args, **kwargs)
  155. # Check for deprecated values.
  156. self.check_deprecated_values(**kwargs)
  157. # Update the config from environment variables.
  158. self.update_from_env()
  159. @staticmethod
  160. def check_deprecated_values(**kwargs):
  161. """Check for deprecated config values.
  162. Args:
  163. **kwargs: The kwargs passed to the config.
  164. Raises:
  165. ValueError: If a deprecated config value is found.
  166. """
  167. if "db_config" in kwargs:
  168. raise ValueError("db_config is deprecated - use db_url instead")
  169. if "admin_dash" in kwargs:
  170. raise ValueError(
  171. "admin_dash is deprecated in the config - pass it as a param to rx.App instead"
  172. )
  173. if "env_path" in kwargs:
  174. raise ValueError(
  175. "env_path is deprecated - use environment variables instead"
  176. )
  177. def update_from_env(self):
  178. """Update the config from environment variables.
  179. Raises:
  180. ValueError: If an environment variable is set to an invalid type.
  181. """
  182. # Iterate over the fields.
  183. for key, field in self.__fields__.items():
  184. # The env var name is the key in uppercase.
  185. env_var = os.environ.get(key.upper())
  186. # If the env var is set, override the config value.
  187. if env_var is not None:
  188. if key.upper() != "DB_URL":
  189. console.info(
  190. f"Overriding config value {key} with env var {key.upper()}={env_var}"
  191. )
  192. # Convert the env var to the expected type.
  193. try:
  194. if issubclass(field.type_, bool):
  195. # special handling for bool values
  196. env_var = env_var.lower() in ["true", "1", "yes"]
  197. else:
  198. env_var = field.type_(env_var)
  199. except ValueError:
  200. console.error(
  201. f"Could not convert {key.upper()}={env_var} to type {field.type_}"
  202. )
  203. raise
  204. # Set the value.
  205. setattr(self, key, env_var)
  206. def get_event_namespace(self) -> str | None:
  207. """Get the websocket event namespace.
  208. Returns:
  209. The namespace for websocket.
  210. """
  211. if self.event_namespace:
  212. return f'/{self.event_namespace.strip("/")}'
  213. event_url = constants.Endpoint.EVENT.get_url()
  214. return urllib.parse.urlsplit(event_url).path
  215. def get_config(reload: bool = False) -> Config:
  216. """Get the app config.
  217. Args:
  218. reload: Re-import the rxconfig module from disk
  219. Returns:
  220. The app config.
  221. """
  222. from reflex.config import Config
  223. sys.path.insert(0, os.getcwd())
  224. try:
  225. rxconfig = __import__(constants.CONFIG_MODULE)
  226. if reload:
  227. importlib.reload(rxconfig)
  228. return rxconfig.config
  229. except ImportError:
  230. return Config(app_name="") # type: ignore