From 0aeb20f903b37b25f7def57f7221a1020cfea8dd Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Wed, 16 Oct 2024 16:34:02 -0700 Subject: [PATCH 01/14] move all environment variables to the same place --- reflex/config.py | 76 +++++++++++++++++++++++++++++++++++ reflex/utils/prerequisites.py | 4 +- reflex/utils/registry.py | 8 +--- reflex/utils/types.py | 14 +++++++ 4 files changed, 94 insertions(+), 8 deletions(-) diff --git a/reflex/config.py b/reflex/config.py index 719d5c21fc..8ec1b56d2e 100644 --- a/reflex/config.py +++ b/reflex/config.py @@ -2,6 +2,7 @@ from __future__ import annotations +import dataclasses import importlib import os import sys @@ -10,6 +11,7 @@ from typing import Any, Dict, List, Optional, Set, Union from reflex.utils.exceptions import ConfigError +from reflex.utils.types import value_inside_optional try: import pydantic.v1 as pydantic @@ -131,6 +133,80 @@ 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" + ) + + +@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 + + def __init__(self): + """Initialize the environment variables.""" + for field in dataclasses.fields(self): + field_name = field.name + + field_type = value_inside_optional(field.type) + + if field_type is bool: + true_values = ["true", "1", "yes"] + false_values = ["false", "0", "no"] + + value = os.getenv(field_name, None) + + if value is not None: + if value.lower() in true_values: + value = True + elif value.lower() in false_values: + value = False + else: + raise ValueError( + f"Invalid value for environment variable {field_name}: {value}" + ) + else: + value = get_default_value_for_field(field) + + elif field_type is str: + value = os.getenv(field_name, None) + + if value is None: + value = get_default_value_for_field(field) + + else: + raise ValueError( + f"Invalid type for environment variable {field_name}: {field_type}. This is probably an issue in Reflex." + ) + + setattr(self, field_name, value) + + +environment = EnvironmentVariables() + + class Config(Base): """The config defines runtime settings for the app. diff --git a/reflex/utils/prerequisites.py b/reflex/utils/prerequisites.py index 44d1f6acc2..989dce3fa1 100644 --- a/reflex/utils/prerequisites.py +++ b/reflex/utils/prerequisites.py @@ -33,7 +33,7 @@ from reflex import constants, model from reflex.compiler import templates -from reflex.config import Config, get_config +from reflex.config import Config, environment, 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 @@ -250,7 +250,7 @@ def windows_npm_escape_hatch() -> bool: Returns: If the user has set REFLEX_USE_NPM. """ - return os.environ.get("REFLEX_USE_NPM", "").lower() in ["true", "1", "yes"] + return environment.REFLEX_USE_NPM def get_app(reload: bool = False) -> ModuleType: diff --git a/reflex/utils/registry.py b/reflex/utils/registry.py index 6b87c163d8..77c3d31cdf 100644 --- a/reflex/utils/registry.py +++ b/reflex/utils/registry.py @@ -1,9 +1,8 @@ """Utilities for working with registries.""" -import os - import httpx +from reflex.config import environment from reflex.utils import console, net @@ -56,7 +55,4 @@ def _get_npm_registry() -> str: Returns: str: """ - if npm_registry := os.environ.get("NPM_CONFIG_REGISTRY", ""): - return npm_registry - else: - return get_best_registry() + return environment.NPM_CONFIG_REGISTRY or get_best_registry() diff --git a/reflex/utils/types.py b/reflex/utils/types.py index 0a3aed110e..3d79920112 100644 --- a/reflex/utils/types.py +++ b/reflex/utils/types.py @@ -274,6 +274,20 @@ def is_optional(cls: GenericType) -> bool: return is_union(cls) and type(None) in get_args(cls) +def value_inside_optional(cls: GenericType) -> GenericType: + """Get the value inside an Optional type or the original type. + + Args: + cls: The class to check. + + Returns: + The value inside the Optional type or the original type. + """ + if is_union(cls) and len(args := get_args(cls)) >= 2 and type(None) in args: + return unionize(*[arg for arg in args if arg is not type(None)]) + return cls + + def get_property_hint(attr: Any | None) -> GenericType | None: """Check if an attribute is a property and return its type hint. From 5dffc77f336d0029ccfe27ddbb3e8fac1e2be110 Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Wed, 16 Oct 2024 16:39:27 -0700 Subject: [PATCH 02/14] reorder things around --- reflex/config.py | 85 ++++++++++++++++++++++++++++++------------------ 1 file changed, 53 insertions(+), 32 deletions(-) diff --git a/reflex/config.py b/reflex/config.py index 8ec1b56d2e..ed9ce78202 100644 --- a/reflex/config.py +++ b/reflex/config.py @@ -155,6 +155,56 @@ def get_default_value_for_field(field: dataclasses.Field) -> Any: ) +def interpret_boolean_env(value: str) -> bool: + """Interpret a boolean environment variable value. + + Args: + value: The environment variable value. + + Returns: + The interpreted value. + + Raises: + ValueError: If the value is invalid. + """ + true_values = ["true", "1", "yes"] + false_values = ["false", "0", "no"] + + if value.lower() in true_values: + return True + elif value.lower() in false_values: + return False + else: + raise ValueError(f"Invalid boolean value: {value}") + + +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 + + 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.""" @@ -168,40 +218,11 @@ class EnvironmentVariables: def __init__(self): """Initialize the environment variables.""" for field in dataclasses.fields(self): - field_name = field.name + raw_value = os.getenv(field.name, None) - field_type = value_inside_optional(field.type) - - if field_type is bool: - true_values = ["true", "1", "yes"] - false_values = ["false", "0", "no"] - - value = os.getenv(field_name, None) - - if value is not None: - if value.lower() in true_values: - value = True - elif value.lower() in false_values: - value = False - else: - raise ValueError( - f"Invalid value for environment variable {field_name}: {value}" - ) - else: - value = get_default_value_for_field(field) - - elif field_type is str: - value = os.getenv(field_name, None) - - if value is None: - value = get_default_value_for_field(field) - - else: - raise ValueError( - f"Invalid type for environment variable {field_name}: {field_type}. This is probably an issue in Reflex." - ) + value = interpret_env_var_value(raw_value, field) if raw_value else None - setattr(self, field_name, value) + setattr(self, field.name, value) environment = EnvironmentVariables() From 11da6ecb258445d3adf85c4c448a134bc4a4ca60 Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Wed, 16 Oct 2024 16:57:00 -0700 Subject: [PATCH 03/14] move more variables to environment --- reflex/config.py | 36 +++++++++++++++++++ reflex/constants/installer.py | 6 ++-- reflex/custom_components/custom_components.py | 6 ++-- reflex/utils/exec.py | 4 +-- reflex/utils/path_ops.py | 21 ++--------- reflex/utils/prerequisites.py | 3 +- 6 files changed, 48 insertions(+), 28 deletions(-) diff --git a/reflex/config.py b/reflex/config.py index ed9ce78202..34466962a3 100644 --- a/reflex/config.py +++ b/reflex/config.py @@ -178,6 +178,21 @@ def interpret_boolean_env(value: str) -> bool: raise ValueError(f"Invalid boolean value: {value}") +def interpret_path_env(value: str) -> Path: + """Interpret a path environment variable value. + + Args: + value: The environment variable value. + + Returns: + The interpreted value. + + Raises: + ValueError: If the value is invalid. + """ + return Path(value) + + def interpret_env_var_value(value: str, field: dataclasses.Field) -> Any: """Interpret an environment variable value based on the field type. @@ -199,6 +214,9 @@ def interpret_env_var_value(value: str, field: dataclasses.Field) -> Any: elif field_type is str: return 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." @@ -215,6 +233,24 @@ class EnvironmentVariables: # 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) + def __init__(self): """Initialize the environment variables.""" for field in dataclasses.fields(self): diff --git a/reflex/constants/installer.py b/reflex/constants/installer.py index b12d56c78b..a6acf277e0 100644 --- a/reflex/constants/installer.py +++ b/reflex/constants/installer.py @@ -5,6 +5,8 @@ import platform from types import SimpleNamespace +from reflex.config import environment + from .base import IS_WINDOWS, Reflex @@ -54,7 +56,7 @@ class Bun(SimpleNamespace): CONFIG_PATH = "bunfig.toml" # The environment variable to use the system installed bun. - USE_SYSTEM_VAR = "REFLEX_USE_SYSTEM_BUN" + USE_SYSTEM = environment.REFLEX_USE_SYSTEM_BUN # FNM config. @@ -100,7 +102,7 @@ class Node(SimpleNamespace): NPM_PATH = BIN_PATH / "npm" # The environment variable to use the system installed node. - USE_SYSTEM_VAR = "REFLEX_USE_SYSTEM_NODE" + USE_SYSTEM = environment.REFLEX_USE_SYSTEM_NODE class PackageJson(SimpleNamespace): diff --git a/reflex/custom_components/custom_components.py b/reflex/custom_components/custom_components.py index ee24a7cd07..ddda3de56f 100644 --- a/reflex/custom_components/custom_components.py +++ b/reflex/custom_components/custom_components.py @@ -17,7 +17,7 @@ from tomlkit.exceptions import TOMLKitError from reflex import constants -from reflex.config import get_config +from reflex.config import environment, 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( - os.getenv("TWINE_USERNAME"), + environment.TWINE_USERNAME, "-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( - os.getenv("TWINE_PASSWORD"), + environment.TWINE_PASSWORD, "-p", "--password", show_default="TWINE_PASSWORD environment variable value if set", diff --git a/reflex/utils/exec.py b/reflex/utils/exec.py index acb69ee194..293a778c47 100644 --- a/reflex/utils/exec.py +++ b/reflex/utils/exec.py @@ -15,7 +15,7 @@ import psutil from reflex import constants -from reflex.config import get_config +from reflex.config import environment, 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 os.getenv("REFLEX_USE_GRANIAN", "0") == "1" + return environment.REFLEX_USE_GRANIAN def get_app_module(): diff --git a/reflex/utils/path_ops.py b/reflex/utils/path_ops.py index f795e1aa4c..902f37c97c 100644 --- a/reflex/utils/path_ops.py +++ b/reflex/utils/path_ops.py @@ -129,30 +129,13 @@ def which(program: str | Path) -> str | Path | None: return shutil.which(str(program)) -def use_system_install(var_name: str) -> bool: - """Check if the system install should be used. - - Args: - var_name: The name of the environment variable. - - Raises: - ValueError: If the variable name is invalid. - - Returns: - Whether the associated env var should use the system install. - """ - if not var_name.startswith("REFLEX_USE_SYSTEM_"): - raise ValueError("Invalid system install variable name.") - return os.getenv(var_name, "").lower() in ["true", "1", "yes"] - - def use_system_node() -> bool: """Check if the system node should be used. Returns: Whether the system node should be used. """ - return use_system_install(constants.Node.USE_SYSTEM_VAR) + return constants.Node.USE_SYSTEM def use_system_bun() -> bool: @@ -161,7 +144,7 @@ def use_system_bun() -> bool: Returns: Whether the system bun should be used. """ - return use_system_install(constants.Bun.USE_SYSTEM_VAR) + return constants.Bun.USE_SYSTEM def get_node_bin_path() -> Path | None: diff --git a/reflex/utils/prerequisites.py b/reflex/utils/prerequisites.py index 989dce3fa1..8bb78cad77 100644 --- a/reflex/utils/prerequisites.py +++ b/reflex/utils/prerequisites.py @@ -69,8 +69,7 @@ def get_web_dir() -> Path: Returns: The working directory. """ - workdir = Path(os.getenv("REFLEX_WEB_WORKDIR", constants.Dirs.WEB)) - return workdir + return environment.REFLEX_WEB_WORKDIR def _python_version_check(): From 3893ccf8dcba700165230db9c09e7721b2d00184 Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Wed, 16 Oct 2024 17:00:02 -0700 Subject: [PATCH 04/14] remove cyclical imports --- reflex/constants/installer.py | 8 -------- reflex/utils/path_ops.py | 5 +++-- 2 files changed, 3 insertions(+), 10 deletions(-) diff --git a/reflex/constants/installer.py b/reflex/constants/installer.py index a6acf277e0..a3325f0749 100644 --- a/reflex/constants/installer.py +++ b/reflex/constants/installer.py @@ -5,8 +5,6 @@ import platform from types import SimpleNamespace -from reflex.config import environment - from .base import IS_WINDOWS, Reflex @@ -55,9 +53,6 @@ class Bun(SimpleNamespace): # Path of the bunfig file CONFIG_PATH = "bunfig.toml" - # The environment variable to use the system installed bun. - USE_SYSTEM = environment.REFLEX_USE_SYSTEM_BUN - # FNM config. class Fnm(SimpleNamespace): @@ -101,9 +96,6 @@ class Node(SimpleNamespace): # The default path where npm is installed. NPM_PATH = BIN_PATH / "npm" - # The environment variable to use the system installed node. - USE_SYSTEM = environment.REFLEX_USE_SYSTEM_NODE - class PackageJson(SimpleNamespace): """Constants used to build the package.json file.""" diff --git a/reflex/utils/path_ops.py b/reflex/utils/path_ops.py index 902f37c97c..ee93b24cf0 100644 --- a/reflex/utils/path_ops.py +++ b/reflex/utils/path_ops.py @@ -9,6 +9,7 @@ from pathlib import Path from reflex import constants +from reflex.config import environment # Shorthand for join. join = os.linesep.join @@ -135,7 +136,7 @@ def use_system_node() -> bool: Returns: Whether the system node should be used. """ - return constants.Node.USE_SYSTEM + return environment.REFLEX_USE_SYSTEM_NODE def use_system_bun() -> bool: @@ -144,7 +145,7 @@ def use_system_bun() -> bool: Returns: Whether the system bun should be used. """ - return constants.Bun.USE_SYSTEM + return environment.REFLEX_USE_SYSTEM_BUN def get_node_bin_path() -> Path | None: From d680edf7c03f82cb3531de2f77faeeb1494fce6e Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Wed, 16 Oct 2024 17:03:37 -0700 Subject: [PATCH 05/14] forgot default value for field --- reflex/config.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/reflex/config.py b/reflex/config.py index 34466962a3..4e202419af 100644 --- a/reflex/config.py +++ b/reflex/config.py @@ -256,7 +256,11 @@ def __init__(self): for field in dataclasses.fields(self): raw_value = os.getenv(field.name, None) - value = interpret_env_var_value(raw_value, field) if raw_value else None + 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) From 3736844b3062e9a23616d1f9f1f08841902cf273 Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Wed, 16 Oct 2024 17:11:52 -0700 Subject: [PATCH 06/14] for some reason type hints aren't being interpreted --- reflex/config.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/reflex/config.py b/reflex/config.py index 4e202419af..3fc03ea019 100644 --- a/reflex/config.py +++ b/reflex/config.py @@ -10,6 +10,8 @@ from pathlib import Path from typing import Any, Dict, List, Optional, Set, Union +from typing_extensions import get_type_hints + from reflex.utils.exceptions import ConfigError from reflex.utils.types import value_inside_optional @@ -253,6 +255,8 @@ class EnvironmentVariables: 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) @@ -262,6 +266,8 @@ def __init__(self): else get_default_value_for_field(field) ) + field.type = type_hints[field.name] or field.type + setattr(self, field.name, value) From e38a85ae75e8ba124d180fab507f5b0411fbb1d5 Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Wed, 16 Oct 2024 17:17:27 -0700 Subject: [PATCH 07/14] put the field type *before* not after --- reflex/config.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/reflex/config.py b/reflex/config.py index 3fc03ea019..436611f483 100644 --- a/reflex/config.py +++ b/reflex/config.py @@ -188,9 +188,6 @@ def interpret_path_env(value: str) -> Path: Returns: The interpreted value. - - Raises: - ValueError: If the value is invalid. """ return Path(value) @@ -260,14 +257,14 @@ def __init__(self): for field in dataclasses.fields(self): raw_value = os.getenv(field.name, None) + field.type = type_hints[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) ) - field.type = type_hints[field.name] or field.type - setattr(self, field.name, value) From 03089123867b599b06774381be6d33eebe486af2 Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Wed, 16 Oct 2024 17:18:07 -0700 Subject: [PATCH 08/14] make it get --- reflex/config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/reflex/config.py b/reflex/config.py index 436611f483..219024f7c6 100644 --- a/reflex/config.py +++ b/reflex/config.py @@ -257,7 +257,7 @@ def __init__(self): for field in dataclasses.fields(self): raw_value = os.getenv(field.name, None) - field.type = type_hints[field.name] or field.type + field.type = type_hints.get(field.name) or field.type value = ( interpret_env_var_value(raw_value, field) From a9b7c284e7cc91e68c1d2aa8e542a2d8f1843df3 Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Wed, 16 Oct 2024 17:37:10 -0700 Subject: [PATCH 09/14] move a bit more --- reflex/components/core/upload.py | 6 ++---- reflex/config.py | 13 +++++++++++-- reflex/constants/base.py | 2 ++ reflex/constants/config.py | 3 +-- reflex/model.py | 16 +++++++--------- reflex/reflex.py | 4 ++-- reflex/utils/build.py | 12 ------------ reflex/utils/net.py | 6 ++---- reflex/utils/prerequisites.py | 4 ++-- 9 files changed, 29 insertions(+), 37 deletions(-) diff --git a/reflex/components/core/upload.py b/reflex/components/core/upload.py index be97b170d3..94c1e07064 100644 --- a/reflex/components/core/upload.py +++ b/reflex/components/core/upload.py @@ -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, @@ -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 diff --git a/reflex/config.py b/reflex/config.py index 219024f7c6..0845528cf4 100644 --- a/reflex/config.py +++ b/reflex/config.py @@ -169,8 +169,8 @@ def interpret_boolean_env(value: str) -> bool: Raises: ValueError: If the value is invalid. """ - true_values = ["true", "1", "yes"] - false_values = ["false", "0", "no"] + true_values = ["true", "1", "yes", "y"] + false_values = ["false", "0", "no", "n"] if value.lower() in true_values: return True @@ -250,6 +250,15 @@ class EnvironmentVariables: # 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) + def __init__(self): """Initialize the environment variables.""" type_hints = get_type_hints(type(self)) diff --git a/reflex/constants/base.py b/reflex/constants/base.py index b86f083cc1..6354278959 100644 --- a/reflex/constants/base.py +++ b/reflex/constants/base.py @@ -20,6 +20,8 @@ class Dirs(SimpleNamespace): # The frontend directories in a project. # The web folder where the NextJS app is compiled to. WEB = ".web" + # The directory where uploaded files are stored. + UPLOADED_FILES = "uploaded_files" # The name of the assets directory. APP_ASSETS = "assets" # The name of the assets directory for external ressource (a subfolder of APP_ASSETS). diff --git a/reflex/constants/config.py b/reflex/constants/config.py index 3ff7aade50..970e67844a 100644 --- a/reflex/constants/config.py +++ b/reflex/constants/config.py @@ -1,6 +1,5 @@ """Config constants.""" -import os from pathlib import Path from types import SimpleNamespace @@ -9,7 +8,7 @@ from .compiler import Ext # Alembic migrations -ALEMBIC_CONFIG = os.environ.get("ALEMBIC_CONFIG", "alembic.ini") +ALEMBIC_CONFIG = "alembic.ini" class Config(SimpleNamespace): diff --git a/reflex/model.py b/reflex/model.py index 0e8d62e90d..d1fc91587d 100644 --- a/reflex/model.py +++ b/reflex/model.py @@ -4,7 +4,6 @@ import os from collections import defaultdict -from pathlib import Path from typing import Any, ClassVar, Optional, Type, Union import alembic.autogenerate @@ -18,9 +17,8 @@ import sqlalchemy.exc import sqlalchemy.orm -from reflex import constants from reflex.base import Base -from reflex.config import get_config +from reflex.config import environment, get_config from reflex.utils import console from reflex.utils.compat import sqlmodel, sqlmodel_field_has_primary_key @@ -41,7 +39,7 @@ 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 Path(constants.ALEMBIC_CONFIG).exists(): + if environment.ALEMBIC_CONFIG.exists(): console.warn( "Database is not initialized, run [bold]reflex db init[/bold] first." ) @@ -234,7 +232,7 @@ def _alembic_config(): Returns: tuple of (config, script_directory) """ - config = alembic.config.Config(constants.ALEMBIC_CONFIG) + config = alembic.config.Config(environment.ALEMBIC_CONFIG) return config, alembic.script.ScriptDirectory( config.get_main_option("script_location", default="version"), ) @@ -269,8 +267,8 @@ def _alembic_render_item( def alembic_init(cls): """Initialize alembic for the project.""" alembic.command.init( - config=alembic.config.Config(constants.ALEMBIC_CONFIG), - directory=str(Path(constants.ALEMBIC_CONFIG).parent / "alembic"), + config=alembic.config.Config(environment.ALEMBIC_CONFIG), + directory=str(environment.ALEMBIC_CONFIG.parent / "alembic"), ) @classmethod @@ -290,7 +288,7 @@ def alembic_autogenerate( Returns: True when changes have been detected. """ - if not Path(constants.ALEMBIC_CONFIG).exists(): + if not environment.ALEMBIC_CONFIG.exists(): return False config, script_directory = cls._alembic_config() @@ -391,7 +389,7 @@ def migrate(cls, autogenerate: bool = False) -> bool | None: True - indicating the process was successful. None - indicating the process was skipped. """ - if not Path(constants.ALEMBIC_CONFIG).exists(): + if not environment.ALEMBIC_CONFIG.exists(): return with cls.get_db_engine().connect() as connection: diff --git a/reflex/reflex.py b/reflex/reflex.py index bd6904d061..2589fd7a3e 100644 --- a/reflex/reflex.py +++ b/reflex/reflex.py @@ -13,7 +13,7 @@ from reflex_cli.utils import dependency from reflex import constants -from reflex.config import get_config +from reflex.config import environment, 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 @@ -406,7 +406,7 @@ def db_init(): return # Check the alembic config. - if Path(constants.ALEMBIC_CONFIG).exists(): + if environment.ALEMBIC_CONFIG.exists(): console.error( "Database is already initialized. Use " "[bold]reflex db makemigrations[/bold] to create schema change " diff --git a/reflex/utils/build.py b/reflex/utils/build.py index 7708090150..14709d99ce 100644 --- a/reflex/utils/build.py +++ b/reflex/utils/build.py @@ -23,18 +23,6 @@ def set_env_json(): ) -def set_os_env(**kwargs): - """Set os environment variables. - - Args: - kwargs: env key word args. - """ - for key, value in kwargs.items(): - if not value: - continue - os.environ[key.upper()] = value - - def generate_sitemap_config(deploy_url: str, export=False): """Generate the sitemap config file. diff --git a/reflex/utils/net.py b/reflex/utils/net.py index 83e25559cc..2c6f227641 100644 --- a/reflex/utils/net.py +++ b/reflex/utils/net.py @@ -1,9 +1,8 @@ """Helpers for downloading files from the network.""" -import os - import httpx +from ..config import environment from . import console @@ -13,8 +12,7 @@ def _httpx_verify_kwarg() -> bool: Returns: True if SSL verification is enabled, False otherwise """ - ssl_no_verify = os.environ.get("SSL_NO_VERIFY", "").lower() in ["true", "1", "yes"] - return not ssl_no_verify + return not environment.SSL_NO_VERIFY def get(url: str, **kwargs) -> httpx.Response: diff --git a/reflex/utils/prerequisites.py b/reflex/utils/prerequisites.py index 8bb78cad77..abc35ad228 100644 --- a/reflex/utils/prerequisites.py +++ b/reflex/utils/prerequisites.py @@ -1144,7 +1144,7 @@ def check_db_initialized() -> bool: Returns: True if alembic is initialized (or if database is not used). """ - if get_config().db_url is not None and not Path(constants.ALEMBIC_CONFIG).exists(): + if get_config().db_url is not None and not environment.ALEMBIC_CONFIG.exists(): console.error( "Database is not initialized. Run [bold]reflex db init[/bold] first." ) @@ -1154,7 +1154,7 @@ 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 Path(constants.ALEMBIC_CONFIG).exists(): + if get_config().db_url is None or not environment.ALEMBIC_CONFIG.exists(): return with model.Model.get_db_engine().connect() as connection: try: From b7fdb1dfa82478ee4d94e143dd79848c5461969f Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Wed, 16 Oct 2024 17:47:01 -0700 Subject: [PATCH 10/14] add more fields --- reflex/app.py | 9 +++++---- reflex/config.py | 28 ++++++++++++++++++++++++++-- 2 files changed, 31 insertions(+), 6 deletions(-) diff --git a/reflex/app.py b/reflex/app.py index 584b8a321b..abf0b5d411 100644 --- a/reflex/app.py +++ b/reflex/app.py @@ -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 ( @@ -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: diff --git a/reflex/config.py b/reflex/config.py index 0845528cf4..f4b4e32457 100644 --- a/reflex/config.py +++ b/reflex/config.py @@ -180,6 +180,24 @@ def interpret_boolean_env(value: str) -> bool: raise ValueError(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: + ValueError: If the value is invalid. + """ + try: + return int(value) + except ValueError as ve: + raise ValueError(f"Invalid integer value: {value}") from ve + + def interpret_path_env(value: str) -> Path: """Interpret a path environment variable value. @@ -209,10 +227,10 @@ def interpret_env_var_value(value: str, field: dataclasses.Field) -> Any: 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) @@ -259,6 +277,12 @@ class EnvironmentVariables: # 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 + def __init__(self): """Initialize the environment variables.""" type_hints = get_type_hints(type(self)) From 43a89a489ee65990291427359c9dd4afaf783b6b Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Wed, 16 Oct 2024 18:21:37 -0700 Subject: [PATCH 11/14] move reflex dir --- reflex/config.py | 3 + reflex/constants/base.py | 20 ++----- reflex/constants/installer.py | 108 ++++++++++++++++++++++++++-------- reflex/utils/prerequisites.py | 6 +- tests/units/test_config.py | 3 +- 5 files changed, 99 insertions(+), 41 deletions(-) diff --git a/reflex/config.py b/reflex/config.py index f4b4e32457..a16418fef0 100644 --- a/reflex/config.py +++ b/reflex/config.py @@ -283,6 +283,9 @@ class EnvironmentVariables: # 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) + def __init__(self): """Initialize the environment variables.""" type_hints = get_type_hints(type(self)) diff --git a/reflex/constants/base.py b/reflex/constants/base.py index 6354278959..63edbfbe92 100644 --- a/reflex/constants/base.py +++ b/reflex/constants/base.py @@ -66,21 +66,13 @@ class Reflex(SimpleNamespace): # Files and directories used to init a new project. # The directory to store reflex dependencies. - # Get directory value from enviroment variables if it exists. - _dir = os.environ.get("REFLEX_DIR", "") - - DIR = Path( - _dir - or ( - # on windows, we use C:/Users//AppData/Local/reflex. - # on macOS, we use ~/Library/Application Support/reflex. - # on linux, we use ~/.local/share/reflex. - # If user sets REFLEX_DIR envroment variable use that instead. - PlatformDirs(MODULE_NAME, False).user_data_dir - ) - ) - # The root directory of the reflex library. + # on windows, we use C:/Users//AppData/Local/reflex. + # on macOS, we use ~/Library/Application Support/reflex. + # on linux, we use ~/.local/share/reflex. + # If user sets REFLEX_DIR envroment variable use that instead. + DIR = PlatformDirs(MODULE_NAME, False).user_data_path + # The root directory of the reflex library. ROOT_DIR = Path(__file__).parents[2] RELEASES_URL = f"https://api.github.com/repos/reflex-dev/templates/releases" diff --git a/reflex/constants/installer.py b/reflex/constants/installer.py index a3325f0749..f26d07fc41 100644 --- a/reflex/constants/installer.py +++ b/reflex/constants/installer.py @@ -3,9 +3,11 @@ from __future__ import annotations import platform +from pathlib import Path from types import SimpleNamespace +from typing import Any, Callable, Generic, Type, TypeVar -from .base import IS_WINDOWS, Reflex +from .base import IS_WINDOWS def get_fnm_name() -> str | None: @@ -30,18 +32,43 @@ def get_fnm_name() -> str | None: return None +T = TypeVar("T") +V = TypeVar("V") + + +class classproperty(Generic[T, V]): + """A class property decorator.""" + + def __init__(self, getter: Callable[[Type[T]], V]) -> None: + """Initialize the class property. + + Args: + getter: The getter function. + """ + self.getter = getattr(getter, "__func__", getter) + + def __get__(self, instance: Any, owner: Type[T]) -> V: + """Get the value of the class property. + + Args: + instance: The instance of the class. + owner: The class itself. + + Returns: + The value of the class property. + """ + return self.getter(owner) + + # Bun config. class Bun(SimpleNamespace): """Bun constants.""" # The Bun version. VERSION = "1.1.29" + # Min Bun Version MIN_VERSION = "0.7.0" - # The directory to store the bun. - ROOT_PATH = Reflex.DIR / "bun" - # Default bun path. - DEFAULT_PATH = ROOT_PATH / "bin" / ("bun" if not IS_WINDOWS else "bun.exe") # URL to bun install script. INSTALL_URL = "https://raw.githubusercontent.com/reflex-dev/reflex/main/scripts/bun_install.sh" @@ -50,9 +77,24 @@ class Bun(SimpleNamespace): WINDOWS_INSTALL_URL = ( "https://raw.githubusercontent.com/reflex-dev/reflex/main/scripts/install.ps1" ) + # Path of the bunfig file CONFIG_PATH = "bunfig.toml" + @classproperty + @classmethod + def ROOT_PATH(cls): + """The directory to store the bun.""" + from reflex.config import environment + + return environment.REFLEX_DIR / "bun" + + @classproperty + @classmethod + def DEFAULT_PATH(cls): + """Default bun path.""" + return cls.ROOT_PATH / "bin" / ("bun" if not IS_WINDOWS else "bun.exe") + # FNM config. class Fnm(SimpleNamespace): @@ -60,17 +102,28 @@ class Fnm(SimpleNamespace): # The FNM version. VERSION = "1.35.1" - # The directory to store fnm. - DIR = Reflex.DIR / "fnm" + FILENAME = get_fnm_name() - # The fnm executable binary. - EXE = DIR / ("fnm.exe" if IS_WINDOWS else "fnm") # The URL to the fnm release binary INSTALL_URL = ( f"https://github.com/Schniz/fnm/releases/download/v{VERSION}/{FILENAME}.zip" ) + @classproperty + @classmethod + def DIR(cls) -> Path: + """The directory to store fnm.""" + from reflex.config import environment + + return environment.REFLEX_DIR / "fnm" + + @classproperty + @classmethod + def EXE(cls): + """The fnm executable binary.""" + return cls.DIR / ("fnm.exe" if IS_WINDOWS else "fnm") + # Node / NPM config class Node(SimpleNamespace): @@ -81,20 +134,29 @@ class Node(SimpleNamespace): # The minimum required node version. MIN_VERSION = "18.17.0" - # The node bin path. - BIN_PATH = ( - Fnm.DIR - / "node-versions" - / f"v{VERSION}" - / "installation" - / ("bin" if not IS_WINDOWS else "") - ) - - # The default path where node is installed. - PATH = BIN_PATH / ("node.exe" if IS_WINDOWS else "node") - - # The default path where npm is installed. - NPM_PATH = BIN_PATH / "npm" + @classproperty + @classmethod + def BIN_PATH(cls): + """The node bin path.""" + return ( + Fnm.DIR + / "node-versions" + / f"v{cls.VERSION}" + / "installation" + / ("bin" if not IS_WINDOWS else "") + ) + + @classproperty + @classmethod + def PATH(cls): + """The default path where node is installed.""" + return cls.BIN_PATH / ("node.exe" if IS_WINDOWS else "node") + + @classproperty + @classmethod + def NPM_PATH(cls): + """The default path where npm is installed.""" + return cls.BIN_PATH / "npm" class PackageJson(SimpleNamespace): diff --git a/reflex/utils/prerequisites.py b/reflex/utils/prerequisites.py index abc35ad228..33165af0e9 100644 --- a/reflex/utils/prerequisites.py +++ b/reflex/utils/prerequisites.py @@ -991,7 +991,7 @@ def needs_reinit(frontend: bool = True) -> bool: return False # Make sure the .reflex directory exists. - if not constants.Reflex.DIR.exists(): + if not environment.REFLEX_DIR.exists(): return True # Make sure the .web directory exists in frontend mode. @@ -1096,7 +1096,7 @@ def ensure_reflex_installation_id() -> Optional[int]: """ try: initialize_reflex_user_directory() - installation_id_file = constants.Reflex.DIR / "installation_id" + installation_id_file = environment.REFLEX_DIR / "installation_id" installation_id = None if installation_id_file.exists(): @@ -1121,7 +1121,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(constants.Reflex.DIR) + path_ops.mkdir(environment.REFLEX_DIR) def initialize_frontend_dependencies(): diff --git a/tests/units/test_config.py b/tests/units/test_config.py index a6c6fe697e..c4a143bc53 100644 --- a/tests/units/test_config.py +++ b/tests/units/test_config.py @@ -5,6 +5,7 @@ import reflex as rx import reflex.config +from reflex.config import environment from reflex.constants import Endpoint @@ -178,7 +179,7 @@ def test_replace_defaults( def reflex_dir_constant(): - return rx.constants.Reflex.DIR + return environment.REFLEX_DIR def test_reflex_dir_env_var(monkeypatch, tmp_path): From 4d2a85b4411f333d90cc13bb332fbd3269aea016 Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Wed, 16 Oct 2024 18:25:14 -0700 Subject: [PATCH 12/14] add return --- reflex/constants/installer.py | 42 +++++++++++++++++++++++++++++------ 1 file changed, 35 insertions(+), 7 deletions(-) diff --git a/reflex/constants/installer.py b/reflex/constants/installer.py index f26d07fc41..a30808b9fa 100644 --- a/reflex/constants/installer.py +++ b/reflex/constants/installer.py @@ -84,7 +84,11 @@ class Bun(SimpleNamespace): @classproperty @classmethod def ROOT_PATH(cls): - """The directory to store the bun.""" + """The directory to store the bun. + + Returns: + The directory to store the bun. + """ from reflex.config import environment return environment.REFLEX_DIR / "bun" @@ -92,7 +96,11 @@ def ROOT_PATH(cls): @classproperty @classmethod def DEFAULT_PATH(cls): - """Default bun path.""" + """Default bun path. + + Returns: + The default bun path. + """ return cls.ROOT_PATH / "bin" / ("bun" if not IS_WINDOWS else "bun.exe") @@ -113,7 +121,11 @@ class Fnm(SimpleNamespace): @classproperty @classmethod def DIR(cls) -> Path: - """The directory to store fnm.""" + """The directory to store fnm. + + Returns: + The directory to store fnm. + """ from reflex.config import environment return environment.REFLEX_DIR / "fnm" @@ -121,7 +133,11 @@ def DIR(cls) -> Path: @classproperty @classmethod def EXE(cls): - """The fnm executable binary.""" + """The fnm executable binary. + + Returns: + The fnm executable binary. + """ return cls.DIR / ("fnm.exe" if IS_WINDOWS else "fnm") @@ -137,7 +153,11 @@ class Node(SimpleNamespace): @classproperty @classmethod def BIN_PATH(cls): - """The node bin path.""" + """The node bin path. + + Returns: + The node bin path. + """ return ( Fnm.DIR / "node-versions" @@ -149,13 +169,21 @@ def BIN_PATH(cls): @classproperty @classmethod def PATH(cls): - """The default path where node is installed.""" + """The default path where node is installed. + + Returns: + The default path where node is installed. + """ return cls.BIN_PATH / ("node.exe" if IS_WINDOWS else "node") @classproperty @classmethod def NPM_PATH(cls): - """The default path where npm is installed.""" + """The default path where npm is installed. + + Returns: + The default path where npm is installed. + """ return cls.BIN_PATH / "npm" From 71033413a1f84141d1396019f5384208b9011c42 Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Wed, 16 Oct 2024 18:53:01 -0700 Subject: [PATCH 13/14] put things somewhere else --- reflex/compiler/compiler.py | 5 ++- reflex/config.py | 15 ++++++++ reflex/constants/base.py | 65 ++++++++++++++++++++++++----------- reflex/constants/installer.py | 30 +--------------- reflex/constants/utils.py | 32 +++++++++++++++++ reflex/model.py | 3 +- reflex/state.py | 8 ++--- 7 files changed, 98 insertions(+), 60 deletions(-) create mode 100644 reflex/constants/utils.py diff --git a/reflex/compiler/compiler.py b/reflex/compiler/compiler.py index 0c29f941de..909299635c 100644 --- a/reflex/compiler/compiler.py +++ b/reflex/compiler/compiler.py @@ -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 @@ -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 @@ -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 diff --git a/reflex/config.py b/reflex/config.py index a16418fef0..c3ee3f9a1f 100644 --- a/reflex/config.py +++ b/reflex/config.py @@ -286,6 +286,21 @@ class EnvironmentVariables: # 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)) diff --git a/reflex/constants/base.py b/reflex/constants/base.py index 63edbfbe92..789d31cd4b 100644 --- a/reflex/constants/base.py +++ b/reflex/constants/base.py @@ -2,7 +2,6 @@ from __future__ import annotations -import os import platform from enum import Enum from importlib import metadata @@ -11,6 +10,8 @@ from platformdirs import PlatformDirs +from .utils import classproperty + IS_WINDOWS = platform.system() == "Windows" @@ -95,27 +96,51 @@ class Templates(SimpleNamespace): DEFAULT = "blank" # The reflex.build frontend host - REFLEX_BUILD_FRONTEND = os.environ.get( - "REFLEX_BUILD_FRONTEND", "https://flexgen.reflex.run" - ) + REFLEX_BUILD_FRONTEND = "https://flexgen.reflex.run" # The reflex.build backend host - REFLEX_BUILD_BACKEND = os.environ.get( - "REFLEX_BUILD_BACKEND", "https://flexgen-prod-flexgen.fly.dev" - ) - - # The URL to redirect to reflex.build - REFLEX_BUILD_URL = ( - REFLEX_BUILD_FRONTEND + "/gen?reflex_init_token={reflex_init_token}" - ) - - # The URL to poll waiting for the user to select a generation. - REFLEX_BUILD_POLL_URL = REFLEX_BUILD_BACKEND + "/api/init/{reflex_init_token}" - - # The URL to fetch the generation's reflex code - REFLEX_BUILD_CODE_URL = ( - REFLEX_BUILD_BACKEND + "/api/gen/{generation_hash}/refactored" - ) + REFLEX_BUILD_BACKEND = "https://flexgen-prod-flexgen.fly.dev" + + @classproperty + @classmethod + def REFLEX_BUILD_URL(cls): + """The URL to redirect to reflex.build. + + Returns: + The URL to redirect to reflex.build. + """ + from reflex.config import environment + + return ( + environment.REFLEX_BUILD_FRONTEND + + "/gen?reflex_init_token={reflex_init_token}" + ) + + @classproperty + @classmethod + def REFLEX_BUILD_POLL_URL(cls): + """The URL to poll waiting for the user to select a generation. + + Returns: + The URL to poll waiting for the user to select a generation. + """ + from reflex.config import environment + + return environment.REFLEX_BUILD_BACKEND + "/api/init/{reflex_init_token}" + + @classproperty + @classmethod + def REFLEX_BUILD_CODE_URL(cls): + """The URL to fetch the generation's reflex code. + + Returns: + The URL to fetch the generation's reflex code. + """ + from reflex.config import environment + + return ( + environment.REFLEX_BUILD_BACKEND + "/api/gen/{generation_hash}/refactored" + ) class Dirs(SimpleNamespace): """Folders used by the template system of Reflex.""" diff --git a/reflex/constants/installer.py b/reflex/constants/installer.py index a30808b9fa..a6756cbd4f 100644 --- a/reflex/constants/installer.py +++ b/reflex/constants/installer.py @@ -5,9 +5,9 @@ import platform from pathlib import Path from types import SimpleNamespace -from typing import Any, Callable, Generic, Type, TypeVar from .base import IS_WINDOWS +from .utils import classproperty def get_fnm_name() -> str | None: @@ -32,34 +32,6 @@ def get_fnm_name() -> str | None: return None -T = TypeVar("T") -V = TypeVar("V") - - -class classproperty(Generic[T, V]): - """A class property decorator.""" - - def __init__(self, getter: Callable[[Type[T]], V]) -> None: - """Initialize the class property. - - Args: - getter: The getter function. - """ - self.getter = getattr(getter, "__func__", getter) - - def __get__(self, instance: Any, owner: Type[T]) -> V: - """Get the value of the class property. - - Args: - instance: The instance of the class. - owner: The class itself. - - Returns: - The value of the class property. - """ - return self.getter(owner) - - # Bun config. class Bun(SimpleNamespace): """Bun constants.""" diff --git a/reflex/constants/utils.py b/reflex/constants/utils.py new file mode 100644 index 0000000000..48797afbfe --- /dev/null +++ b/reflex/constants/utils.py @@ -0,0 +1,32 @@ +"""Utility functions for constants.""" + +from typing import Any, Callable, Generic, Type + +from typing_extensions import TypeVar + +T = TypeVar("T") +V = TypeVar("V") + + +class classproperty(Generic[T, V]): + """A class property decorator.""" + + def __init__(self, getter: Callable[[Type[T]], V]) -> None: + """Initialize the class property. + + Args: + getter: The getter function. + """ + self.getter = getattr(getter, "__func__", getter) + + def __get__(self, instance: Any, owner: Type[T]) -> V: + """Get the value of the class property. + + Args: + instance: The instance of the class. + owner: The class itself. + + Returns: + The value of the class property. + """ + return self.getter(owner) diff --git a/reflex/model.py b/reflex/model.py index d1fc91587d..b269044c59 100644 --- a/reflex/model.py +++ b/reflex/model.py @@ -2,7 +2,6 @@ from __future__ import annotations -import os from collections import defaultdict from typing import Any, ClassVar, Optional, Type, Union @@ -44,7 +43,7 @@ def get_engine(url: str | None = None) -> sqlalchemy.engine.Engine: "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 = os.environ.get("SQLALCHEMY_ECHO") == "True" + echo_db_query = environment.SQLALCHEMY_ECHO # 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) diff --git a/reflex/state.py b/reflex/state.py index 0d6eed0edc..0f8df189e0 100644 --- a/reflex/state.py +++ b/reflex/state.py @@ -8,7 +8,6 @@ import dataclasses import functools import inspect -import os import pickle import sys import uuid @@ -64,6 +63,7 @@ import reflex.istate.dynamic from reflex import constants from reflex.base import Base +from reflex.config import environment from reflex.event import ( BACKGROUND_TASK_MARKER, Event, @@ -3272,11 +3272,7 @@ async def _wait_lock(self, lock_key: bytes, lock_id: bytes) -> None: ) except ResponseError: # Some redis servers only allow out-of-band configuration, so ignore errors here. - ignore_config_error = os.environ.get( - "REFLEX_IGNORE_REDIS_CONFIG_ERROR", - None, - ) - if not ignore_config_error: + if not environment.REFLEX_IGNORE_REDIS_CONFIG_ERROR: raise async with self.redis.pubsub() as pubsub: await pubsub.psubscribe(lock_key_channel) From fe0a791efbf250bbb36195ef7403f680d4e0bd8a Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Thu, 17 Oct 2024 11:59:56 -0700 Subject: [PATCH 14/14] add custom error --- reflex/config.py | 19 ++++++++++++------- reflex/utils/exceptions.py | 4 ++++ 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/reflex/config.py b/reflex/config.py index c3ee3f9a1f..2e16e2eb03 100644 --- a/reflex/config.py +++ b/reflex/config.py @@ -12,7 +12,7 @@ from typing_extensions import get_type_hints -from reflex.utils.exceptions import ConfigError +from reflex.utils.exceptions import ConfigError, EnvironmentVarValueError from reflex.utils.types import value_inside_optional try: @@ -167,7 +167,7 @@ def interpret_boolean_env(value: str) -> bool: The interpreted value. Raises: - ValueError: If the value is invalid. + EnvironmentVarValueError: If the value is invalid. """ true_values = ["true", "1", "yes", "y"] false_values = ["false", "0", "no", "n"] @@ -176,8 +176,7 @@ def interpret_boolean_env(value: str) -> bool: return True elif value.lower() in false_values: return False - else: - raise ValueError(f"Invalid boolean value: {value}") + raise EnvironmentVarValueError(f"Invalid boolean value: {value}") def interpret_int_env(value: str) -> int: @@ -190,12 +189,12 @@ def interpret_int_env(value: str) -> int: The interpreted value. Raises: - ValueError: If the value is invalid. + EnvironmentVarValueError: If the value is invalid. """ try: return int(value) except ValueError as ve: - raise ValueError(f"Invalid integer value: {value}") from ve + raise EnvironmentVarValueError(f"Invalid integer value: {value}") from ve def interpret_path_env(value: str) -> Path: @@ -206,8 +205,14 @@ def interpret_path_env(value: str) -> Path: Returns: The interpreted value. + + Raises: + EnvironmentVarValueError: If the path does not exist. """ - return Path(value) + 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: diff --git a/reflex/utils/exceptions.py b/reflex/utils/exceptions.py index 35f59a0e14..cd3d108b4b 100644 --- a/reflex/utils/exceptions.py +++ b/reflex/utils/exceptions.py @@ -135,3 +135,7 @@ class SetUndefinedStateVarError(ReflexError, AttributeError): class StateSchemaMismatchError(ReflexError, TypeError): """Raised when the serialized schema of a state class does not match the current schema.""" + + +class EnvironmentVarValueError(ReflexError, ValueError): + """Raised when an environment variable is set to an invalid value."""