Skip to content
Merged
Show file tree
Hide file tree
Changes from 27 commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
2efa0ef
fix and test bug in config env loading
benedikt-bartscher Oct 20, 2024
44d60d1
Merge remote-tracking branch 'upstream/main' into fix-rx-config
benedikt-bartscher Oct 21, 2024
4ddc014
streamline env var interpretation with @adhami3310
benedikt-bartscher Oct 21, 2024
27edea4
improve error messages, fix invalid value for TELEMETRY_ENABLED
benedikt-bartscher Oct 21, 2024
83b2f2e
just a small hint
benedikt-bartscher Oct 21, 2024
3a361d1
Merge remote-tracking branch 'upstream/main' into fix-rx-config
benedikt-bartscher Oct 23, 2024
3f4b049
ruffing
benedikt-bartscher Oct 23, 2024
a7ffab9
fix typo from review
benedikt-bartscher Oct 23, 2024
1499282
refactor - ruff broke the imports..
benedikt-bartscher Oct 24, 2024
85be850
Merge remote-tracking branch 'upstream/main' into more-env-var-cleanup
benedikt-bartscher Oct 26, 2024
8af7845
cleanup imports
benedikt-bartscher Oct 26, 2024
79be002
more
benedikt-bartscher Oct 26, 2024
832b803
add internal and enum env var support
benedikt-bartscher Oct 26, 2024
53470f1
ruff cleanup
benedikt-bartscher Oct 26, 2024
d305162
more global imports
benedikt-bartscher Oct 26, 2024
36c5b4f
revert telemetry, it lives in rx.Config
benedikt-bartscher Oct 26, 2024
960394b
Merge remote-tracking branch 'upstream/main' into more-env-var-cleanup
benedikt-bartscher Oct 28, 2024
cb4aa7d
Merge remote-tracking branch 'upstream/main' into more-env-var-cleanup
benedikt-bartscher Oct 28, 2024
7cdd6f2
minor fixes/cleanup
benedikt-bartscher Oct 28, 2024
73a80a6
i missed some refs
benedikt-bartscher Oct 28, 2024
4ee16ba
fix darglint
benedikt-bartscher Oct 28, 2024
9f655db
reload config is internal
benedikt-bartscher Oct 28, 2024
48457cc
fix EnvVar name
benedikt-bartscher Oct 28, 2024
c1a6270
add test for EnvVar + minor typing improvement
benedikt-bartscher Oct 28, 2024
ba0e760
bool tests
benedikt-bartscher Oct 28, 2024
467308e
was this broken?
benedikt-bartscher Oct 28, 2024
8405709
retain old behavior
benedikt-bartscher Oct 28, 2024
f16273f
migrate APP_HARNESS_HEADLESS to new env var system
benedikt-bartscher Oct 28, 2024
e1ec51a
migrate more APP_HARNESS env vars to new config system
benedikt-bartscher Oct 28, 2024
93a570b
migrate SCREENSHOT_DIR to new env var system
benedikt-bartscher Oct 28, 2024
9e7e108
Merge remote-tracking branch 'upstream/main' into more-env-var-cleanup
benedikt-bartscher Oct 29, 2024
9d44cd7
refactor EnvVar.get to be a method
benedikt-bartscher Oct 31, 2024
2f91465
readd deleted functions and deprecate them
benedikt-bartscher Oct 31, 2024
826660b
improve EnvVar api, cleanup RELOAD_CONFIG question
benedikt-bartscher Oct 31, 2024
f1fea10
move is_prod_mode back to where it was
benedikt-bartscher Oct 31, 2024
aa79783
Merge remote-tracking branch 'upstream/main' into more-env-var-cleanup
benedikt-bartscher Nov 1, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 8 additions & 5 deletions reflex/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@
code_uses_state_contexts,
)
from reflex.utils import codespaces, console, exceptions, format, prerequisites, types
from reflex.utils.exec import is_prod_mode, is_testing_env, should_skip_compile
from reflex.utils.exec import is_prod_mode, is_testing_env
from reflex.utils.imports import ImportVar

if TYPE_CHECKING:
Expand Down Expand Up @@ -505,7 +505,10 @@ def add_page(
# Check if the route given is valid
verify_route_validity(route)

if route in self.unevaluated_pages and os.getenv(constants.RELOAD_CONFIG):
# TODO: this was broken?
if route in self.unevaluated_pages and os.getenv(
environment.RELOAD_CONFIG.name
):
# when the app is reloaded(typically for app harness tests), we should maintain
# the latest render function of a route.This applies typically to decorated pages
# since they are only added when app._compile is called.
Expand Down Expand Up @@ -724,7 +727,7 @@ def _should_compile(self) -> bool:
Whether the app should be compiled.
"""
# Check the environment variable.
if should_skip_compile():
if environment.REFLEX_SKIP_COMPILE.get:
return False

nocompile = prerequisites.get_web_dir() / constants.NOCOMPILE_FILE
Expand Down Expand Up @@ -945,7 +948,7 @@ def get_compilation_time() -> str:
executor = None
if (
platform.system() in ("Linux", "Darwin")
and (number_of_processes := environment.REFLEX_COMPILE_PROCESSES)
and (number_of_processes := environment.REFLEX_COMPILE_PROCESSES.get)
is not None
):
executor = concurrent.futures.ProcessPoolExecutor(
Expand All @@ -954,7 +957,7 @@ def get_compilation_time() -> str:
)
else:
executor = concurrent.futures.ThreadPoolExecutor(
max_workers=environment.REFLEX_COMPILE_THREADS
max_workers=environment.REFLEX_COMPILE_THREADS.get
)

for route, component in zip(self.pages, page_components):
Expand Down
6 changes: 2 additions & 4 deletions reflex/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,6 @@
from pydantic.fields import ModelField # type: ignore


from reflex import constants


def validate_field_name(bases: List[Type["BaseModel"]], field_name: str) -> None:
"""Ensure that the field's name does not shadow an existing attribute of the model.

Expand All @@ -31,7 +28,8 @@ def validate_field_name(bases: List[Type["BaseModel"]], field_name: str) -> None
"""
from reflex.utils.exceptions import VarNameError

reload = os.getenv(constants.RELOAD_CONFIG) == "True"
# can't use reflex.config.environment here cause of circular import
reload = os.getenv("__RELOAD_CONFIG", "").lower() == "true"
for base in bases:
try:
if not reload and getattr(base, field_name, None):
Expand Down
2 changes: 1 addition & 1 deletion reflex/compiler/compiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -527,7 +527,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 environment.REFLEX_PERSIST_WEB_DIR:
if not is_prod_mode() and environment.REFLEX_PERSIST_WEB_DIR.get:
# Skip purging the web directory in dev mode if REFLEX_PERSIST_WEB_DIR is set.
return

Expand Down
2 changes: 1 addition & 1 deletion reflex/components/core/upload.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ def get_upload_dir() -> Path:
"""
Upload.is_used = True

uploaded_files_dir = environment.REFLEX_UPLOADED_FILES_DIR
uploaded_files_dir = environment.REFLEX_UPLOADED_FILES_DIR.get
uploaded_files_dir.mkdir(parents=True, exist_ok=True)
return uploaded_files_dir

Expand Down
198 changes: 163 additions & 35 deletions reflex/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,17 @@
import sys
import urllib.parse
from pathlib import Path
from typing import Any, Dict, List, Optional, Set
from typing import (
TYPE_CHECKING,
Any,
Dict,
Generic,
List,
Optional,
Set,
TypeVar,
get_args,
)

from typing_extensions import get_type_hints

Expand Down Expand Up @@ -285,83 +295,201 @@ def interpret_env_var_value(
)


@dataclasses.dataclass(init=False)
T = TypeVar("T")


class EnvVar(Generic[T]):
"""Environment variable."""

name: str
default: Any
type_: T

def __init__(self, name: str, default: Any, type_: T) -> None:
"""Initialize the environment variable.

Args:
name: The environment variable name.
default: The default value.
type_: The type of the value.
"""
self.name = name
self.default = default
self.type_ = type_

@property
def getenv(self) -> Optional[str]:
"""Get the environment variable from os.environ.

Returns:
The environment variable value.
"""
return os.getenv(self.name, None)

@property
def get(self) -> T:
"""Get the interpreted environment variable value.

Returns:
The interpreted value.
"""
env_value = self.getenv
if env_value is not None:
return interpret_env_var_value(env_value, self.type_, self.name)
return self.default

def set(self, value: T | None) -> None:
"""Set the environment variable. None unsets the variable.

Args:
value: The value to set.
"""
if value is None:
_ = os.environ.pop(self.name, None)
else:
if isinstance(value, enum.Enum):
value = value.value
os.environ[self.name] = str(value)


class env_var: # type: ignore
"""Descriptor for environment variables."""

name: str
default: Any
internal: bool = False

def __init__(self, default: Any, internal: bool = False) -> None:
"""Initialize the descriptor.

Args:
default: The default value.
internal: Whether the environment variable is reflex internal.
"""
self.default = default
self.internal = internal

def __set_name__(self, owner, name):
"""Set the name of the descriptor.

Args:
owner: The owner class.
name: The name of the descriptor.
"""
self.name = name

def __get__(self, instance, owner):
"""Get the EnvVar instance.

Args:
instance: The instance.
owner: The owner class.

Returns:
The EnvVar instance.
"""
type_ = get_args(get_type_hints(owner)[self.name])[0]
env_name = self.name
if self.internal:
env_name = f"__{env_name}"
return EnvVar(name=env_name, default=self.default, type_=type_)


if TYPE_CHECKING:

def env_var(default, internal=False) -> EnvVar:
"""Typing helper for the env_var descriptor.

Args:
default: The default value.
internal: Whether the environment variable is reflex internal.

Returns:
The EnvVar instance.
"""
return default


class EnvironmentVariables:
"""Environment variables class to instantiate environment variables."""

# Whether to use npm over bun to install frontend packages.
REFLEX_USE_NPM: bool = False
REFLEX_USE_NPM: EnvVar[bool] = env_var(False)

# The npm registry to use.
NPM_CONFIG_REGISTRY: Optional[str] = None
NPM_CONFIG_REGISTRY: EnvVar[Optional[str]] = env_var(None)

# Whether to use Granian for the backend. Otherwise, use Uvicorn.
REFLEX_USE_GRANIAN: bool = False
REFLEX_USE_GRANIAN: EnvVar[bool] = env_var(False)

# The username to use for authentication on python package repository. Username and password must both be provided.
TWINE_USERNAME: Optional[str] = None
TWINE_USERNAME: EnvVar[Optional[str]] = env_var(None)

# The password to use for authentication on python package repository. Username and password must both be provided.
TWINE_PASSWORD: Optional[str] = None
TWINE_PASSWORD: EnvVar[Optional[str]] = env_var(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
REFLEX_USE_SYSTEM_BUN: EnvVar[bool] = env_var(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
REFLEX_USE_SYSTEM_NODE: EnvVar[bool] = env_var(False)

# The working directory for the next.js commands.
REFLEX_WEB_WORKDIR: Path = Path(constants.Dirs.WEB)
REFLEX_WEB_WORKDIR: EnvVar[Path] = env_var(Path(constants.Dirs.WEB))

# Path to the alembic config file
ALEMBIC_CONFIG: Path = Path(constants.ALEMBIC_CONFIG)
ALEMBIC_CONFIG: EnvVar[Path] = env_var(Path(constants.ALEMBIC_CONFIG))

# Disable SSL verification for HTTPX requests.
SSL_NO_VERIFY: bool = False
SSL_NO_VERIFY: EnvVar[bool] = env_var(False)

# The directory to store uploaded files.
REFLEX_UPLOADED_FILES_DIR: Path = Path(constants.Dirs.UPLOADED_FILES)
REFLEX_UPLOADED_FILES_DIR: EnvVar[Path] = env_var(
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 separate processes to compile the frontend and how many. If not set, defaults to thread executor.
REFLEX_COMPILE_PROCESSES: EnvVar[Optional[int]] = env_var(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
# Whether to use separate threads to compile the frontend and how many. Defaults to `min(32, os.cpu_count() + 4)`.
REFLEX_COMPILE_THREADS: EnvVar[Optional[int]] = env_var(None)

# The directory to store reflex dependencies.
REFLEX_DIR: Path = Path(constants.Reflex.DIR)
REFLEX_DIR: EnvVar[Path] = env_var(Path(constants.Reflex.DIR))

# Whether to print the SQL queries if the log level is INFO or lower.
SQLALCHEMY_ECHO: bool = False
SQLALCHEMY_ECHO: EnvVar[bool] = env_var(False)

# Whether to ignore the redis config error. Some redis servers only allow out-of-band configuration.
REFLEX_IGNORE_REDIS_CONFIG_ERROR: bool = False
REFLEX_IGNORE_REDIS_CONFIG_ERROR: EnvVar[bool] = env_var(False)

# Whether to skip purging the web directory in dev mode.
REFLEX_PERSIST_WEB_DIR: bool = False
REFLEX_PERSIST_WEB_DIR: EnvVar[bool] = env_var(False)

# The reflex.build frontend host.
REFLEX_BUILD_FRONTEND: str = constants.Templates.REFLEX_BUILD_FRONTEND
REFLEX_BUILD_FRONTEND: EnvVar[str] = env_var(
constants.Templates.REFLEX_BUILD_FRONTEND
)

# The reflex.build backend host.
REFLEX_BUILD_BACKEND: str = constants.Templates.REFLEX_BUILD_BACKEND
REFLEX_BUILD_BACKEND: EnvVar[str] = env_var(
constants.Templates.REFLEX_BUILD_BACKEND
)

def __init__(self):
"""Initialize the environment variables."""
type_hints = get_type_hints(type(self))
# This env var stores the execution mode of the app
REFLEX_ENV_MODE: EnvVar[constants.Env] = env_var(constants.Env.DEV)

for field in dataclasses.fields(self):
raw_value = os.getenv(field.name, None)
# Whether to run the backend only. Exclusive with REFLEX_FRONTEND_ONLY.
REFLEX_BACKEND_ONLY: EnvVar[bool] = env_var(False)

field.type = type_hints.get(field.name) or field.type
# Whether to run the frontend only. Exclusive with REFLEX_BACKEND_ONLY.
REFLEX_FRONTEND_ONLY: EnvVar[bool] = env_var(False)

value = (
interpret_env_var_value(raw_value, field.type, field.name)
if raw_value is not None
else get_default_value_for_field(field)
)
# Reflex internal env to reload the config.
RELOAD_CONFIG: EnvVar[bool] = env_var(False, internal=True)

setattr(self, field.name, value)
# If this env var is set to "yes", App.compile will be a no-op
REFLEX_SKIP_COMPILE: EnvVar[bool] = env_var(False, internal=True)


environment = EnvironmentVariables()
Expand Down
7 changes: 0 additions & 7 deletions reflex/constants/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,13 @@

from .base import (
COOKIES,
ENV_BACKEND_ONLY_ENV_VAR,
ENV_FRONTEND_ONLY_ENV_VAR,
ENV_MODE_ENV_VAR,
IS_WINDOWS,
LOCAL_STORAGE,
POLLING_MAX_HTTP_BUFFER_SIZE,
PYTEST_CURRENT_TEST,
REFLEX_VAR_CLOSING_TAG,
REFLEX_VAR_OPENING_TAG,
RELOAD_CONFIG,
SESSION_STORAGE,
SKIP_COMPILE_ENV_VAR,
ColorMode,
Dirs,
Env,
Expand Down Expand Up @@ -106,7 +101,6 @@
POLLING_MAX_HTTP_BUFFER_SIZE,
PYTEST_CURRENT_TEST,
Reflex,
RELOAD_CONFIG,
RequirementsTxt,
RouteArgType,
RouteRegex,
Expand All @@ -116,7 +110,6 @@
ROUTER_DATA_INCLUDE,
ROUTE_NOT_FOUND,
SETTER_PREFIX,
SKIP_COMPILE_ENV_VAR,
SocketEvent,
StateManagerMode,
Tailwind,
Expand Down
Loading
Loading