Merge branch 'main' into masenf/unrestrict-show-built-with-reflex

This commit is contained in:
Masen Furer 2025-02-19 09:32:47 -08:00 committed by GitHub
commit 93a01373c0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 89 additions and 33 deletions

View File

@ -111,7 +111,7 @@ from reflex.utils import (
prerequisites, prerequisites,
types, types,
) )
from reflex.utils.exec import is_prod_mode, is_testing_env from reflex.utils.exec import get_compile_context, is_prod_mode, is_testing_env
from reflex.utils.imports import ImportVar from reflex.utils.imports import ImportVar
if TYPE_CHECKING: if TYPE_CHECKING:
@ -201,14 +201,17 @@ def default_overlay_component() -> Component:
Returns: Returns:
The default overlay_component, which is a connection_modal. The default overlay_component, which is a connection_modal.
""" """
config = get_config()
from reflex.components.component import memo from reflex.components.component import memo
def default_overlay_components(): def default_overlay_components():
return Fragment.create( return Fragment.create(
connection_pulser(), connection_pulser(),
connection_toaster(), connection_toaster(),
*([backend_disabled()] if config.is_reflex_cloud else []), *(
[backend_disabled()]
if get_compile_context() == constants.CompileContext.DEPLOY
else []
),
*codespaces.codespaces_auto_redirect(), *codespaces.codespaces_auto_redirect(),
) )
@ -1136,6 +1139,16 @@ class App(MiddlewareMixin, LifespanMixin):
self._validate_var_dependencies() self._validate_var_dependencies()
self._setup_overlay_component() self._setup_overlay_component()
if config.show_built_with_reflex is None:
if (
get_compile_context() == constants.CompileContext.DEPLOY
and prerequisites.get_user_tier() in ["pro", "team", "enterprise"]
):
config.show_built_with_reflex = False
else:
config.show_built_with_reflex = True
if is_prod_mode() and config.show_built_with_reflex: if is_prod_mode() and config.show_built_with_reflex:
self._setup_sticky_badge() self._setup_sticky_badge()

View File

@ -589,6 +589,11 @@ class ExecutorType(enum.Enum):
class EnvironmentVariables: class EnvironmentVariables:
"""Environment variables class to instantiate environment variables.""" """Environment variables class to instantiate environment variables."""
# Indicate the current command that was invoked in the reflex CLI.
REFLEX_COMPILE_CONTEXT: EnvVar[constants.CompileContext] = env_var(
constants.CompileContext.UNDEFINED, internal=True
)
# Whether to use npm over bun to install frontend packages. # Whether to use npm over bun to install frontend packages.
REFLEX_USE_NPM: EnvVar[bool] = env_var(False) REFLEX_USE_NPM: EnvVar[bool] = env_var(False)
@ -636,7 +641,7 @@ class EnvironmentVariables:
REFLEX_COMPILE_THREADS: EnvVar[Optional[int]] = env_var(None) REFLEX_COMPILE_THREADS: EnvVar[Optional[int]] = env_var(None)
# The directory to store reflex dependencies. # The directory to store reflex dependencies.
REFLEX_DIR: EnvVar[Path] = env_var(Path(constants.Reflex.DIR)) REFLEX_DIR: EnvVar[Path] = env_var(constants.Reflex.DIR)
# Whether to print the SQL queries if the log level is INFO or lower. # Whether to print the SQL queries if the log level is INFO or lower.
SQLALCHEMY_ECHO: EnvVar[bool] = env_var(False) SQLALCHEMY_ECHO: EnvVar[bool] = env_var(False)
@ -844,7 +849,7 @@ class Config(Base):
env_file: Optional[str] = None env_file: Optional[str] = None
# Whether to display the sticky "Built with Reflex" badge on all pages. # Whether to display the sticky "Built with Reflex" badge on all pages.
show_built_with_reflex: bool = True show_built_with_reflex: Optional[bool] = None
# Whether the app is running in the reflex cloud environment. # Whether the app is running in the reflex cloud environment.
is_reflex_cloud: bool = False is_reflex_cloud: bool = False

View File

@ -25,6 +25,7 @@ from .base import (
from .compiler import ( from .compiler import (
NOCOMPILE_FILE, NOCOMPILE_FILE,
SETTER_PREFIX, SETTER_PREFIX,
CompileContext,
CompileVars, CompileVars,
ComponentName, ComponentName,
Ext, Ext,
@ -65,6 +66,7 @@ __ALL__ = [
ColorMode, ColorMode,
Config, Config,
COOKIES, COOKIES,
CompileContext,
ComponentName, ComponentName,
CustomComponents, CustomComponents,
DefaultPage, DefaultPage,

View File

@ -111,6 +111,15 @@ class ComponentName(Enum):
return self.value.lower() + Ext.ZIP return self.value.lower() + Ext.ZIP
class CompileContext(str, Enum):
"""The context in which the compiler is running."""
RUN = "run"
EXPORT = "export"
DEPLOY = "deploy"
UNDEFINED = "undefined"
class Imports(SimpleNamespace): class Imports(SimpleNamespace):
"""Common sets of import vars.""" """Common sets of import vars."""

View File

@ -292,6 +292,8 @@ def run(
if frontend and backend: if frontend and backend:
console.error("Cannot use both --frontend-only and --backend-only options.") console.error("Cannot use both --frontend-only and --backend-only options.")
raise typer.Exit(1) raise typer.Exit(1)
environment.REFLEX_COMPILE_CONTEXT.set(constants.CompileContext.RUN)
environment.REFLEX_BACKEND_ONLY.set(backend) environment.REFLEX_BACKEND_ONLY.set(backend)
environment.REFLEX_FRONTEND_ONLY.set(frontend) environment.REFLEX_FRONTEND_ONLY.set(frontend)
@ -338,6 +340,8 @@ def export(
from reflex.utils import export as export_utils from reflex.utils import export as export_utils
from reflex.utils import prerequisites from reflex.utils import prerequisites
environment.REFLEX_COMPILE_CONTEXT.set(constants.CompileContext.EXPORT)
frontend, backend = prerequisites.check_running_mode(frontend, backend) frontend, backend = prerequisites.check_running_mode(frontend, backend)
if prerequisites.needs_reinit(frontend=frontend or not backend): if prerequisites.needs_reinit(frontend=frontend or not backend):
@ -537,6 +541,8 @@ def deploy(
check_version() check_version()
environment.REFLEX_COMPILE_CONTEXT.set(constants.CompileContext.DEPLOY)
# Set the log level. # Set the log level.
console.set_log_level(loglevel) console.set_log_level(loglevel)

View File

@ -584,3 +584,12 @@ def is_prod_mode() -> bool:
""" """
current_mode = environment.REFLEX_ENV_MODE.get() current_mode = environment.REFLEX_ENV_MODE.get()
return current_mode == constants.Env.PROD return current_mode == constants.Env.PROD
def get_compile_context() -> constants.CompileContext:
"""Check if the app is compiled for deploy.
Returns:
Whether the app is being compiled for deploy.
"""
return environment.REFLEX_COMPILE_CONTEXT.get()

View File

@ -2001,6 +2001,22 @@ def is_generation_hash(template: str) -> bool:
return re.match(r"^[0-9a-f]{32,}$", template) is not None return re.match(r"^[0-9a-f]{32,}$", template) is not None
def get_user_tier():
"""Get the current user's tier.
Returns:
The current user's tier.
"""
from reflex_cli.v2.utils import hosting
authenticated_token = hosting.authenticated_token()
return (
authenticated_token[1].get("tier", "").lower()
if authenticated_token[0]
else "anonymous"
)
def check_config_option_in_tier( def check_config_option_in_tier(
option_name: str, option_name: str,
allowed_tiers: list[str], allowed_tiers: list[str],
@ -2015,23 +2031,21 @@ def check_config_option_in_tier(
fallback_value: The fallback value if the option is not allowed. fallback_value: The fallback value if the option is not allowed.
help_link: The help link to show to a user that is authenticated. help_link: The help link to show to a user that is authenticated.
""" """
from reflex_cli.v2.utils import hosting
config = get_config() config = get_config()
authenticated_token = hosting.authenticated_token() current_tier = get_user_tier()
if not authenticated_token[0]:
if current_tier == "anonymous":
the_remedy = ( the_remedy = (
"You are currently logged out. Run `reflex login` to access this option." "You are currently logged out. Run `reflex login` to access this option."
) )
current_tier = "anonymous"
else: else:
current_tier = authenticated_token[1].get("tier", "").lower()
the_remedy = ( the_remedy = (
f"Your current subscription tier is `{current_tier}`. " f"Your current subscription tier is `{current_tier}`. "
f"Please upgrade to {allowed_tiers} to access this option. " f"Please upgrade to {allowed_tiers} to access this option. "
) )
if help_link: if help_link:
the_remedy += f"See {help_link} for more information." the_remedy += f"See {help_link} for more information."
if current_tier not in allowed_tiers: if current_tier not in allowed_tiers:
console.warn(f"Config option `{option_name}` is restricted. {the_remedy}") console.warn(f"Config option `{option_name}` is restricted. {the_remedy}")
setattr(config, option_name, fallback_value) setattr(config, option_name, fallback_value)

View File

@ -7,24 +7,19 @@ import pytest
from selenium.common.exceptions import NoSuchElementException from selenium.common.exceptions import NoSuchElementException
from selenium.webdriver.common.by import By from selenium.webdriver.common.by import By
from reflex import constants
from reflex.config import environment
from reflex.testing import AppHarness, WebDriver from reflex.testing import AppHarness, WebDriver
from .utils import SessionStorage from .utils import SessionStorage
def ConnectionBanner(is_reflex_cloud: bool = False): def ConnectionBanner():
"""App with a connection banner. """App with a connection banner."""
Args:
is_reflex_cloud: The value for config.is_reflex_cloud.
"""
import asyncio import asyncio
import reflex as rx import reflex as rx
# Simulate reflex cloud deploy
rx.config.get_config().is_reflex_cloud = is_reflex_cloud
class State(rx.State): class State(rx.State):
foo: int = 0 foo: int = 0
@ -49,16 +44,17 @@ def ConnectionBanner(is_reflex_cloud: bool = False):
@pytest.fixture( @pytest.fixture(
params=[False, True], ids=["reflex_cloud_disabled", "reflex_cloud_enabled"] params=[constants.CompileContext.RUN, constants.CompileContext.DEPLOY],
ids=["compile_context_run", "compile_context_deploy"],
) )
def simulate_is_reflex_cloud(request) -> bool: def simulate_compile_context(request) -> constants.CompileContext:
"""Fixture to simulate reflex cloud deployment. """Fixture to simulate reflex cloud deployment.
Args: Args:
request: pytest request fixture. request: pytest request fixture.
Returns: Returns:
True if reflex cloud is enabled, False otherwise. The context to run the app with.
""" """
return request.param return request.param
@ -66,25 +62,27 @@ def simulate_is_reflex_cloud(request) -> bool:
@pytest.fixture() @pytest.fixture()
def connection_banner( def connection_banner(
tmp_path, tmp_path,
simulate_is_reflex_cloud: bool, simulate_compile_context: constants.CompileContext,
) -> Generator[AppHarness, None, None]: ) -> Generator[AppHarness, None, None]:
"""Start ConnectionBanner app at tmp_path via AppHarness. """Start ConnectionBanner app at tmp_path via AppHarness.
Args: Args:
tmp_path: pytest tmp_path fixture tmp_path: pytest tmp_path fixture
simulate_is_reflex_cloud: Whether is_reflex_cloud is set for the app. simulate_compile_context: Which context to run the app with.
Yields: Yields:
running AppHarness instance running AppHarness instance
""" """
environment.REFLEX_COMPILE_CONTEXT.set(simulate_compile_context)
with AppHarness.create( with AppHarness.create(
root=tmp_path, root=tmp_path,
app_source=functools.partial( app_source=functools.partial(ConnectionBanner),
ConnectionBanner, is_reflex_cloud=simulate_is_reflex_cloud app_name=(
"connection_banner_reflex_cloud"
if simulate_compile_context == constants.CompileContext.DEPLOY
else "connection_banner"
), ),
app_name="connection_banner_reflex_cloud"
if simulate_is_reflex_cloud
else "connection_banner",
) as harness: ) as harness:
yield harness yield harness
@ -194,13 +192,13 @@ async def test_connection_banner(connection_banner: AppHarness):
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_cloud_banner( async def test_cloud_banner(
connection_banner: AppHarness, simulate_is_reflex_cloud: bool connection_banner: AppHarness, simulate_compile_context: constants.CompileContext
): ):
"""Test that the connection banner is displayed when the websocket drops. """Test that the connection banner is displayed when the websocket drops.
Args: Args:
connection_banner: AppHarness instance. connection_banner: AppHarness instance.
simulate_is_reflex_cloud: Whether is_reflex_cloud is set for the app. simulate_compile_context: Which context to set for the app.
""" """
assert connection_banner.app_instance is not None assert connection_banner.app_instance is not None
assert connection_banner.backend is not None assert connection_banner.backend is not None
@ -213,7 +211,7 @@ async def test_cloud_banner(
driver.add_cookie({"name": "backend-enabled", "value": "false"}) driver.add_cookie({"name": "backend-enabled", "value": "false"})
driver.refresh() driver.refresh()
if simulate_is_reflex_cloud: if simulate_compile_context == constants.CompileContext.DEPLOY:
assert connection_banner._poll_for(lambda: has_cloud_banner(driver)) assert connection_banner._poll_for(lambda: has_cloud_banner(driver))
else: else:
_assert_token(connection_banner, driver) _assert_token(connection_banner, driver)