code cleanup (split constants into a folder) (#1866)
This commit is contained in:
parent
8326abf5d5
commit
dcb17103bb
@ -183,8 +183,8 @@ class App(Base):
|
||||
else config.cors_allowed_origins,
|
||||
cors_credentials=True,
|
||||
max_http_buffer_size=constants.POLLING_MAX_HTTP_BUFFER_SIZE,
|
||||
ping_interval=constants.PING_INTERVAL,
|
||||
ping_timeout=constants.PING_TIMEOUT,
|
||||
ping_interval=constants.Ping.INTERVAL,
|
||||
ping_timeout=constants.Ping.TIMEOUT,
|
||||
)
|
||||
|
||||
# Create the socket app. Note event endpoint constant replaces the default 'socket.io' path.
|
||||
@ -340,14 +340,14 @@ class App(Base):
|
||||
self,
|
||||
component: Component | ComponentCallable,
|
||||
route: str | None = None,
|
||||
title: str = constants.DEFAULT_TITLE,
|
||||
description: str = constants.DEFAULT_DESCRIPTION,
|
||||
image=constants.DEFAULT_IMAGE,
|
||||
title: str = constants.DefaultPage.TITLE,
|
||||
description: str = constants.DefaultPage.DESCRIPTION,
|
||||
image: str = constants.DefaultPage.IMAGE,
|
||||
on_load: EventHandler
|
||||
| EventSpec
|
||||
| list[EventHandler | EventSpec]
|
||||
| None = None,
|
||||
meta: list[dict[str, str]] = constants.DEFAULT_META_LIST,
|
||||
meta: list[dict[str, str]] = constants.DefaultPage.META_LIST,
|
||||
script_tags: list[Component] | None = None,
|
||||
):
|
||||
"""Add a page to the app.
|
||||
@ -433,7 +433,7 @@ class App(Base):
|
||||
"""
|
||||
route = route.lstrip("/")
|
||||
if route == "":
|
||||
route = constants.INDEX_ROUTE
|
||||
route = constants.PageNames.INDEX_ROUTE
|
||||
return self.load_events.get(route, [])
|
||||
|
||||
def _check_routes_conflict(self, new_route: str):
|
||||
@ -472,14 +472,14 @@ class App(Base):
|
||||
def add_custom_404_page(
|
||||
self,
|
||||
component: Component | ComponentCallable | None = None,
|
||||
title: str = constants.TITLE_404,
|
||||
image: str = constants.FAVICON_404,
|
||||
description: str = constants.DESCRIPTION_404,
|
||||
title: str = constants.Page404.TITLE,
|
||||
image: str = constants.Page404.IMAGE,
|
||||
description: str = constants.Page404.DESCRIPTION,
|
||||
on_load: EventHandler
|
||||
| EventSpec
|
||||
| list[EventHandler | EventSpec]
|
||||
| None = None,
|
||||
meta: list[dict[str, str]] = constants.DEFAULT_META_LIST,
|
||||
meta: list[dict[str, str]] = constants.DefaultPage.META_LIST,
|
||||
):
|
||||
"""Define a custom 404 page for any url having no match.
|
||||
|
||||
@ -498,10 +498,10 @@ class App(Base):
|
||||
component = Default404Page.create()
|
||||
self.add_page(
|
||||
component=wait_for_client_redirect(self._generate_component(component)),
|
||||
route=constants.SLUG_404,
|
||||
title=title or constants.TITLE_404,
|
||||
image=image or constants.FAVICON_404,
|
||||
description=description or constants.DESCRIPTION_404,
|
||||
route=constants.Page404.SLUG,
|
||||
title=title or constants.Page404.TITLE,
|
||||
image=image or constants.Page404.IMAGE,
|
||||
description=description or constants.Page404.DESCRIPTION,
|
||||
on_load=on_load,
|
||||
meta=meta,
|
||||
)
|
||||
@ -541,11 +541,13 @@ class App(Base):
|
||||
page_imports = {
|
||||
i
|
||||
for i, tags in imports.items()
|
||||
if i not in compiler.DEFAULT_IMPORTS.keys()
|
||||
and i != "focus-visible/dist/focus-visible"
|
||||
and "next" not in i
|
||||
and not i.startswith("/")
|
||||
and not i.startswith(".")
|
||||
if i
|
||||
not in [
|
||||
*compiler.DEFAULT_IMPORTS.keys(),
|
||||
*constants.PackageJson.DEPENDENCIES.keys(),
|
||||
*constants.PackageJson.DEV_DEPENDENCIES.keys(),
|
||||
]
|
||||
and not any(i.startswith(prefix) for prefix in ["/", ".", "next/"])
|
||||
and i != ""
|
||||
and any(tag.install for tag in tags)
|
||||
}
|
||||
@ -581,7 +583,7 @@ class App(Base):
|
||||
self.add_page(render, **kwargs)
|
||||
|
||||
# Render a default 404 page if the user didn't supply one
|
||||
if constants.SLUG_404 not in self.pages:
|
||||
if constants.Page404.SLUG not in self.pages:
|
||||
self.add_custom_404_page()
|
||||
|
||||
task = progress.add_task("Compiling: ", total=len(self.pages))
|
||||
@ -649,7 +651,7 @@ class App(Base):
|
||||
# Compile the Tailwind config.
|
||||
if config.tailwind is not None:
|
||||
config.tailwind["content"] = config.tailwind.get(
|
||||
"content", constants.TAILWIND_CONTENT
|
||||
"content", constants.Tailwind.CONTENT
|
||||
)
|
||||
compile_results.append(compiler.compile_tailwind(config.tailwind))
|
||||
|
||||
|
@ -23,7 +23,7 @@ DEFAULT_IMPORTS: imports.ImportDict = {
|
||||
ImportVar(tag="useContext"),
|
||||
},
|
||||
"next/router": {ImportVar(tag="useRouter")},
|
||||
f"/{constants.STATE_PATH}": {
|
||||
f"/{constants.Dirs.STATE_PATH}": {
|
||||
ImportVar(tag="uploadFiles"),
|
||||
ImportVar(tag="Event"),
|
||||
ImportVar(tag="isTrue"),
|
||||
@ -40,9 +40,9 @@ DEFAULT_IMPORTS: imports.ImportDict = {
|
||||
ImportVar(tag="initialEvents"),
|
||||
ImportVar(tag="StateContext"),
|
||||
},
|
||||
"": {ImportVar(tag="focus-visible/dist/focus-visible")},
|
||||
"": {ImportVar(tag="focus-visible/dist/focus-visible", install=False)},
|
||||
"@chakra-ui/react": {
|
||||
ImportVar(tag=constants.USE_COLOR_MODE),
|
||||
ImportVar(tag=constants.ColorMode.USE),
|
||||
ImportVar(tag="Box"),
|
||||
ImportVar(tag="Text"),
|
||||
},
|
||||
@ -151,7 +151,7 @@ def _compile_root_stylesheet(stylesheets: list[str]) -> str:
|
||||
"""
|
||||
# Add tailwind css if enabled.
|
||||
sheets = (
|
||||
[constants.TAILWIND_ROOT_STYLE_PATH]
|
||||
[constants.Tailwind.ROOT_STYLE_PATH]
|
||||
if get_config().tailwind is not None
|
||||
else []
|
||||
)
|
||||
@ -159,7 +159,7 @@ def _compile_root_stylesheet(stylesheets: list[str]) -> str:
|
||||
if not utils.is_valid_url(stylesheet):
|
||||
# check if stylesheet provided exists.
|
||||
stylesheet_full_path = (
|
||||
Path.cwd() / constants.APP_ASSETS_DIR / stylesheet.strip("/")
|
||||
Path.cwd() / constants.Dirs.APP_ASSETS / stylesheet.strip("/")
|
||||
)
|
||||
if not os.path.exists(stylesheet_full_path):
|
||||
raise FileNotFoundError(
|
||||
@ -193,7 +193,7 @@ def _compile_components(components: set[CustomComponent]) -> str:
|
||||
"""
|
||||
imports = {
|
||||
"react": {ImportVar(tag="memo")},
|
||||
f"/{constants.STATE_PATH}": {ImportVar(tag="E"), ImportVar(tag="isTrue")},
|
||||
f"/{constants.Dirs.STATE_PATH}": {ImportVar(tag="E"), ImportVar(tag="isTrue")},
|
||||
}
|
||||
component_renders = []
|
||||
|
||||
@ -236,7 +236,7 @@ def compile_document_root(head_components: list[Component]) -> tuple[str, str]:
|
||||
The path and code of the compiled document root.
|
||||
"""
|
||||
# Get the path for the output file.
|
||||
output_path = utils.get_page_path(constants.DOCUMENT_ROOT)
|
||||
output_path = utils.get_page_path(constants.PageNames.DOCUMENT_ROOT)
|
||||
|
||||
# Create the document root.
|
||||
document_root = utils.create_document_root(head_components)
|
||||
@ -330,7 +330,7 @@ def compile_tailwind(
|
||||
The compiled Tailwind config.
|
||||
"""
|
||||
# Get the path for the output file.
|
||||
output_path = constants.TAILWIND_CONFIG
|
||||
output_path = constants.Tailwind.CONFIG
|
||||
|
||||
# Compile the config.
|
||||
code = _compile_tailwind(config)
|
||||
@ -339,4 +339,4 @@ def compile_tailwind(
|
||||
|
||||
def purge_web_pages_dir():
|
||||
"""Empty out .web directory."""
|
||||
utils.empty_dir(constants.WEB_PAGES_DIR, keep_files=["_app.js"])
|
||||
utils.empty_dir(constants.Dirs.WEB_PAGES, keep_files=["_app.js"])
|
||||
|
@ -19,26 +19,26 @@ class ReflexJinjaEnvironment(Environment):
|
||||
)
|
||||
self.filters["json_dumps"] = json_dumps
|
||||
self.filters["react_setter"] = lambda state: f"set{state.capitalize()}"
|
||||
self.loader = FileSystemLoader(constants.JINJA_TEMPLATE_DIR)
|
||||
self.loader = FileSystemLoader(constants.Templates.Dirs.JINJA_TEMPLATE)
|
||||
self.globals["const"] = {
|
||||
"socket": constants.SOCKET,
|
||||
"result": constants.RESULT,
|
||||
"router": constants.ROUTER,
|
||||
"socket": constants.CompileVars.SOCKET,
|
||||
"result": constants.CompileVars.RESULT,
|
||||
"router": constants.CompileVars.ROUTER,
|
||||
"event_endpoint": constants.Endpoint.EVENT.name,
|
||||
"events": constants.EVENTS,
|
||||
"state": constants.STATE,
|
||||
"final": constants.FINAL,
|
||||
"processing": constants.PROCESSING,
|
||||
"events": constants.CompileVars.EVENTS,
|
||||
"state": constants.CompileVars.STATE,
|
||||
"final": constants.CompileVars.FINAL,
|
||||
"processing": constants.CompileVars.PROCESSING,
|
||||
"initial_result": {
|
||||
constants.STATE: None,
|
||||
constants.EVENTS: [],
|
||||
constants.FINAL: True,
|
||||
constants.PROCESSING: False,
|
||||
constants.CompileVars.STATE: None,
|
||||
constants.CompileVars.EVENTS: [],
|
||||
constants.CompileVars.FINAL: True,
|
||||
constants.CompileVars.PROCESSING: False,
|
||||
},
|
||||
"color_mode": constants.COLOR_MODE,
|
||||
"toggle_color_mode": constants.TOGGLE_COLOR_MODE,
|
||||
"use_color_mode": constants.USE_COLOR_MODE,
|
||||
"hydrate": constants.HYDRATE,
|
||||
"color_mode": constants.ColorMode.NAME,
|
||||
"toggle_color_mode": constants.ColorMode.TOGGLE,
|
||||
"use_color_mode": constants.ColorMode.USE,
|
||||
"hydrate": constants.CompileVars.HYDRATE,
|
||||
}
|
||||
|
||||
|
||||
|
@ -93,7 +93,7 @@ def compile_imports(imports: imports.ImportDict) -> list[dict]:
|
||||
default, rest = compile_import_statement(fields)
|
||||
|
||||
# prevent lib from being rendered on the page if all imports are non rendered kind
|
||||
if all({not f.render for f in fields}): # type: ignore
|
||||
if not any({f.render for f in fields}): # type: ignore
|
||||
continue
|
||||
|
||||
if not lib:
|
||||
@ -104,9 +104,7 @@ def compile_imports(imports: imports.ImportDict) -> list[dict]:
|
||||
continue
|
||||
|
||||
# remove the version before rendering the package imports
|
||||
lib, at, version = lib.rpartition("@")
|
||||
if not lib:
|
||||
lib = at + version
|
||||
lib = format.format_library_name(lib)
|
||||
|
||||
import_dicts.append(get_import_dict(lib, default, rest))
|
||||
return import_dicts
|
||||
@ -313,7 +311,7 @@ def get_page_path(path: str) -> str:
|
||||
Returns:
|
||||
The path of the compiled JS file.
|
||||
"""
|
||||
return os.path.join(constants.WEB_PAGES_DIR, path + constants.JS_EXT)
|
||||
return os.path.join(constants.Dirs.WEB_PAGES, path + constants.Ext.JS)
|
||||
|
||||
|
||||
def get_theme_path() -> str:
|
||||
@ -322,7 +320,9 @@ def get_theme_path() -> str:
|
||||
Returns:
|
||||
The path of the theme style.
|
||||
"""
|
||||
return os.path.join(constants.WEB_UTILS_DIR, constants.THEME + constants.JS_EXT)
|
||||
return os.path.join(
|
||||
constants.Dirs.WEB_UTILS, constants.PageNames.THEME + constants.Ext.JS
|
||||
)
|
||||
|
||||
|
||||
def get_root_stylesheet_path() -> str:
|
||||
@ -332,7 +332,7 @@ def get_root_stylesheet_path() -> str:
|
||||
The path of the app root file.
|
||||
"""
|
||||
return os.path.join(
|
||||
constants.STYLES_DIR, constants.STYLESHEET_ROOT + constants.CSS_EXT
|
||||
constants.STYLES_DIR, constants.PageNames.STYLESHEET_ROOT + constants.Ext.CSS
|
||||
)
|
||||
|
||||
|
||||
@ -342,7 +342,7 @@ def get_context_path() -> str:
|
||||
Returns:
|
||||
The path of the context module.
|
||||
"""
|
||||
return os.path.join(constants.WEB_UTILS_DIR, "context" + constants.JS_EXT)
|
||||
return os.path.join(constants.Dirs.WEB_UTILS, "context" + constants.Ext.JS)
|
||||
|
||||
|
||||
def get_components_path() -> str:
|
||||
@ -351,7 +351,7 @@ def get_components_path() -> str:
|
||||
Returns:
|
||||
The path of the compiled components.
|
||||
"""
|
||||
return os.path.join(constants.WEB_UTILS_DIR, "components" + constants.JS_EXT)
|
||||
return os.path.join(constants.Dirs.WEB_UTILS, "components" + constants.Ext.JS)
|
||||
|
||||
|
||||
def get_asset_path(filename: str | None = None) -> str:
|
||||
@ -364,9 +364,9 @@ def get_asset_path(filename: str | None = None) -> str:
|
||||
The path of the asset.
|
||||
"""
|
||||
if filename is None:
|
||||
return constants.WEB_ASSETS_DIR
|
||||
return constants.Dirs.WEB_ASSETS
|
||||
else:
|
||||
return os.path.join(constants.WEB_ASSETS_DIR, filename)
|
||||
return os.path.join(constants.Dirs.WEB_ASSETS, filename)
|
||||
|
||||
|
||||
def add_meta(
|
||||
|
@ -7,10 +7,9 @@ from abc import ABC
|
||||
from functools import wraps
|
||||
from typing import Any, Callable, Dict, List, Optional, Set, Type, Union
|
||||
|
||||
from reflex import constants
|
||||
from reflex.base import Base
|
||||
from reflex.components.tags import Tag
|
||||
from reflex.constants import EventTriggers
|
||||
from reflex.constants import Dirs, EventTriggers
|
||||
from reflex.event import (
|
||||
EventChain,
|
||||
EventHandler,
|
||||
@ -762,7 +761,7 @@ class CustomComponent(Component):
|
||||
"""A custom user-defined component."""
|
||||
|
||||
# Use the components library.
|
||||
library = f"/{constants.COMPONENTS_PATH}"
|
||||
library = f"/{Dirs.COMPONENTS_PATH}"
|
||||
|
||||
# The function that creates the component.
|
||||
component_fn: Callable[..., Component] = Component.create
|
||||
|
@ -163,7 +163,7 @@ class Config(Base):
|
||||
telemetry_enabled: bool = True
|
||||
|
||||
# The bun path
|
||||
bun_path: str = constants.DEFAULT_BUN_PATH
|
||||
bun_path: str = constants.Bun.DEFAULT_PATH
|
||||
|
||||
# List of origins that are allowed to connect to the backend API.
|
||||
cors_allowed_origins: List[str] = ["*"]
|
||||
@ -284,11 +284,9 @@ def get_config(reload: bool = False) -> Config:
|
||||
Returns:
|
||||
The app config.
|
||||
"""
|
||||
from reflex.config import Config
|
||||
|
||||
sys.path.insert(0, os.getcwd())
|
||||
try:
|
||||
rxconfig = __import__(constants.CONFIG_MODULE)
|
||||
rxconfig = __import__(constants.Config.MODULE)
|
||||
if reload:
|
||||
importlib.reload(rxconfig)
|
||||
return rxconfig.config
|
||||
|
@ -1,444 +0,0 @@
|
||||
"""Constants used throughout the package."""
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import platform
|
||||
import re
|
||||
from enum import Enum
|
||||
from types import SimpleNamespace
|
||||
|
||||
from platformdirs import PlatformDirs
|
||||
|
||||
# importlib is only available for Python 3.8+ so we need the backport for Python 3.7
|
||||
try:
|
||||
from importlib import metadata
|
||||
except ImportError:
|
||||
import importlib_metadata as metadata # pyright: ignore[reportMissingImports]
|
||||
|
||||
IS_WINDOWS = platform.system() == "Windows"
|
||||
|
||||
|
||||
def get_fnm_name() -> str | None:
|
||||
"""Get the appropriate fnm executable name based on the current platform.
|
||||
|
||||
Returns:
|
||||
The fnm executable name for the current platform.
|
||||
"""
|
||||
platform_os = platform.system()
|
||||
|
||||
if platform_os == "Windows":
|
||||
return "fnm-windows"
|
||||
elif platform_os == "Darwin":
|
||||
return "fnm-macos"
|
||||
elif platform_os == "Linux":
|
||||
machine = platform.machine()
|
||||
if machine == "arm" or machine.startswith("armv7"):
|
||||
return "fnm-arm32"
|
||||
elif machine.startswith("aarch") or machine.startswith("armv8"):
|
||||
return "fnm-arm64"
|
||||
return "fnm-linux"
|
||||
return None
|
||||
|
||||
|
||||
# App names and versions.
|
||||
# The name of the Reflex package.
|
||||
MODULE_NAME = "reflex"
|
||||
# The current version of Reflex.
|
||||
VERSION = metadata.version(MODULE_NAME)
|
||||
|
||||
# Files and directories used to init a new project.
|
||||
# The directory to store reflex dependencies.
|
||||
REFLEX_DIR = (
|
||||
# on windows, we use C:/Users/<username>/AppData/Local/reflex.
|
||||
# on macOS, we use ~/Library/Application Support/reflex.
|
||||
# on linux, we use ~/.local/share/reflex.
|
||||
PlatformDirs(MODULE_NAME, False).user_data_dir
|
||||
)
|
||||
# The root directory of the reflex library.
|
||||
ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
# The name of the assets directory.
|
||||
APP_ASSETS_DIR = "assets"
|
||||
# The template directory used during reflex init.
|
||||
TEMPLATE_DIR = os.path.join(ROOT_DIR, MODULE_NAME, ".templates")
|
||||
# The web subdirectory of the template directory.
|
||||
WEB_TEMPLATE_DIR = os.path.join(TEMPLATE_DIR, "web")
|
||||
# The assets subdirectory of the template directory.
|
||||
ASSETS_TEMPLATE_DIR = os.path.join(TEMPLATE_DIR, APP_ASSETS_DIR)
|
||||
# The jinja template directory.
|
||||
JINJA_TEMPLATE_DIR = os.path.join(TEMPLATE_DIR, "jinja")
|
||||
|
||||
# Bun config.
|
||||
# The Bun version.
|
||||
BUN_VERSION = "0.7.3"
|
||||
# Min Bun Version
|
||||
MIN_BUN_VERSION = "0.7.0"
|
||||
# The directory to store the bun.
|
||||
BUN_ROOT_PATH = os.path.join(REFLEX_DIR, "bun")
|
||||
# Default bun path.
|
||||
DEFAULT_BUN_PATH = os.path.join(BUN_ROOT_PATH, "bin", "bun")
|
||||
# URL to bun install script.
|
||||
BUN_INSTALL_URL = "https://bun.sh/install"
|
||||
|
||||
# FNM / Node config.
|
||||
# The FNM version.
|
||||
FNM_VERSION = "1.35.1"
|
||||
# The Node version.
|
||||
NODE_VERSION = "18.17.0"
|
||||
# The minimum required node version.
|
||||
NODE_VERSION_MIN = "16.8.0"
|
||||
# The directory to store fnm.
|
||||
FNM_DIR = os.path.join(REFLEX_DIR, "fnm")
|
||||
FNM_FILENAME = get_fnm_name()
|
||||
# The fnm executable binary.
|
||||
FNM_EXE = os.path.join(FNM_DIR, "fnm.exe" if IS_WINDOWS else "fnm")
|
||||
# The node bin path.
|
||||
NODE_BIN_PATH = os.path.join(
|
||||
FNM_DIR,
|
||||
"node-versions",
|
||||
f"v{NODE_VERSION}",
|
||||
"installation",
|
||||
"bin" if not IS_WINDOWS else "",
|
||||
)
|
||||
# The default path where node is installed.
|
||||
NODE_PATH = os.path.join(NODE_BIN_PATH, "node.exe" if IS_WINDOWS else "node")
|
||||
# The default path where npm is installed.
|
||||
NPM_PATH = os.path.join(NODE_BIN_PATH, "npm")
|
||||
# The URL to the fnm release binary
|
||||
FNM_INSTALL_URL = (
|
||||
f"https://github.com/Schniz/fnm/releases/download/v{FNM_VERSION}/{FNM_FILENAME}.zip"
|
||||
)
|
||||
# The frontend directories in a project.
|
||||
# The web folder where the NextJS app is compiled to.
|
||||
WEB_DIR = ".web"
|
||||
# The name of the utils file.
|
||||
UTILS_DIR = "utils"
|
||||
# The name of the output static directory.
|
||||
STATIC_DIR = "_static"
|
||||
# The name of the state file.
|
||||
STATE_PATH = "/".join([UTILS_DIR, "state"])
|
||||
# The name of the components file.
|
||||
COMPONENTS_PATH = "/".join([UTILS_DIR, "components"])
|
||||
# The directory where the app pages are compiled to.
|
||||
WEB_PAGES_DIR = os.path.join(WEB_DIR, "pages")
|
||||
# The directory where the static build is located.
|
||||
WEB_STATIC_DIR = os.path.join(WEB_DIR, STATIC_DIR)
|
||||
# The directory where the utils file is located.
|
||||
WEB_UTILS_DIR = os.path.join(WEB_DIR, UTILS_DIR)
|
||||
# The directory where the assets are located.
|
||||
WEB_ASSETS_DIR = os.path.join(WEB_DIR, "public")
|
||||
# The directory where styles are located.
|
||||
STYLES_DIR = os.path.join(WEB_DIR, "styles")
|
||||
# The Tailwind config.
|
||||
TAILWIND_CONFIG = os.path.join(WEB_DIR, "tailwind.config.js")
|
||||
# Default Tailwind content paths
|
||||
TAILWIND_CONTENT = ["./pages/**/*.{js,ts,jsx,tsx}"]
|
||||
# Relative tailwind style path to root stylesheet in STYLES_DIR.
|
||||
TAILWIND_ROOT_STYLE_PATH = "./tailwind.css"
|
||||
# The Tailwindcss version
|
||||
TAILWIND_VERSION = "tailwindcss@^3.3.2"
|
||||
# The package json file
|
||||
PACKAGE_JSON_PATH = os.path.join(WEB_DIR, "package.json")
|
||||
# The NextJS config file
|
||||
NEXT_CONFIG_FILE = "next.config.js"
|
||||
# The sitemap config file.
|
||||
SITEMAP_CONFIG_FILE = os.path.join(WEB_DIR, "next-sitemap.config.js")
|
||||
# The node modules directory.
|
||||
NODE_MODULES = "node_modules"
|
||||
# The package lock file.
|
||||
PACKAGE_LOCK = "package-lock.json"
|
||||
# The reflex json file.
|
||||
REFLEX_JSON = os.path.join(WEB_DIR, "reflex.json")
|
||||
# The env json file.
|
||||
ENV_JSON = os.path.join(WEB_DIR, "env.json")
|
||||
|
||||
# Compiler variables.
|
||||
# The extension for compiled Javascript files.
|
||||
JS_EXT = ".js"
|
||||
# The extension for python files.
|
||||
PY_EXT = ".py"
|
||||
# The extension for css files.
|
||||
CSS_EXT = ".css"
|
||||
# The expected variable name where the rx.App is stored.
|
||||
APP_VAR = "app"
|
||||
# The expected variable name where the API object is stored for deployment.
|
||||
API_VAR = "api"
|
||||
# The name of the router variable.
|
||||
ROUTER = "router"
|
||||
# The name of the socket variable.
|
||||
SOCKET = "socket"
|
||||
# The name of the variable to hold API results.
|
||||
RESULT = "result"
|
||||
# The name of the final variable.
|
||||
FINAL = "final"
|
||||
# The name of the process variable.
|
||||
PROCESSING = "processing"
|
||||
# The name of the state variable.
|
||||
STATE = "state"
|
||||
# The name of the events variable.
|
||||
EVENTS = "events"
|
||||
# The name of the initial hydrate event.
|
||||
HYDRATE = "hydrate"
|
||||
# The name of the is_hydrated variable.
|
||||
IS_HYDRATED = "is_hydrated"
|
||||
# The name of the index page.
|
||||
INDEX_ROUTE = "index"
|
||||
# The name of the app root page.
|
||||
APP_ROOT = "_app"
|
||||
# The root stylesheet filename.
|
||||
STYLESHEET_ROOT = "styles"
|
||||
# The name of the document root page.
|
||||
DOCUMENT_ROOT = "_document"
|
||||
# The name of the theme page.
|
||||
THEME = "theme"
|
||||
# The prefix used to create setters for state vars.
|
||||
SETTER_PREFIX = "set_"
|
||||
# The name of the frontend zip during deployment.
|
||||
FRONTEND_ZIP = "frontend.zip"
|
||||
# The name of the backend zip during deployment.
|
||||
BACKEND_ZIP = "backend.zip"
|
||||
# The default title to show for Reflex apps.
|
||||
DEFAULT_TITLE = "Reflex App"
|
||||
# The default description to show for Reflex apps.
|
||||
DEFAULT_DESCRIPTION = "A Reflex app."
|
||||
# The default image to show for Reflex apps.
|
||||
DEFAULT_IMAGE = "favicon.ico"
|
||||
# The default meta list to show for Reflex apps.
|
||||
DEFAULT_META_LIST = []
|
||||
|
||||
# The gitignore file.
|
||||
GITIGNORE_FILE = ".gitignore"
|
||||
# Files to gitignore.
|
||||
DEFAULT_GITIGNORE = {WEB_DIR, "*.db", "__pycache__/", "*.py[cod]"}
|
||||
# The name of the reflex config module.
|
||||
CONFIG_MODULE = "rxconfig"
|
||||
# The python config file.
|
||||
CONFIG_FILE = f"{CONFIG_MODULE}{PY_EXT}"
|
||||
# The previous config file.
|
||||
OLD_CONFIG_FILE = f"pcconfig{PY_EXT}"
|
||||
# The deployment URL.
|
||||
PRODUCTION_BACKEND_URL = "https://{username}-{app_name}.api.pynecone.app"
|
||||
# Token expiration time in seconds.
|
||||
TOKEN_EXPIRATION = 60 * 60
|
||||
# Maximum time in milliseconds that a state can be locked for exclusive access.
|
||||
LOCK_EXPIRATION = 10000
|
||||
|
||||
# Testing variables.
|
||||
# Testing os env set by pytest when running a test case.
|
||||
PYTEST_CURRENT_TEST = "PYTEST_CURRENT_TEST"
|
||||
|
||||
|
||||
# Env modes
|
||||
class Env(str, Enum):
|
||||
"""The environment modes."""
|
||||
|
||||
DEV = "dev"
|
||||
PROD = "prod"
|
||||
|
||||
|
||||
# Log levels
|
||||
class LogLevel(str, Enum):
|
||||
"""The log levels."""
|
||||
|
||||
DEBUG = "debug"
|
||||
INFO = "info"
|
||||
WARNING = "warning"
|
||||
ERROR = "error"
|
||||
CRITICAL = "critical"
|
||||
|
||||
def __le__(self, other: LogLevel) -> bool:
|
||||
"""Compare log levels.
|
||||
|
||||
Args:
|
||||
other: The other log level.
|
||||
|
||||
Returns:
|
||||
True if the log level is less than or equal to the other log level.
|
||||
"""
|
||||
levels = list(LogLevel)
|
||||
return levels.index(self) <= levels.index(other)
|
||||
|
||||
|
||||
# Templates
|
||||
class Template(str, Enum):
|
||||
"""The templates to use for the app."""
|
||||
|
||||
DEFAULT = "default"
|
||||
COUNTER = "counter"
|
||||
|
||||
|
||||
class Endpoint(Enum):
|
||||
"""Endpoints for the reflex backend API."""
|
||||
|
||||
PING = "ping"
|
||||
EVENT = "_event"
|
||||
UPLOAD = "_upload"
|
||||
|
||||
def __str__(self) -> str:
|
||||
"""Get the string representation of the endpoint.
|
||||
|
||||
Returns:
|
||||
The path for the endpoint.
|
||||
"""
|
||||
return f"/{self.value}"
|
||||
|
||||
def get_url(self) -> str:
|
||||
"""Get the URL for the endpoint.
|
||||
|
||||
Returns:
|
||||
The full URL for the endpoint.
|
||||
"""
|
||||
# Import here to avoid circular imports.
|
||||
from reflex.config import get_config
|
||||
|
||||
# Get the API URL from the config.
|
||||
config = get_config()
|
||||
url = "".join([config.api_url, str(self)])
|
||||
|
||||
# The event endpoint is a websocket.
|
||||
if self == Endpoint.EVENT:
|
||||
# Replace the protocol with ws.
|
||||
url = url.replace("https://", "wss://").replace("http://", "ws://")
|
||||
|
||||
# Return the url.
|
||||
return url
|
||||
|
||||
|
||||
class SocketEvent(Enum):
|
||||
"""Socket events sent by the reflex backend API."""
|
||||
|
||||
PING = "ping"
|
||||
EVENT = "event"
|
||||
|
||||
def __str__(self) -> str:
|
||||
"""Get the string representation of the event name.
|
||||
|
||||
Returns:
|
||||
The event name string.
|
||||
"""
|
||||
return str(self.value)
|
||||
|
||||
|
||||
class RouteArgType(SimpleNamespace):
|
||||
"""Type of dynamic route arg extracted from URI route."""
|
||||
|
||||
# Typecast to str is needed for Enum to work.
|
||||
SINGLE = str("arg_single")
|
||||
LIST = str("arg_list")
|
||||
|
||||
|
||||
# the name of the backend var containing path and client information
|
||||
ROUTER_DATA = "router_data"
|
||||
|
||||
|
||||
class RouteVar(SimpleNamespace):
|
||||
"""Names of variables used in the router_data dict stored in State."""
|
||||
|
||||
CLIENT_IP = "ip"
|
||||
CLIENT_TOKEN = "token"
|
||||
HEADERS = "headers"
|
||||
PATH = "pathname"
|
||||
ORIGIN = "asPath"
|
||||
SESSION_ID = "sid"
|
||||
QUERY = "query"
|
||||
COOKIE = "cookie"
|
||||
|
||||
|
||||
class RouteRegex(SimpleNamespace):
|
||||
"""Regex used for extracting route args in route."""
|
||||
|
||||
ARG = re.compile(r"\[(?!\.)([^\[\]]+)\]")
|
||||
# group return the catchall pattern (i.e. "[[..slug]]")
|
||||
CATCHALL = re.compile(r"(\[?\[\.{3}(?![0-9]).*\]?\])")
|
||||
# group return the arg name (i.e. "slug")
|
||||
STRICT_CATCHALL = re.compile(r"\[\.{3}([a-zA-Z_][\w]*)\]")
|
||||
# group return the arg name (i.e. "slug")
|
||||
OPT_CATCHALL = re.compile(r"\[\[\.{3}([a-zA-Z_][\w]*)\]\]")
|
||||
|
||||
|
||||
class PackageJsonCommands(SimpleNamespace):
|
||||
"""Commands used in package.json file."""
|
||||
|
||||
DEV = "next dev"
|
||||
EXPORT = "next build && next export -o _static"
|
||||
EXPORT_SITEMAP = "next build && next-sitemap && next export -o _static"
|
||||
PROD = "next start"
|
||||
|
||||
|
||||
PACKAGE_DEPENDENCIES = {
|
||||
"@chakra-ui/react": "^2.6.0",
|
||||
"@chakra-ui/system": "^2.5.6",
|
||||
"@emotion/react": "^11.10.6",
|
||||
"@emotion/styled": "^11.10.6",
|
||||
"axios": "^1.4.0",
|
||||
"chakra-react-select": "^4.6.0",
|
||||
"focus-visible": "^5.2.0",
|
||||
"json5": "^2.2.3",
|
||||
"next": "^13.3.1",
|
||||
"next-sitemap": "^4.1.8",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"socket.io-client": "^4.6.1",
|
||||
"universal-cookie": "^4.0.4",
|
||||
}
|
||||
PACKAGE_DEV_DEPENDENCIES = {
|
||||
"autoprefixer": "^10.4.14",
|
||||
"postcss": "^8.4.24",
|
||||
}
|
||||
|
||||
# 404 variables
|
||||
SLUG_404 = "404"
|
||||
TITLE_404 = "404 - Not Found"
|
||||
FAVICON_404 = "favicon.ico"
|
||||
DESCRIPTION_404 = "The page was not found"
|
||||
ROUTE_NOT_FOUND = "routeNotFound"
|
||||
|
||||
# Color mode variables
|
||||
USE_COLOR_MODE = "useColorMode"
|
||||
COLOR_MODE = "colorMode"
|
||||
TOGGLE_COLOR_MODE = "toggleColorMode"
|
||||
|
||||
# Server socket configuration variables
|
||||
POLLING_MAX_HTTP_BUFFER_SIZE = 1000 * 1000
|
||||
PING_INTERVAL = 25
|
||||
PING_TIMEOUT = 120
|
||||
|
||||
# Alembic migrations
|
||||
ALEMBIC_CONFIG = os.environ.get("ALEMBIC_CONFIG", "alembic.ini")
|
||||
|
||||
# Keys in the client_side_storage dict
|
||||
COOKIES = "cookies"
|
||||
LOCAL_STORAGE = "local_storage"
|
||||
|
||||
|
||||
class EventTriggers(SimpleNamespace):
|
||||
"""All trigger names used in Reflex."""
|
||||
|
||||
ON_FOCUS = "on_focus"
|
||||
ON_BLUR = "on_blur"
|
||||
ON_CANCEL = "on_cancel"
|
||||
ON_CLICK = "on_click"
|
||||
ON_CHANGE = "on_change"
|
||||
ON_CHANGE_END = "on_change_end"
|
||||
ON_CHANGE_START = "on_change_start"
|
||||
ON_COMPLETE = "on_complete"
|
||||
ON_CONTEXT_MENU = "on_context_menu"
|
||||
ON_DOUBLE_CLICK = "on_double_click"
|
||||
ON_DROP = "on_drop"
|
||||
ON_EDIT = "on_edit"
|
||||
ON_KEY_DOWN = "on_key_down"
|
||||
ON_KEY_UP = "on_key_up"
|
||||
ON_MOUSE_DOWN = "on_mouse_down"
|
||||
ON_MOUSE_ENTER = "on_mouse_enter"
|
||||
ON_MOUSE_LEAVE = "on_mouse_leave"
|
||||
ON_MOUSE_MOVE = "on_mouse_move"
|
||||
ON_MOUSE_OUT = "on_mouse_out"
|
||||
ON_MOUSE_OVER = "on_mouse_over"
|
||||
ON_MOUSE_UP = "on_mouse_up"
|
||||
ON_SCROLL = "on_scroll"
|
||||
ON_SUBMIT = "on_submit"
|
||||
ON_MOUNT = "on_mount"
|
||||
ON_UNMOUNT = "on_unmount"
|
||||
|
||||
|
||||
# If this env var is set to "yes", App.compile will be a no-op
|
||||
SKIP_COMPILE_ENV_VAR = "__REFLEX_SKIP_COMPILE"
|
92
reflex/constants/__init__.py
Normal file
92
reflex/constants/__init__.py
Normal file
@ -0,0 +1,92 @@
|
||||
"""The constants package."""
|
||||
|
||||
from .base import (
|
||||
COOKIES,
|
||||
IS_WINDOWS,
|
||||
LOCAL_STORAGE,
|
||||
POLLING_MAX_HTTP_BUFFER_SIZE,
|
||||
PYTEST_CURRENT_TEST,
|
||||
SKIP_COMPILE_ENV_VAR,
|
||||
ColorMode,
|
||||
Dirs,
|
||||
Env,
|
||||
LogLevel,
|
||||
Next,
|
||||
Ping,
|
||||
Reflex,
|
||||
Templates,
|
||||
)
|
||||
from .compiler import (
|
||||
SETTER_PREFIX,
|
||||
CompileVars,
|
||||
ComponentName,
|
||||
Ext,
|
||||
PageNames,
|
||||
)
|
||||
from .config import (
|
||||
ALEMBIC_CONFIG,
|
||||
PRODUCTION_BACKEND_URL,
|
||||
Config,
|
||||
Expiration,
|
||||
GitIgnore,
|
||||
)
|
||||
from .event import Endpoint, EventTriggers, SocketEvent
|
||||
from .installer import (
|
||||
Bun,
|
||||
Fnm,
|
||||
Node,
|
||||
PackageJson,
|
||||
)
|
||||
from .route import (
|
||||
ROUTE_NOT_FOUND,
|
||||
ROUTER_DATA,
|
||||
DefaultPage,
|
||||
Page404,
|
||||
RouteArgType,
|
||||
RouteRegex,
|
||||
RouteVar,
|
||||
)
|
||||
from .style import STYLES_DIR, Tailwind
|
||||
|
||||
__ALL__ = [
|
||||
ALEMBIC_CONFIG,
|
||||
Bun,
|
||||
ColorMode,
|
||||
Config,
|
||||
COOKIES,
|
||||
ComponentName,
|
||||
DefaultPage,
|
||||
Dirs,
|
||||
Endpoint,
|
||||
Env,
|
||||
EventTriggers,
|
||||
Expiration,
|
||||
Ext,
|
||||
Fnm,
|
||||
GitIgnore,
|
||||
IS_WINDOWS,
|
||||
LOCAL_STORAGE,
|
||||
LogLevel,
|
||||
Next,
|
||||
Node,
|
||||
PackageJson,
|
||||
PageNames,
|
||||
Page404,
|
||||
Ping,
|
||||
POLLING_MAX_HTTP_BUFFER_SIZE,
|
||||
PYTEST_CURRENT_TEST,
|
||||
PRODUCTION_BACKEND_URL,
|
||||
Reflex,
|
||||
RouteVar,
|
||||
RouteRegex,
|
||||
RouteArgType,
|
||||
ROUTER_DATA,
|
||||
ROUTE_NOT_FOUND,
|
||||
SETTER_PREFIX,
|
||||
SKIP_COMPILE_ENV_VAR,
|
||||
SocketEvent,
|
||||
STYLES_DIR,
|
||||
Tailwind,
|
||||
Templates,
|
||||
CompileVars,
|
||||
]
|
174
reflex/constants/base.py
Normal file
174
reflex/constants/base.py
Normal file
@ -0,0 +1,174 @@
|
||||
"""Base file for constants that don't fit any other categories."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import platform
|
||||
from enum import Enum
|
||||
from types import SimpleNamespace
|
||||
|
||||
from platformdirs import PlatformDirs
|
||||
|
||||
# importlib is only available for Python 3.8+ so we need the backport for Python 3.7
|
||||
try:
|
||||
from importlib import metadata
|
||||
except ImportError:
|
||||
import importlib_metadata as metadata # pyright: ignore[reportMissingImports]
|
||||
|
||||
|
||||
IS_WINDOWS = platform.system() == "Windows"
|
||||
|
||||
|
||||
class Dirs(SimpleNamespace):
|
||||
"""Various directories/paths used by Reflex."""
|
||||
|
||||
# The frontend directories in a project.
|
||||
# The web folder where the NextJS app is compiled to.
|
||||
WEB = ".web"
|
||||
# The name of the assets directory.
|
||||
APP_ASSETS = "assets"
|
||||
# The name of the utils file.
|
||||
UTILS = "utils"
|
||||
# The name of the output static directory.
|
||||
STATIC = "_static"
|
||||
# The name of the state file.
|
||||
STATE_PATH = "/".join([UTILS, "state"])
|
||||
# The name of the components file.
|
||||
COMPONENTS_PATH = "/".join([UTILS, "components"])
|
||||
# The directory where the app pages are compiled to.
|
||||
WEB_PAGES = os.path.join(WEB, "pages")
|
||||
# The directory where the static build is located.
|
||||
WEB_STATIC = os.path.join(WEB, STATIC)
|
||||
# The directory where the utils file is located.
|
||||
WEB_UTILS = os.path.join(WEB, UTILS)
|
||||
# The directory where the assets are located.
|
||||
WEB_ASSETS = os.path.join(WEB, "public")
|
||||
# The env json file.
|
||||
ENV_JSON = os.path.join(WEB, "env.json")
|
||||
|
||||
|
||||
class Reflex(SimpleNamespace):
|
||||
"""Base constants concerning Reflex."""
|
||||
|
||||
# App names and versions.
|
||||
# The name of the Reflex package.
|
||||
MODULE_NAME = "reflex"
|
||||
# The current version of Reflex.
|
||||
VERSION = metadata.version(MODULE_NAME)
|
||||
|
||||
# The reflex json file.
|
||||
JSON = os.path.join(Dirs.WEB, "reflex.json")
|
||||
|
||||
# Files and directories used to init a new project.
|
||||
# The directory to store reflex dependencies.
|
||||
DIR = (
|
||||
# on windows, we use C:/Users/<username>/AppData/Local/reflex.
|
||||
# on macOS, we use ~/Library/Application Support/reflex.
|
||||
# on linux, we use ~/.local/share/reflex.
|
||||
PlatformDirs(MODULE_NAME, False).user_data_dir
|
||||
)
|
||||
# The root directory of the reflex library.
|
||||
|
||||
ROOT_DIR = os.path.dirname(
|
||||
os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
)
|
||||
|
||||
|
||||
class Templates(SimpleNamespace):
|
||||
"""Constants related to Templates."""
|
||||
|
||||
class Kind(str, Enum):
|
||||
"""The templates to use for the app."""
|
||||
|
||||
DEFAULT = "default"
|
||||
COUNTER = "counter"
|
||||
|
||||
class Dirs(SimpleNamespace):
|
||||
"""Folders used by the template system of Reflex."""
|
||||
|
||||
# The template directory used during reflex init.
|
||||
BASE = os.path.join(Reflex.ROOT_DIR, Reflex.MODULE_NAME, ".templates")
|
||||
# The web subdirectory of the template directory.
|
||||
WEB_TEMPLATE = os.path.join(BASE, "web")
|
||||
# The assets subdirectory of the template directory.
|
||||
ASSETS_TEMPLATE = os.path.join(BASE, Dirs.APP_ASSETS)
|
||||
# The jinja template directory.
|
||||
JINJA_TEMPLATE = os.path.join(BASE, "jinja")
|
||||
|
||||
|
||||
class Next(SimpleNamespace):
|
||||
"""Constants related to NextJS."""
|
||||
|
||||
# The NextJS config file
|
||||
CONFIG_FILE = "next.config.js"
|
||||
# The sitemap config file.
|
||||
SITEMAP_CONFIG_FILE = os.path.join(Dirs.WEB, "next-sitemap.config.js")
|
||||
# The node modules directory.
|
||||
NODE_MODULES = "node_modules"
|
||||
# The package lock file.
|
||||
PACKAGE_LOCK = "package-lock.json"
|
||||
|
||||
|
||||
# Color mode variables
|
||||
class ColorMode(SimpleNamespace):
|
||||
"""Constants related to ColorMode."""
|
||||
|
||||
NAME = "colorMode"
|
||||
USE = "useColorMode"
|
||||
TOGGLE = "toggleColorMode"
|
||||
|
||||
|
||||
# Env modes
|
||||
class Env(str, Enum):
|
||||
"""The environment modes."""
|
||||
|
||||
DEV = "dev"
|
||||
PROD = "prod"
|
||||
|
||||
|
||||
# Log levels
|
||||
class LogLevel(str, Enum):
|
||||
"""The log levels."""
|
||||
|
||||
DEBUG = "debug"
|
||||
INFO = "info"
|
||||
WARNING = "warning"
|
||||
ERROR = "error"
|
||||
CRITICAL = "critical"
|
||||
|
||||
def __le__(self, other: LogLevel) -> bool:
|
||||
"""Compare log levels.
|
||||
|
||||
Args:
|
||||
other: The other log level.
|
||||
|
||||
Returns:
|
||||
True if the log level is less than or equal to the other log level.
|
||||
"""
|
||||
levels = list(LogLevel)
|
||||
return levels.index(self) <= levels.index(other)
|
||||
|
||||
|
||||
# Server socket configuration variables
|
||||
POLLING_MAX_HTTP_BUFFER_SIZE = 1000 * 1000
|
||||
|
||||
|
||||
class Ping(SimpleNamespace):
|
||||
"""PING constants."""
|
||||
|
||||
# The 'ping' interval
|
||||
INTERVAL = 25
|
||||
# The 'ping' timeout
|
||||
TIMEOUT = 120
|
||||
|
||||
|
||||
# Keys in the client_side_storage dict
|
||||
COOKIES = "cookies"
|
||||
LOCAL_STORAGE = "local_storage"
|
||||
|
||||
# If this env var is set to "yes", App.compile will be a no-op
|
||||
SKIP_COMPILE_ENV_VAR = "__REFLEX_SKIP_COMPILE"
|
||||
|
||||
# Testing variables.
|
||||
# Testing os env set by pytest when running a test case.
|
||||
PYTEST_CURRENT_TEST = "PYTEST_CURRENT_TEST"
|
76
reflex/constants/compiler.py
Normal file
76
reflex/constants/compiler.py
Normal file
@ -0,0 +1,76 @@
|
||||
"""Compiler variables."""
|
||||
from enum import Enum
|
||||
from types import SimpleNamespace
|
||||
|
||||
# The prefix used to create setters for state vars.
|
||||
SETTER_PREFIX = "set_"
|
||||
|
||||
|
||||
class Ext(SimpleNamespace):
|
||||
"""Extension used in Reflex."""
|
||||
|
||||
# The extension for JS files.
|
||||
JS = ".js"
|
||||
# The extension for python files.
|
||||
PY = ".py"
|
||||
# The extension for css files.
|
||||
CSS = ".css"
|
||||
# The extension for zip files.
|
||||
ZIP = ".zip"
|
||||
|
||||
|
||||
class CompileVars(SimpleNamespace):
|
||||
"""The variables used during compilation."""
|
||||
|
||||
# The expected variable name where the rx.App is stored.
|
||||
APP = "app"
|
||||
# The expected variable name where the API object is stored for deployment.
|
||||
API = "api"
|
||||
# The name of the router variable.
|
||||
ROUTER = "router"
|
||||
# The name of the socket variable.
|
||||
SOCKET = "socket"
|
||||
# The name of the variable to hold API results.
|
||||
RESULT = "result"
|
||||
# The name of the final variable.
|
||||
FINAL = "final"
|
||||
# The name of the process variable.
|
||||
PROCESSING = "processing"
|
||||
# The name of the state variable.
|
||||
STATE = "state"
|
||||
# The name of the events variable.
|
||||
EVENTS = "events"
|
||||
# The name of the initial hydrate event.
|
||||
HYDRATE = "hydrate"
|
||||
# The name of the is_hydrated variable.
|
||||
IS_HYDRATED = "is_hydrated"
|
||||
|
||||
|
||||
class PageNames(SimpleNamespace):
|
||||
"""The name of basic pages deployed in NextJS."""
|
||||
|
||||
# The name of the index page.
|
||||
INDEX_ROUTE = "index"
|
||||
# The name of the app root page.
|
||||
APP_ROOT = "_app"
|
||||
# The root stylesheet filename.
|
||||
STYLESHEET_ROOT = "styles"
|
||||
# The name of the document root page.
|
||||
DOCUMENT_ROOT = "_document"
|
||||
# The name of the theme page.
|
||||
THEME = "theme"
|
||||
|
||||
|
||||
class ComponentName(Enum):
|
||||
"""Component names."""
|
||||
|
||||
BACKEND = "Backend"
|
||||
FRONTEND = "Frontend"
|
||||
|
||||
def zip(self):
|
||||
"""Give the zip filename for the component.
|
||||
|
||||
Returns:
|
||||
The lower-case filename with zip extension.
|
||||
"""
|
||||
return self.value.lower() + Ext.ZIP
|
45
reflex/constants/config.py
Normal file
45
reflex/constants/config.py
Normal file
@ -0,0 +1,45 @@
|
||||
"""Config constants."""
|
||||
import os
|
||||
from types import SimpleNamespace
|
||||
|
||||
from reflex.constants.base import Dirs
|
||||
|
||||
from .compiler import Ext
|
||||
|
||||
# Alembic migrations
|
||||
ALEMBIC_CONFIG = os.environ.get("ALEMBIC_CONFIG", "alembic.ini")
|
||||
|
||||
|
||||
class Config(SimpleNamespace):
|
||||
"""Config constants."""
|
||||
|
||||
# The name of the reflex config module.
|
||||
MODULE = "rxconfig"
|
||||
# The python config file.
|
||||
FILE = f"{MODULE}{Ext.PY}"
|
||||
# The previous config file.
|
||||
PREVIOUS_FILE = f"pcconfig{Ext.PY}"
|
||||
|
||||
|
||||
class Expiration(SimpleNamespace):
|
||||
"""Expiration constants."""
|
||||
|
||||
# Token expiration time in seconds
|
||||
TOKEN = 60 * 60
|
||||
# Maximum time in milliseconds that a state can be locked for exclusive access.
|
||||
LOCK = 10000
|
||||
# The PING timeout
|
||||
PING = 120
|
||||
|
||||
|
||||
class GitIgnore(SimpleNamespace):
|
||||
"""Gitignore constants."""
|
||||
|
||||
# The gitignore file.
|
||||
FILE = ".gitignore"
|
||||
# Files to gitignore.
|
||||
DEFAULTS = {Dirs.WEB, "*.db", "__pycache__/", "*.py[cod]"}
|
||||
|
||||
|
||||
# The deployment URL.
|
||||
PRODUCTION_BACKEND_URL = "https://{username}-{app_name}.api.pynecone.app"
|
85
reflex/constants/event.py
Normal file
85
reflex/constants/event.py
Normal file
@ -0,0 +1,85 @@
|
||||
"""Event-related constants."""
|
||||
from enum import Enum
|
||||
from types import SimpleNamespace
|
||||
|
||||
|
||||
class Endpoint(Enum):
|
||||
"""Endpoints for the reflex backend API."""
|
||||
|
||||
PING = "ping"
|
||||
EVENT = "_event"
|
||||
UPLOAD = "_upload"
|
||||
|
||||
def __str__(self) -> str:
|
||||
"""Get the string representation of the endpoint.
|
||||
|
||||
Returns:
|
||||
The path for the endpoint.
|
||||
"""
|
||||
return f"/{self.value}"
|
||||
|
||||
def get_url(self) -> str:
|
||||
"""Get the URL for the endpoint.
|
||||
|
||||
Returns:
|
||||
The full URL for the endpoint.
|
||||
"""
|
||||
# Import here to avoid circular imports.
|
||||
from reflex.config import get_config
|
||||
|
||||
# Get the API URL from the config.
|
||||
config = get_config()
|
||||
url = "".join([config.api_url, str(self)])
|
||||
|
||||
# The event endpoint is a websocket.
|
||||
if self == Endpoint.EVENT:
|
||||
# Replace the protocol with ws.
|
||||
url = url.replace("https://", "wss://").replace("http://", "ws://")
|
||||
|
||||
# Return the url.
|
||||
return url
|
||||
|
||||
|
||||
class SocketEvent(SimpleNamespace):
|
||||
"""Socket events sent by the reflex backend API."""
|
||||
|
||||
PING = "ping"
|
||||
EVENT = "event"
|
||||
|
||||
def __str__(self) -> str:
|
||||
"""Get the string representation of the event name.
|
||||
|
||||
Returns:
|
||||
The event name string.
|
||||
"""
|
||||
return str(self.value)
|
||||
|
||||
|
||||
class EventTriggers(SimpleNamespace):
|
||||
"""All trigger names used in Reflex."""
|
||||
|
||||
ON_FOCUS = "on_focus"
|
||||
ON_BLUR = "on_blur"
|
||||
ON_CANCEL = "on_cancel"
|
||||
ON_CLICK = "on_click"
|
||||
ON_CHANGE = "on_change"
|
||||
ON_CHANGE_END = "on_change_end"
|
||||
ON_CHANGE_START = "on_change_start"
|
||||
ON_COMPLETE = "on_complete"
|
||||
ON_CONTEXT_MENU = "on_context_menu"
|
||||
ON_DOUBLE_CLICK = "on_double_click"
|
||||
ON_DROP = "on_drop"
|
||||
ON_EDIT = "on_edit"
|
||||
ON_KEY_DOWN = "on_key_down"
|
||||
ON_KEY_UP = "on_key_up"
|
||||
ON_MOUSE_DOWN = "on_mouse_down"
|
||||
ON_MOUSE_ENTER = "on_mouse_enter"
|
||||
ON_MOUSE_LEAVE = "on_mouse_leave"
|
||||
ON_MOUSE_MOVE = "on_mouse_move"
|
||||
ON_MOUSE_OUT = "on_mouse_out"
|
||||
ON_MOUSE_OVER = "on_mouse_over"
|
||||
ON_MOUSE_UP = "on_mouse_up"
|
||||
ON_SCROLL = "on_scroll"
|
||||
ON_SUBMIT = "on_submit"
|
||||
ON_MOUNT = "on_mount"
|
||||
ON_UNMOUNT = "on_unmount"
|
123
reflex/constants/installer.py
Normal file
123
reflex/constants/installer.py
Normal file
@ -0,0 +1,123 @@
|
||||
"""File for constants related to the installation process. (Bun/FNM/Node)."""
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import platform
|
||||
from types import SimpleNamespace
|
||||
|
||||
from .base import IS_WINDOWS, Dirs, Reflex
|
||||
|
||||
|
||||
def get_fnm_name() -> str | None:
|
||||
"""Get the appropriate fnm executable name based on the current platform.
|
||||
|
||||
Returns:
|
||||
The fnm executable name for the current platform.
|
||||
"""
|
||||
platform_os = platform.system()
|
||||
|
||||
if platform_os == "Windows":
|
||||
return "fnm-windows"
|
||||
elif platform_os == "Darwin":
|
||||
return "fnm-macos"
|
||||
elif platform_os == "Linux":
|
||||
machine = platform.machine()
|
||||
if machine == "arm" or machine.startswith("armv7"):
|
||||
return "fnm-arm32"
|
||||
elif machine.startswith("aarch") or machine.startswith("armv8"):
|
||||
return "fnm-arm64"
|
||||
return "fnm-linux"
|
||||
return None
|
||||
|
||||
|
||||
# Bun config.
|
||||
class Bun(SimpleNamespace):
|
||||
"""Bun constants."""
|
||||
|
||||
# The Bun version.
|
||||
VERSION = "0.7.3"
|
||||
# Min Bun Version
|
||||
MIN_VERSION = "0.7.0"
|
||||
# The directory to store the bun.
|
||||
ROOT_PATH = os.path.join(Reflex.DIR, "bun")
|
||||
# Default bun path.
|
||||
DEFAULT_PATH = os.path.join(ROOT_PATH, "bin", "bun")
|
||||
# URL to bun install script.
|
||||
INSTALL_URL = "https://bun.sh/install"
|
||||
|
||||
|
||||
# FNM config.
|
||||
class Fnm(SimpleNamespace):
|
||||
"""FNM constants."""
|
||||
|
||||
# The FNM version.
|
||||
VERSION = "1.35.1"
|
||||
# The directory to store fnm.
|
||||
DIR = os.path.join(Reflex.DIR, "fnm")
|
||||
FILENAME = get_fnm_name()
|
||||
# The fnm executable binary.
|
||||
EXE = os.path.join(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"
|
||||
)
|
||||
|
||||
|
||||
# Node / NPM config
|
||||
class Node(SimpleNamespace):
|
||||
"""Node/ NPM constants."""
|
||||
|
||||
# The Node version.
|
||||
VERSION = "18.17.0"
|
||||
# The minimum required node version.
|
||||
MIN_VERSION = "16.8.0"
|
||||
|
||||
# The node bin path.
|
||||
BIN_PATH = os.path.join(
|
||||
Fnm.DIR,
|
||||
"node-versions",
|
||||
f"v{VERSION}",
|
||||
"installation",
|
||||
"bin" if not IS_WINDOWS else "",
|
||||
)
|
||||
# The default path where node is installed.
|
||||
PATH = os.path.join(BIN_PATH, "node.exe" if IS_WINDOWS else "node")
|
||||
|
||||
# The default path where npm is installed.
|
||||
NPM_PATH = os.path.join(BIN_PATH, "npm")
|
||||
|
||||
|
||||
class PackageJson(SimpleNamespace):
|
||||
"""Constants used to build the package.json file."""
|
||||
|
||||
class Commands(SimpleNamespace):
|
||||
"""The commands to define in package.json."""
|
||||
|
||||
DEV = "next dev"
|
||||
EXPORT = "next build && next export -o _static"
|
||||
EXPORT_SITEMAP = "next build && next-sitemap && next export -o _static"
|
||||
PROD = "next start"
|
||||
|
||||
PATH = os.path.join(Dirs.WEB, "package.json")
|
||||
|
||||
DEPENDENCIES = {
|
||||
"@chakra-ui/react": "^2.6.0",
|
||||
"@chakra-ui/system": "^2.5.6",
|
||||
"@emotion/react": "^11.10.6",
|
||||
"@emotion/styled": "^11.10.6",
|
||||
"axios": "^1.4.0",
|
||||
"chakra-react-select": "^4.6.0",
|
||||
"focus-visible": "^5.2.0",
|
||||
"json5": "^2.2.3",
|
||||
"next": "^13.3.1",
|
||||
"next-sitemap": "^4.1.8",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"socket.io-client": "^4.6.1",
|
||||
"universal-cookie": "^4.0.4",
|
||||
}
|
||||
DEV_DEPENDENCIES = {
|
||||
"autoprefixer": "^10.4.14",
|
||||
"postcss": "^8.4.24",
|
||||
}
|
67
reflex/constants/route.py
Normal file
67
reflex/constants/route.py
Normal file
@ -0,0 +1,67 @@
|
||||
"""Route constants."""
|
||||
|
||||
import re
|
||||
from types import SimpleNamespace
|
||||
|
||||
|
||||
class RouteArgType(SimpleNamespace):
|
||||
"""Type of dynamic route arg extracted from URI route."""
|
||||
|
||||
# Typecast to str is needed for Enum to work.
|
||||
SINGLE = str("arg_single")
|
||||
LIST = str("arg_list")
|
||||
|
||||
|
||||
# the name of the backend var containing path and client information
|
||||
ROUTER_DATA = "router_data"
|
||||
|
||||
|
||||
class RouteVar(SimpleNamespace):
|
||||
"""Names of variables used in the router_data dict stored in State."""
|
||||
|
||||
CLIENT_IP = "ip"
|
||||
CLIENT_TOKEN = "token"
|
||||
HEADERS = "headers"
|
||||
PATH = "pathname"
|
||||
ORIGIN = "asPath"
|
||||
SESSION_ID = "sid"
|
||||
QUERY = "query"
|
||||
COOKIE = "cookie"
|
||||
|
||||
|
||||
class RouteRegex(SimpleNamespace):
|
||||
"""Regex used for extracting route args in route."""
|
||||
|
||||
ARG = re.compile(r"\[(?!\.)([^\[\]]+)\]")
|
||||
# group return the catchall pattern (i.e. "[[..slug]]")
|
||||
CATCHALL = re.compile(r"(\[?\[\.{3}(?![0-9]).*\]?\])")
|
||||
# group return the arg name (i.e. "slug")
|
||||
STRICT_CATCHALL = re.compile(r"\[\.{3}([a-zA-Z_][\w]*)\]")
|
||||
# group return the arg name (i.e. "slug") (optional arg can be empty)
|
||||
OPT_CATCHALL = re.compile(r"\[\[\.{3}([a-zA-Z_][\w]*)\]\]")
|
||||
|
||||
|
||||
class DefaultPage(SimpleNamespace):
|
||||
"""Default page constants."""
|
||||
|
||||
# The default title to show for Reflex apps.
|
||||
TITLE = "Reflex App"
|
||||
# The default description to show for Reflex apps.
|
||||
DESCRIPTION = "A Reflex app."
|
||||
# The default image to show for Reflex apps.
|
||||
IMAGE = "favicon.ico"
|
||||
# The default meta list to show for Reflex apps.
|
||||
META_LIST = []
|
||||
|
||||
|
||||
# 404 variables
|
||||
class Page404(SimpleNamespace):
|
||||
"""Page 404 constants."""
|
||||
|
||||
SLUG = "404"
|
||||
TITLE = "404 - Not Found"
|
||||
IMAGE = "favicon.ico"
|
||||
DESCRIPTION = "The page was not found"
|
||||
|
||||
|
||||
ROUTE_NOT_FOUND = "routeNotFound"
|
22
reflex/constants/style.py
Normal file
22
reflex/constants/style.py
Normal file
@ -0,0 +1,22 @@
|
||||
"""Style constants."""
|
||||
|
||||
import os
|
||||
from types import SimpleNamespace
|
||||
|
||||
from reflex.constants.base import Dirs
|
||||
|
||||
# The directory where styles are located.
|
||||
STYLES_DIR = os.path.join(Dirs.WEB, "styles")
|
||||
|
||||
|
||||
class Tailwind(SimpleNamespace):
|
||||
"""Tailwind constants."""
|
||||
|
||||
# The Tailwindcss version
|
||||
VERSION = "tailwindcss@^3.3.2"
|
||||
# The Tailwind config.
|
||||
CONFIG = os.path.join(Dirs.WEB, "tailwind.config.js")
|
||||
# Default Tailwind content paths
|
||||
CONTENT = ["./pages/**/*.{js,ts,jsx,tsx}"]
|
||||
# Relative tailwind style path to root stylesheet in STYLES_DIR.
|
||||
ROOT_STYLE_PATH = "./tailwind.css"
|
@ -33,7 +33,7 @@ def attr_to_prop(attr_name: str) -> str:
|
||||
|
||||
|
||||
# Names of HTML attributes that are provided by Reflex out of the box.
|
||||
PYNECONE_PROVIDED_ATTRS = {"class", "id", "style"}
|
||||
REFLEX_PROVIDED_ATTRS = {"class", "id", "style"}
|
||||
|
||||
# ATTR_TO_ELEMENTS contains HTML attribute names, which might be invalid as
|
||||
# Reflex prop names. PROP_TO_ELEMENTS contains the corresponding Reflex
|
||||
@ -41,7 +41,7 @@ PYNECONE_PROVIDED_ATTRS = {"class", "id", "style"}
|
||||
PROP_TO_ELEMENTS = {
|
||||
attr_to_prop(attr_name): elements
|
||||
for attr_name, elements in ATTR_TO_ELEMENTS.items()
|
||||
if attr_name not in PYNECONE_PROVIDED_ATTRS
|
||||
if attr_name not in REFLEX_PROVIDED_ATTRS
|
||||
}
|
||||
|
||||
# Invert PROP_TO_ELEMENTS to enable easier lookup.
|
||||
|
@ -192,7 +192,7 @@ class EventChain(Base):
|
||||
|
||||
events: List[EventSpec]
|
||||
|
||||
args_spec: Optional[ArgsSpec]
|
||||
args_spec: Optional[Callable]
|
||||
|
||||
|
||||
class Target(Base):
|
||||
@ -481,7 +481,7 @@ def get_hydrate_event(state) -> str:
|
||||
Returns:
|
||||
The name of the hydrate event.
|
||||
"""
|
||||
return get_event(state, constants.HYDRATE)
|
||||
return get_event(state, constants.CompileVars.HYDRATE)
|
||||
|
||||
|
||||
def call_event_handler(
|
||||
@ -507,12 +507,12 @@ def call_event_handler(
|
||||
|
||||
# handle new API using lambda to define triggers
|
||||
if isinstance(arg_spec, ArgsSpec):
|
||||
parsed_args = parse_args_spec(arg_spec)
|
||||
parsed_args = parse_args_spec(arg_spec) # type: ignore
|
||||
|
||||
if len(args) == len(["self", *parsed_args]):
|
||||
return event_handler(*parsed_args) # type: ignore
|
||||
else:
|
||||
source = inspect.getsource(arg_spec)
|
||||
source = inspect.getsource(arg_spec) # type: ignore
|
||||
raise ValueError(
|
||||
f"number of arguments in {event_handler.fn.__name__} "
|
||||
f"doesn't match the definition '{source.strip().strip(',')}'"
|
||||
@ -524,12 +524,12 @@ def call_event_handler(
|
||||
deprecation_version="0.2.8",
|
||||
removal_version="0.2.9",
|
||||
)
|
||||
if len(args) == 1:
|
||||
return event_handler()
|
||||
assert (
|
||||
len(args) == 2
|
||||
), f"Event handler {event_handler.fn} must have 1 or 2 arguments."
|
||||
return event_handler(arg_spec)
|
||||
if len(args) == 1:
|
||||
return event_handler()
|
||||
assert (
|
||||
len(args) == 2
|
||||
), f"Event handler {event_handler.fn} must have 1 or 2 arguments."
|
||||
return event_handler(arg_spec) # type: ignore
|
||||
|
||||
|
||||
def parse_args_spec(arg_spec: ArgsSpec):
|
||||
@ -578,7 +578,7 @@ def call_event_fn(fn: Callable, arg: Union[Var, ArgsSpec]) -> list[EventSpec]:
|
||||
args = inspect.getfullargspec(fn).args
|
||||
|
||||
if isinstance(arg, ArgsSpec):
|
||||
out = fn(*parse_args_spec(arg))
|
||||
out = fn(*parse_args_spec(arg)) # type: ignore
|
||||
else:
|
||||
# Call the lambda.
|
||||
if len(args) == 0:
|
||||
|
@ -13,7 +13,7 @@ if TYPE_CHECKING:
|
||||
from reflex.app import App
|
||||
|
||||
|
||||
State.add_var(constants.IS_HYDRATED, type_=bool, default_value=False)
|
||||
State.add_var(constants.CompileVars.IS_HYDRATED, type_=bool, default_value=False)
|
||||
|
||||
|
||||
class HydrateMiddleware(Middleware):
|
||||
@ -40,7 +40,7 @@ class HydrateMiddleware(Middleware):
|
||||
state._reset_client_storage()
|
||||
|
||||
# Mark state as not hydrated (until on_loads are complete)
|
||||
setattr(state, constants.IS_HYDRATED, False)
|
||||
setattr(state, constants.CompileVars.IS_HYDRATED, False)
|
||||
|
||||
# Apply client side storage values to state
|
||||
for storage_type in (constants.COOKIES, constants.LOCAL_STORAGE):
|
||||
|
@ -29,7 +29,7 @@ def version(value: bool):
|
||||
typer.Exit: If the version flag was passed.
|
||||
"""
|
||||
if value:
|
||||
console.print(constants.VERSION)
|
||||
console.print(constants.Reflex.VERSION)
|
||||
raise typer.Exit()
|
||||
|
||||
|
||||
@ -53,8 +53,9 @@ def init(
|
||||
name: str = typer.Option(
|
||||
None, metavar="APP_NAME", help="The name of the app to initialize."
|
||||
),
|
||||
template: constants.Template = typer.Option(
|
||||
constants.Template.DEFAULT, help="The template to initialize the app with."
|
||||
template: constants.Templates.Kind = typer.Option(
|
||||
constants.Templates.Kind.DEFAULT,
|
||||
help="The template to initialize the app with.",
|
||||
),
|
||||
loglevel: constants.LogLevel = typer.Option(
|
||||
config.loglevel, help="The log level to use."
|
||||
@ -78,7 +79,7 @@ def init(
|
||||
prerequisites.migrate_to_reflex()
|
||||
|
||||
# Set up the app directory, only if the config doesn't exist.
|
||||
if not os.path.exists(constants.CONFIG_FILE):
|
||||
if not os.path.exists(constants.Config.FILE):
|
||||
prerequisites.create_config(app_name)
|
||||
prerequisites.initialize_app_directory(app_name, template)
|
||||
telemetry.send("init")
|
||||
@ -193,7 +194,7 @@ def run(
|
||||
def deploy(
|
||||
dry_run: bool = typer.Option(False, help="Whether to run a dry run."),
|
||||
loglevel: constants.LogLevel = typer.Option(
|
||||
console.LOG_LEVEL, help="The log level to use."
|
||||
console._LOG_LEVEL, help="The log level to use."
|
||||
),
|
||||
):
|
||||
"""Deploy the app to the Reflex hosting service."""
|
||||
@ -223,10 +224,10 @@ def deploy(
|
||||
backend = response["backend_resources_url"]
|
||||
|
||||
# Upload the frontend and backend.
|
||||
with open(constants.FRONTEND_ZIP, "rb") as f:
|
||||
with open(constants.ComponentName.FRONTEND.zip(), "rb") as f:
|
||||
httpx.put(frontend, data=f) # type: ignore
|
||||
|
||||
with open(constants.BACKEND_ZIP, "rb") as f:
|
||||
with open(constants.ComponentName.BACKEND.zip(), "rb") as f:
|
||||
httpx.put(backend, data=f) # type: ignore
|
||||
|
||||
|
||||
@ -242,7 +243,7 @@ def export(
|
||||
True, "--frontend-only", help="Export only frontend.", show_default=False
|
||||
),
|
||||
loglevel: constants.LogLevel = typer.Option(
|
||||
console.LOG_LEVEL, help="The log level to use."
|
||||
console._LOG_LEVEL, help="The log level to use."
|
||||
),
|
||||
):
|
||||
"""Export the app to a zip file."""
|
||||
|
@ -1102,7 +1102,7 @@ class StateProxy(wrapt.ObjectProxy):
|
||||
state_instance: The state instance to proxy.
|
||||
"""
|
||||
super().__init__(state_instance)
|
||||
self._self_app = getattr(prerequisites.get_app(), constants.APP_VAR)
|
||||
self._self_app = getattr(prerequisites.get_app(), constants.CompileVars.APP)
|
||||
self._self_substate_path = state_instance.get_full_name().split(".")
|
||||
self._self_actx = None
|
||||
self._self_mutable = False
|
||||
@ -1355,10 +1355,10 @@ class StateManagerRedis(StateManager):
|
||||
redis: Redis
|
||||
|
||||
# The token expiration time (s).
|
||||
token_expiration: int = constants.TOKEN_EXPIRATION
|
||||
token_expiration: int = constants.Expiration.TOKEN
|
||||
|
||||
# The maximum time to hold a lock (ms).
|
||||
lock_expiration: int = constants.LOCK_EXPIRATION
|
||||
lock_expiration: int = constants.Expiration.LOCK
|
||||
|
||||
# The keyspace subscription string when redis is waiting for lock to be released
|
||||
_redis_notify_keyspace_events: str = (
|
||||
|
@ -7,8 +7,8 @@ from reflex.event import EventChain
|
||||
from reflex.utils import format
|
||||
from reflex.vars import BaseVar, Var
|
||||
|
||||
color_mode = BaseVar(name=constants.COLOR_MODE, type_="str")
|
||||
toggle_color_mode = BaseVar(name=constants.TOGGLE_COLOR_MODE, type_=EventChain)
|
||||
color_mode = BaseVar(name=constants.ColorMode.NAME, type_="str")
|
||||
toggle_color_mode = BaseVar(name=constants.ColorMode.TOGGLE, type_=EventChain)
|
||||
|
||||
|
||||
def convert(style_dict):
|
||||
|
@ -154,7 +154,7 @@ class AppHarness:
|
||||
with chdir(self.app_path):
|
||||
reflex.reflex.init(
|
||||
name=self.app_name,
|
||||
template=reflex.constants.Template.DEFAULT,
|
||||
template=reflex.constants.Templates.Kind.DEFAULT,
|
||||
loglevel=reflex.constants.LogLevel.INFO,
|
||||
)
|
||||
self.app_module_path.write_text(source_code)
|
||||
@ -211,7 +211,7 @@ class AppHarness:
|
||||
# Start the frontend.
|
||||
self.frontend_process = reflex.utils.processes.new_process(
|
||||
[reflex.utils.prerequisites.get_package_manager(), "run", "dev"],
|
||||
cwd=self.app_path / reflex.constants.WEB_DIR,
|
||||
cwd=self.app_path / reflex.constants.Dirs.WEB,
|
||||
env={"PORT": "0"},
|
||||
**FRONTEND_POPEN_ARGS,
|
||||
)
|
||||
@ -647,7 +647,7 @@ class AppHarnessProd(AppHarness):
|
||||
frontend_server: Optional[Subdir404TCPServer] = None
|
||||
|
||||
def _run_frontend(self):
|
||||
web_root = self.app_path / reflex.constants.WEB_DIR / "_static"
|
||||
web_root = self.app_path / reflex.constants.Dirs.WEB / "_static"
|
||||
error_page_map = {
|
||||
404: web_root / "404.html",
|
||||
}
|
||||
|
@ -6,7 +6,6 @@ import json
|
||||
import os
|
||||
import subprocess
|
||||
import zipfile
|
||||
from enum import Enum
|
||||
from pathlib import Path
|
||||
|
||||
from rich.progress import MofNCompleteColumn, Progress, TimeElapsedColumn
|
||||
@ -19,7 +18,7 @@ from reflex.utils import console, path_ops, prerequisites, processes
|
||||
def set_env_json():
|
||||
"""Write the upload url to a REFLEX_JSON."""
|
||||
path_ops.update_json_file(
|
||||
constants.ENV_JSON,
|
||||
constants.Dirs.ENV_JSON,
|
||||
{endpoint.name: endpoint.get_url() for endpoint in constants.Endpoint},
|
||||
)
|
||||
|
||||
@ -52,17 +51,12 @@ def generate_sitemap_config(deploy_url: str):
|
||||
}
|
||||
)
|
||||
|
||||
with open(constants.SITEMAP_CONFIG_FILE, "w") as f:
|
||||
with open(constants.Next.SITEMAP_CONFIG_FILE, "w") as f:
|
||||
f.write(templates.SITEMAP_CONFIG(config=config))
|
||||
|
||||
|
||||
class _ComponentName(Enum):
|
||||
BACKEND = "Backend"
|
||||
FRONTEND = "Frontend"
|
||||
|
||||
|
||||
def _zip(
|
||||
component_name: _ComponentName,
|
||||
component_name: constants.ComponentName,
|
||||
target: str,
|
||||
root_dir: str,
|
||||
dirs_to_exclude: set[str] | None = None,
|
||||
@ -130,7 +124,7 @@ def export(
|
||||
deploy_url: The URL of the deployed app.
|
||||
"""
|
||||
# Remove the static folder.
|
||||
path_ops.rm(constants.WEB_STATIC_DIR)
|
||||
path_ops.rm(constants.Dirs.WEB_STATIC)
|
||||
|
||||
# The export command to run.
|
||||
command = "export"
|
||||
@ -155,25 +149,28 @@ def export(
|
||||
# Start the subprocess with the progress bar.
|
||||
process = processes.new_process(
|
||||
[prerequisites.get_package_manager(), "run", command],
|
||||
cwd=constants.WEB_DIR,
|
||||
cwd=constants.Dirs.WEB,
|
||||
shell=constants.IS_WINDOWS,
|
||||
)
|
||||
processes.show_progress("Creating Production Build", process, checkpoints)
|
||||
|
||||
# Zip up the app.
|
||||
if zip:
|
||||
files_to_exclude = {constants.FRONTEND_ZIP, constants.BACKEND_ZIP}
|
||||
files_to_exclude = {
|
||||
constants.ComponentName.FRONTEND.zip(),
|
||||
constants.ComponentName.BACKEND.zip(),
|
||||
}
|
||||
if frontend:
|
||||
_zip(
|
||||
component_name=_ComponentName.FRONTEND,
|
||||
target=constants.FRONTEND_ZIP,
|
||||
component_name=constants.ComponentName.FRONTEND,
|
||||
target=constants.ComponentName.FRONTEND.zip(),
|
||||
root_dir=".web/_static",
|
||||
files_to_exclude=files_to_exclude,
|
||||
)
|
||||
if backend:
|
||||
_zip(
|
||||
component_name=_ComponentName.BACKEND,
|
||||
target=constants.BACKEND_ZIP,
|
||||
component_name=constants.ComponentName.BACKEND,
|
||||
target=constants.ComponentName.BACKEND.zip(),
|
||||
root_dir=".",
|
||||
dirs_to_exclude={"assets", "__pycache__"},
|
||||
files_to_exclude=files_to_exclude,
|
||||
@ -192,8 +189,8 @@ def setup_frontend(
|
||||
"""
|
||||
# Copy asset files to public folder.
|
||||
path_ops.cp(
|
||||
src=str(root / constants.APP_ASSETS_DIR),
|
||||
dest=str(root / constants.WEB_ASSETS_DIR),
|
||||
src=str(root / constants.Dirs.APP_ASSETS),
|
||||
dest=str(root / constants.Dirs.WEB_ASSETS),
|
||||
)
|
||||
|
||||
# Set the environment variables in client (env.json).
|
||||
@ -209,7 +206,7 @@ def setup_frontend(
|
||||
"telemetry",
|
||||
"disable",
|
||||
],
|
||||
cwd=constants.WEB_DIR,
|
||||
cwd=constants.Dirs.WEB,
|
||||
stdout=subprocess.DEVNULL,
|
||||
shell=constants.IS_WINDOWS,
|
||||
)
|
||||
|
@ -14,7 +14,7 @@ from reflex.constants import LogLevel
|
||||
_console = Console()
|
||||
|
||||
# The current log level.
|
||||
LOG_LEVEL = LogLevel.INFO
|
||||
_LOG_LEVEL = LogLevel.INFO
|
||||
|
||||
|
||||
def set_log_level(log_level: LogLevel):
|
||||
@ -23,8 +23,8 @@ def set_log_level(log_level: LogLevel):
|
||||
Args:
|
||||
log_level: The log level to set.
|
||||
"""
|
||||
global LOG_LEVEL
|
||||
LOG_LEVEL = log_level
|
||||
global _LOG_LEVEL
|
||||
_LOG_LEVEL = log_level
|
||||
|
||||
|
||||
def print(msg: str, **kwargs):
|
||||
@ -44,7 +44,7 @@ def debug(msg: str, **kwargs):
|
||||
msg: The debug message.
|
||||
kwargs: Keyword arguments to pass to the print function.
|
||||
"""
|
||||
if LOG_LEVEL <= LogLevel.DEBUG:
|
||||
if _LOG_LEVEL <= LogLevel.DEBUG:
|
||||
print(f"[blue]Debug: {msg}[/blue]", **kwargs)
|
||||
|
||||
|
||||
@ -55,7 +55,7 @@ def info(msg: str, **kwargs):
|
||||
msg: The info message.
|
||||
kwargs: Keyword arguments to pass to the print function.
|
||||
"""
|
||||
if LOG_LEVEL <= LogLevel.INFO:
|
||||
if _LOG_LEVEL <= LogLevel.INFO:
|
||||
print(f"[cyan]Info: {msg}[/cyan]", **kwargs)
|
||||
|
||||
|
||||
@ -66,7 +66,7 @@ def success(msg: str, **kwargs):
|
||||
msg: The success message.
|
||||
kwargs: Keyword arguments to pass to the print function.
|
||||
"""
|
||||
if LOG_LEVEL <= LogLevel.INFO:
|
||||
if _LOG_LEVEL <= LogLevel.INFO:
|
||||
print(f"[green]Success: {msg}[/green]", **kwargs)
|
||||
|
||||
|
||||
@ -77,7 +77,7 @@ def log(msg: str, **kwargs):
|
||||
msg: The message to log.
|
||||
kwargs: Keyword arguments to pass to the print function.
|
||||
"""
|
||||
if LOG_LEVEL <= LogLevel.INFO:
|
||||
if _LOG_LEVEL <= LogLevel.INFO:
|
||||
_console.log(msg, **kwargs)
|
||||
|
||||
|
||||
@ -98,7 +98,7 @@ def warn(msg: str, **kwargs):
|
||||
msg: The warning message.
|
||||
kwargs: Keyword arguments to pass to the print function.
|
||||
"""
|
||||
if LOG_LEVEL <= LogLevel.WARNING:
|
||||
if _LOG_LEVEL <= LogLevel.WARNING:
|
||||
print(f"[orange1]Warning: {msg}[/orange1]", **kwargs)
|
||||
|
||||
|
||||
@ -123,7 +123,7 @@ def deprecate(
|
||||
f"{feature_name} has been deprecated in version {deprecation_version} {reason}. It will be completely "
|
||||
f"removed in {removal_version}"
|
||||
)
|
||||
if LOG_LEVEL <= LogLevel.WARNING:
|
||||
if _LOG_LEVEL <= LogLevel.WARNING:
|
||||
print(f"[yellow]DeprecationWarning: {msg}[/yellow]", **kwargs)
|
||||
|
||||
|
||||
@ -134,7 +134,7 @@ def error(msg: str, **kwargs):
|
||||
msg: The error message.
|
||||
kwargs: Keyword arguments to pass to the print function.
|
||||
"""
|
||||
if LOG_LEVEL <= LogLevel.ERROR:
|
||||
if _LOG_LEVEL <= LogLevel.ERROR:
|
||||
print(f"[red]{msg}[/red]", **kwargs)
|
||||
|
||||
|
||||
|
@ -73,7 +73,7 @@ def run_process_and_launch_url(run_command: list[str]):
|
||||
Args:
|
||||
run_command: The command to run.
|
||||
"""
|
||||
json_file_path = os.path.join(constants.WEB_DIR, "package.json")
|
||||
json_file_path = os.path.join(constants.Dirs.WEB, "package.json")
|
||||
last_hash = detect_package_change(json_file_path)
|
||||
process = None
|
||||
first_run = True
|
||||
@ -81,7 +81,7 @@ def run_process_and_launch_url(run_command: list[str]):
|
||||
while True:
|
||||
if process is None:
|
||||
process = processes.new_process(
|
||||
run_command, cwd=constants.WEB_DIR, shell=constants.IS_WINDOWS
|
||||
run_command, cwd=constants.Dirs.WEB, shell=constants.IS_WINDOWS
|
||||
)
|
||||
if process.stdout:
|
||||
for line in processes.stream_logs("Starting frontend", process):
|
||||
@ -153,9 +153,9 @@ def run_backend(
|
||||
loglevel: The log level.
|
||||
"""
|
||||
config = get_config()
|
||||
app_module = f"{config.app_name}.{config.app_name}:{constants.APP_VAR}"
|
||||
app_module = f"{config.app_name}.{config.app_name}:{constants.CompileVars.APP}"
|
||||
uvicorn.run(
|
||||
app=f"{app_module}.{constants.API_VAR}",
|
||||
app=f"{app_module}.{constants.CompileVars.API}",
|
||||
host=host,
|
||||
port=port,
|
||||
log_level=loglevel.value,
|
||||
@ -180,7 +180,7 @@ def run_backend_prod(
|
||||
config = get_config()
|
||||
RUN_BACKEND_PROD = f"gunicorn --worker-class uvicorn.workers.UvicornH11Worker --preload --timeout {config.timeout} --log-level critical".split()
|
||||
RUN_BACKEND_PROD_WINDOWS = f"uvicorn --timeout-keep-alive {config.timeout}".split()
|
||||
app_module = f"{config.app_name}.{config.app_name}:{constants.APP_VAR}"
|
||||
app_module = f"{config.app_name}.{config.app_name}:{constants.CompileVars.APP}"
|
||||
command = (
|
||||
[
|
||||
*RUN_BACKEND_PROD_WINDOWS,
|
||||
@ -217,7 +217,7 @@ def run_backend_prod(
|
||||
|
||||
def output_system_info():
|
||||
"""Show system information if the loglevel is in DEBUG."""
|
||||
if console.LOG_LEVEL > constants.LogLevel.DEBUG:
|
||||
if console._LOG_LEVEL > constants.LogLevel.DEBUG:
|
||||
return
|
||||
|
||||
config = get_config()
|
||||
@ -231,8 +231,8 @@ def output_system_info():
|
||||
console.debug(f"Config: {config}")
|
||||
|
||||
dependencies = [
|
||||
f"[Reflex {constants.VERSION} with Python {platform.python_version()} (PATH: {sys.executable})]",
|
||||
f"[Node {prerequisites.get_node_version()} (Expected: {constants.NODE_VERSION}) (PATH:{path_ops.get_node_path()})]",
|
||||
f"[Reflex {constants.Reflex.VERSION} with Python {platform.python_version()} (PATH: {sys.executable})]",
|
||||
f"[Node {prerequisites.get_node_version()} (Expected: {constants.Node.VERSION}) (PATH:{path_ops.get_node_path()})]",
|
||||
]
|
||||
|
||||
system = platform.system()
|
||||
@ -240,13 +240,13 @@ def output_system_info():
|
||||
if system != "Windows":
|
||||
dependencies.extend(
|
||||
[
|
||||
f"[FNM {constants.FNM_VERSION} (Expected: {constants.FNM_VERSION}) (PATH: {constants.FNM_EXE})]",
|
||||
f"[Bun {prerequisites.get_bun_version()} (Expected: {constants.BUN_VERSION}) (PATH: {config.bun_path})]",
|
||||
f"[FNM {constants.Fnm.VERSION} (Expected: {constants.Fnm.VERSION}) (PATH: {constants.Fnm.EXE})]",
|
||||
f"[Bun {prerequisites.get_bun_version()} (Expected: {constants.Bun.VERSION}) (PATH: {config.bun_path})]",
|
||||
],
|
||||
)
|
||||
else:
|
||||
dependencies.append(
|
||||
f"[FNM {constants.FNM_VERSION} (Expected: {constants.FNM_VERSION}) (PATH: {constants.FNM_EXE})]",
|
||||
f"[FNM {constants.Fnm.VERSION} (Expected: {constants.Fnm.VERSION}) (PATH: {constants.Fnm.EXE})]",
|
||||
)
|
||||
|
||||
if system == "Linux":
|
||||
|
@ -5,7 +5,6 @@ from __future__ import annotations
|
||||
import inspect
|
||||
import json
|
||||
import os
|
||||
import os.path as op
|
||||
import re
|
||||
import sys
|
||||
from typing import TYPE_CHECKING, Any, Union
|
||||
@ -230,7 +229,7 @@ def format_route(route: str, format_case=True) -> str:
|
||||
|
||||
# If the route is empty, return the index route.
|
||||
if route == "":
|
||||
return constants.INDEX_ROUTE
|
||||
return constants.PageNames.INDEX_ROUTE
|
||||
|
||||
return route
|
||||
|
||||
@ -559,11 +558,27 @@ def format_breadcrumbs(route: str) -> list[tuple[str, str]]:
|
||||
|
||||
# create and return breadcrumbs
|
||||
return [
|
||||
(part, op.join("/", *route_parts[: i + 1]))
|
||||
(part, "/".join(["", *route_parts[: i + 1]]))
|
||||
for i, part in enumerate(route_parts)
|
||||
]
|
||||
|
||||
|
||||
def format_library_name(library_fullname: str):
|
||||
"""Format the name of a library.
|
||||
|
||||
Args:
|
||||
library_fullname: The fullname of the library.
|
||||
|
||||
Returns:
|
||||
The name without the @version if it was part of the name
|
||||
"""
|
||||
lib, at, version = library_fullname.rpartition("@")
|
||||
if not lib:
|
||||
lib = at + version
|
||||
|
||||
return lib
|
||||
|
||||
|
||||
def json_dumps(obj: Any) -> str:
|
||||
"""Takes an object and returns a jsonified string.
|
||||
|
||||
|
@ -118,10 +118,10 @@ def get_node_bin_path() -> str | None:
|
||||
Returns:
|
||||
The path to the node bin folder.
|
||||
"""
|
||||
if not os.path.exists(constants.NODE_BIN_PATH):
|
||||
if not os.path.exists(constants.Node.BIN_PATH):
|
||||
str_path = which("node")
|
||||
return str(Path(str_path).parent) if str_path else str_path
|
||||
return constants.NODE_BIN_PATH
|
||||
return constants.Node.BIN_PATH
|
||||
|
||||
|
||||
def get_node_path() -> str | None:
|
||||
@ -130,9 +130,9 @@ def get_node_path() -> str | None:
|
||||
Returns:
|
||||
The path to the node binary file.
|
||||
"""
|
||||
if not os.path.exists(constants.NODE_PATH):
|
||||
if not os.path.exists(constants.Node.PATH):
|
||||
return which("node")
|
||||
return constants.NODE_PATH
|
||||
return constants.Node.PATH
|
||||
|
||||
|
||||
def get_npm_path() -> str | None:
|
||||
@ -141,9 +141,9 @@ def get_npm_path() -> str | None:
|
||||
Returns:
|
||||
The path to the npm binary file.
|
||||
"""
|
||||
if not os.path.exists(constants.NODE_PATH):
|
||||
if not os.path.exists(constants.Node.PATH):
|
||||
return which("npm")
|
||||
return constants.NPM_PATH
|
||||
return constants.Node.NPM_PATH
|
||||
|
||||
|
||||
def update_json_file(file_path: str, update_dict: dict[str, int | str]):
|
||||
|
@ -39,9 +39,9 @@ def check_node_version() -> bool:
|
||||
if current_version:
|
||||
# Compare the version numbers
|
||||
return (
|
||||
current_version >= version.parse(constants.NODE_VERSION_MIN)
|
||||
current_version >= version.parse(constants.Node.MIN_VERSION)
|
||||
if constants.IS_WINDOWS
|
||||
else current_version == version.parse(constants.NODE_VERSION)
|
||||
else current_version == version.parse(constants.Node.VERSION)
|
||||
)
|
||||
return False
|
||||
|
||||
@ -111,7 +111,7 @@ def get_app(reload: bool = False) -> ModuleType:
|
||||
config = get_config()
|
||||
module = ".".join([config.app_name, config.app_name])
|
||||
sys.path.insert(0, os.getcwd())
|
||||
app = __import__(module, fromlist=(constants.APP_VAR,))
|
||||
app = __import__(module, fromlist=(constants.CompileVars.APP,))
|
||||
if reload:
|
||||
importlib.reload(app)
|
||||
return app
|
||||
@ -160,9 +160,9 @@ def get_default_app_name() -> str:
|
||||
app_name = os.getcwd().split(os.path.sep)[-1].replace("-", "_")
|
||||
|
||||
# Make sure the app is not named "reflex".
|
||||
if app_name == constants.MODULE_NAME:
|
||||
if app_name == constants.Reflex.MODULE_NAME:
|
||||
console.error(
|
||||
f"The app directory cannot be named [bold]{constants.MODULE_NAME}[/bold]."
|
||||
f"The app directory cannot be named [bold]{constants.Reflex.MODULE_NAME}[/bold]."
|
||||
)
|
||||
raise typer.Exit(1)
|
||||
|
||||
@ -179,28 +179,28 @@ def create_config(app_name: str):
|
||||
from reflex.compiler import templates
|
||||
|
||||
config_name = f"{re.sub(r'[^a-zA-Z]', '', app_name).capitalize()}Config"
|
||||
with open(constants.CONFIG_FILE, "w") as f:
|
||||
console.debug(f"Creating {constants.CONFIG_FILE}")
|
||||
with open(constants.Config.FILE, "w") as f:
|
||||
console.debug(f"Creating {constants.Config.FILE}")
|
||||
f.write(templates.RXCONFIG.render(app_name=app_name, config_name=config_name))
|
||||
|
||||
|
||||
def initialize_gitignore():
|
||||
"""Initialize the template .gitignore file."""
|
||||
# The files to add to the .gitignore file.
|
||||
files = constants.DEFAULT_GITIGNORE
|
||||
files = constants.GitIgnore.DEFAULTS
|
||||
|
||||
# Subtract current ignored files.
|
||||
if os.path.exists(constants.GITIGNORE_FILE):
|
||||
with open(constants.GITIGNORE_FILE, "r") as f:
|
||||
if os.path.exists(constants.GitIgnore.FILE):
|
||||
with open(constants.GitIgnore.FILE, "r") as f:
|
||||
files |= set([line.strip() for line in f.readlines()])
|
||||
|
||||
# Write files to the .gitignore file.
|
||||
with open(constants.GITIGNORE_FILE, "w") as f:
|
||||
console.debug(f"Creating {constants.GITIGNORE_FILE}")
|
||||
with open(constants.GitIgnore.FILE, "w") as f:
|
||||
console.debug(f"Creating {constants.GitIgnore.FILE}")
|
||||
f.write(f"{(path_ops.join(sorted(files))).lstrip()}")
|
||||
|
||||
|
||||
def initialize_app_directory(app_name: str, template: constants.Template):
|
||||
def initialize_app_directory(app_name: str, template: constants.Templates.Kind):
|
||||
"""Initialize the app directory on reflex init.
|
||||
|
||||
Args:
|
||||
@ -208,26 +208,28 @@ def initialize_app_directory(app_name: str, template: constants.Template):
|
||||
template: The template to use.
|
||||
"""
|
||||
console.log("Initializing the app directory.")
|
||||
path_ops.cp(os.path.join(constants.TEMPLATE_DIR, "apps", template.value), app_name)
|
||||
path_ops.cp(
|
||||
os.path.join(constants.Templates.Dirs.BASE, "apps", template.value), app_name
|
||||
)
|
||||
path_ops.mv(
|
||||
os.path.join(app_name, template.value + ".py"),
|
||||
os.path.join(app_name, app_name + constants.PY_EXT),
|
||||
os.path.join(app_name, app_name + constants.Ext.PY),
|
||||
)
|
||||
path_ops.cp(constants.ASSETS_TEMPLATE_DIR, constants.APP_ASSETS_DIR)
|
||||
path_ops.cp(constants.Templates.Dirs.ASSETS_TEMPLATE, constants.Dirs.APP_ASSETS)
|
||||
|
||||
|
||||
def initialize_web_directory():
|
||||
"""Initialize the web directory on reflex init."""
|
||||
console.log("Initializing the web directory.")
|
||||
|
||||
path_ops.cp(constants.WEB_TEMPLATE_DIR, constants.WEB_DIR)
|
||||
path_ops.cp(constants.Templates.Dirs.WEB_TEMPLATE, constants.Dirs.WEB)
|
||||
|
||||
initialize_package_json()
|
||||
|
||||
path_ops.mkdir(constants.WEB_ASSETS_DIR)
|
||||
path_ops.mkdir(constants.Dirs.WEB_ASSETS)
|
||||
|
||||
# update nextJS config based on rxConfig
|
||||
next_config_file = os.path.join(constants.WEB_DIR, constants.NEXT_CONFIG_FILE)
|
||||
next_config_file = os.path.join(constants.Dirs.WEB, constants.Next.CONFIG_FILE)
|
||||
|
||||
with open(next_config_file, "r") as file:
|
||||
next_config = file.read()
|
||||
@ -243,19 +245,19 @@ def initialize_web_directory():
|
||||
def _compile_package_json():
|
||||
return templates.PACKAGE_JSON.render(
|
||||
scripts={
|
||||
"dev": constants.PackageJsonCommands.DEV,
|
||||
"export": constants.PackageJsonCommands.EXPORT,
|
||||
"export_sitemap": constants.PackageJsonCommands.EXPORT_SITEMAP,
|
||||
"prod": constants.PackageJsonCommands.PROD,
|
||||
"dev": constants.PackageJson.Commands.DEV,
|
||||
"export": constants.PackageJson.Commands.EXPORT,
|
||||
"export_sitemap": constants.PackageJson.Commands.EXPORT_SITEMAP,
|
||||
"prod": constants.PackageJson.Commands.PROD,
|
||||
},
|
||||
dependencies=constants.PACKAGE_DEPENDENCIES,
|
||||
dev_dependencies=constants.PACKAGE_DEV_DEPENDENCIES,
|
||||
dependencies=constants.PackageJson.DEPENDENCIES,
|
||||
dev_dependencies=constants.PackageJson.DEV_DEPENDENCIES,
|
||||
)
|
||||
|
||||
|
||||
def initialize_package_json():
|
||||
"""Render and write in .web the package.json file."""
|
||||
output_path = constants.PACKAGE_JSON_PATH
|
||||
output_path = constants.PackageJson.PATH
|
||||
code = _compile_package_json()
|
||||
with open(output_path, "w") as file:
|
||||
file.write(code)
|
||||
@ -269,10 +271,10 @@ def init_reflex_json():
|
||||
|
||||
# Write the hash and version to the reflex json file.
|
||||
reflex_json = {
|
||||
"version": constants.VERSION,
|
||||
"version": constants.Reflex.VERSION,
|
||||
"project_hash": project_hash,
|
||||
}
|
||||
path_ops.update_json_file(constants.REFLEX_JSON, reflex_json)
|
||||
path_ops.update_json_file(constants.Reflex.JSON, reflex_json)
|
||||
|
||||
|
||||
def update_next_config(next_config: str, config: Config) -> str:
|
||||
@ -302,7 +304,7 @@ def remove_existing_bun_installation():
|
||||
"""Remove existing bun installation."""
|
||||
console.debug("Removing existing bun installation.")
|
||||
if os.path.exists(get_config().bun_path):
|
||||
path_ops.rm(constants.BUN_ROOT_PATH)
|
||||
path_ops.rm(constants.Bun.ROOT_PATH)
|
||||
|
||||
|
||||
def download_and_run(url: str, *args, show_status: bool = False, **env):
|
||||
@ -339,9 +341,9 @@ def download_and_extract_fnm_zip():
|
||||
Exit: If an error occurs while downloading or extracting the FNM zip.
|
||||
"""
|
||||
# Download the zip file
|
||||
url = constants.FNM_INSTALL_URL
|
||||
url = constants.Fnm.INSTALL_URL
|
||||
console.debug(f"Downloading {url}")
|
||||
fnm_zip_file = os.path.join(constants.FNM_DIR, f"{constants.FNM_FILENAME}.zip")
|
||||
fnm_zip_file = os.path.join(constants.Fnm.DIR, f"{constants.Fnm.FILENAME}.zip")
|
||||
# Function to download and extract the FNM zip release.
|
||||
try:
|
||||
# Download the FNM zip release.
|
||||
@ -354,7 +356,7 @@ def download_and_extract_fnm_zip():
|
||||
|
||||
# Extract the downloaded zip file.
|
||||
with zipfile.ZipFile(fnm_zip_file, "r") as zip_ref:
|
||||
zip_ref.extractall(constants.FNM_DIR)
|
||||
zip_ref.extractall(constants.Fnm.DIR)
|
||||
|
||||
console.debug("FNM package downloaded and extracted successfully.")
|
||||
except Exception as e:
|
||||
@ -369,13 +371,13 @@ def install_node():
|
||||
"""Install fnm and nodejs for use by Reflex.
|
||||
Independent of any existing system installations.
|
||||
"""
|
||||
if not constants.FNM_FILENAME:
|
||||
if not constants.Fnm.FILENAME:
|
||||
# fnm only support Linux, macOS and Windows distros.
|
||||
console.debug("")
|
||||
return
|
||||
|
||||
path_ops.mkdir(constants.FNM_DIR)
|
||||
if not os.path.exists(constants.FNM_EXE):
|
||||
path_ops.mkdir(constants.Fnm.DIR)
|
||||
if not os.path.exists(constants.Fnm.EXE):
|
||||
download_and_extract_fnm_zip()
|
||||
|
||||
if constants.IS_WINDOWS:
|
||||
@ -384,13 +386,13 @@ def install_node():
|
||||
[
|
||||
"powershell",
|
||||
"-Command",
|
||||
f'& "{constants.FNM_EXE}" install {constants.NODE_VERSION} --fnm-dir "{constants.FNM_DIR}"',
|
||||
f'& "{constants.Fnm.EXE}" install {constants.Node.VERSION} --fnm-dir "{constants.Fnm.DIR}"',
|
||||
],
|
||||
)
|
||||
else: # All other platforms (Linux, MacOS).
|
||||
# TODO we can skip installation if check_node_version() checks out
|
||||
# Add execute permissions to fnm executable.
|
||||
os.chmod(constants.FNM_EXE, stat.S_IXUSR)
|
||||
os.chmod(constants.Fnm.EXE, stat.S_IXUSR)
|
||||
# Install node.
|
||||
# Specify arm64 arch explicitly for M1s and M2s.
|
||||
architecture_arg = (
|
||||
@ -401,12 +403,12 @@ def install_node():
|
||||
|
||||
process = processes.new_process(
|
||||
[
|
||||
constants.FNM_EXE,
|
||||
constants.Fnm.EXE,
|
||||
"install",
|
||||
*architecture_arg,
|
||||
constants.NODE_VERSION,
|
||||
constants.Node.VERSION,
|
||||
"--fnm-dir",
|
||||
constants.FNM_DIR,
|
||||
constants.Fnm.DIR,
|
||||
],
|
||||
)
|
||||
processes.show_status("Installing node", process)
|
||||
@ -435,9 +437,9 @@ def install_bun():
|
||||
|
||||
# Run the bun install script.
|
||||
download_and_run(
|
||||
constants.BUN_INSTALL_URL,
|
||||
f"bun-v{constants.BUN_VERSION}",
|
||||
BUN_INSTALL=constants.BUN_ROOT_PATH,
|
||||
constants.Bun.INSTALL_URL,
|
||||
f"bun-v{constants.Bun.VERSION}",
|
||||
BUN_INSTALL=constants.Bun.ROOT_PATH,
|
||||
)
|
||||
|
||||
|
||||
@ -453,7 +455,7 @@ def install_frontend_packages(packages: set[str]):
|
||||
# Install the base packages.
|
||||
process = processes.new_process(
|
||||
[get_install_package_manager(), "install", "--loglevel", "silly"],
|
||||
cwd=constants.WEB_DIR,
|
||||
cwd=constants.Dirs.WEB,
|
||||
shell=constants.IS_WINDOWS,
|
||||
)
|
||||
|
||||
@ -467,10 +469,10 @@ def install_frontend_packages(packages: set[str]):
|
||||
get_install_package_manager(),
|
||||
"add",
|
||||
"-d",
|
||||
constants.TAILWIND_VERSION,
|
||||
constants.Tailwind.VERSION,
|
||||
*((config.tailwind or {}).get("plugins", [])),
|
||||
],
|
||||
cwd=constants.WEB_DIR,
|
||||
cwd=constants.Dirs.WEB,
|
||||
shell=constants.IS_WINDOWS,
|
||||
)
|
||||
processes.show_status("Installing tailwind", process)
|
||||
@ -479,7 +481,7 @@ def install_frontend_packages(packages: set[str]):
|
||||
if len(packages) > 0:
|
||||
process = processes.new_process(
|
||||
[get_install_package_manager(), "add", *packages],
|
||||
cwd=constants.WEB_DIR,
|
||||
cwd=constants.Dirs.WEB,
|
||||
shell=constants.IS_WINDOWS,
|
||||
)
|
||||
processes.show_status(
|
||||
@ -496,14 +498,14 @@ def check_initialized(frontend: bool = True):
|
||||
Raises:
|
||||
Exit: If the app is not initialized.
|
||||
"""
|
||||
has_config = os.path.exists(constants.CONFIG_FILE)
|
||||
has_reflex_dir = not frontend or os.path.exists(constants.REFLEX_DIR)
|
||||
has_web_dir = not frontend or os.path.exists(constants.WEB_DIR)
|
||||
has_config = os.path.exists(constants.Config.FILE)
|
||||
has_reflex_dir = not frontend or os.path.exists(constants.Reflex.DIR)
|
||||
has_web_dir = not frontend or os.path.exists(constants.Dirs.WEB)
|
||||
|
||||
# Check if the app is initialized.
|
||||
if not (has_config and has_reflex_dir and has_web_dir):
|
||||
console.error(
|
||||
f"The app is not initialized. Run [bold]{constants.MODULE_NAME} init[/bold] first."
|
||||
f"The app is not initialized. Run [bold]{constants.Reflex.MODULE_NAME} init[/bold] first."
|
||||
)
|
||||
raise typer.Exit(1)
|
||||
|
||||
@ -527,11 +529,11 @@ def is_latest_template() -> bool:
|
||||
Returns:
|
||||
Whether the app is using the latest template.
|
||||
"""
|
||||
if not os.path.exists(constants.REFLEX_JSON):
|
||||
if not os.path.exists(constants.Reflex.JSON):
|
||||
return False
|
||||
with open(constants.REFLEX_JSON) as f: # type: ignore
|
||||
with open(constants.Reflex.JSON) as f: # type: ignore
|
||||
app_version = json.load(f)["version"]
|
||||
return app_version == constants.VERSION
|
||||
return app_version == constants.Reflex.VERSION
|
||||
|
||||
|
||||
def validate_bun():
|
||||
@ -543,16 +545,16 @@ def validate_bun():
|
||||
# if a custom bun path is provided, make sure its valid
|
||||
# This is specific to non-FHS OS
|
||||
bun_path = get_config().bun_path
|
||||
if bun_path != constants.DEFAULT_BUN_PATH:
|
||||
if bun_path != constants.Bun.DEFAULT_PATH:
|
||||
bun_version = get_bun_version()
|
||||
if not bun_version:
|
||||
console.error(
|
||||
"Failed to obtain bun version. Make sure the specified bun path in your config is correct."
|
||||
)
|
||||
raise typer.Exit(1)
|
||||
elif bun_version < version.parse(constants.MIN_BUN_VERSION):
|
||||
elif bun_version < version.parse(constants.Bun.MIN_VERSION):
|
||||
console.error(
|
||||
f"Reflex requires bun version {constants.BUN_VERSION} or higher to run, but the detected version is "
|
||||
f"Reflex requires bun version {constants.Bun.VERSION} or higher to run, but the detected version is "
|
||||
f"{bun_version}. If you have specified a custom bun path in your config, make sure to provide one "
|
||||
f"that satisfies the minimum version requirement."
|
||||
)
|
||||
@ -582,7 +584,7 @@ def validate_frontend_dependencies(init=True):
|
||||
if not check_node_version():
|
||||
node_version = get_node_version()
|
||||
console.error(
|
||||
f"Reflex requires node version {constants.NODE_VERSION_MIN} or higher to run, but the detected version is {node_version}",
|
||||
f"Reflex requires node version {constants.Node.MIN_VERSION} or higher to run, but the detected version is {node_version}",
|
||||
)
|
||||
raise typer.Exit(1)
|
||||
|
||||
@ -597,7 +599,7 @@ def validate_frontend_dependencies(init=True):
|
||||
def initialize_frontend_dependencies():
|
||||
"""Initialize all the frontend dependencies."""
|
||||
# Create the reflex directory.
|
||||
path_ops.mkdir(constants.REFLEX_DIR)
|
||||
path_ops.mkdir(constants.Reflex.DIR)
|
||||
# validate dependencies before install
|
||||
validate_frontend_dependencies()
|
||||
# Install the frontend dependencies.
|
||||
@ -644,7 +646,7 @@ def check_schema_up_to_date():
|
||||
def migrate_to_reflex():
|
||||
"""Migration from Pynecone to Reflex."""
|
||||
# Check if the old config file exists.
|
||||
if not os.path.exists(constants.OLD_CONFIG_FILE):
|
||||
if not os.path.exists(constants.Config.PREVIOUS_FILE):
|
||||
return
|
||||
|
||||
# Ask the user if they want to migrate.
|
||||
@ -657,16 +659,16 @@ def migrate_to_reflex():
|
||||
|
||||
# Rename pcconfig to rxconfig.
|
||||
console.log(
|
||||
f"[bold]Renaming {constants.OLD_CONFIG_FILE} to {constants.CONFIG_FILE}"
|
||||
f"[bold]Renaming {constants.Config.PREVIOUS_FILE} to {constants.Config.FILE}"
|
||||
)
|
||||
os.rename(constants.OLD_CONFIG_FILE, constants.CONFIG_FILE)
|
||||
os.rename(constants.Config.PREVIOUS_FILE, constants.Config.FILE)
|
||||
|
||||
# Find all python files in the app directory.
|
||||
file_pattern = os.path.join(get_config().app_name, "**/*.py")
|
||||
file_list = glob.glob(file_pattern, recursive=True)
|
||||
|
||||
# Add the config file to the list of files to be migrated.
|
||||
file_list.append(constants.CONFIG_FILE)
|
||||
file_list.append(constants.Config.FILE)
|
||||
|
||||
# Migrate all files.
|
||||
updates = {
|
||||
|
@ -39,7 +39,7 @@ def get_reflex_version() -> str:
|
||||
Returns:
|
||||
The Reflex version.
|
||||
"""
|
||||
return constants.VERSION
|
||||
return constants.Reflex.VERSION
|
||||
|
||||
|
||||
def get_cpu_count() -> int:
|
||||
|
@ -4,7 +4,6 @@ from __future__ import annotations
|
||||
|
||||
import contextlib
|
||||
import typing
|
||||
from types import LambdaType
|
||||
from typing import Any, Callable, Type, Union, _GenericAlias # type: ignore
|
||||
|
||||
from reflex.base import Base
|
||||
@ -18,7 +17,8 @@ PrimitiveType = Union[int, float, bool, str, list, dict, set, tuple]
|
||||
StateVar = Union[PrimitiveType, Base, None]
|
||||
StateIterVar = Union[list, set, tuple]
|
||||
|
||||
ArgsSpec = LambdaType
|
||||
# ArgsSpec = Callable[[Var], list[Var]]
|
||||
ArgsSpec = Callable
|
||||
|
||||
|
||||
def get_args(alias: _GenericAlias) -> tuple[Type, ...]:
|
||||
|
@ -8,7 +8,7 @@ import time
|
||||
from watchdog.events import FileSystemEvent, FileSystemEventHandler
|
||||
from watchdog.observers import Observer
|
||||
|
||||
from reflex.constants import APP_ASSETS_DIR, WEB_ASSETS_DIR
|
||||
from reflex.constants import Dirs
|
||||
|
||||
|
||||
class AssetFolderWatch:
|
||||
@ -20,7 +20,7 @@ class AssetFolderWatch:
|
||||
Args:
|
||||
root: root path of the public.
|
||||
"""
|
||||
self.path = str(root / APP_ASSETS_DIR)
|
||||
self.path = str(root / Dirs.APP_ASSETS)
|
||||
self.event_handler = AssetFolderHandler(root)
|
||||
|
||||
def start(self):
|
||||
@ -90,5 +90,5 @@ class AssetFolderHandler(FileSystemEventHandler):
|
||||
The public file path.
|
||||
"""
|
||||
return src_path.replace(
|
||||
str(self.root / APP_ASSETS_DIR), str(self.root / WEB_ASSETS_DIR)
|
||||
str(self.root / Dirs.APP_ASSETS), str(self.root / Dirs.WEB_ASSETS)
|
||||
)
|
||||
|
@ -27,8 +27,7 @@ from pydantic.fields import ModelField
|
||||
|
||||
from reflex import constants
|
||||
from reflex.base import Base
|
||||
from reflex.utils import console, format, types
|
||||
from reflex.utils.serializers import serialize
|
||||
from reflex.utils import console, format, serializers, types
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from reflex.state import State
|
||||
@ -124,7 +123,7 @@ class Var(ABC):
|
||||
|
||||
# Try to serialize the value.
|
||||
type_ = type(value)
|
||||
name = serialize(value)
|
||||
name = serializers.serialize(value)
|
||||
if name is None:
|
||||
raise TypeError(
|
||||
f"No JSON serializer found for var {value} of type {type_}."
|
||||
|
@ -3,7 +3,7 @@ from typing import Any, Dict
|
||||
import pytest
|
||||
|
||||
from reflex.app import App
|
||||
from reflex.constants import IS_HYDRATED
|
||||
from reflex.constants import CompileVars
|
||||
from reflex.middleware.hydrate_middleware import HydrateMiddleware
|
||||
from reflex.state import State, StateUpdate
|
||||
|
||||
@ -17,7 +17,7 @@ def exp_is_hydrated(state: State) -> Dict[str, Any]:
|
||||
Returns:
|
||||
dict similar to that returned by `State.get_delta` with IS_HYDRATED: True
|
||||
"""
|
||||
return {state.get_name(): {IS_HYDRATED: True}}
|
||||
return {state.get_name(): {CompileVars.IS_HYDRATED: True}}
|
||||
|
||||
|
||||
class TestState(State):
|
||||
|
@ -923,7 +923,7 @@ async def test_dynamic_route_var_route_change_completed_on_load(
|
||||
state.get_name(): {
|
||||
arg_name: exp_val,
|
||||
f"comp_{arg_name}": exp_val,
|
||||
constants.IS_HYDRATED: False,
|
||||
constants.CompileVars.IS_HYDRATED: False,
|
||||
"loaded": exp_index,
|
||||
"counter": exp_index,
|
||||
# "side_effect_counter": exp_index,
|
||||
|
@ -15,7 +15,7 @@ from plotly.graph_objects import Figure
|
||||
|
||||
import reflex as rx
|
||||
from reflex.base import Base
|
||||
from reflex.constants import APP_VAR, IS_HYDRATED, RouteVar, SocketEvent
|
||||
from reflex.constants import CompileVars, RouteVar, SocketEvent
|
||||
from reflex.event import Event, EventHandler
|
||||
from reflex.state import (
|
||||
ImmutableStateError,
|
||||
@ -28,7 +28,7 @@ from reflex.state import (
|
||||
StateProxy,
|
||||
StateUpdate,
|
||||
)
|
||||
from reflex.utils import format, prerequisites
|
||||
from reflex.utils import prerequisites
|
||||
from reflex.vars import BaseVar, ComputedVar
|
||||
|
||||
from .states import GenState
|
||||
@ -118,6 +118,15 @@ class GrandchildState(ChildState):
|
||||
pass
|
||||
|
||||
|
||||
class DateTimeState(State):
|
||||
"""A State with some datetime fields."""
|
||||
|
||||
d: datetime.date = datetime.date.fromisoformat("1989-11-09")
|
||||
dt: datetime.datetime = datetime.datetime.fromisoformat("1989-11-09T18:53:00+01:00")
|
||||
t: datetime.time = datetime.time.fromisoformat("18:53:00+01:00")
|
||||
td: datetime.timedelta = datetime.timedelta(days=11, minutes=11)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def test_state() -> TestState:
|
||||
"""A state.
|
||||
@ -214,7 +223,7 @@ def test_class_vars(test_state):
|
||||
"""
|
||||
cls = type(test_state)
|
||||
assert set(cls.vars.keys()) == {
|
||||
IS_HYDRATED, # added by hydrate_middleware to all State
|
||||
CompileVars.IS_HYDRATED, # added by hydrate_middleware to all State
|
||||
"num1",
|
||||
"num2",
|
||||
"key",
|
||||
@ -292,58 +301,6 @@ def test_dict(test_state):
|
||||
)
|
||||
|
||||
|
||||
def test_format_state(test_state):
|
||||
"""Test that the format state is correct.
|
||||
|
||||
Args:
|
||||
test_state: A state.
|
||||
"""
|
||||
formatted_state = format.format_state(test_state.dict())
|
||||
exp_formatted_state = {
|
||||
"array": [1, 2, 3.14],
|
||||
"child_state": {"count": 23, "grandchild_state": {"value2": ""}, "value": ""},
|
||||
"child_state2": {"value": ""},
|
||||
"complex": {
|
||||
1: {"prop1": 42, "prop2": "hello"},
|
||||
2: {"prop1": 42, "prop2": "hello"},
|
||||
},
|
||||
"dt": "1989-11-09 18:53:00+01:00",
|
||||
"fig": [],
|
||||
"is_hydrated": False,
|
||||
"key": "",
|
||||
"map_key": "a",
|
||||
"mapping": {"a": [1, 2, 3], "b": [4, 5, 6]},
|
||||
"num1": 0,
|
||||
"num2": 3.14,
|
||||
"obj": {"prop1": 42, "prop2": "hello"},
|
||||
"sum": 3.14,
|
||||
"upper": "",
|
||||
}
|
||||
assert formatted_state == exp_formatted_state
|
||||
|
||||
|
||||
def test_format_state_datetime():
|
||||
"""Test that the format state is correct for datetime classes."""
|
||||
|
||||
class DateTimeState(State):
|
||||
d: datetime.date = datetime.date.fromisoformat("1989-11-09")
|
||||
dt: datetime.datetime = datetime.datetime.fromisoformat(
|
||||
"1989-11-09T18:53:00+01:00"
|
||||
)
|
||||
t: datetime.time = datetime.time.fromisoformat("18:53:00+01:00")
|
||||
td: datetime.timedelta = datetime.timedelta(days=11, minutes=11)
|
||||
|
||||
formatted_state = format.format_state(DateTimeState().dict())
|
||||
exp_formatted_state = {
|
||||
"d": "1989-11-09",
|
||||
"dt": "1989-11-09 18:53:00+01:00",
|
||||
"is_hydrated": False,
|
||||
"t": "18:53:00+01:00",
|
||||
"td": "11 days, 0:11:00",
|
||||
}
|
||||
assert formatted_state == exp_formatted_state
|
||||
|
||||
|
||||
def test_default_setters(test_state):
|
||||
"""Test that we can set default values.
|
||||
|
||||
@ -750,21 +707,6 @@ async def test_process_event_generator():
|
||||
assert count == 6
|
||||
|
||||
|
||||
def test_format_event_handler():
|
||||
"""Test formatting an event handler."""
|
||||
assert (
|
||||
format.format_event_handler(TestState.do_something) == "test_state.do_something" # type: ignore
|
||||
)
|
||||
assert (
|
||||
format.format_event_handler(ChildState.change_both) # type: ignore
|
||||
== "test_state.child_state.change_both"
|
||||
)
|
||||
assert (
|
||||
format.format_event_handler(GrandchildState.do_nothing) # type: ignore
|
||||
== "test_state.child_state.grandchild_state.do_nothing"
|
||||
)
|
||||
|
||||
|
||||
def test_get_token(test_state, mocker, router_data):
|
||||
"""Test that the token obtained from the router_data is correct.
|
||||
|
||||
@ -1184,17 +1126,17 @@ def test_computed_var_depends_on_parent_non_cached():
|
||||
assert ps.dict() == {
|
||||
cs.get_name(): {"dep_v": 2},
|
||||
"no_cache_v": 1,
|
||||
IS_HYDRATED: False,
|
||||
CompileVars.IS_HYDRATED: False,
|
||||
}
|
||||
assert ps.dict() == {
|
||||
cs.get_name(): {"dep_v": 4},
|
||||
"no_cache_v": 3,
|
||||
IS_HYDRATED: False,
|
||||
CompileVars.IS_HYDRATED: False,
|
||||
}
|
||||
assert ps.dict() == {
|
||||
cs.get_name(): {"dep_v": 6},
|
||||
"no_cache_v": 5,
|
||||
IS_HYDRATED: False,
|
||||
CompileVars.IS_HYDRATED: False,
|
||||
}
|
||||
assert counter == 6
|
||||
|
||||
@ -1595,7 +1537,7 @@ def mock_app(monkeypatch, app: rx.App, state_manager: StateManager) -> rx.App:
|
||||
The app, after mocking out prerequisites.get_app()
|
||||
"""
|
||||
app_module = Mock()
|
||||
setattr(app_module, APP_VAR, app)
|
||||
setattr(app_module, CompileVars.APP, app)
|
||||
app.state = TestState
|
||||
app.state_manager = state_manager
|
||||
assert app.event_namespace is not None
|
||||
|
578
tests/utils/test_format.py
Normal file
578
tests/utils/test_format.py
Normal file
@ -0,0 +1,578 @@
|
||||
from typing import Any
|
||||
|
||||
import pytest
|
||||
|
||||
from reflex.components.tags.tag import Tag
|
||||
from reflex.event import EVENT_ARG, EventChain, EventHandler, EventSpec
|
||||
from reflex.style import Style
|
||||
from reflex.utils import format
|
||||
from reflex.vars import BaseVar, Var
|
||||
from tests.test_state import ChildState, DateTimeState, GrandchildState, TestState
|
||||
|
||||
|
||||
def mock_event(arg):
|
||||
pass
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"input,output",
|
||||
[
|
||||
("{", "}"),
|
||||
("(", ")"),
|
||||
("[", "]"),
|
||||
("<", ">"),
|
||||
('"', '"'),
|
||||
("'", "'"),
|
||||
],
|
||||
)
|
||||
def test_get_close_char(input: str, output: str):
|
||||
"""Test getting the close character for a given open character.
|
||||
|
||||
Args:
|
||||
input: The open character.
|
||||
output: The expected close character.
|
||||
"""
|
||||
assert format.get_close_char(input) == output
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"text,open,expected",
|
||||
[
|
||||
("", "{", False),
|
||||
("{wrap}", "{", True),
|
||||
("{wrap", "{", False),
|
||||
("{wrap}", "(", False),
|
||||
("(wrap)", "(", True),
|
||||
],
|
||||
)
|
||||
def test_is_wrapped(text: str, open: str, expected: bool):
|
||||
"""Test checking if a string is wrapped in the given open and close characters.
|
||||
|
||||
Args:
|
||||
text: The text to check.
|
||||
open: The open character.
|
||||
expected: Whether the text is wrapped.
|
||||
"""
|
||||
assert format.is_wrapped(text, open) == expected
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"text,open,check_first,num,expected",
|
||||
[
|
||||
("", "{", True, 1, "{}"),
|
||||
("wrap", "{", True, 1, "{wrap}"),
|
||||
("wrap", "(", True, 1, "(wrap)"),
|
||||
("wrap", "(", True, 2, "((wrap))"),
|
||||
("(wrap)", "(", True, 1, "(wrap)"),
|
||||
("{wrap}", "{", True, 2, "{wrap}"),
|
||||
("(wrap)", "{", True, 1, "{(wrap)}"),
|
||||
("(wrap)", "(", False, 1, "((wrap))"),
|
||||
],
|
||||
)
|
||||
def test_wrap(text: str, open: str, expected: str, check_first: bool, num: int):
|
||||
"""Test wrapping a string.
|
||||
|
||||
Args:
|
||||
text: The text to wrap.
|
||||
open: The open character.
|
||||
expected: The expected output string.
|
||||
check_first: Whether to check if the text is already wrapped.
|
||||
num: The number of times to wrap the text.
|
||||
"""
|
||||
assert format.wrap(text, open, check_first=check_first, num=num) == expected
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"text,indent_level,expected",
|
||||
[
|
||||
("", 2, ""),
|
||||
("hello", 2, "hello"),
|
||||
("hello\nworld", 2, " hello\n world\n"),
|
||||
("hello\nworld", 4, " hello\n world\n"),
|
||||
(" hello\n world", 2, " hello\n world\n"),
|
||||
],
|
||||
)
|
||||
def test_indent(text: str, indent_level: int, expected: str, windows_platform: bool):
|
||||
"""Test indenting a string.
|
||||
|
||||
Args:
|
||||
text: The text to indent.
|
||||
indent_level: The number of spaces to indent by.
|
||||
expected: The expected output string.
|
||||
windows_platform: Whether the system is windows.
|
||||
"""
|
||||
assert format.indent(text, indent_level) == (
|
||||
expected.replace("\n", "\r\n") if windows_platform else expected
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"input,output",
|
||||
[
|
||||
("", ""),
|
||||
("hello", "hello"),
|
||||
("Hello", "hello"),
|
||||
("camelCase", "camel_case"),
|
||||
("camelTwoHumps", "camel_two_humps"),
|
||||
("_start_with_underscore", "_start_with_underscore"),
|
||||
("__start_with_double_underscore", "__start_with_double_underscore"),
|
||||
],
|
||||
)
|
||||
def test_to_snake_case(input: str, output: str):
|
||||
"""Test converting strings to snake case.
|
||||
|
||||
Args:
|
||||
input: The input string.
|
||||
output: The expected output string.
|
||||
"""
|
||||
assert format.to_snake_case(input) == output
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"input,output",
|
||||
[
|
||||
("", ""),
|
||||
("hello", "hello"),
|
||||
("Hello", "Hello"),
|
||||
("snake_case", "snakeCase"),
|
||||
("snake_case_two", "snakeCaseTwo"),
|
||||
],
|
||||
)
|
||||
def test_to_camel_case(input: str, output: str):
|
||||
"""Test converting strings to camel case.
|
||||
|
||||
Args:
|
||||
input: The input string.
|
||||
output: The expected output string.
|
||||
"""
|
||||
assert format.to_camel_case(input) == output
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"input,output",
|
||||
[
|
||||
("", ""),
|
||||
("hello", "Hello"),
|
||||
("Hello", "Hello"),
|
||||
("snake_case", "SnakeCase"),
|
||||
("snake_case_two", "SnakeCaseTwo"),
|
||||
],
|
||||
)
|
||||
def test_to_title_case(input: str, output: str):
|
||||
"""Test converting strings to title case.
|
||||
|
||||
Args:
|
||||
input: The input string.
|
||||
output: The expected output string.
|
||||
"""
|
||||
assert format.to_title_case(input) == output
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"input,output",
|
||||
[
|
||||
("", ""),
|
||||
("hello", "hello"),
|
||||
("Hello", "hello"),
|
||||
("snake_case", "snake-case"),
|
||||
("snake_case_two", "snake-case-two"),
|
||||
],
|
||||
)
|
||||
def test_to_kebab_case(input: str, output: str):
|
||||
"""Test converting strings to kebab case.
|
||||
|
||||
Args:
|
||||
input: the input string.
|
||||
output: the output string.
|
||||
"""
|
||||
assert format.to_kebab_case(input) == output
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"input,output",
|
||||
[
|
||||
("", "{``}"),
|
||||
("hello", "{`hello`}"),
|
||||
("hello world", "{`hello world`}"),
|
||||
("hello=`world`", "{`hello=\\`world\\``}"),
|
||||
],
|
||||
)
|
||||
def test_format_string(input: str, output: str):
|
||||
"""Test formating the input as JS string literal.
|
||||
|
||||
Args:
|
||||
input: the input string.
|
||||
output: the output string.
|
||||
"""
|
||||
assert format.format_string(input) == output
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"input,output",
|
||||
[
|
||||
(Var.create(value="test"), "{`test`}"),
|
||||
(Var.create(value="test", is_local=True), "{`test`}"),
|
||||
(Var.create(value="test", is_local=False), "{test}"),
|
||||
(Var.create(value="test", is_string=True), "{`test`}"),
|
||||
(Var.create(value="test", is_string=False), "{`test`}"),
|
||||
(Var.create(value="test", is_local=False, is_string=False), "{test}"),
|
||||
],
|
||||
)
|
||||
def test_format_var(input: Var, output: str):
|
||||
assert format.format_var(input) == output
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"route,format_case,expected",
|
||||
[
|
||||
("", True, "index"),
|
||||
("/", True, "index"),
|
||||
("custom-route", True, "custom-route"),
|
||||
("custom-route", False, "custom-route"),
|
||||
("custom-route/", True, "custom-route"),
|
||||
("custom-route/", False, "custom-route"),
|
||||
("/custom-route", True, "custom-route"),
|
||||
("/custom-route", False, "custom-route"),
|
||||
("/custom_route", True, "custom-route"),
|
||||
("/custom_route", False, "custom_route"),
|
||||
("/CUSTOM_route", True, "custom-route"),
|
||||
("/CUSTOM_route", False, "CUSTOM_route"),
|
||||
],
|
||||
)
|
||||
def test_format_route(route: str, format_case: bool, expected: bool):
|
||||
"""Test formatting a route.
|
||||
|
||||
Args:
|
||||
route: The route to format.
|
||||
format_case: Whether to change casing to snake_case.
|
||||
expected: The expected formatted route.
|
||||
"""
|
||||
assert format.format_route(route, format_case=format_case) == expected
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"condition,true_value,false_value,expected",
|
||||
[
|
||||
("cond", "<C1>", '""', '{isTrue(cond) ? <C1> : ""}'),
|
||||
("cond", "<C1>", "<C2>", "{isTrue(cond) ? <C1> : <C2>}"),
|
||||
],
|
||||
)
|
||||
def test_format_cond(condition: str, true_value: str, false_value: str, expected: str):
|
||||
"""Test formatting a cond.
|
||||
|
||||
Args:
|
||||
condition: The condition to check.
|
||||
true_value: The value to return if the condition is true.
|
||||
false_value: The value to return if the condition is false.
|
||||
expected: The expected output string.
|
||||
"""
|
||||
assert format.format_cond(condition, true_value, false_value) == expected
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"prop,formatted",
|
||||
[
|
||||
("string", '"string"'),
|
||||
("{wrapped_string}", "{wrapped_string}"),
|
||||
(True, "{true}"),
|
||||
(False, "{false}"),
|
||||
(123, "{123}"),
|
||||
(3.14, "{3.14}"),
|
||||
([1, 2, 3], "{[1, 2, 3]}"),
|
||||
(["a", "b", "c"], '{["a", "b", "c"]}'),
|
||||
({"a": 1, "b": 2, "c": 3}, '{{"a": 1, "b": 2, "c": 3}}'),
|
||||
({"a": 'foo "bar" baz'}, r'{{"a": "foo \"bar\" baz"}}'),
|
||||
(
|
||||
{
|
||||
"a": 'foo "{ "bar" }" baz',
|
||||
"b": BaseVar(name="val", type_="str"),
|
||||
},
|
||||
r'{{"a": "foo \"{ \"bar\" }\" baz", "b": val}}',
|
||||
),
|
||||
(
|
||||
EventChain(
|
||||
events=[EventSpec(handler=EventHandler(fn=mock_event))], args_spec=None
|
||||
),
|
||||
'{_e => addEvents([Event("mock_event", {})], _e)}',
|
||||
),
|
||||
(
|
||||
EventChain(
|
||||
events=[
|
||||
EventSpec(
|
||||
handler=EventHandler(fn=mock_event),
|
||||
args=((Var.create_safe("arg"), EVENT_ARG.target.value),),
|
||||
)
|
||||
],
|
||||
args_spec=None,
|
||||
),
|
||||
'{_e => addEvents([Event("mock_event", {arg:_e.target.value})], _e)}',
|
||||
),
|
||||
({"a": "red", "b": "blue"}, '{{"a": "red", "b": "blue"}}'),
|
||||
(BaseVar(name="var", type_="int"), "{var}"),
|
||||
(
|
||||
BaseVar(
|
||||
name="_",
|
||||
type_=Any,
|
||||
state="",
|
||||
is_local=True,
|
||||
is_string=False,
|
||||
),
|
||||
"{_}",
|
||||
),
|
||||
(BaseVar(name='state.colors["a"]', type_="str"), '{state.colors["a"]}'),
|
||||
({"a": BaseVar(name="val", type_="str")}, '{{"a": val}}'),
|
||||
({"a": BaseVar(name='"val"', type_="str")}, '{{"a": "val"}}'),
|
||||
(
|
||||
{"a": BaseVar(name='state.colors["val"]', type_="str")},
|
||||
'{{"a": state.colors["val"]}}',
|
||||
),
|
||||
# tricky real-world case from markdown component
|
||||
(
|
||||
{
|
||||
"h1": f"{{({{node, ...props}}) => <Heading {{...props}} {''.join(Tag(name='', props=Style({'as_': 'h1'})).format_props())} />}}"
|
||||
},
|
||||
'{{"h1": ({node, ...props}) => <Heading {...props} as={`h1`} />}}',
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_format_prop(prop: Var, formatted: str):
|
||||
"""Test that the formatted value of an prop is correct.
|
||||
|
||||
Args:
|
||||
prop: The prop to test.
|
||||
formatted: The expected formatted value.
|
||||
"""
|
||||
assert format.format_prop(prop) == formatted
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"single_props,key_value_props,output",
|
||||
[
|
||||
(["string"], {"key": 42}, ["key={42}", "string"]),
|
||||
],
|
||||
)
|
||||
def test_format_props(single_props, key_value_props, output):
|
||||
"""Test the result of formatting a set of props (both single and keyvalue).
|
||||
|
||||
Args:
|
||||
single_props: the list of single props
|
||||
key_value_props: the dict of key value props
|
||||
output: the expected output
|
||||
"""
|
||||
assert format.format_props(*single_props, **key_value_props) == output
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"input,output",
|
||||
[
|
||||
(EventHandler(fn=mock_event), ("", "mock_event")),
|
||||
],
|
||||
)
|
||||
def test_get_handler_parts(input, output):
|
||||
assert format.get_event_handler_parts(input) == output
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"input,output",
|
||||
[
|
||||
(TestState.do_something, "test_state.do_something"),
|
||||
(ChildState.change_both, "test_state.child_state.change_both"),
|
||||
(
|
||||
GrandchildState.do_nothing,
|
||||
"test_state.child_state.grandchild_state.do_nothing",
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_format_event_handler(input, output):
|
||||
"""Test formatting an event handler.
|
||||
|
||||
Args:
|
||||
input: The event handler input.
|
||||
output: The expected output.
|
||||
"""
|
||||
assert format.format_event_handler(input) == output # type: ignore
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"input,output",
|
||||
[
|
||||
(EventSpec(handler=EventHandler(fn=mock_event)), 'Event("mock_event", {})'),
|
||||
],
|
||||
)
|
||||
def test_format_event(input, output):
|
||||
assert format.format_event(input) == output
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"input,output",
|
||||
[
|
||||
(
|
||||
EventChain(
|
||||
events=[
|
||||
EventSpec(handler=EventHandler(fn=mock_event)),
|
||||
EventSpec(handler=EventHandler(fn=mock_event)),
|
||||
],
|
||||
args_spec=None,
|
||||
),
|
||||
'addEvents([Event("mock_event", {}),Event("mock_event", {})])',
|
||||
),
|
||||
(
|
||||
EventChain(
|
||||
events=[
|
||||
EventSpec(handler=EventHandler(fn=mock_event)),
|
||||
EventSpec(handler=EventHandler(fn=mock_event)),
|
||||
],
|
||||
args_spec=lambda e0: [e0],
|
||||
),
|
||||
'addEvents([Event("mock_event", {}),Event("mock_event", {})])',
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_format_event_chain(input, output):
|
||||
assert format.format_event_chain(input) == output
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"input,output",
|
||||
[
|
||||
({"query": {"k1": 1, "k2": 2}}, {"k1": 1, "k2": 2}),
|
||||
({"query": {"k1": 1, "k-2": 2}}, {"k1": 1, "k_2": 2}),
|
||||
],
|
||||
)
|
||||
def test_format_query_params(input, output):
|
||||
assert format.format_query_params(input) == output
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"input, output",
|
||||
[
|
||||
(
|
||||
TestState().dict(), # type: ignore
|
||||
{
|
||||
"array": [1, 2, 3.14],
|
||||
"child_state": {
|
||||
"count": 23,
|
||||
"grandchild_state": {"value2": ""},
|
||||
"value": "",
|
||||
},
|
||||
"child_state2": {"value": ""},
|
||||
"complex": {
|
||||
1: {"prop1": 42, "prop2": "hello"},
|
||||
2: {"prop1": 42, "prop2": "hello"},
|
||||
},
|
||||
"dt": "1989-11-09 18:53:00+01:00",
|
||||
"fig": [],
|
||||
"is_hydrated": False,
|
||||
"key": "",
|
||||
"map_key": "a",
|
||||
"mapping": {"a": [1, 2, 3], "b": [4, 5, 6]},
|
||||
"num1": 0,
|
||||
"num2": 3.14,
|
||||
"obj": {"prop1": 42, "prop2": "hello"},
|
||||
"sum": 3.14,
|
||||
"upper": "",
|
||||
},
|
||||
),
|
||||
(
|
||||
DateTimeState().dict(),
|
||||
{
|
||||
"d": "1989-11-09",
|
||||
"dt": "1989-11-09 18:53:00+01:00",
|
||||
"is_hydrated": False,
|
||||
"t": "18:53:00+01:00",
|
||||
"td": "11 days, 0:11:00",
|
||||
},
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_format_state(input, output):
|
||||
"""Test that the format state is correct.
|
||||
|
||||
Args:
|
||||
input: The state to format.
|
||||
output: The expected formatted state.
|
||||
"""
|
||||
assert format.format_state(input) == output
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"input,output",
|
||||
[
|
||||
("input1", "ref_input1"),
|
||||
("input 1", "ref_input_1"),
|
||||
("input-1", "ref_input_1"),
|
||||
("input_1", "ref_input_1"),
|
||||
("a long test?1! name", "ref_a_long_test_1_name"),
|
||||
],
|
||||
)
|
||||
def test_format_ref(input, output):
|
||||
"""Test formatting a ref.
|
||||
|
||||
Args:
|
||||
input: The name to format.
|
||||
output: The expected formatted name.
|
||||
"""
|
||||
assert format.format_ref(input) == output
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"input,output",
|
||||
[
|
||||
(("my_array", None), "refs_my_array"),
|
||||
(("my_array", Var.create(0)), "refs_my_array[0]"),
|
||||
(("my_array", Var.create(1)), "refs_my_array[1]"),
|
||||
],
|
||||
)
|
||||
def test_format_array_ref(input, output):
|
||||
assert format.format_array_ref(input[0], input[1]) == output
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"input,output",
|
||||
[
|
||||
("/foo", [("foo", "/foo")]),
|
||||
("/foo/bar", [("foo", "/foo"), ("bar", "/foo/bar")]),
|
||||
(
|
||||
"/foo/bar/baz",
|
||||
[("foo", "/foo"), ("bar", "/foo/bar"), ("baz", "/foo/bar/baz")],
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_format_breadcrumbs(input, output):
|
||||
assert format.format_breadcrumbs(input) == output
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"input, output",
|
||||
[
|
||||
("library@^0.1.2", "library"),
|
||||
("library", "library"),
|
||||
("@library@^0.1.2", "@library"),
|
||||
("@library", "@library"),
|
||||
],
|
||||
)
|
||||
def test_format_library_name(input: str, output: str):
|
||||
"""Test formating a library name to remove the @version part.
|
||||
|
||||
Args:
|
||||
input: the input string.
|
||||
output: the output string.
|
||||
"""
|
||||
assert format.format_library_name(input) == output
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"input,output",
|
||||
[
|
||||
(None, "null"),
|
||||
(True, "true"),
|
||||
(1, "1"),
|
||||
(1.0, "1.0"),
|
||||
([], "[]"),
|
||||
([1, 2, 3], "[1, 2, 3]"),
|
||||
({}, "{}"),
|
||||
({"k1": False, "k2": True}, '{"k1": false, "k2": true}'),
|
||||
],
|
||||
)
|
||||
def test_json_dumps(input, output):
|
||||
assert format.json_dumps(input) == output
|
@ -9,20 +9,17 @@ from packaging import version
|
||||
|
||||
from reflex import constants
|
||||
from reflex.base import Base
|
||||
from reflex.components.tags import Tag
|
||||
from reflex.event import EVENT_ARG, EventChain, EventHandler, EventSpec
|
||||
from reflex.event import EventHandler
|
||||
from reflex.state import State
|
||||
from reflex.style import Style
|
||||
from reflex.utils import (
|
||||
build,
|
||||
format,
|
||||
imports,
|
||||
prerequisites,
|
||||
types,
|
||||
)
|
||||
from reflex.utils import exec as utils_exec
|
||||
from reflex.utils.serializers import serialize
|
||||
from reflex.vars import BaseVar, Var
|
||||
from reflex.vars import Var
|
||||
|
||||
|
||||
def mock_event(arg):
|
||||
@ -36,7 +33,7 @@ def get_above_max_version():
|
||||
max bun version plus one.
|
||||
|
||||
"""
|
||||
semantic_version_list = constants.BUN_VERSION.split(".")
|
||||
semantic_version_list = constants.Bun.VERSION.split(".")
|
||||
semantic_version_list[-1] = str(int(semantic_version_list[-1]) + 1) # type: ignore
|
||||
return ".".join(semantic_version_list)
|
||||
|
||||
@ -59,179 +56,6 @@ def test_func():
|
||||
pass
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"input,output",
|
||||
[
|
||||
("", ""),
|
||||
("hello", "hello"),
|
||||
("Hello", "hello"),
|
||||
("camelCase", "camel_case"),
|
||||
("camelTwoHumps", "camel_two_humps"),
|
||||
("_start_with_underscore", "_start_with_underscore"),
|
||||
("__start_with_double_underscore", "__start_with_double_underscore"),
|
||||
],
|
||||
)
|
||||
def test_to_snake_case(input: str, output: str):
|
||||
"""Test converting strings to snake case.
|
||||
|
||||
Args:
|
||||
input: The input string.
|
||||
output: The expected output string.
|
||||
"""
|
||||
assert format.to_snake_case(input) == output
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"input,output",
|
||||
[
|
||||
("", ""),
|
||||
("hello", "hello"),
|
||||
("Hello", "Hello"),
|
||||
("snake_case", "snakeCase"),
|
||||
("snake_case_two", "snakeCaseTwo"),
|
||||
],
|
||||
)
|
||||
def test_to_camel_case(input: str, output: str):
|
||||
"""Test converting strings to camel case.
|
||||
|
||||
Args:
|
||||
input: The input string.
|
||||
output: The expected output string.
|
||||
"""
|
||||
assert format.to_camel_case(input) == output
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"input,output",
|
||||
[
|
||||
("", ""),
|
||||
("hello", "Hello"),
|
||||
("Hello", "Hello"),
|
||||
("snake_case", "SnakeCase"),
|
||||
("snake_case_two", "SnakeCaseTwo"),
|
||||
],
|
||||
)
|
||||
def test_to_title_case(input: str, output: str):
|
||||
"""Test converting strings to title case.
|
||||
|
||||
Args:
|
||||
input: The input string.
|
||||
output: The expected output string.
|
||||
"""
|
||||
assert format.to_title_case(input) == output
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"input,output",
|
||||
[
|
||||
("{", "}"),
|
||||
("(", ")"),
|
||||
("[", "]"),
|
||||
("<", ">"),
|
||||
('"', '"'),
|
||||
("'", "'"),
|
||||
],
|
||||
)
|
||||
def test_get_close_char(input: str, output: str):
|
||||
"""Test getting the close character for a given open character.
|
||||
|
||||
Args:
|
||||
input: The open character.
|
||||
output: The expected close character.
|
||||
"""
|
||||
assert format.get_close_char(input) == output
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"text,open,expected",
|
||||
[
|
||||
("", "{", False),
|
||||
("{wrap}", "{", True),
|
||||
("{wrap", "{", False),
|
||||
("{wrap}", "(", False),
|
||||
("(wrap)", "(", True),
|
||||
],
|
||||
)
|
||||
def test_is_wrapped(text: str, open: str, expected: bool):
|
||||
"""Test checking if a string is wrapped in the given open and close characters.
|
||||
|
||||
Args:
|
||||
text: The text to check.
|
||||
open: The open character.
|
||||
expected: Whether the text is wrapped.
|
||||
"""
|
||||
assert format.is_wrapped(text, open) == expected
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"text,open,check_first,num,expected",
|
||||
[
|
||||
("", "{", True, 1, "{}"),
|
||||
("wrap", "{", True, 1, "{wrap}"),
|
||||
("wrap", "(", True, 1, "(wrap)"),
|
||||
("wrap", "(", True, 2, "((wrap))"),
|
||||
("(wrap)", "(", True, 1, "(wrap)"),
|
||||
("{wrap}", "{", True, 2, "{wrap}"),
|
||||
("(wrap)", "{", True, 1, "{(wrap)}"),
|
||||
("(wrap)", "(", False, 1, "((wrap))"),
|
||||
],
|
||||
)
|
||||
def test_wrap(text: str, open: str, expected: str, check_first: bool, num: int):
|
||||
"""Test wrapping a string.
|
||||
|
||||
Args:
|
||||
text: The text to wrap.
|
||||
open: The open character.
|
||||
expected: The expected output string.
|
||||
check_first: Whether to check if the text is already wrapped.
|
||||
num: The number of times to wrap the text.
|
||||
"""
|
||||
assert format.wrap(text, open, check_first=check_first, num=num) == expected
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"text,indent_level,expected",
|
||||
[
|
||||
("", 2, ""),
|
||||
("hello", 2, "hello"),
|
||||
("hello\nworld", 2, " hello\n world\n"),
|
||||
("hello\nworld", 4, " hello\n world\n"),
|
||||
(" hello\n world", 2, " hello\n world\n"),
|
||||
],
|
||||
)
|
||||
def test_indent(text: str, indent_level: int, expected: str, windows_platform: bool):
|
||||
"""Test indenting a string.
|
||||
|
||||
Args:
|
||||
text: The text to indent.
|
||||
indent_level: The number of spaces to indent by.
|
||||
expected: The expected output string.
|
||||
windows_platform: Whether the system is windows.
|
||||
"""
|
||||
assert format.indent(text, indent_level) == (
|
||||
expected.replace("\n", "\r\n") if windows_platform else expected
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"condition,true_value,false_value,expected",
|
||||
[
|
||||
("cond", "<C1>", '""', '{isTrue(cond) ? <C1> : ""}'),
|
||||
("cond", "<C1>", "<C2>", "{isTrue(cond) ? <C1> : <C2>}"),
|
||||
],
|
||||
)
|
||||
def test_format_cond(condition: str, true_value: str, false_value: str, expected: str):
|
||||
"""Test formatting a cond.
|
||||
|
||||
Args:
|
||||
condition: The condition to check.
|
||||
true_value: The value to return if the condition is true.
|
||||
false_value: The value to return if the condition is false.
|
||||
expected: The expected output string.
|
||||
"""
|
||||
assert format.format_cond(condition, true_value, false_value) == expected
|
||||
|
||||
|
||||
def test_merge_imports():
|
||||
"""Test that imports are merged correctly."""
|
||||
d1 = {"react": {"Component"}}
|
||||
@ -263,110 +87,6 @@ def test_is_generic_alias(cls: type, expected: bool):
|
||||
assert types.is_generic_alias(cls) == expected
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"route,format_case,expected",
|
||||
[
|
||||
("", True, "index"),
|
||||
("/", True, "index"),
|
||||
("custom-route", True, "custom-route"),
|
||||
("custom-route", False, "custom-route"),
|
||||
("custom-route/", True, "custom-route"),
|
||||
("custom-route/", False, "custom-route"),
|
||||
("/custom-route", True, "custom-route"),
|
||||
("/custom-route", False, "custom-route"),
|
||||
("/custom_route", True, "custom-route"),
|
||||
("/custom_route", False, "custom_route"),
|
||||
("/CUSTOM_route", True, "custom-route"),
|
||||
("/CUSTOM_route", False, "CUSTOM_route"),
|
||||
],
|
||||
)
|
||||
def test_format_route(route: str, format_case: bool, expected: bool):
|
||||
"""Test formatting a route.
|
||||
|
||||
Args:
|
||||
route: The route to format.
|
||||
format_case: Whether to change casing to snake_case.
|
||||
expected: The expected formatted route.
|
||||
"""
|
||||
assert format.format_route(route, format_case=format_case) == expected
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"prop,formatted",
|
||||
[
|
||||
("string", '"string"'),
|
||||
("{wrapped_string}", "{wrapped_string}"),
|
||||
(True, "{true}"),
|
||||
(False, "{false}"),
|
||||
(123, "{123}"),
|
||||
(3.14, "{3.14}"),
|
||||
([1, 2, 3], "{[1, 2, 3]}"),
|
||||
(["a", "b", "c"], '{["a", "b", "c"]}'),
|
||||
({"a": 1, "b": 2, "c": 3}, '{{"a": 1, "b": 2, "c": 3}}'),
|
||||
({"a": 'foo "bar" baz'}, r'{{"a": "foo \"bar\" baz"}}'),
|
||||
(
|
||||
{
|
||||
"a": 'foo "{ "bar" }" baz',
|
||||
"b": BaseVar(name="val", type_="str"),
|
||||
},
|
||||
r'{{"a": "foo \"{ \"bar\" }\" baz", "b": val}}',
|
||||
),
|
||||
(
|
||||
EventChain(
|
||||
events=[EventSpec(handler=EventHandler(fn=mock_event))], args_spec=None
|
||||
),
|
||||
'{_e => addEvents([Event("mock_event", {})], _e)}',
|
||||
),
|
||||
(
|
||||
EventChain(
|
||||
events=[
|
||||
EventSpec(
|
||||
handler=EventHandler(fn=mock_event),
|
||||
args=((Var.create_safe("arg"), EVENT_ARG.target.value),),
|
||||
)
|
||||
],
|
||||
args_spec=None,
|
||||
),
|
||||
'{_e => addEvents([Event("mock_event", {arg:_e.target.value})], _e)}',
|
||||
),
|
||||
({"a": "red", "b": "blue"}, '{{"a": "red", "b": "blue"}}'),
|
||||
(BaseVar(name="var", type_="int"), "{var}"),
|
||||
(
|
||||
BaseVar(
|
||||
name="_",
|
||||
type_=Any,
|
||||
state="",
|
||||
is_local=True,
|
||||
is_string=False,
|
||||
),
|
||||
"{_}",
|
||||
),
|
||||
(BaseVar(name='state.colors["a"]', type_="str"), '{state.colors["a"]}'),
|
||||
({"a": BaseVar(name="val", type_="str")}, '{{"a": val}}'),
|
||||
({"a": BaseVar(name='"val"', type_="str")}, '{{"a": "val"}}'),
|
||||
(
|
||||
{"a": BaseVar(name='state.colors["val"]', type_="str")},
|
||||
'{{"a": state.colors["val"]}}',
|
||||
),
|
||||
# tricky real-world case from markdown component
|
||||
(
|
||||
{
|
||||
"h1": f"{{({{node, ...props}}) => <Heading {{...props}} {''.join(Tag(name='', props=Style({'as_': 'h1'})).format_props())} />}}"
|
||||
},
|
||||
'{{"h1": ({node, ...props}) => <Heading {...props} as={`h1`} />}}',
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_format_prop(prop: Var, formatted: str):
|
||||
"""Test that the formatted value of an prop is correct.
|
||||
|
||||
Args:
|
||||
prop: The prop to test.
|
||||
formatted: The expected formatted value.
|
||||
"""
|
||||
assert format.format_prop(prop) == formatted
|
||||
|
||||
|
||||
def test_validate_invalid_bun_path(mocker):
|
||||
"""Test that an error is thrown when a custom specified bun path is not valid
|
||||
or does not exist.
|
||||
@ -523,31 +243,11 @@ def test_create_config_e2e(tmp_working_dir):
|
||||
app_name = "e2e"
|
||||
prerequisites.create_config(app_name)
|
||||
eval_globals = {}
|
||||
exec((tmp_working_dir / constants.CONFIG_FILE).read_text(), eval_globals)
|
||||
exec((tmp_working_dir / constants.Config.FILE).read_text(), eval_globals)
|
||||
config = eval_globals["config"]
|
||||
assert config.app_name == app_name
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"name,expected",
|
||||
[
|
||||
("input1", "ref_input1"),
|
||||
("input 1", "ref_input_1"),
|
||||
("input-1", "ref_input_1"),
|
||||
("input_1", "ref_input_1"),
|
||||
("a long test?1! name", "ref_a_long_test_1_name"),
|
||||
],
|
||||
)
|
||||
def test_format_ref(name, expected):
|
||||
"""Test formatting a ref.
|
||||
|
||||
Args:
|
||||
name: The name to format.
|
||||
expected: The expected formatted name.
|
||||
"""
|
||||
assert format.format_ref(name) == expected
|
||||
|
||||
|
||||
class DataFrame:
|
||||
"""A Fake pandas DataFrame class."""
|
||||
|
||||
@ -585,8 +285,8 @@ def test_initialize_non_existent_gitignore(tmp_path, mocker, gitignore_exists):
|
||||
mocker: The mock object.
|
||||
gitignore_exists: Whether a gitignore file exists in the root dir.
|
||||
"""
|
||||
expected = constants.DEFAULT_GITIGNORE.copy()
|
||||
mocker.patch("reflex.constants.GITIGNORE_FILE", tmp_path / ".gitignore")
|
||||
expected = constants.GitIgnore.DEFAULTS.copy()
|
||||
mocker.patch("reflex.constants.GitIgnore.FILE", tmp_path / ".gitignore")
|
||||
|
||||
gitignore_file = tmp_path / ".gitignore"
|
||||
|
||||
@ -633,8 +333,8 @@ def test_node_install_windows(tmp_path, mocker):
|
||||
fnm_root_path = tmp_path / "reflex" / "fnm"
|
||||
fnm_exe = fnm_root_path / "fnm.exe"
|
||||
|
||||
mocker.patch("reflex.utils.prerequisites.constants.FNM_DIR", fnm_root_path)
|
||||
mocker.patch("reflex.utils.prerequisites.constants.FNM_EXE", fnm_exe)
|
||||
mocker.patch("reflex.utils.prerequisites.constants.Fnm.DIR", fnm_root_path)
|
||||
mocker.patch("reflex.utils.prerequisites.constants.Fnm.EXE", fnm_exe)
|
||||
mocker.patch("reflex.utils.prerequisites.constants.IS_WINDOWS", True)
|
||||
mocker.patch("reflex.utils.processes.new_process")
|
||||
mocker.patch("reflex.utils.processes.stream_logs")
|
||||
@ -675,8 +375,8 @@ def test_node_install_unix(tmp_path, mocker, machine, system):
|
||||
fnm_root_path = tmp_path / "reflex" / "fnm"
|
||||
fnm_exe = fnm_root_path / "fnm"
|
||||
|
||||
mocker.patch("reflex.utils.prerequisites.constants.FNM_DIR", fnm_root_path)
|
||||
mocker.patch("reflex.utils.prerequisites.constants.FNM_EXE", fnm_exe)
|
||||
mocker.patch("reflex.utils.prerequisites.constants.Fnm.DIR", fnm_root_path)
|
||||
mocker.patch("reflex.utils.prerequisites.constants.Fnm.EXE", fnm_exe)
|
||||
mocker.patch("reflex.utils.prerequisites.constants.IS_WINDOWS", False)
|
||||
mocker.patch("reflex.utils.prerequisites.platform.machine", return_value=machine)
|
||||
mocker.patch("reflex.utils.prerequisites.platform.system", return_value=system)
|
||||
@ -701,14 +401,14 @@ def test_node_install_unix(tmp_path, mocker, machine, system):
|
||||
fnm_exe,
|
||||
"install",
|
||||
"--arch=arm64",
|
||||
constants.NODE_VERSION,
|
||||
constants.Node.VERSION,
|
||||
"--fnm-dir",
|
||||
fnm_root_path,
|
||||
]
|
||||
)
|
||||
else:
|
||||
process.assert_called_with(
|
||||
[fnm_exe, "install", constants.NODE_VERSION, "--fnm-dir", fnm_root_path]
|
||||
[fnm_exe, "install", constants.Node.VERSION, "--fnm-dir", fnm_root_path]
|
||||
)
|
||||
chmod.assert_called_once()
|
||||
|
||||
@ -759,7 +459,7 @@ def test_output_system_info(mocker):
|
||||
This test makes no assertions about the output, other than it executes
|
||||
without crashing.
|
||||
"""
|
||||
mocker.patch("reflex.utils.console.LOG_LEVEL", constants.LogLevel.DEBUG)
|
||||
mocker.patch("reflex.utils.console._LOG_LEVEL", constants.LogLevel.DEBUG)
|
||||
utils_exec.output_system_info()
|
||||
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user