123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368 |
- """The Reflex config."""
- from __future__ import annotations
- import importlib
- import os
- import sys
- import urllib.parse
- from typing import Any, Dict, List, Optional, Set
- import pydantic
- from reflex_cli.constants.hosting import Hosting
- from reflex import constants
- from reflex.base import Base
- from reflex.utils import console
- 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: str | None = None,
- host: str | None = None,
- port: int | None = 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: str | None = None,
- host: str | None = None,
- port: int | None = 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 Reflex config."""
- class Config:
- """Pydantic config for the config."""
- validate_assignment = True
- # The name of the app.
- app_name: str
- # The log level to use.
- loglevel: constants.LogLevel = constants.LogLevel.INFO
- # The port to run the frontend on.
- frontend_port: int = 3000
- # The path to run the frontend on.
- frontend_path: str = ""
- # The port to run the backend on.
- backend_port: int = 8000
- # The backend url the frontend will connect to.
- api_url: str = f"http://localhost:{backend_port}"
- # The url the frontend will be hosted on.
- deploy_url: Optional[str] = f"http://localhost:{frontend_port}"
- # The url the backend will be hosted on.
- backend_host: str = "0.0.0.0"
- # The database url.
- db_url: Optional[str] = "sqlite:///reflex.db"
- # The redis url.
- redis_url: Optional[str] = None
- # Telemetry opt-in.
- telemetry_enabled: bool = True
- # The bun path
- bun_path: str = constants.Bun.DEFAULT_PATH
- # List of origins that are allowed to connect to the backend API.
- cors_allowed_origins: List[str] = ["*"]
- # Tailwind config.
- tailwind: Optional[Dict[str, Any]] = {}
- # Timeout when launching the gunicorn server. TODO(rename this to backend_timeout?)
- timeout: int = 120
- # Whether to enable or disable nextJS gzip compression.
- next_compression: bool = True
- # The event namespace for ws connection
- event_namespace: Optional[str] = None
- # Additional frontend packages to install.
- frontend_packages: List[str] = []
- # The hosting service backend URL.
- cp_backend_url: str = Hosting.CP_BACKEND_URL
- # The hosting service frontend URL.
- cp_web_url: str = Hosting.CP_WEB_URL
- # The worker class used in production mode
- gunicorn_worker_class: str = "uvicorn.workers.UvicornH11Worker"
- # Attributes that were explicitly set by the user.
- _non_default_attributes: Set[str] = pydantic.PrivateAttr(set())
- def __init__(self, *args, **kwargs):
- """Initialize the config values.
- Args:
- *args: The args to pass to the Pydantic init method.
- **kwargs: The kwargs to pass to the Pydantic init method.
- """
- super().__init__(*args, **kwargs)
- # Check for deprecated values.
- self.check_deprecated_values(**kwargs)
- # Update the config from environment variables.
- env_kwargs = self.update_from_env()
- for key, env_value in env_kwargs.items():
- setattr(self, key, env_value)
- # Update default URLs if ports were set
- kwargs.update(env_kwargs)
- self._non_default_attributes.update(kwargs)
- self._replace_defaults(**kwargs)
- @property
- def module(self) -> str:
- """Get the module name of the app.
- Returns:
- The module name.
- """
- return ".".join([self.app_name, self.app_name])
- @staticmethod
- def check_deprecated_values(**kwargs):
- """Check for deprecated config values.
- Args:
- **kwargs: The kwargs passed to the config.
- Raises:
- ValueError: If a deprecated config value is found.
- """
- if "db_config" in kwargs:
- raise ValueError("db_config is deprecated - use db_url instead")
- if "admin_dash" in kwargs:
- raise ValueError(
- "admin_dash is deprecated in the config - pass it as a param to rx.App instead"
- )
- if "env_path" in kwargs:
- raise ValueError(
- "env_path is deprecated - use environment variables instead"
- )
- def update_from_env(self) -> dict[str, Any]:
- """Update the config from environment variables.
- Returns:
- The updated config values.
- Raises:
- ValueError: If an environment variable is set to an invalid type.
- """
- updated_values = {}
- # Iterate over the fields.
- for key, field in self.__fields__.items():
- # The env var name is the key in uppercase.
- env_var = os.environ.get(key.upper())
- # If the env var is set, override the config value.
- if env_var is not None:
- if key.upper() != "DB_URL":
- console.info(
- f"Overriding config value {key} with env var {key.upper()}={env_var}"
- )
- # Convert the env var to the expected type.
- try:
- if issubclass(field.type_, bool):
- # special handling for bool values
- env_var = env_var.lower() in ["true", "1", "yes"]
- else:
- env_var = field.type_(env_var)
- except ValueError:
- console.error(
- f"Could not convert {key.upper()}={env_var} to type {field.type_}"
- )
- raise
- # Set the value.
- updated_values[key] = env_var
- return updated_values
- def get_event_namespace(self) -> str | None:
- """Get the websocket event namespace.
- Returns:
- The namespace for websocket.
- """
- if self.event_namespace:
- console.deprecate(
- feature_name="Passing event_namespace in the config",
- reason="",
- deprecation_version="0.3.5",
- removal_version="0.5.0",
- )
- return f'/{self.event_namespace.strip("/")}'
- event_url = constants.Endpoint.EVENT.get_url()
- return urllib.parse.urlsplit(event_url).path
- def _replace_defaults(self, **kwargs):
- """Replace formatted defaults when the caller provides updates.
- Args:
- **kwargs: The kwargs passed to the config or from the env.
- """
- if "api_url" not in self._non_default_attributes and "backend_port" in kwargs:
- self.api_url = f"http://localhost:{kwargs['backend_port']}"
- if (
- "deploy_url" not in self._non_default_attributes
- and "frontend_port" in kwargs
- ):
- self.deploy_url = f"http://localhost:{kwargs['frontend_port']}"
- # If running in Github Codespaces, override API_URL
- codespace_name = os.getenv("CODESPACE_NAME")
- if "api_url" not in self._non_default_attributes and codespace_name:
- GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN = os.getenv(
- "GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN"
- )
- if codespace_name:
- self.api_url = (
- f"https://{codespace_name}-{kwargs.get('backend_port', self.backend_port)}"
- f".{GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN}"
- )
- def _set_persistent(self, **kwargs):
- """Set values in this config and in the environment so they persist into subprocess.
- Args:
- **kwargs: The kwargs passed to the config.
- """
- for key, value in kwargs.items():
- if value is not None:
- os.environ[key.upper()] = str(value)
- setattr(self, key, value)
- self._non_default_attributes.update(kwargs)
- self._replace_defaults(**kwargs)
- def get_config(reload: bool = False) -> Config:
- """Get the app config.
- Args:
- reload: Re-import the rxconfig module from disk
- Returns:
- The app config.
- """
- sys.path.insert(0, os.getcwd())
- try:
- rxconfig = __import__(constants.Config.MODULE)
- if reload:
- importlib.reload(rxconfig)
- return rxconfig.config
- except ImportError:
- return Config(app_name="") # type: ignore
|