reflex/pynecone/config.py
2023-06-09 12:48:18 -07:00

260 lines
6.8 KiB
Python

"""The Pynecone config."""
from __future__ import annotations
import importlib
import os
import sys
import urllib.parse
from typing import List, Optional
from dotenv import load_dotenv
from pynecone import constants
from pynecone.admin import AdminDash
from pynecone.base import Base
class DBConfig(Base):
"""Database config."""
engine: str
username: Optional[str] = ""
password: Optional[str] = ""
host: Optional[str] = ""
port: Optional[int] = None
database: str
@classmethod
def postgresql(
cls,
database: str,
username: str,
password: Optional[str] = None,
host: Optional[str] = None,
port: Optional[int] = 5432,
) -> DBConfig:
"""Create an instance with postgresql engine.
Args:
database: Database name.
username: Database username.
password: Database password.
host: Database host.
port: Database port.
Returns:
DBConfig instance.
"""
return cls(
engine="postgresql",
username=username,
password=password,
host=host,
port=port,
database=database,
)
@classmethod
def postgresql_psycopg2(
cls,
database: str,
username: str,
password: Optional[str] = None,
host: Optional[str] = None,
port: Optional[int] = 5432,
) -> DBConfig:
"""Create an instance with postgresql+psycopg2 engine.
Args:
database: Database name.
username: Database username.
password: Database password.
host: Database host.
port: Database port.
Returns:
DBConfig instance.
"""
return cls(
engine="postgresql+psycopg2",
username=username,
password=password,
host=host,
port=port,
database=database,
)
@classmethod
def sqlite(
cls,
database: str,
) -> DBConfig:
"""Create an instance with sqlite engine.
Args:
database: Database name.
Returns:
DBConfig instance.
"""
return cls(
engine="sqlite",
database=database,
)
def get_url(self) -> str:
"""Get database URL.
Returns:
The database URL.
"""
host = (
f"{self.host}:{self.port}" if self.host and self.port else self.host or ""
)
username = urllib.parse.quote_plus(self.username) if self.username else ""
password = urllib.parse.quote_plus(self.password) if self.password else ""
if username:
path = f"{username}:{password}@{host}" if password else f"{username}@{host}"
else:
path = f"{host}"
return f"{self.engine}://{path}/{self.database}"
class Config(Base):
"""A Pynecone config."""
# The name of the app.
app_name: str
# The username.
username: Optional[str] = None
# The frontend port.
frontend_port: str = constants.FRONTEND_PORT
# The backend port.
backend_port: str = constants.BACKEND_PORT
# The backend host.
backend_host: str = constants.BACKEND_HOST
# The backend API url.
api_url: str = constants.API_URL
# The deploy url.
deploy_url: Optional[str] = constants.DEPLOY_URL
# The database url.
db_url: Optional[str] = constants.DB_URL
# The database config.
db_config: Optional[DBConfig] = None
# The redis url.
redis_url: Optional[str] = constants.REDIS_URL
# Telemetry opt-in.
telemetry_enabled: bool = True
# The pcdeploy url.
pcdeploy_url: Optional[str] = None
# The environment mode.
env: constants.Env = constants.Env.DEV
# The path to the bun executable.
bun_path: str = constants.BUN_PATH
# Disable bun.
disable_bun: bool = False
# Additional frontend packages to install.
frontend_packages: List[str] = []
# The Admin Dash
admin_dash: Optional[AdminDash] = None
# Backend transport methods.
backend_transports: Optional[
constants.Transports
] = constants.Transports.WEBSOCKET_POLLING
# List of origins that are allowed to connect to the backend API.
cors_allowed_origins: Optional[list] = constants.CORS_ALLOWED_ORIGINS
# Whether credentials (cookies, authentication) are allowed in requests to the backend API.
cors_credentials: Optional[bool] = True
# The maximum size of a message when using the polling backend transport.
polling_max_http_buffer_size: Optional[int] = constants.POLLING_MAX_HTTP_BUFFER_SIZE
# Dotenv file path
env_path: Optional[str] = constants.DOT_ENV_FILE
# Whether to override OS environment variables
override_os_envs: Optional[bool] = True
def __init__(self, *args, **kwargs):
"""Initialize the config values.
If db_url is not provided gets it from db_config.
Args:
*args: The args to pass to the Pydantic init method.
**kwargs: The kwargs to pass to the Pydantic init method.
"""
if "db_url" not in kwargs and "db_config" in kwargs:
kwargs["db_url"] = kwargs["db_config"].get_url()
super().__init__(*args, **kwargs)
# set overriden class attribute values as os env variables to avoid losing them
for key, value in dict(self).items():
key = key.upper()
if (
key.startswith("_")
or key in os.environ
or (value is None and key != "DB_URL")
):
continue
os.environ[key] = str(value)
# Avoid overriding if env_path is not provided or does not exist
if self.env_path is not None and os.path.isfile(self.env_path):
load_dotenv(self.env_path, override=self.override_os_envs) # type: ignore
# Recompute constants after loading env variables
importlib.reload(constants)
# Recompute instance attributes
self.recompute_field_values()
def recompute_field_values(self):
"""Recompute instance field values to reflect new values after reloading
constant values.
"""
for field in self.get_fields():
try:
if field.startswith("_"):
continue
setattr(self, field, getattr(constants, f"{field.upper()}"))
except AttributeError:
pass
def get_config() -> Config:
"""Get the app config.
Returns:
The app config.
"""
from pynecone.config import Config
sys.path.append(os.getcwd())
try:
return __import__(constants.CONFIG_MODULE).config
except ImportError:
return Config(app_name="") # type: ignore