Skip to content

Commit

Permalink
CLI script to maintain Chakra backed components in rx namespace in ol…
Browse files Browse the repository at this point in the history
…der apps (reflex-dev#2322)
  • Loading branch information
jackie-pc authored Feb 8, 2024
1 parent ea2a590 commit a4ee985
Show file tree
Hide file tree
Showing 3 changed files with 135 additions and 0 deletions.
16 changes: 16 additions & 0 deletions reflex/reflex.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,9 @@ def _init(
# Migrate Pynecone projects to Reflex.
prerequisites.migrate_to_reflex()

if prerequisites.should_show_rx_chakra_migration_instructions():
prerequisites.show_rx_chakra_migration_instructions()

# Initialize the .gitignore.
prerequisites.initialize_gitignore()

Expand Down Expand Up @@ -336,6 +339,7 @@ def logout(


db_cli = typer.Typer()
script_cli = typer.Typer()


def _skip_compile():
Expand Down Expand Up @@ -414,6 +418,17 @@ def makemigrations(
)


@script_cli.command(
name="keep-chakra",
help="Change all rx.<component> references to rx.chakra.<component>, to preserve Chakra UI usage.",
)
def keep_chakra():
"""Change all rx.<component> references to rx.chakra.<component>, to preserve Chakra UI usage."""
from reflex.utils import prerequisites

prerequisites.migrate_to_rx_chakra()


@cli.command()
def deploy(
key: Optional[str] = typer.Option(
Expand Down Expand Up @@ -555,6 +570,7 @@ def demo(


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",
Expand Down
106 changes: 106 additions & 0 deletions reflex/utils/prerequisites.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

import glob
import importlib
import inspect
import json
import os
import platform
Expand All @@ -25,6 +26,7 @@
from packaging import version
from redis.asyncio import Redis

import reflex
from reflex import constants, model
from reflex.compiler import templates
from reflex.config import Config, get_config
Expand Down Expand Up @@ -938,6 +940,110 @@ def prompt_for_template() -> constants.Templates.Kind:
return constants.Templates.Kind(template)


def should_show_rx_chakra_migration_instructions() -> bool:
"""Should we show the migration instructions for rx.chakra.* => rx.*?.
Returns:
bool: True if we should show the migration instructions.
"""
if os.getenv("REFLEX_PROMPT_MIGRATE_TO_RX_CHAKRA") == "yes":
return True

with open(constants.Dirs.REFLEX_JSON, "r") as f:
data = json.load(f)
existing_init_reflex_version = data.get("version", None)

if existing_init_reflex_version is None:
# They clone a reflex app from git for the first time.
# That app may or may not be 0.4 compatible.
# So let's just show these instructions THIS TIME.
return True

if constants.Reflex.VERSION < "0.4":
return False
else:
return existing_init_reflex_version < "0.4"


def show_rx_chakra_migration_instructions():
"""Show the migration instructions for rx.chakra.* => rx.*."""
console.log(
"Prior to reflex 0.4.0, rx.* components are based on Chakra UI. They are now based on Radix UI. To stick to Chakra UI, use rx.chakra.*."
)
console.log("")
console.log(
"[bold]Run `reflex script keep-chakra` to automatically update your app."
)
console.log("")
console.log("For more details, please see https://TODO") # TODO add link to docs


def migrate_to_rx_chakra():
"""Migrate rx.button => r.chakra.button, etc."""
file_pattern = os.path.join(get_config().app_name, "**/*.py")
file_list = glob.glob(file_pattern, recursive=True)

# Populate with all rx.<x> components that have been moved to rx.chakra.<x>
patterns = {
rf"\brx\.{name}\b": f"rx.chakra.{name}"
for name in _get_rx_chakra_component_to_migrate()
}

for file_path in file_list:
with FileInput(file_path, inplace=True) as file:
for _line_num, line in enumerate(file):
for old, new in patterns.items():
line = re.sub(old, new, line)
print(line, end="")


def _get_rx_chakra_component_to_migrate() -> set[str]:
from reflex.components import ChakraComponent

rx_chakra_names = set(dir(reflex.chakra))

names_to_migrate = set()
whitelist = {
"CodeBlock",
"ColorModeIcon",
"MultiSelect",
"MultiSelectOption",
"base",
"code_block",
"color_mode_cond",
"color_mode_icon",
"multi_select",
"multi_select_option",
}
for rx_chakra_name in sorted(rx_chakra_names):
if rx_chakra_name.startswith("_"):
continue

rx_chakra_object = getattr(reflex.chakra, rx_chakra_name)
try:
if (
inspect.ismethod(rx_chakra_object)
and inspect.isclass(rx_chakra_object.__self__)
and issubclass(rx_chakra_object.__self__, ChakraComponent)
):
names_to_migrate.add(rx_chakra_name)

elif inspect.isclass(rx_chakra_object) and issubclass(
rx_chakra_object, ChakraComponent
):
names_to_migrate.add(rx_chakra_name)
pass
else:
# For the given rx.chakra.<x>, does rx.<x> exist?
# And of these, should we include in migration?
if hasattr(reflex, rx_chakra_name) and rx_chakra_name in whitelist:
names_to_migrate.add(rx_chakra_name)

except Exception:
raise
return names_to_migrate


def migrate_to_reflex():
"""Migration from Pynecone to Reflex."""
# Check if the old config file exists.
Expand Down
13 changes: 13 additions & 0 deletions scripts/migrate_project_to_rx_chakra.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
"""Migrate project to rx.chakra. I.e. switch usage of rx.<component> to rx.chakra.<component>."""

import argparse

if __name__ == "__main__":
# parse args just for the help message (-h, etc)
parser = argparse.ArgumentParser(
description="Migrate project to rx.chakra. I.e. switch usage of rx.<component> to rx.chakra.<component>."
)
args = parser.parse_args()
from reflex.utils.prerequisites import migrate_to_rx_chakra

migrate_to_rx_chakra()

0 comments on commit a4ee985

Please sign in to comment.