config.py 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255
  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 List, Optional
  8. from dotenv import load_dotenv
  9. from pynecone import constants
  10. from pynecone.base import Base
  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: Optional[str] = None,
  25. host: Optional[str] = None,
  26. port: Optional[int] = 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: Optional[str] = None,
  52. host: Optional[str] = None,
  53. port: Optional[int] = 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 Pynecone config."""
  105. # The name of the app.
  106. app_name: str
  107. # The username.
  108. username: Optional[str] = None
  109. # The frontend port.
  110. frontend_port: str = constants.FRONTEND_PORT
  111. # The backend port.
  112. backend_port: str = constants.BACKEND_PORT
  113. # The backend host.
  114. backend_host: str = constants.BACKEND_HOST
  115. # The backend API url.
  116. api_url: str = constants.API_URL
  117. # The deploy url.
  118. deploy_url: Optional[str] = constants.DEPLOY_URL
  119. # The database url.
  120. db_url: Optional[str] = constants.DB_URL
  121. # The database config.
  122. db_config: Optional[DBConfig] = None
  123. # The redis url.
  124. redis_url: Optional[str] = constants.REDIS_URL
  125. # Telemetry opt-in.
  126. telemetry_enabled: bool = True
  127. # The pcdeploy url.
  128. pcdeploy_url: Optional[str] = None
  129. # The environment mode.
  130. env: constants.Env = constants.Env.DEV
  131. # The path to the bun executable.
  132. bun_path: str = constants.BUN_PATH
  133. # Disable bun.
  134. disable_bun: bool = False
  135. # Additional frontend packages to install.
  136. frontend_packages: List[str] = []
  137. # Backend transport methods.
  138. backend_transports: Optional[
  139. constants.Transports
  140. ] = constants.Transports.WEBSOCKET_POLLING
  141. # List of origins that are allowed to connect to the backend API.
  142. cors_allowed_origins: Optional[list] = constants.CORS_ALLOWED_ORIGINS
  143. # Whether credentials (cookies, authentication) are allowed in requests to the backend API.
  144. cors_credentials: Optional[bool] = True
  145. # The maximum size of a message when using the polling backend transport.
  146. polling_max_http_buffer_size: Optional[int] = constants.POLLING_MAX_HTTP_BUFFER_SIZE
  147. # Dotenv file path
  148. env_path: Optional[str] = constants.DOT_ENV_FILE
  149. # Whether to override OS environment variables
  150. override_os_envs: Optional[bool] = True
  151. def __init__(self, *args, **kwargs):
  152. """Initialize the config values.
  153. If db_url is not provided gets it from db_config.
  154. Args:
  155. *args: The args to pass to the Pydantic init method.
  156. **kwargs: The kwargs to pass to the Pydantic init method.
  157. """
  158. if "db_url" not in kwargs and "db_config" in kwargs:
  159. kwargs["db_url"] = kwargs["db_config"].get_url()
  160. super().__init__(*args, **kwargs)
  161. # set overriden class attribute values as os env variables to avoid losing them
  162. for key, value in dict(self).items():
  163. key = key.upper()
  164. if (
  165. key.startswith("_")
  166. or key in os.environ
  167. or (value is None and key != "DB_URL")
  168. ):
  169. continue
  170. os.environ[key] = str(value)
  171. # Avoid overriding if env_path is not provided or does not exist
  172. if self.env_path is not None and os.path.isfile(self.env_path):
  173. load_dotenv(self.env_path, override=self.override_os_envs) # type: ignore
  174. # Recompute constants after loading env variables
  175. importlib.reload(constants)
  176. # Recompute instance attributes
  177. self.recompute_field_values()
  178. def recompute_field_values(self):
  179. """Recompute instance field values to reflect new values after reloading
  180. constant values.
  181. """
  182. for field in self.get_fields():
  183. try:
  184. if field.startswith("_"):
  185. continue
  186. setattr(self, field, getattr(constants, f"{field.upper()}"))
  187. except AttributeError:
  188. pass
  189. def get_config() -> Config:
  190. """Get the app config.
  191. Returns:
  192. The app config.
  193. """
  194. from pynecone.config import Config
  195. sys.path.append(os.getcwd())
  196. try:
  197. return __import__(constants.CONFIG_MODULE).config
  198. except ImportError:
  199. return Config(app_name="") # type: ignore