Skip to content

Commit

Permalink
move all environment variables to the same place (#4192)
Browse files Browse the repository at this point in the history
* move all environment variables to the same place

* reorder things around

* move more variables to environment

* remove cyclical imports

* forgot default value for field

* for some reason type hints aren't being interpreted

* put the field type *before* not after

* make it get

* move a bit more

* add more fields

* move reflex dir

* add return

* put things somewhere else

* add custom error
  • Loading branch information
adhami3310 authored Oct 21, 2024
1 parent c05da48 commit f39e8c9
Show file tree
Hide file tree
Showing 21 changed files with 428 additions and 151 deletions.
9 changes: 5 additions & 4 deletions reflex/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@
)
from reflex.components.core.upload import Upload, get_upload_dir
from reflex.components.radix import themes
from reflex.config import get_config
from reflex.config import environment, get_config
from reflex.event import Event, EventHandler, EventSpec, window_alert
from reflex.model import Model, get_db_status
from reflex.page import (
Expand Down Expand Up @@ -957,15 +957,16 @@ def get_compilation_time() -> str:
executor = None
if (
platform.system() in ("Linux", "Darwin")
and os.environ.get("REFLEX_COMPILE_PROCESSES") is not None
and (number_of_processes := environment.REFLEX_COMPILE_PROCESSES)
is not None
):
executor = concurrent.futures.ProcessPoolExecutor(
max_workers=int(os.environ.get("REFLEX_COMPILE_PROCESSES", 0)) or None,
max_workers=number_of_processes,
mp_context=multiprocessing.get_context("fork"),
)
else:
executor = concurrent.futures.ThreadPoolExecutor(
max_workers=int(os.environ.get("REFLEX_COMPILE_THREADS", 0)) or None,
max_workers=environment.REFLEX_COMPILE_THREADS
)

with executor:
Expand Down
5 changes: 2 additions & 3 deletions reflex/compiler/compiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

from __future__ import annotations

import os
from datetime import datetime
from pathlib import Path
from typing import Dict, Iterable, Optional, Type, Union
Expand All @@ -16,7 +15,7 @@
CustomComponent,
StatefulComponent,
)
from reflex.config import get_config
from reflex.config import environment, get_config
from reflex.state import BaseState
from reflex.style import SYSTEM_COLOR_MODE
from reflex.utils.exec import is_prod_mode
Expand Down Expand Up @@ -527,7 +526,7 @@ def remove_tailwind_from_postcss() -> tuple[str, str]:

def purge_web_pages_dir():
"""Empty out .web/pages directory."""
if not is_prod_mode() and os.environ.get("REFLEX_PERSIST_WEB_DIR"):
if not is_prod_mode() and environment.REFLEX_PERSIST_WEB_DIR:
# Skip purging the web directory in dev mode if REFLEX_PERSIST_WEB_DIR is set.
return

Expand Down
6 changes: 2 additions & 4 deletions reflex/components/core/upload.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@

from __future__ import annotations

import os
from pathlib import Path
from typing import Any, Callable, ClassVar, Dict, List, Optional, Tuple

from reflex.components.component import Component, ComponentNamespace, MemoizationLeaf
from reflex.components.el.elements.forms import Input
from reflex.components.radix.themes.layout.box import Box
from reflex.config import environment
from reflex.constants import Dirs
from reflex.event import (
CallableEventSpec,
Expand Down Expand Up @@ -125,9 +125,7 @@ def get_upload_dir() -> Path:
"""
Upload.is_used = True

uploaded_files_dir = Path(
os.environ.get("REFLEX_UPLOADED_FILES_DIR", "./uploaded_files")
)
uploaded_files_dir = environment.REFLEX_UPLOADED_FILES_DIR
uploaded_files_dir.mkdir(parents=True, exist_ok=True)
return uploaded_files_dir

Expand Down
198 changes: 197 additions & 1 deletion reflex/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,18 @@

from __future__ import annotations

import dataclasses
import importlib
import os
import sys
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
Expand Down Expand Up @@ -131,6 +135,198 @@ def get_url(self) -> str:
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.
Expand Down
Loading

0 comments on commit f39e8c9

Please sign in to comment.