|
@@ -2,6 +2,7 @@
|
|
|
|
|
|
from __future__ import annotations
|
|
|
|
|
|
+import dataclasses
|
|
|
import importlib
|
|
|
import os
|
|
|
import sys
|
|
@@ -9,7 +10,10 @@ import urllib.parse
|
|
|
from pathlib import Path
|
|
|
from typing import Any, Dict, List, Optional, Set, Union
|
|
|
|
|
|
-from reflex.utils.exceptions import ConfigError
|
|
|
+from typing_extensions import get_type_hints
|
|
|
+
|
|
|
+from reflex.utils.exceptions import ConfigError, EnvironmentVarValueError
|
|
|
+from reflex.utils.types import value_inside_optional
|
|
|
|
|
|
try:
|
|
|
import pydantic.v1 as pydantic
|
|
@@ -131,6 +135,198 @@ class DBConfig(Base):
|
|
|
return f"{self.engine}://{path}/{self.database}"
|
|
|
|
|
|
|
|
|
+def get_default_value_for_field(field: dataclasses.Field) -> Any:
|
|
|
+ """Get the default value for a field.
|
|
|
+
|
|
|
+ Args:
|
|
|
+ field: The field.
|
|
|
+
|
|
|
+ Returns:
|
|
|
+ The default value.
|
|
|
+
|
|
|
+ Raises:
|
|
|
+ ValueError: If no default value is found.
|
|
|
+ """
|
|
|
+ if field.default != dataclasses.MISSING:
|
|
|
+ return field.default
|
|
|
+ elif field.default_factory != dataclasses.MISSING:
|
|
|
+ return field.default_factory()
|
|
|
+ else:
|
|
|
+ raise ValueError(
|
|
|
+ f"Missing value for environment variable {field.name} and no default value found"
|
|
|
+ )
|
|
|
+
|
|
|
+
|
|
|
+def interpret_boolean_env(value: str) -> bool:
|
|
|
+ """Interpret a boolean environment variable value.
|
|
|
+
|
|
|
+ Args:
|
|
|
+ value: The environment variable value.
|
|
|
+
|
|
|
+ Returns:
|
|
|
+ The interpreted value.
|
|
|
+
|
|
|
+ Raises:
|
|
|
+ EnvironmentVarValueError: If the value is invalid.
|
|
|
+ """
|
|
|
+ true_values = ["true", "1", "yes", "y"]
|
|
|
+ false_values = ["false", "0", "no", "n"]
|
|
|
+
|
|
|
+ if value.lower() in true_values:
|
|
|
+ return True
|
|
|
+ elif value.lower() in false_values:
|
|
|
+ return False
|
|
|
+ raise EnvironmentVarValueError(f"Invalid boolean value: {value}")
|
|
|
+
|
|
|
+
|
|
|
+def interpret_int_env(value: str) -> int:
|
|
|
+ """Interpret an integer environment variable value.
|
|
|
+
|
|
|
+ Args:
|
|
|
+ value: The environment variable value.
|
|
|
+
|
|
|
+ Returns:
|
|
|
+ The interpreted value.
|
|
|
+
|
|
|
+ Raises:
|
|
|
+ EnvironmentVarValueError: If the value is invalid.
|
|
|
+ """
|
|
|
+ try:
|
|
|
+ return int(value)
|
|
|
+ except ValueError as ve:
|
|
|
+ raise EnvironmentVarValueError(f"Invalid integer value: {value}") from ve
|
|
|
+
|
|
|
+
|
|
|
+def interpret_path_env(value: str) -> Path:
|
|
|
+ """Interpret a path environment variable value.
|
|
|
+
|
|
|
+ Args:
|
|
|
+ value: The environment variable value.
|
|
|
+
|
|
|
+ Returns:
|
|
|
+ The interpreted value.
|
|
|
+
|
|
|
+ Raises:
|
|
|
+ EnvironmentVarValueError: If the path does not exist.
|
|
|
+ """
|
|
|
+ path = Path(value)
|
|
|
+ if not path.exists():
|
|
|
+ raise EnvironmentVarValueError(f"Path does not exist: {path}")
|
|
|
+ return path
|
|
|
+
|
|
|
+
|
|
|
+def interpret_env_var_value(value: str, field: dataclasses.Field) -> Any:
|
|
|
+ """Interpret an environment variable value based on the field type.
|
|
|
+
|
|
|
+ Args:
|
|
|
+ value: The environment variable value.
|
|
|
+ field: The field.
|
|
|
+
|
|
|
+ Returns:
|
|
|
+ The interpreted value.
|
|
|
+
|
|
|
+ Raises:
|
|
|
+ ValueError: If the value is invalid.
|
|
|
+ """
|
|
|
+ field_type = value_inside_optional(field.type)
|
|
|
+
|
|
|
+ if field_type is bool:
|
|
|
+ return interpret_boolean_env(value)
|
|
|
+ elif field_type is str:
|
|
|
+ return value
|
|
|
+ elif field_type is int:
|
|
|
+ return interpret_int_env(value)
|
|
|
+ elif field_type is Path:
|
|
|
+ return interpret_path_env(value)
|
|
|
+
|
|
|
+ else:
|
|
|
+ raise ValueError(
|
|
|
+ f"Invalid type for environment variable {field.name}: {field_type}. This is probably an issue in Reflex."
|
|
|
+ )
|
|
|
+
|
|
|
+
|
|
|
+@dataclasses.dataclass(init=False)
|
|
|
+class EnvironmentVariables:
|
|
|
+ """Environment variables class to instantiate environment variables."""
|
|
|
+
|
|
|
+ # Whether to use npm over bun to install frontend packages.
|
|
|
+ REFLEX_USE_NPM: bool = False
|
|
|
+
|
|
|
+ # The npm registry to use.
|
|
|
+ NPM_CONFIG_REGISTRY: Optional[str] = None
|
|
|
+
|
|
|
+ # Whether to use Granian for the backend. Otherwise, use Uvicorn.
|
|
|
+ REFLEX_USE_GRANIAN: bool = False
|
|
|
+
|
|
|
+ # The username to use for authentication on python package repository. Username and password must both be provided.
|
|
|
+ TWINE_USERNAME: Optional[str] = None
|
|
|
+
|
|
|
+ # The password to use for authentication on python package repository. Username and password must both be provided.
|
|
|
+ TWINE_PASSWORD: Optional[str] = None
|
|
|
+
|
|
|
+ # Whether to use the system installed bun. If set to false, bun will be bundled with the app.
|
|
|
+ REFLEX_USE_SYSTEM_BUN: bool = False
|
|
|
+
|
|
|
+ # Whether to use the system installed node and npm. If set to false, node and npm will be bundled with the app.
|
|
|
+ REFLEX_USE_SYSTEM_NODE: bool = False
|
|
|
+
|
|
|
+ # The working directory for the next.js commands.
|
|
|
+ REFLEX_WEB_WORKDIR: Path = Path(constants.Dirs.WEB)
|
|
|
+
|
|
|
+ # Path to the alembic config file
|
|
|
+ ALEMBIC_CONFIG: Path = Path(constants.ALEMBIC_CONFIG)
|
|
|
+
|
|
|
+ # Disable SSL verification for HTTPX requests.
|
|
|
+ SSL_NO_VERIFY: bool = False
|
|
|
+
|
|
|
+ # The directory to store uploaded files.
|
|
|
+ REFLEX_UPLOADED_FILES_DIR: Path = Path(constants.Dirs.UPLOADED_FILES)
|
|
|
+
|
|
|
+ # Whether to use seperate processes to compile the frontend and how many. If not set, defaults to thread executor.
|
|
|
+ REFLEX_COMPILE_PROCESSES: Optional[int] = None
|
|
|
+
|
|
|
+ # Whether to use seperate threads to compile the frontend and how many. Defaults to `min(32, os.cpu_count() + 4)`.
|
|
|
+ REFLEX_COMPILE_THREADS: Optional[int] = None
|
|
|
+
|
|
|
+ # The directory to store reflex dependencies.
|
|
|
+ REFLEX_DIR: Path = Path(constants.Reflex.DIR)
|
|
|
+
|
|
|
+ # Whether to print the SQL queries if the log level is INFO or lower.
|
|
|
+ SQLALCHEMY_ECHO: bool = False
|
|
|
+
|
|
|
+ # Whether to ignore the redis config error. Some redis servers only allow out-of-band configuration.
|
|
|
+ REFLEX_IGNORE_REDIS_CONFIG_ERROR: bool = False
|
|
|
+
|
|
|
+ # Whether to skip purging the web directory in dev mode.
|
|
|
+ REFLEX_PERSIST_WEB_DIR: bool = False
|
|
|
+
|
|
|
+ # The reflex.build frontend host.
|
|
|
+ REFLEX_BUILD_FRONTEND: str = constants.Templates.REFLEX_BUILD_FRONTEND
|
|
|
+
|
|
|
+ # The reflex.build backend host.
|
|
|
+ REFLEX_BUILD_BACKEND: str = constants.Templates.REFLEX_BUILD_BACKEND
|
|
|
+
|
|
|
+ def __init__(self):
|
|
|
+ """Initialize the environment variables."""
|
|
|
+ type_hints = get_type_hints(type(self))
|
|
|
+
|
|
|
+ for field in dataclasses.fields(self):
|
|
|
+ raw_value = os.getenv(field.name, None)
|
|
|
+
|
|
|
+ field.type = type_hints.get(field.name) or field.type
|
|
|
+
|
|
|
+ value = (
|
|
|
+ interpret_env_var_value(raw_value, field)
|
|
|
+ if raw_value is not None
|
|
|
+ else get_default_value_for_field(field)
|
|
|
+ )
|
|
|
+
|
|
|
+ setattr(self, field.name, value)
|
|
|
+
|
|
|
+
|
|
|
+environment = EnvironmentVariables()
|
|
|
+
|
|
|
+
|
|
|
class Config(Base):
|
|
|
"""The config defines runtime settings for the app.
|
|
|
|