config.py 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265
  1. """The Pynecone 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 dotenv import load_dotenv
  9. from pynecone import constants
  10. from pynecone.admin import AdminDash
  11. from pynecone.base import Base
  12. class DBConfig(Base):
  13. """Database config."""
  14. engine: str
  15. username: Optional[str] = ""
  16. password: Optional[str] = ""
  17. host: Optional[str] = ""
  18. port: Optional[int] = None
  19. database: str
  20. @classmethod
  21. def postgresql(
  22. cls,
  23. database: str,
  24. username: str,
  25. password: Optional[str] = None,
  26. host: Optional[str] = None,
  27. port: Optional[int] = 5432,
  28. ) -> DBConfig:
  29. """Create an instance with postgresql engine.
  30. Args:
  31. database: Database name.
  32. username: Database username.
  33. password: Database password.
  34. host: Database host.
  35. port: Database port.
  36. Returns:
  37. DBConfig instance.
  38. """
  39. return cls(
  40. engine="postgresql",
  41. username=username,
  42. password=password,
  43. host=host,
  44. port=port,
  45. database=database,
  46. )
  47. @classmethod
  48. def postgresql_psycopg2(
  49. cls,
  50. database: str,
  51. username: str,
  52. password: Optional[str] = None,
  53. host: Optional[str] = None,
  54. port: Optional[int] = 5432,
  55. ) -> DBConfig:
  56. """Create an instance with postgresql+psycopg2 engine.
  57. Args:
  58. database: Database name.
  59. username: Database username.
  60. password: Database password.
  61. host: Database host.
  62. port: Database port.
  63. Returns:
  64. DBConfig instance.
  65. """
  66. return cls(
  67. engine="postgresql+psycopg2",
  68. username=username,
  69. password=password,
  70. host=host,
  71. port=port,
  72. database=database,
  73. )
  74. @classmethod
  75. def sqlite(
  76. cls,
  77. database: str,
  78. ) -> DBConfig:
  79. """Create an instance with sqlite engine.
  80. Args:
  81. database: Database name.
  82. Returns:
  83. DBConfig instance.
  84. """
  85. return cls(
  86. engine="sqlite",
  87. database=database,
  88. )
  89. def get_url(self) -> str:
  90. """Get database URL.
  91. Returns:
  92. The database URL.
  93. """
  94. host = (
  95. f"{self.host}:{self.port}" if self.host and self.port else self.host or ""
  96. )
  97. username = urllib.parse.quote_plus(self.username) if self.username else ""
  98. password = urllib.parse.quote_plus(self.password) if self.password else ""
  99. if username:
  100. path = f"{username}:{password}@{host}" if password else f"{username}@{host}"
  101. else:
  102. path = f"{host}"
  103. return f"{self.engine}://{path}/{self.database}"
  104. class Config(Base):
  105. """A Pynecone config."""
  106. # The name of the app.
  107. app_name: str
  108. # The username.
  109. username: Optional[str] = None
  110. # The frontend port.
  111. frontend_port: str = constants.FRONTEND_PORT
  112. # The backend port.
  113. backend_port: str = constants.BACKEND_PORT
  114. # The backend host.
  115. backend_host: str = constants.BACKEND_HOST
  116. # The backend API url.
  117. api_url: str = constants.API_URL
  118. # The deploy url.
  119. deploy_url: Optional[str] = constants.DEPLOY_URL
  120. # The database url.
  121. db_url: Optional[str] = constants.DB_URL
  122. # The database config.
  123. db_config: Optional[DBConfig] = None
  124. # The redis url.
  125. redis_url: Optional[str] = constants.REDIS_URL
  126. # Telemetry opt-in.
  127. telemetry_enabled: bool = True
  128. # The pcdeploy url.
  129. pcdeploy_url: Optional[str] = None
  130. # The environment mode.
  131. env: constants.Env = constants.Env.DEV
  132. # The path to the bun executable.
  133. bun_path: str = constants.BUN_PATH
  134. # Disable bun.
  135. disable_bun: bool = False
  136. # Additional frontend packages to install.
  137. frontend_packages: List[str] = []
  138. # The Admin Dash.
  139. admin_dash: Optional[AdminDash] = None
  140. # Backend transport methods.
  141. backend_transports: Optional[
  142. constants.Transports
  143. ] = constants.Transports.WEBSOCKET_POLLING
  144. # List of origins that are allowed to connect to the backend API.
  145. cors_allowed_origins: Optional[list] = constants.CORS_ALLOWED_ORIGINS
  146. # Whether credentials (cookies, authentication) are allowed in requests to the backend API.
  147. cors_credentials: Optional[bool] = True
  148. # The maximum size of a message when using the polling backend transport.
  149. polling_max_http_buffer_size: Optional[int] = constants.POLLING_MAX_HTTP_BUFFER_SIZE
  150. # Dotenv file path.
  151. env_path: Optional[str] = constants.DOT_ENV_FILE
  152. # Whether to override OS environment variables.
  153. override_os_envs: Optional[bool] = True
  154. # Tailwind config.
  155. tailwind: Optional[Dict[str, Any]] = None
  156. # Timeout when launching the gunicorn server.
  157. timeout: int = constants.TIMEOUT
  158. def __init__(self, *args, **kwargs):
  159. """Initialize the config values.
  160. If db_url is not provided gets it from db_config.
  161. Args:
  162. *args: The args to pass to the Pydantic init method.
  163. **kwargs: The kwargs to pass to the Pydantic init method.
  164. """
  165. if "db_url" not in kwargs and "db_config" in kwargs:
  166. kwargs["db_url"] = kwargs["db_config"].get_url()
  167. super().__init__(*args, **kwargs)
  168. # set overriden class attribute values as os env variables to avoid losing them
  169. for key, value in dict(self).items():
  170. key = key.upper()
  171. if (
  172. key.startswith("_")
  173. or key in os.environ
  174. or (value is None and key != "DB_URL")
  175. ):
  176. continue
  177. os.environ[key] = str(value)
  178. # Avoid overriding if env_path is not provided or does not exist
  179. if self.env_path is not None and os.path.isfile(self.env_path):
  180. load_dotenv(self.env_path, override=self.override_os_envs) # type: ignore
  181. # Recompute constants after loading env variables
  182. importlib.reload(constants)
  183. # Recompute instance attributes
  184. self.recompute_field_values()
  185. def recompute_field_values(self):
  186. """Recompute instance field values to reflect new values after reloading
  187. constant values.
  188. """
  189. for field in self.get_fields():
  190. try:
  191. if field.startswith("_"):
  192. continue
  193. setattr(self, field, getattr(constants, f"{field.upper()}"))
  194. except AttributeError:
  195. pass
  196. def get_config() -> Config:
  197. """Get the app config.
  198. Returns:
  199. The app config.
  200. """
  201. from pynecone.config import Config
  202. sys.path.append(os.getcwd())
  203. try:
  204. return __import__(constants.CONFIG_MODULE).config
  205. except ImportError:
  206. return Config(app_name="") # type: ignore