Merge remote-tracking branch 'upstream/main' into unchanged-vars-dirty
This commit is contained in:
commit
f5987ea652
29
.github/workflows/integration_tests.yml
vendored
29
.github/workflows/integration_tests.yml
vendored
@ -162,7 +162,36 @@ jobs:
|
||||
--python-version "${{ matrix.python-version }}" --commit-sha "${{ github.sha }}"
|
||||
--pr-id "${{ github.event.pull_request.id }}" --branch-name "${{ github.head_ref || github.ref_name }}"
|
||||
--app-name "reflex-web" --path ./reflex-web/.web
|
||||
|
||||
rx-shout-from-template:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: ./.github/actions/setup_build_env
|
||||
with:
|
||||
python-version: '3.11.4'
|
||||
run-poetry-install: true
|
||||
create-venv-at-path: .venv
|
||||
- name: Create app directory
|
||||
run: mkdir rx-shout-from-template
|
||||
- name: Init reflex-web from template
|
||||
run: poetry run reflex init --template https://github.com/masenf/rx_shout
|
||||
working-directory: ./rx-shout-from-template
|
||||
- name: ignore reflex pin in requirements
|
||||
run: sed -i -e '/reflex==/d' requirements.txt
|
||||
working-directory: ./rx-shout-from-template
|
||||
- name: Install additional dependencies
|
||||
run: poetry run uv pip install -r requirements.txt
|
||||
working-directory: ./rx-shout-from-template
|
||||
- name: Run Website and Check for errors
|
||||
run: |
|
||||
# Check that npm is home
|
||||
npm -v
|
||||
poetry run bash scripts/integration.sh ./rx-shout-from-template prod
|
||||
|
||||
|
||||
reflex-web-macos:
|
||||
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
||||
strategy:
|
||||
|
16
poetry.lock
generated
16
poetry.lock
generated
@ -2206,13 +2206,13 @@ reflex = ">=0.6.0a"
|
||||
|
||||
[[package]]
|
||||
name = "reflex-hosting-cli"
|
||||
version = "0.1.17"
|
||||
version = "0.1.29"
|
||||
description = "Reflex Hosting CLI"
|
||||
optional = false
|
||||
python-versions = "<4.0,>=3.8"
|
||||
files = [
|
||||
{file = "reflex_hosting_cli-0.1.17-py3-none-any.whl", hash = "sha256:cf1accec70745557a40125ffa2a8929e6ef9834808afe78e4f4a01933ac0cb67"},
|
||||
{file = "reflex_hosting_cli-0.1.17.tar.gz", hash = "sha256:263d8dc217eb24d4198ac0bcfd710980bd7795d9818a5e522027657f94752710"},
|
||||
{file = "reflex_hosting_cli-0.1.29-py3-none-any.whl", hash = "sha256:fcbdad829762287f32397cd8a5d46536ab0db396e7fdb8a23c7f9343d7dc8de0"},
|
||||
{file = "reflex_hosting_cli-0.1.29.tar.gz", hash = "sha256:7b421fec6936c26549c8c65c9dda34fc042eaaec79b238dce6b9c020f848563b"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@ -2224,7 +2224,7 @@ pydantic = ">=1.10.2,<3.0"
|
||||
python-dateutil = ">=2.8.1"
|
||||
rich = ">=13.0.0,<14.0"
|
||||
tabulate = ">=0.9.0,<0.10.0"
|
||||
typer = ">=0.4.2,<1"
|
||||
typer = ">=0.15.0,<1"
|
||||
websockets = ">=10.4"
|
||||
|
||||
[[package]]
|
||||
@ -2722,13 +2722,13 @@ urllib3 = ">=1.26.0"
|
||||
|
||||
[[package]]
|
||||
name = "typer"
|
||||
version = "0.13.1"
|
||||
version = "0.15.1"
|
||||
description = "Typer, build great CLIs. Easy to code. Based on Python type hints."
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "typer-0.13.1-py3-none-any.whl", hash = "sha256:5b59580fd925e89463a29d363e0a43245ec02765bde9fb77d39e5d0f29dd7157"},
|
||||
{file = "typer-0.13.1.tar.gz", hash = "sha256:9d444cb96cc268ce6f8b94e13b4335084cef4c079998a9f4851a90229a3bd25c"},
|
||||
{file = "typer-0.15.1-py3-none-any.whl", hash = "sha256:7994fb7b8155b64d3402518560648446072864beefd44aa2dc36972a5972e847"},
|
||||
{file = "typer-0.15.1.tar.gz", hash = "sha256:a0588c0a7fa68a1978a069818657778f86abe6ff5ea6abf472f940a08bfe4f0a"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@ -3041,4 +3041,4 @@ type = ["pytest-mypy"]
|
||||
[metadata]
|
||||
lock-version = "2.0"
|
||||
python-versions = "^3.9"
|
||||
content-hash = "8000601d48cfc1b10d0ae18c6046cc59a50cb6c45e6d3ef4775a3203769f2154"
|
||||
content-hash = "3810e99ff4d09952e62d88b2c26651a0d8e0ffe4007bc3274c2fb83b68243951"
|
||||
|
@ -49,7 +49,7 @@ wrapt = [
|
||||
{version = ">=1.11.0,<2.0", python = "<3.11"},
|
||||
]
|
||||
packaging = ">=23.1,<25.0"
|
||||
reflex-hosting-cli = ">=0.1.17,<2.0"
|
||||
reflex-hosting-cli = ">=0.1.29,<2.0"
|
||||
charset-normalizer = ">=3.3.2,<4.0"
|
||||
wheel = ">=0.42.0,<1.0"
|
||||
build = ">=1.0.3,<2.0"
|
||||
|
@ -652,9 +652,9 @@ class Config(Base):
|
||||
frontend_packages: List[str] = []
|
||||
|
||||
# The hosting service backend URL.
|
||||
cp_backend_url: str = Hosting.CP_BACKEND_URL
|
||||
cp_backend_url: str = Hosting.HOSTING_SERVICE
|
||||
# The hosting service frontend URL.
|
||||
cp_web_url: str = Hosting.CP_WEB_URL
|
||||
cp_web_url: str = Hosting.HOSTING_SERVICE_UI
|
||||
|
||||
# The worker class used in production mode
|
||||
gunicorn_worker_class: str = "uvicorn.workers.UvicornH11Worker"
|
||||
|
@ -827,11 +827,11 @@ def _collect_details_for_gallery():
|
||||
Raises:
|
||||
Exit: If pyproject.toml file is ill-formed or the request to the backend services fails.
|
||||
"""
|
||||
from reflex.reflex import _login
|
||||
from reflex_cli.utils import hosting
|
||||
|
||||
console.rule("[bold]Authentication with Reflex Services")
|
||||
console.print("First let's log in to Reflex backend services.")
|
||||
access_token = _login()
|
||||
access_token, _ = hosting.authenticated_token()
|
||||
|
||||
console.rule("[bold]Custom Component Information")
|
||||
params = {}
|
||||
|
205
reflex/reflex.py
205
reflex/reflex.py
@ -9,8 +9,6 @@ from typing import List, Optional
|
||||
|
||||
import typer
|
||||
import typer.core
|
||||
from reflex_cli.deployments import deployments_cli
|
||||
from reflex_cli.utils import dependency
|
||||
from reflex_cli.v2.deployments import check_version, hosting_cli
|
||||
|
||||
from reflex import constants
|
||||
@ -330,47 +328,16 @@ def export(
|
||||
)
|
||||
|
||||
|
||||
def _login() -> str:
|
||||
"""Helper function to authenticate with Reflex hosting service."""
|
||||
from reflex_cli.utils import hosting
|
||||
|
||||
access_token, invitation_code = hosting.authenticated_token()
|
||||
if access_token:
|
||||
console.print("You already logged in.")
|
||||
return access_token
|
||||
|
||||
# If not already logged in, open a browser window/tab to the login page.
|
||||
access_token = hosting.authenticate_on_browser(invitation_code)
|
||||
|
||||
if not access_token:
|
||||
console.error("Unable to authenticate. Please try again or contact support.")
|
||||
raise typer.Exit(1)
|
||||
|
||||
console.print("Successfully logged in.")
|
||||
return access_token
|
||||
|
||||
|
||||
@cli.command()
|
||||
def login(
|
||||
loglevel: constants.LogLevel = typer.Option(
|
||||
config.loglevel, help="The log level to use."
|
||||
),
|
||||
):
|
||||
"""Authenticate with Reflex hosting service."""
|
||||
# Set the log level.
|
||||
console.set_log_level(loglevel)
|
||||
|
||||
_login()
|
||||
|
||||
|
||||
@cli.command()
|
||||
def loginv2(loglevel: constants.LogLevel = typer.Option(config.loglevel)):
|
||||
def login(loglevel: constants.LogLevel = typer.Option(config.loglevel)):
|
||||
"""Authenicate with experimental Reflex hosting service."""
|
||||
from reflex_cli.v2 import cli as hosting_cli
|
||||
|
||||
check_version()
|
||||
|
||||
hosting_cli.login()
|
||||
validated_info = hosting_cli.login()
|
||||
if validated_info is not None:
|
||||
telemetry.send("login", user_uuid=validated_info.get("user_id"))
|
||||
|
||||
|
||||
@cli.command()
|
||||
@ -380,31 +347,11 @@ def logout(
|
||||
),
|
||||
):
|
||||
"""Log out of access to Reflex hosting service."""
|
||||
from reflex_cli.utils import hosting
|
||||
|
||||
console.set_log_level(loglevel)
|
||||
|
||||
hosting.log_out_on_browser()
|
||||
console.debug("Deleting access token from config locally")
|
||||
hosting.delete_token_from_config(include_invitation_code=True)
|
||||
|
||||
|
||||
@cli.command()
|
||||
def logoutv2(
|
||||
loglevel: constants.LogLevel = typer.Option(
|
||||
config.loglevel, help="The log level to use."
|
||||
),
|
||||
):
|
||||
"""Log out of access to Reflex hosting service."""
|
||||
from reflex_cli.v2.utils import hosting
|
||||
from reflex_cli.v2.cli import logout
|
||||
|
||||
check_version()
|
||||
|
||||
console.set_log_level(loglevel)
|
||||
|
||||
hosting.log_out_on_browser()
|
||||
console.debug("Deleting access token from config locally")
|
||||
hosting.delete_token_from_config()
|
||||
logout(loglevel) # type: ignore
|
||||
|
||||
|
||||
db_cli = typer.Typer()
|
||||
@ -489,126 +436,6 @@ def makemigrations(
|
||||
|
||||
@cli.command()
|
||||
def deploy(
|
||||
key: Optional[str] = typer.Option(
|
||||
None,
|
||||
"-k",
|
||||
"--deployment-key",
|
||||
help="The name of the deployment. Domain name safe characters only.",
|
||||
),
|
||||
app_name: str = typer.Option(
|
||||
config.app_name,
|
||||
"--app-name",
|
||||
help="The name of the App to deploy under.",
|
||||
hidden=True,
|
||||
),
|
||||
regions: List[str] = typer.Option(
|
||||
list(),
|
||||
"-r",
|
||||
"--region",
|
||||
help="The regions to deploy to.",
|
||||
),
|
||||
envs: List[str] = typer.Option(
|
||||
list(),
|
||||
"--env",
|
||||
help="The environment variables to set: <key>=<value>. For multiple envs, repeat this option, e.g. --env k1=v2 --env k2=v2.",
|
||||
),
|
||||
cpus: Optional[int] = typer.Option(
|
||||
None, help="The number of CPUs to allocate.", hidden=True
|
||||
),
|
||||
memory_mb: Optional[int] = typer.Option(
|
||||
None, help="The amount of memory to allocate.", hidden=True
|
||||
),
|
||||
auto_start: Optional[bool] = typer.Option(
|
||||
None,
|
||||
help="Whether to auto start the instance.",
|
||||
hidden=True,
|
||||
),
|
||||
auto_stop: Optional[bool] = typer.Option(
|
||||
None,
|
||||
help="Whether to auto stop the instance.",
|
||||
hidden=True,
|
||||
),
|
||||
frontend_hostname: Optional[str] = typer.Option(
|
||||
None,
|
||||
"--frontend-hostname",
|
||||
help="The hostname of the frontend.",
|
||||
hidden=True,
|
||||
),
|
||||
interactive: bool = typer.Option(
|
||||
True,
|
||||
help="Whether to list configuration options and ask for confirmation.",
|
||||
),
|
||||
with_metrics: Optional[str] = typer.Option(
|
||||
None,
|
||||
help="Setting for metrics scraping for the deployment. Setup required in user code.",
|
||||
hidden=True,
|
||||
),
|
||||
with_tracing: Optional[str] = typer.Option(
|
||||
None,
|
||||
help="Setting to export tracing for the deployment. Setup required in user code.",
|
||||
hidden=True,
|
||||
),
|
||||
upload_db_file: bool = typer.Option(
|
||||
False,
|
||||
help="Whether to include local sqlite db files when uploading to hosting service.",
|
||||
hidden=True,
|
||||
),
|
||||
loglevel: constants.LogLevel = typer.Option(
|
||||
config.loglevel, help="The log level to use."
|
||||
),
|
||||
):
|
||||
"""Deploy the app to the Reflex hosting service."""
|
||||
from reflex_cli import cli as hosting_cli
|
||||
|
||||
from reflex.utils import export as export_utils
|
||||
from reflex.utils import prerequisites
|
||||
|
||||
# Set the log level.
|
||||
console.set_log_level(loglevel)
|
||||
|
||||
# Only check requirements if interactive. There is user interaction for requirements update.
|
||||
if interactive:
|
||||
dependency.check_requirements()
|
||||
|
||||
# Check if we are set up.
|
||||
if prerequisites.needs_reinit(frontend=True):
|
||||
_init(name=config.app_name, loglevel=loglevel)
|
||||
prerequisites.check_latest_package_version(constants.ReflexHostingCLI.MODULE_NAME)
|
||||
|
||||
hosting_cli.deploy(
|
||||
app_name=app_name,
|
||||
export_fn=lambda zip_dest_dir,
|
||||
api_url,
|
||||
deploy_url,
|
||||
frontend,
|
||||
backend,
|
||||
zipping: export_utils.export(
|
||||
zip_dest_dir=zip_dest_dir,
|
||||
api_url=api_url,
|
||||
deploy_url=deploy_url,
|
||||
frontend=frontend,
|
||||
backend=backend,
|
||||
zipping=zipping,
|
||||
loglevel=loglevel.subprocess_level(),
|
||||
upload_db_file=upload_db_file,
|
||||
),
|
||||
key=key,
|
||||
regions=regions,
|
||||
envs=envs,
|
||||
cpus=cpus,
|
||||
memory_mb=memory_mb,
|
||||
auto_start=auto_start,
|
||||
auto_stop=auto_stop,
|
||||
frontend_hostname=frontend_hostname,
|
||||
interactive=interactive,
|
||||
with_metrics=with_metrics,
|
||||
with_tracing=with_tracing,
|
||||
loglevel=loglevel.subprocess_level(),
|
||||
)
|
||||
|
||||
|
||||
@cli.command()
|
||||
def deployv2(
|
||||
app_name: str = typer.Option(
|
||||
config.app_name,
|
||||
"--app-name",
|
||||
@ -660,8 +487,8 @@ def deployv2(
|
||||
),
|
||||
):
|
||||
"""Deploy the app to the Reflex hosting service."""
|
||||
from reflex_cli.utils import dependency
|
||||
from reflex_cli.v2 import cli as hosting_cli
|
||||
from reflex_cli.v2.utils import dependency
|
||||
|
||||
from reflex.utils import export as export_utils
|
||||
from reflex.utils import prerequisites
|
||||
@ -671,6 +498,13 @@ def deployv2(
|
||||
# Set the log level.
|
||||
console.set_log_level(loglevel)
|
||||
|
||||
if not token:
|
||||
# make sure user is logged in.
|
||||
if interactive:
|
||||
hosting_cli.login()
|
||||
else:
|
||||
raise SystemExit("Token is required for non-interactive mode.")
|
||||
|
||||
# Only check requirements if interactive.
|
||||
# There is user interaction for requirements update.
|
||||
if interactive:
|
||||
@ -703,7 +537,7 @@ def deployv2(
|
||||
envfile=envfile,
|
||||
hostname=hostname,
|
||||
interactive=interactive,
|
||||
loglevel=loglevel.subprocess_level(),
|
||||
loglevel=type(loglevel).INFO, # type: ignore
|
||||
token=token,
|
||||
project=project,
|
||||
)
|
||||
@ -711,15 +545,10 @@ def deployv2(
|
||||
|
||||
cli.add_typer(db_cli, name="db", help="Subcommands for managing the database schema.")
|
||||
cli.add_typer(script_cli, name="script", help="Subcommands running helper scripts.")
|
||||
cli.add_typer(
|
||||
deployments_cli,
|
||||
name="deployments",
|
||||
help="Subcommands for managing the Deployments.",
|
||||
)
|
||||
cli.add_typer(
|
||||
hosting_cli,
|
||||
name="apps",
|
||||
help="Subcommands for managing the Deployments.",
|
||||
name="cloud",
|
||||
help="Subcommands for managing the reflex cloud.",
|
||||
)
|
||||
cli.add_typer(
|
||||
custom_components_cli,
|
||||
|
@ -1408,13 +1408,22 @@ def validate_and_create_app_using_remote_template(app_name, template, templates)
|
||||
"""
|
||||
# If user selects a template, it needs to exist.
|
||||
if template in templates:
|
||||
from reflex_cli.v2.utils import hosting
|
||||
|
||||
authenticated_token = hosting.authenticated_token()
|
||||
if not authenticated_token or not authenticated_token[0]:
|
||||
console.print(
|
||||
f"Please use `reflex login` to access the '{template}' template."
|
||||
)
|
||||
raise typer.Exit(3)
|
||||
|
||||
template_url = templates[template].code_url
|
||||
else:
|
||||
# Check if the template is a github repo.
|
||||
if template.startswith("https://github.com"):
|
||||
template_url = f"{template.strip('/').replace('.git', '')}/archive/main.zip"
|
||||
else:
|
||||
console.error(f"Template `{template}` not found.")
|
||||
console.error(f"Template `{template}` not found or invalid.")
|
||||
raise typer.Exit(1)
|
||||
|
||||
if template_url is None:
|
||||
@ -1451,7 +1460,7 @@ def generate_template_using_ai(template: str | None = None) -> str:
|
||||
|
||||
|
||||
def fetch_remote_templates(
|
||||
template: Optional[str] = None,
|
||||
template: str,
|
||||
) -> tuple[str, dict[str, Template]]:
|
||||
"""Fetch the available remote templates.
|
||||
|
||||
@ -1460,9 +1469,6 @@ def fetch_remote_templates(
|
||||
|
||||
Returns:
|
||||
The selected template and the available templates.
|
||||
|
||||
Raises:
|
||||
Exit: If the template is not valid or if the template is not specified.
|
||||
"""
|
||||
available_templates = {}
|
||||
|
||||
@ -1474,19 +1480,7 @@ def fetch_remote_templates(
|
||||
console.debug(f"Error while fetching templates: {e}")
|
||||
template = constants.Templates.DEFAULT
|
||||
|
||||
if template == constants.Templates.DEFAULT:
|
||||
return template, available_templates
|
||||
|
||||
if template in available_templates:
|
||||
return template, available_templates
|
||||
|
||||
else:
|
||||
if template is not None:
|
||||
console.error(f"{template!r} is not a valid template name.")
|
||||
console.print(
|
||||
f"Go to the templates page ({constants.Templates.REFLEX_TEMPLATES_URL}) and copy the command to init with a template."
|
||||
)
|
||||
raise typer.Exit(0)
|
||||
return template, available_templates
|
||||
|
||||
|
||||
def initialize_app(
|
||||
@ -1501,6 +1495,9 @@ def initialize_app(
|
||||
|
||||
Returns:
|
||||
The name of the template.
|
||||
|
||||
Raises:
|
||||
Exit: If the template is not valid or unspecified.
|
||||
"""
|
||||
# Local imports to avoid circular imports.
|
||||
from reflex.utils import telemetry
|
||||
@ -1528,7 +1525,10 @@ def initialize_app(
|
||||
# change to the default to allow creation of default app
|
||||
template = constants.Templates.DEFAULT
|
||||
elif template == constants.Templates.CHOOSE_TEMPLATES:
|
||||
template, templates = fetch_remote_templates()
|
||||
console.print(
|
||||
f"Go to the templates page ({constants.Templates.REFLEX_TEMPLATES_URL}) and copy the command to init with a template."
|
||||
)
|
||||
raise typer.Exit(0)
|
||||
|
||||
# If the blank template is selected, create a blank app.
|
||||
if template in (constants.Templates.DEFAULT,):
|
||||
|
@ -129,7 +129,7 @@ def _prepare_event(event: str, **kwargs) -> dict:
|
||||
|
||||
cpuinfo = get_cpu_info()
|
||||
|
||||
additional_keys = ["template", "context", "detail"]
|
||||
additional_keys = ["template", "context", "detail", "user_uuid"]
|
||||
additional_fields = {
|
||||
key: value for key in additional_keys if (value := kwargs.get(key)) is not None
|
||||
}
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import time
|
||||
from typing import Callable, Coroutine, Generator, Type
|
||||
from urllib.parse import urlsplit
|
||||
|
||||
@ -89,6 +90,11 @@ def DynamicRoute():
|
||||
@rx.page(route="/arg/[arg_str]")
|
||||
def arg() -> rx.Component:
|
||||
return rx.vstack(
|
||||
rx.input(
|
||||
value=DynamicState.router.session.client_token,
|
||||
read_only=True,
|
||||
id="token",
|
||||
),
|
||||
rx.data_list.root(
|
||||
rx.data_list.item(
|
||||
rx.data_list.label("rx.State.arg_str (dynamic)"),
|
||||
@ -172,6 +178,8 @@ def driver(dynamic_route: AppHarness) -> Generator[WebDriver, None, None]:
|
||||
"""
|
||||
assert dynamic_route.app_instance is not None, "app is not running"
|
||||
driver = dynamic_route.frontend()
|
||||
# TODO: drop after flakiness is resolved
|
||||
driver.implicitly_wait(30)
|
||||
try:
|
||||
yield driver
|
||||
finally:
|
||||
@ -373,17 +381,22 @@ async def test_on_load_navigate_non_dynamic(
|
||||
async def test_render_dynamic_arg(
|
||||
dynamic_route: AppHarness,
|
||||
driver: WebDriver,
|
||||
token: str,
|
||||
):
|
||||
"""Assert that dynamic arg var is rendered correctly in different contexts.
|
||||
|
||||
Args:
|
||||
dynamic_route: harness for DynamicRoute app.
|
||||
driver: WebDriver instance.
|
||||
token: The token visible in the driver browser.
|
||||
"""
|
||||
assert dynamic_route.app_instance is not None
|
||||
with poll_for_navigation(driver):
|
||||
driver.get(f"{dynamic_route.frontend_url}/arg/0")
|
||||
|
||||
# TODO: drop after flakiness is resolved
|
||||
time.sleep(3)
|
||||
|
||||
def assert_content(expected: str, expect_not: str):
|
||||
ids = [
|
||||
"state-arg_str",
|
||||
@ -398,7 +411,8 @@ async def test_render_dynamic_arg(
|
||||
el = driver.find_element(By.ID, id)
|
||||
assert el
|
||||
assert (
|
||||
dynamic_route.poll_for_content(el, exp_not_equal=expect_not) == expected
|
||||
dynamic_route.poll_for_content(el, timeout=30, exp_not_equal=expect_not)
|
||||
== expected
|
||||
)
|
||||
|
||||
assert_content("0", "")
|
||||
|
Loading…
Reference in New Issue
Block a user