From d32604713c8944bb2111bb5badf26c0c3100012c Mon Sep 17 00:00:00 2001 From: Benedikt Bartscher Date: Fri, 1 Nov 2024 22:48:36 +0100 Subject: [PATCH] implement default_factory for EnvVar, improve env_var typing also migrate environment to be a class singleton to prevent unintended chaos with default factories --- reflex/app.py | 16 +++- reflex/compiler/compiler.py | 4 +- reflex/components/core/upload.py | 4 +- reflex/config.py | 92 ++++++++++++++----- reflex/constants/base.py | 15 +-- reflex/constants/compiler.py | 30 +----- reflex/constants/installer.py | 8 +- reflex/custom_components/custom_components.py | 6 +- reflex/model.py | 16 ++-- reflex/reflex.py | 16 ++-- reflex/state.py | 6 +- reflex/testing.py | 18 ++-- reflex/utils/exec.py | 16 ++-- reflex/utils/net.py | 4 +- reflex/utils/path_ops.py | 6 +- reflex/utils/prerequisites.py | 21 +++-- reflex/utils/registry.py | 4 +- reflex/utils/telemetry.py | 4 +- tests/integration/conftest.py | 13 ++- tests/integration/test_minified_states.py | 11 +-- tests/units/test_config.py | 21 ++++- tests/units/utils/test_utils.py | 6 +- 22 files changed, 193 insertions(+), 144 deletions(-) diff --git a/reflex/app.py b/reflex/app.py index ae3f904c0..dea50d981 100644 --- a/reflex/app.py +++ b/reflex/app.py @@ -67,7 +67,7 @@ from reflex.components.core.client_side_routing import ( ) from reflex.components.core.upload import Upload, get_upload_dir from reflex.components.radix import themes -from reflex.config import environment, get_config +from reflex.config import EnvironmentVariables, get_config from reflex.event import ( Event, EventHandler, @@ -506,7 +506,10 @@ class App(MiddlewareMixin, LifespanMixin): # Check if the route given is valid verify_route_validity(route) - if route in self.unevaluated_pages and environment.RELOAD_CONFIG.is_set(): + if ( + route in self.unevaluated_pages + and EnvironmentVariables.RELOAD_CONFIG.is_set() + ): # 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. @@ -723,7 +726,7 @@ class App(MiddlewareMixin, LifespanMixin): Whether the app should be compiled. """ # Check the environment variable. - if environment.REFLEX_SKIP_COMPILE.get(): + if EnvironmentVariables.REFLEX_SKIP_COMPILE.get(): return False nocompile = prerequisites.get_web_dir() / constants.NOCOMPILE_FILE @@ -946,7 +949,10 @@ class App(MiddlewareMixin, LifespanMixin): executor = None if ( platform.system() in ("Linux", "Darwin") - and (number_of_processes := environment.REFLEX_COMPILE_PROCESSES.get()) + and ( + number_of_processes + := EnvironmentVariables.REFLEX_COMPILE_PROCESSES.get() + ) is not None ): executor = concurrent.futures.ProcessPoolExecutor( @@ -955,7 +961,7 @@ class App(MiddlewareMixin, LifespanMixin): ) else: executor = concurrent.futures.ThreadPoolExecutor( - max_workers=environment.REFLEX_COMPILE_THREADS.get() + max_workers=EnvironmentVariables.REFLEX_COMPILE_THREADS.get() ) for route, component in zip(self.pages, page_components): diff --git a/reflex/compiler/compiler.py b/reflex/compiler/compiler.py index 4122a0938..d6bc533ca 100644 --- a/reflex/compiler/compiler.py +++ b/reflex/compiler/compiler.py @@ -16,7 +16,7 @@ from reflex.components.component import ( CustomComponent, StatefulComponent, ) -from reflex.config import environment, get_config +from reflex.config import EnvironmentVariables, get_config from reflex.state import BaseState from reflex.style import SYSTEM_COLOR_MODE from reflex.utils.exec import is_prod_mode @@ -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.get(): + if not is_prod_mode() and EnvironmentVariables.REFLEX_PERSIST_WEB_DIR.get(): # Skip purging the web directory in dev mode if REFLEX_PERSIST_WEB_DIR is set. return diff --git a/reflex/components/core/upload.py b/reflex/components/core/upload.py index fe8845e8f..377fe9afa 100644 --- a/reflex/components/core/upload.py +++ b/reflex/components/core/upload.py @@ -13,7 +13,7 @@ from reflex.components.component import ( ) from reflex.components.el.elements.forms import Input from reflex.components.radix.themes.layout.box import Box -from reflex.config import environment +from reflex.config import EnvironmentVariables from reflex.constants import Dirs from reflex.constants.compiler import Hooks, Imports from reflex.event import ( @@ -132,7 +132,7 @@ def get_upload_dir() -> Path: """ Upload.is_used = True - uploaded_files_dir = environment.REFLEX_UPLOADED_FILES_DIR.get() + uploaded_files_dir = EnvironmentVariables.REFLEX_UPLOADED_FILES_DIR.get() uploaded_files_dir.mkdir(parents=True, exist_ok=True) return uploaded_files_dir diff --git a/reflex/config.py b/reflex/config.py index 9fb02965e..81e7d3b36 100644 --- a/reflex/config.py +++ b/reflex/config.py @@ -13,6 +13,7 @@ from pathlib import Path from typing import ( TYPE_CHECKING, Any, + Callable, Dict, Generic, List, @@ -312,26 +313,47 @@ def interpret_env_var_value( T = TypeVar("T") +ENV_VAR_DEFAULT_FACTORY = Callable[[], T] + class EnvVar(Generic[T]): """Environment variable.""" name: str default: Any + default_factory: Optional[ENV_VAR_DEFAULT_FACTORY] type_: T - def __init__(self, name: str, default: Any, type_: T) -> None: + def __init__( + self, + name: str, + default: Any, + default_factory: Optional[ENV_VAR_DEFAULT_FACTORY], + type_: T, + ) -> None: """Initialize the environment variable. Args: name: The environment variable name. default: The default value. + default_factory: The default factory. type_: The type of the value. """ self.name = name self.default = default + self.default_factory = default_factory self.type_ = type_ + def get_default(self) -> T: + """Get the default value. + + Returns: + The default value. + """ + if self.default_factory is not None: + return self.default_factory() + return self.default + def interpret(self, value: str) -> T: """Interpret the environment variable value. @@ -371,7 +393,7 @@ class EnvVar(Generic[T]): env_value = self.getenv() if env_value is not None: return env_value - return self.default + return self.get_default() def set(self, value: T | None) -> None: """Set the environment variable. None unsets the variable. @@ -392,16 +414,24 @@ class env_var: # type: ignore name: str default: Any + default_factory: Optional[ENV_VAR_DEFAULT_FACTORY] internal: bool = False - def __init__(self, default: Any, internal: bool = False) -> None: + def __init__( + self, + default: Any = None, + default_factory: Optional[ENV_VAR_DEFAULT_FACTORY] = None, + internal: bool = False, + ) -> None: """Initialize the descriptor. Args: default: The default value. + default_factory: The default factory. internal: Whether the environment variable is reflex internal. """ self.default = default + self.default_factory = default_factory self.internal = internal def __set_name__(self, owner, name): @@ -427,22 +457,30 @@ class env_var: # type: ignore env_name = self.name if self.internal: env_name = f"__{env_name}" - return EnvVar(name=env_name, default=self.default, type_=type_) + return EnvVar( + name=env_name, + default=self.default, + type_=type_, + default_factory=self.default_factory, + ) + if TYPE_CHECKING: -if TYPE_CHECKING: + def __new__( + cls, + default: Optional[T] = None, + default_factory: Optional[ENV_VAR_DEFAULT_FACTORY[T]] = None, + internal: bool = False, + ) -> EnvVar[T]: + """Create a new EnvVar instance. - 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 + Args: + cls: The class. + default: The default value. + default_factory: The default factory. + internal: Whether the environment variable is reflex internal. + """ + ... class PathExistsFlag: @@ -455,6 +493,16 @@ ExistingPath = Annotated[Path, PathExistsFlag] class EnvironmentVariables: """Environment variables class to instantiate environment variables.""" + def __init__(self): + """Initialize the environment variables. + + Raises: + NotImplementedError: Always. + """ + raise NotImplementedError( + f"{type(self).__name__} is a class singleton and not meant to be instantiated." + ) + # Whether to use npm over bun to install frontend packages. REFLEX_USE_NPM: EnvVar[bool] = env_var(False) @@ -545,11 +593,13 @@ class EnvironmentVariables: # Where to save screenshots when tests fail. SCREENSHOT_DIR: EnvVar[Optional[Path]] = env_var(None) - # Whether to minify state names. - REFLEX_MINIFY_STATES: EnvVar[Optional[bool]] = env_var(False) - - -environment = EnvironmentVariables() + # Whether to minify state names. Default to true in prod mode and false otherwise. + REFLEX_MINIFY_STATES: EnvVar[Optional[bool]] = env_var( + default_factory=lambda: ( + EnvironmentVariables.REFLEX_ENV_MODE.get() == constants.Env.PROD + ) + or False + ) class Config(Base): diff --git a/reflex/constants/base.py b/reflex/constants/base.py index 6ec73cdf0..59505884c 100644 --- a/reflex/constants/base.py +++ b/reflex/constants/base.py @@ -109,10 +109,10 @@ class Templates(SimpleNamespace): Returns: The URL to redirect to reflex.build. """ - from reflex.config import environment + from reflex.config import EnvironmentVariables return ( - environment.REFLEX_BUILD_FRONTEND.get() + EnvironmentVariables.REFLEX_BUILD_FRONTEND.get() + "/gen?reflex_init_token={reflex_init_token}" ) @@ -124,9 +124,12 @@ class Templates(SimpleNamespace): Returns: The URL to poll waiting for the user to select a generation. """ - from reflex.config import environment + from reflex.config import EnvironmentVariables - return environment.REFLEX_BUILD_BACKEND.get() + "/api/init/{reflex_init_token}" + return ( + EnvironmentVariables.REFLEX_BUILD_BACKEND.get() + + "/api/init/{reflex_init_token}" + ) @classproperty @classmethod @@ -136,10 +139,10 @@ class Templates(SimpleNamespace): Returns: The URL to fetch the generation's reflex code. """ - from reflex.config import environment + from reflex.config import EnvironmentVariables return ( - environment.REFLEX_BUILD_BACKEND.get() + EnvironmentVariables.REFLEX_BUILD_BACKEND.get() + "/api/gen/{generation_hash}/refactored" ) diff --git a/reflex/constants/compiler.py b/reflex/constants/compiler.py index 3fdbf2c48..d2966cd21 100644 --- a/reflex/constants/compiler.py +++ b/reflex/constants/compiler.py @@ -5,7 +5,7 @@ from enum import Enum from types import SimpleNamespace from reflex.base import Base -from reflex.constants import Dirs, Env +from reflex.constants import Dirs from reflex.utils.imports import ImportVar # The prefix used to create setters for state vars. @@ -14,9 +14,6 @@ SETTER_PREFIX = "set_" # The file used to specify no compilation. NOCOMPILE_FILE = "nocompile" -# The env var to toggle minification of states. -ENV_MINIFY_STATES = "REFLEX_MINIFY_STATES" - class Ext(SimpleNamespace): """Extension used in Reflex.""" @@ -33,22 +30,6 @@ class Ext(SimpleNamespace): EXE = ".exe" -def minify_states() -> bool: - """Whether to minify states. - - Returns: - True if states should be minified. - """ - from reflex.config import environment - - env = environment.REFLEX_MINIFY_STATES.get() - if env is not None: - return env - - # minify states in prod by default - return environment.REFLEX_ENV_MODE.get() == Env.PROD - - class CompileVars(SimpleNamespace): """The variables used during compilation.""" @@ -81,15 +62,6 @@ class CompileVars(SimpleNamespace): # The name of the function for converting a dict to an event. TO_EVENT = "Event" - @classmethod - def MINIFY_STATES(cls) -> bool: - """Whether to minify states. - - Returns: - True if states should be minified. - """ - return minify_states() - class PageNames(SimpleNamespace): """The name of basic pages deployed in NextJS.""" diff --git a/reflex/constants/installer.py b/reflex/constants/installer.py index 26a53f2d8..378027395 100644 --- a/reflex/constants/installer.py +++ b/reflex/constants/installer.py @@ -61,9 +61,9 @@ class Bun(SimpleNamespace): Returns: The directory to store the bun. """ - from reflex.config import environment + from reflex.config import EnvironmentVariables - return environment.REFLEX_DIR.get() / "bun" + return EnvironmentVariables.REFLEX_DIR.get() / "bun" @classproperty @classmethod @@ -98,9 +98,9 @@ class Fnm(SimpleNamespace): Returns: The directory to store fnm. """ - from reflex.config import environment + from reflex.config import EnvironmentVariables - return environment.REFLEX_DIR.get() / "fnm" + return EnvironmentVariables.REFLEX_DIR.get() / "fnm" @classproperty @classmethod diff --git a/reflex/custom_components/custom_components.py b/reflex/custom_components/custom_components.py index f9ea41d6d..73e11542c 100644 --- a/reflex/custom_components/custom_components.py +++ b/reflex/custom_components/custom_components.py @@ -17,7 +17,7 @@ import typer from tomlkit.exceptions import TOMLKitError from reflex import constants -from reflex.config import environment, get_config +from reflex.config import EnvironmentVariables, get_config from reflex.constants import CustomComponents from reflex.utils import console @@ -609,14 +609,14 @@ def publish( help="The API token to use for authentication on python package repository. If token is provided, no username/password should be provided at the same time", ), username: Optional[str] = typer.Option( - environment.TWINE_USERNAME.get(), + EnvironmentVariables.TWINE_USERNAME.get(), "-u", "--username", show_default="TWINE_USERNAME environment variable value if set", help="The username to use for authentication on python package repository. Username and password must both be provided.", ), password: Optional[str] = typer.Option( - environment.TWINE_PASSWORD.get(), + EnvironmentVariables.TWINE_PASSWORD.get(), "-p", "--password", show_default="TWINE_PASSWORD environment variable value if set", diff --git a/reflex/model.py b/reflex/model.py index 4b070ec67..9252b3a5c 100644 --- a/reflex/model.py +++ b/reflex/model.py @@ -17,7 +17,7 @@ import sqlalchemy.exc import sqlalchemy.orm from reflex.base import Base -from reflex.config import environment, get_config +from reflex.config import EnvironmentVariables, get_config from reflex.utils import console from reflex.utils.compat import sqlmodel, sqlmodel_field_has_primary_key @@ -38,12 +38,12 @@ def get_engine(url: str | None = None) -> sqlalchemy.engine.Engine: url = url or conf.db_url if url is None: raise ValueError("No database url configured") - if not environment.ALEMBIC_CONFIG.get().exists(): + if not EnvironmentVariables.ALEMBIC_CONFIG.get().exists(): console.warn( "Database is not initialized, run [bold]reflex db init[/bold] first." ) # Print the SQL queries if the log level is INFO or lower. - echo_db_query = environment.SQLALCHEMY_ECHO.get() + echo_db_query = EnvironmentVariables.SQLALCHEMY_ECHO.get() # Needed for the admin dash on sqlite. connect_args = {"check_same_thread": False} if url.startswith("sqlite") else {} return sqlmodel.create_engine(url, echo=echo_db_query, connect_args=connect_args) @@ -231,7 +231,7 @@ class Model(Base, sqlmodel.SQLModel): # pyright: ignore [reportGeneralTypeIssue Returns: tuple of (config, script_directory) """ - config = alembic.config.Config(environment.ALEMBIC_CONFIG.get()) + config = alembic.config.Config(EnvironmentVariables.ALEMBIC_CONFIG.get()) return config, alembic.script.ScriptDirectory( config.get_main_option("script_location", default="version"), ) @@ -266,8 +266,8 @@ class Model(Base, sqlmodel.SQLModel): # pyright: ignore [reportGeneralTypeIssue def alembic_init(cls): """Initialize alembic for the project.""" alembic.command.init( - config=alembic.config.Config(environment.ALEMBIC_CONFIG.get()), - directory=str(environment.ALEMBIC_CONFIG.get().parent / "alembic"), + config=alembic.config.Config(EnvironmentVariables.ALEMBIC_CONFIG.get()), + directory=str(EnvironmentVariables.ALEMBIC_CONFIG.get().parent / "alembic"), ) @classmethod @@ -287,7 +287,7 @@ class Model(Base, sqlmodel.SQLModel): # pyright: ignore [reportGeneralTypeIssue Returns: True when changes have been detected. """ - if not environment.ALEMBIC_CONFIG.get().exists(): + if not EnvironmentVariables.ALEMBIC_CONFIG.get().exists(): return False config, script_directory = cls._alembic_config() @@ -388,7 +388,7 @@ class Model(Base, sqlmodel.SQLModel): # pyright: ignore [reportGeneralTypeIssue True - indicating the process was successful. None - indicating the process was skipped. """ - if not environment.ALEMBIC_CONFIG.get().exists(): + if not EnvironmentVariables.ALEMBIC_CONFIG.get().exists(): return with cls.get_db_engine().connect() as connection: diff --git a/reflex/reflex.py b/reflex/reflex.py index 6ccba01d3..45ec28c7e 100644 --- a/reflex/reflex.py +++ b/reflex/reflex.py @@ -13,7 +13,7 @@ from reflex_cli.deployments import deployments_cli from reflex_cli.utils import dependency from reflex import constants -from reflex.config import environment, get_config +from reflex.config import EnvironmentVariables, get_config from reflex.custom_components.custom_components import custom_components_cli from reflex.state import reset_disk_state_manager from reflex.utils import console, redir, telemetry @@ -160,7 +160,7 @@ def _run( console.set_log_level(loglevel) # Set env mode in the environment - environment.REFLEX_ENV_MODE.set(env) + EnvironmentVariables.REFLEX_ENV_MODE.set(env) # Show system info exec.output_system_info() @@ -277,13 +277,13 @@ def run( False, "--frontend-only", help="Execute only frontend.", - envvar=environment.REFLEX_FRONTEND_ONLY.name, + envvar=EnvironmentVariables.REFLEX_FRONTEND_ONLY.name, ), backend: bool = typer.Option( False, "--backend-only", help="Execute only backend.", - envvar=environment.REFLEX_BACKEND_ONLY.name, + envvar=EnvironmentVariables.REFLEX_BACKEND_ONLY.name, ), frontend_port: str = typer.Option( config.frontend_port, help="Specify a different frontend port." @@ -302,8 +302,8 @@ def run( if frontend and backend: console.error("Cannot use both --frontend-only and --backend-only options.") raise typer.Exit(1) - environment.REFLEX_BACKEND_ONLY.set(backend) - environment.REFLEX_FRONTEND_ONLY.set(frontend) + EnvironmentVariables.REFLEX_BACKEND_ONLY.set(backend) + EnvironmentVariables.REFLEX_FRONTEND_ONLY.set(frontend) _run(env, frontend, backend, frontend_port, backend_port, backend_host, loglevel) @@ -405,7 +405,7 @@ script_cli = typer.Typer() def _skip_compile(): """Skip the compile step.""" - environment.REFLEX_SKIP_COMPILE.set(True) + EnvironmentVariables.REFLEX_SKIP_COMPILE.set(True) @db_cli.command(name="init") @@ -420,7 +420,7 @@ def db_init(): return # Check the alembic config. - if environment.ALEMBIC_CONFIG.get().exists(): + if EnvironmentVariables.ALEMBIC_CONFIG.get().exists(): console.error( "Database is already initialized. Use " "[bold]reflex db makemigrations[/bold] to create schema change " diff --git a/reflex/state.py b/reflex/state.py index fce6a1464..d47aa52da 100644 --- a/reflex/state.py +++ b/reflex/state.py @@ -69,7 +69,7 @@ from redis.exceptions import ResponseError import reflex.istate.dynamic from reflex import constants from reflex.base import Base -from reflex.config import environment +from reflex.config import EnvironmentVariables from reflex.event import ( BACKGROUND_TASK_MARKER, Event, @@ -932,7 +932,7 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow): """ module = cls.__module__.replace(".", "___") state_name = format.to_snake_case(f"{module}___{cls.__name__}") - if constants.compiler.CompileVars.MINIFY_STATES(): + if EnvironmentVariables.REFLEX_MINIFY_STATES.get(): return get_minified_state_name(state_name) return state_name @@ -3435,7 +3435,7 @@ class StateManagerRedis(StateManager): ) except ResponseError: # Some redis servers only allow out-of-band configuration, so ignore errors here. - if not environment.REFLEX_IGNORE_REDIS_CONFIG_ERROR.get(): + if not EnvironmentVariables.REFLEX_IGNORE_REDIS_CONFIG_ERROR.get(): raise async with self.redis.pubsub() as pubsub: await pubsub.psubscribe(lock_key_channel) diff --git a/reflex/testing.py b/reflex/testing.py index 117280f2e..810a4ed57 100644 --- a/reflex/testing.py +++ b/reflex/testing.py @@ -44,7 +44,7 @@ import reflex.utils.format import reflex.utils.prerequisites import reflex.utils.processes from reflex.components.component import StatefulComponent -from reflex.config import environment +from reflex.config import EnvironmentVariables from reflex.state import ( BaseState, State, @@ -199,7 +199,7 @@ class AppHarness: state_name = reflex.utils.format.to_snake_case( f"{self.app_name}___{self.app_name}___" + state_cls_name ) - if reflex.constants.CompileVars.MINIFY_STATES(): + if EnvironmentVariables.REFLEX_MINIFY_STATES.get(): return minified_state_names.get(state_name, state_name) return state_name @@ -626,10 +626,10 @@ class AppHarness: if self.frontend_url is None: raise RuntimeError("Frontend is not running.") want_headless = False - if environment.APP_HARNESS_HEADLESS.get(): + if EnvironmentVariables.APP_HARNESS_HEADLESS.get(): want_headless = True if driver_clz is None: - requested_driver = environment.APP_HARNESS_DRIVER.get() + requested_driver = EnvironmentVariables.APP_HARNESS_DRIVER.get() driver_clz = getattr(webdriver, requested_driver) if driver_options is None: driver_options = getattr(webdriver, f"{requested_driver}Options")() @@ -651,7 +651,7 @@ class AppHarness: driver_options.add_argument("headless") if driver_options is None: raise RuntimeError(f"Could not determine options for {driver_clz}") - if args := environment.APP_HARNESS_DRIVER_ARGS.get(): + if args := EnvironmentVariables.APP_HARNESS_DRIVER_ARGS.get(): for arg in args.split(","): driver_options.add_argument(arg) if driver_option_args is not None: @@ -958,7 +958,7 @@ class AppHarnessProd(AppHarness): def _start_backend(self): if self.app_instance is None: raise RuntimeError("App was not initialized.") - environment.REFLEX_SKIP_COMPILE.set(True) + EnvironmentVariables.REFLEX_SKIP_COMPILE.set(True) self.backend = uvicorn.Server( uvicorn.Config( app=self.app_instance, @@ -976,7 +976,7 @@ class AppHarnessProd(AppHarness): try: return super()._poll_for_servers(timeout) finally: - environment.REFLEX_SKIP_COMPILE.set(None) + EnvironmentVariables.REFLEX_SKIP_COMPILE.set(None) @override def start(self) -> AppHarnessProd: @@ -985,7 +985,7 @@ class AppHarnessProd(AppHarness): Returns: self """ - environment.REFLEX_ENV_MODE.set(reflex.constants.base.Env.PROD) + EnvironmentVariables.REFLEX_ENV_MODE.set(reflex.constants.base.Env.PROD) _ = super().start() return self @@ -997,4 +997,4 @@ class AppHarnessProd(AppHarness): self.frontend_server.shutdown() if self.frontend_thread is not None: self.frontend_thread.join() - environment.REFLEX_ENV_MODE.set(None) + EnvironmentVariables.REFLEX_ENV_MODE.set(None) diff --git a/reflex/utils/exec.py b/reflex/utils/exec.py index fb613810a..17cfc23b9 100644 --- a/reflex/utils/exec.py +++ b/reflex/utils/exec.py @@ -15,7 +15,7 @@ from urllib.parse import urljoin import psutil from reflex import constants -from reflex.config import environment, get_config +from reflex.config import EnvironmentVariables, get_config from reflex.constants.base import LogLevel from reflex.utils import console, path_ops from reflex.utils.prerequisites import get_web_dir @@ -184,7 +184,7 @@ def should_use_granian(): Returns: True if Granian should be used. """ - return environment.REFLEX_USE_GRANIAN.get() + return EnvironmentVariables.REFLEX_USE_GRANIAN.get() def get_app_module(): @@ -370,7 +370,7 @@ def run_uvicorn_backend_prod(host, port, loglevel): run=True, show_logs=True, env={ - environment.REFLEX_SKIP_COMPILE.name: "true" + EnvironmentVariables.REFLEX_SKIP_COMPILE.name: "true" }, # skip compile for prod backend ) @@ -407,7 +407,7 @@ def run_granian_backend_prod(host, port, loglevel): run=True, show_logs=True, env={ - environment.REFLEX_SKIP_COMPILE.name: "true" + EnvironmentVariables.REFLEX_SKIP_COMPILE.name: "true" }, # skip compile for prod backend ) except ImportError: @@ -493,7 +493,7 @@ def is_prod_mode() -> bool: Returns: True if the app is running in production mode or False if running in dev mode. """ - current_mode = environment.REFLEX_ENV_MODE.get() + current_mode = EnvironmentVariables.REFLEX_ENV_MODE.get() return current_mode == constants.Env.PROD @@ -509,7 +509,7 @@ def is_frontend_only() -> bool: deprecation_version="0.6.5", removal_version="0.7.0", ) - return environment.REFLEX_FRONTEND_ONLY.get() + return EnvironmentVariables.REFLEX_FRONTEND_ONLY.get() def is_backend_only() -> bool: @@ -524,7 +524,7 @@ def is_backend_only() -> bool: deprecation_version="0.6.5", removal_version="0.7.0", ) - return environment.REFLEX_BACKEND_ONLY.get() + return EnvironmentVariables.REFLEX_BACKEND_ONLY.get() def should_skip_compile() -> bool: @@ -539,4 +539,4 @@ def should_skip_compile() -> bool: deprecation_version="0.6.5", removal_version="0.7.0", ) - return environment.REFLEX_SKIP_COMPILE.get() + return EnvironmentVariables.REFLEX_SKIP_COMPILE.get() diff --git a/reflex/utils/net.py b/reflex/utils/net.py index acc202912..f664bf9b6 100644 --- a/reflex/utils/net.py +++ b/reflex/utils/net.py @@ -2,7 +2,7 @@ import httpx -from ..config import environment +from ..config import EnvironmentVariables from . import console @@ -12,7 +12,7 @@ def _httpx_verify_kwarg() -> bool: Returns: True if SSL verification is enabled, False otherwise """ - return not environment.SSL_NO_VERIFY.get() + return not EnvironmentVariables.SSL_NO_VERIFY.get() def get(url: str, **kwargs) -> httpx.Response: diff --git a/reflex/utils/path_ops.py b/reflex/utils/path_ops.py index a2ba2b151..de3dbe32c 100644 --- a/reflex/utils/path_ops.py +++ b/reflex/utils/path_ops.py @@ -9,7 +9,7 @@ import shutil from pathlib import Path from reflex import constants -from reflex.config import environment +from reflex.config import EnvironmentVariables # Shorthand for join. join = os.linesep.join @@ -136,7 +136,7 @@ def use_system_node() -> bool: Returns: Whether the system node should be used. """ - return environment.REFLEX_USE_SYSTEM_NODE.get() + return EnvironmentVariables.REFLEX_USE_SYSTEM_NODE.get() def use_system_bun() -> bool: @@ -145,7 +145,7 @@ def use_system_bun() -> bool: Returns: Whether the system bun should be used. """ - return environment.REFLEX_USE_SYSTEM_BUN.get() + return EnvironmentVariables.REFLEX_USE_SYSTEM_BUN.get() def get_node_bin_path() -> Path | None: diff --git a/reflex/utils/prerequisites.py b/reflex/utils/prerequisites.py index 6ceea4b05..a00aa6e75 100644 --- a/reflex/utils/prerequisites.py +++ b/reflex/utils/prerequisites.py @@ -33,7 +33,7 @@ from redis.asyncio import Redis from reflex import constants, model from reflex.compiler import templates -from reflex.config import Config, environment, get_config +from reflex.config import Config, EnvironmentVariables, get_config from reflex.utils import console, net, path_ops, processes from reflex.utils.exceptions import GeneratedCodeHasNoFunctionDefs from reflex.utils.format import format_library_name @@ -69,7 +69,7 @@ def get_web_dir() -> Path: Returns: The working directory. """ - return environment.REFLEX_WEB_WORKDIR.get() + return EnvironmentVariables.REFLEX_WEB_WORKDIR.get() def _python_version_check(): @@ -260,7 +260,7 @@ def windows_npm_escape_hatch() -> bool: Returns: If the user has set REFLEX_USE_NPM. """ - return environment.REFLEX_USE_NPM.get() + return EnvironmentVariables.REFLEX_USE_NPM.get() def get_app(reload: bool = False) -> ModuleType: @@ -278,7 +278,7 @@ def get_app(reload: bool = False) -> ModuleType: from reflex.utils import telemetry try: - environment.RELOAD_CONFIG.set(reload) + EnvironmentVariables.RELOAD_CONFIG.set(reload) config = get_config() if not config.app_name: raise RuntimeError( @@ -1019,7 +1019,7 @@ def needs_reinit(frontend: bool = True) -> bool: return False # Make sure the .reflex directory exists. - if not environment.REFLEX_DIR.get().exists(): + if not EnvironmentVariables.REFLEX_DIR.get().exists(): return True # Make sure the .web directory exists in frontend mode. @@ -1124,7 +1124,7 @@ def ensure_reflex_installation_id() -> Optional[int]: """ try: initialize_reflex_user_directory() - installation_id_file = environment.REFLEX_DIR.get() / "installation_id" + installation_id_file = EnvironmentVariables.REFLEX_DIR.get() / "installation_id" installation_id = None if installation_id_file.exists(): @@ -1149,7 +1149,7 @@ def ensure_reflex_installation_id() -> Optional[int]: def initialize_reflex_user_directory(): """Initialize the reflex user directory.""" # Create the reflex directory. - path_ops.mkdir(environment.REFLEX_DIR.get()) + path_ops.mkdir(EnvironmentVariables.REFLEX_DIR.get()) def initialize_frontend_dependencies(): @@ -1174,7 +1174,7 @@ def check_db_initialized() -> bool: """ if ( get_config().db_url is not None - and not environment.ALEMBIC_CONFIG.get().exists() + and not EnvironmentVariables.ALEMBIC_CONFIG.get().exists() ): console.error( "Database is not initialized. Run [bold]reflex db init[/bold] first." @@ -1185,7 +1185,10 @@ def check_db_initialized() -> bool: def check_schema_up_to_date(): """Check if the sqlmodel metadata matches the current database schema.""" - if get_config().db_url is None or not environment.ALEMBIC_CONFIG.get().exists(): + if ( + get_config().db_url is None + or not EnvironmentVariables.ALEMBIC_CONFIG.get().exists() + ): return with model.Model.get_db_engine().connect() as connection: try: diff --git a/reflex/utils/registry.py b/reflex/utils/registry.py index d98178c61..988186aa1 100644 --- a/reflex/utils/registry.py +++ b/reflex/utils/registry.py @@ -2,7 +2,7 @@ import httpx -from reflex.config import environment +from reflex.config import EnvironmentVariables from reflex.utils import console, net @@ -55,4 +55,4 @@ def _get_npm_registry() -> str: Returns: str: """ - return environment.NPM_CONFIG_REGISTRY.get() or get_best_registry() + return EnvironmentVariables.NPM_CONFIG_REGISTRY.get() or get_best_registry() diff --git a/reflex/utils/telemetry.py b/reflex/utils/telemetry.py index 806b916fc..1d265e9ab 100644 --- a/reflex/utils/telemetry.py +++ b/reflex/utils/telemetry.py @@ -8,7 +8,7 @@ import multiprocessing import platform import warnings -from reflex.config import environment +from reflex.config import EnvironmentVariables try: from datetime import UTC, datetime @@ -95,7 +95,7 @@ def _raise_on_missing_project_hash() -> bool: False when compilation should be skipped (i.e. no .web directory is required). Otherwise return True. """ - return not environment.REFLEX_SKIP_COMPILE.get() + return not EnvironmentVariables.REFLEX_SKIP_COMPILE.get() def _prepare_event(event: str, **kwargs) -> dict: diff --git a/tests/integration/conftest.py b/tests/integration/conftest.py index 5c456ec75..712c30a4e 100644 --- a/tests/integration/conftest.py +++ b/tests/integration/conftest.py @@ -8,7 +8,7 @@ from typing import Generator, Type import pytest import reflex.constants -from reflex.config import environment +from reflex.config import EnvironmentVariables from reflex.testing import AppHarness, AppHarnessProd DISPLAY = None @@ -24,7 +24,10 @@ def xvfb(): Yields: the pyvirtualdisplay object that the browser will be open on """ - if os.environ.get("GITHUB_ACTIONS") and not environment.APP_HARNESS_HEADLESS.get(): + if ( + os.environ.get("GITHUB_ACTIONS") + and not EnvironmentVariables.APP_HARNESS_HEADLESS.get() + ): from pyvirtualdisplay.smartdisplay import ( # pyright: ignore [reportMissingImports] SmartDisplay, ) @@ -45,7 +48,7 @@ def pytest_exception_interact(node, call, report): call: The pytest call describing when/where the test was invoked. report: The pytest log report object. """ - screenshot_dir = environment.SCREENSHOT_DIR.get() + screenshot_dir = EnvironmentVariables.SCREENSHOT_DIR.get() if DISPLAY is None or screenshot_dir is None: return @@ -89,7 +92,7 @@ def app_harness_env( """ harness: Type[AppHarness] = request.param if issubclass(harness, AppHarnessProd): - environment.REFLEX_ENV_MODE.set(reflex.constants.Env.PROD) + EnvironmentVariables.REFLEX_ENV_MODE.set(reflex.constants.Env.PROD) yield harness if issubclass(harness, AppHarnessProd): - environment.REFLEX_ENV_MODE.set(None) + EnvironmentVariables.REFLEX_ENV_MODE.set(None) diff --git a/tests/integration/test_minified_states.py b/tests/integration/test_minified_states.py index dc6c275f1..37c5c8a0f 100644 --- a/tests/integration/test_minified_states.py +++ b/tests/integration/test_minified_states.py @@ -2,7 +2,6 @@ from __future__ import annotations -import os from functools import partial from typing import Generator, Optional, Type @@ -10,7 +9,7 @@ import pytest from selenium.webdriver.common.by import By from selenium.webdriver.remote.webdriver import WebDriver -from reflex.constants.compiler import ENV_MINIFY_STATES +from reflex.config import EnvironmentVariables from reflex.testing import AppHarness, AppHarnessProd @@ -63,13 +62,9 @@ def minify_state_env( minify_states: whether to minify state names """ minify_states: Optional[bool] = request.param - if minify_states is None: - _ = os.environ.pop(ENV_MINIFY_STATES, None) - else: - os.environ[ENV_MINIFY_STATES] = str(minify_states).lower() + EnvironmentVariables.REFLEX_MINIFY_STATES.set(minify_states) yield minify_states - if minify_states is not None: - os.environ.pop(ENV_MINIFY_STATES, None) + EnvironmentVariables.REFLEX_MINIFY_STATES.set(None) @pytest.fixture diff --git a/tests/units/test_config.py b/tests/units/test_config.py index e5d4622bd..b0603aec6 100644 --- a/tests/units/test_config.py +++ b/tests/units/test_config.py @@ -8,9 +8,9 @@ import pytest import reflex as rx import reflex.config from reflex.config import ( + EnvironmentVariables, EnvVar, env_var, - environment, interpret_boolean_env, interpret_enum_env, interpret_int_env, @@ -216,7 +216,7 @@ def test_replace_defaults( def reflex_dir_constant() -> Path: - return environment.REFLEX_DIR.get() + return EnvironmentVariables.REFLEX_DIR.get() def test_reflex_dir_env_var(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None: @@ -253,6 +253,11 @@ def test_env_var(): INTERNAL: EnvVar[str] = env_var("default", internal=True) BOOLEAN: EnvVar[bool] = env_var(False) + # default_factory with other env_var as fallback + BLUBB_OR_BLA: EnvVar[str] = env_var( + default_factory=lambda: TestEnv.BLUBB.getenv() or "bla" + ) + assert TestEnv.BLUBB.get() == "default" assert TestEnv.BLUBB.name == "BLUBB" TestEnv.BLUBB.set("new") @@ -280,3 +285,15 @@ def test_env_var(): assert TestEnv.BOOLEAN.get() is False TestEnv.BOOLEAN.set(None) assert "BOOLEAN" not in os.environ + + assert TestEnv.BLUBB_OR_BLA.get() == "bla" + TestEnv.BLUBB.set("new") + assert TestEnv.BLUBB_OR_BLA.get() == "new" + TestEnv.BLUBB.set(None) + assert TestEnv.BLUBB_OR_BLA.get() == "bla" + TestEnv.BLUBB_OR_BLA.set("test") + assert TestEnv.BLUBB_OR_BLA.get() == "test" + TestEnv.BLUBB.set("other") + assert TestEnv.BLUBB_OR_BLA.get() == "test" + TestEnv.BLUBB_OR_BLA.set(None) + TestEnv.BLUBB.set(None) diff --git a/tests/units/utils/test_utils.py b/tests/units/utils/test_utils.py index abe3dd5fc..f7a64b4cf 100644 --- a/tests/units/utils/test_utils.py +++ b/tests/units/utils/test_utils.py @@ -10,7 +10,7 @@ from packaging import version from reflex import constants from reflex.base import Base -from reflex.config import environment +from reflex.config import EnvironmentVariables from reflex.event import EventHandler from reflex.state import BaseState from reflex.utils import ( @@ -598,7 +598,7 @@ def test_style_prop_with_event_handler_value(callable): def test_is_prod_mode() -> None: """Test that the prod mode is correctly determined.""" - environment.REFLEX_ENV_MODE.set(constants.Env.PROD) + EnvironmentVariables.REFLEX_ENV_MODE.set(constants.Env.PROD) assert utils_exec.is_prod_mode() - environment.REFLEX_ENV_MODE.set(None) + EnvironmentVariables.REFLEX_ENV_MODE.set(None) assert not utils_exec.is_prod_mode()