diff --git a/reflex/config.py b/reflex/config.py index 719d5c21f..8ec1b56d2 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 pathlib import Path 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 @@ class DBConfig(Base): return f"{self.engine}://{path}/{self.database}" +def get_default_value_for_field(field: dataclasses.Field) -> Any: + """Get the default value for a field. + + Args: + field: The field. + + Returns: + The default value. + + Raises: + ValueError: If no default value is found. + """ + if field.default != dataclasses.MISSING: + return field.default + elif field.default_factory != dataclasses.MISSING: + return field.default_factory() + else: + raise ValueError( + f"Missing value for environment variable {field.name} and no default value found" + ) + + +@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 44d1f6acc..989dce3fa 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, 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 6b87c163d..77c3d31cd 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 0a3aed110..3d7992011 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.