merging
This commit is contained in:
commit
fcf6aa6cf3
@ -74,7 +74,7 @@ jobs:
|
||||
echo "$outdated"
|
||||
|
||||
# Ignore 3rd party dependencies that are not updated.
|
||||
filtered_outdated=$(echo "$outdated" | grep -vE 'Package|@chakra-ui|lucide-react|@splinetool/runtime|ag-grid-react|framer-motion|react-markdown|remark-math|remark-gfm|rehype-katex|rehype-raw' || true)
|
||||
filtered_outdated=$(echo "$outdated" | grep -vE 'Package|@chakra-ui|lucide-react|@splinetool/runtime|ag-grid-react|framer-motion|react-markdown|remark-math|remark-gfm|rehype-katex|rehype-raw|remark-unwrap-images' || true)
|
||||
no_extra=$(echo "$filtered_outdated" | grep -vE '\|\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-' || true)
|
||||
|
||||
|
||||
|
@ -3,7 +3,7 @@ fail_fast: true
|
||||
repos:
|
||||
|
||||
- repo: https://github.com/charliermarsh/ruff-pre-commit
|
||||
rev: v0.6.9
|
||||
rev: v0.7.0
|
||||
hooks:
|
||||
- id: ruff-format
|
||||
args: [reflex, tests]
|
||||
|
1126
poetry.lock
generated
1126
poetry.lock
generated
File diff suppressed because it is too large
Load Diff
@ -1,6 +1,6 @@
|
||||
[tool.poetry]
|
||||
name = "reflex"
|
||||
version = "0.6.3dev1"
|
||||
version = "0.6.4dev1"
|
||||
description = "Web apps in pure Python."
|
||||
license = "Apache-2.0"
|
||||
authors = [
|
||||
@ -33,6 +33,7 @@ jinja2 = ">=3.1.2,<4.0"
|
||||
psutil = ">=5.9.4,<7.0"
|
||||
pydantic = ">=1.10.2,<3.0"
|
||||
python-multipart = ">=0.0.5,<0.1"
|
||||
python-dotenv = ">=1.0.1"
|
||||
python-socketio = ">=5.7.0,<6.0"
|
||||
redis = ">=4.3.5,<6.0"
|
||||
rich = ">=13.0.0,<14.0"
|
||||
@ -68,9 +69,9 @@ darglint = ">=1.8.1,<2.0"
|
||||
toml = ">=0.10.2,<1.0"
|
||||
pytest-asyncio = ">=0.24.0"
|
||||
pytest-cov = ">=4.0.0,<6.0"
|
||||
ruff = "^0.6.9"
|
||||
ruff = "^0.7.0"
|
||||
pandas = ">=2.1.1,<3.0"
|
||||
pillow = ">=10.0.0,<11.0"
|
||||
pillow = ">=10.0.0,<12.0"
|
||||
plotly = ">=5.13.0,<6.0"
|
||||
asynctest = ">=0.13.0,<1.0"
|
||||
pre-commit = ">=3.2.1"
|
||||
@ -91,7 +92,7 @@ build-backend = "poetry.core.masonry.api"
|
||||
[tool.ruff]
|
||||
target-version = "py39"
|
||||
lint.select = ["B", "D", "E", "F", "I", "SIM", "W"]
|
||||
lint.ignore = ["B008", "D203", "D205", "D213", "D401", "D406", "D407", "E501", "F403", "F405", "F541"]
|
||||
lint.ignore = ["B008", "D203", "D205", "D213", "D401", "D406", "D407", "E501", "F403", "F405", "F541", "SIM115"]
|
||||
lint.pydocstyle.convention = "google"
|
||||
|
||||
[tool.ruff.lint.per-file-ignores]
|
||||
|
29
reflex/.templates/web/components/shiki/code.js
Normal file
29
reflex/.templates/web/components/shiki/code.js
Normal file
@ -0,0 +1,29 @@
|
||||
import { useEffect, useState } from "react"
|
||||
import { codeToHtml} from "shiki"
|
||||
|
||||
export function Code ({code, theme, language, transformers, ...divProps}) {
|
||||
const [codeResult, setCodeResult] = useState("")
|
||||
useEffect(() => {
|
||||
async function fetchCode() {
|
||||
let final_code;
|
||||
|
||||
if (Array.isArray(code)) {
|
||||
final_code = code[0];
|
||||
} else {
|
||||
final_code = code;
|
||||
}
|
||||
const result = await codeToHtml(final_code, {
|
||||
lang: language,
|
||||
theme,
|
||||
transformers
|
||||
});
|
||||
setCodeResult(result);
|
||||
}
|
||||
fetchCode();
|
||||
}, [code, language, theme, transformers]
|
||||
|
||||
)
|
||||
return (
|
||||
<div dangerouslySetInnerHTML={{__html: codeResult}} {...divProps} ></div>
|
||||
)
|
||||
}
|
@ -743,6 +743,7 @@ export const useEventLoop = (
|
||||
addEvents([
|
||||
Event(`${exception_state_name}.handle_frontend_exception`, {
|
||||
stack: error.stack,
|
||||
component_stack: "",
|
||||
}),
|
||||
]);
|
||||
return false;
|
||||
@ -754,6 +755,7 @@ export const useEventLoop = (
|
||||
addEvents([
|
||||
Event(`${exception_state_name}.handle_frontend_exception`, {
|
||||
stack: event.reason.stack,
|
||||
component_stack: "",
|
||||
}),
|
||||
]);
|
||||
return false;
|
||||
|
@ -320,13 +320,15 @@ _MAPPING: dict = {
|
||||
"upload_files",
|
||||
"window_alert",
|
||||
],
|
||||
"istate.storage": [
|
||||
"Cookie",
|
||||
"LocalStorage",
|
||||
"SessionStorage",
|
||||
],
|
||||
"middleware": ["middleware", "Middleware"],
|
||||
"model": ["session", "Model"],
|
||||
"state": [
|
||||
"var",
|
||||
"Cookie",
|
||||
"LocalStorage",
|
||||
"SessionStorage",
|
||||
"ComponentState",
|
||||
"State",
|
||||
],
|
||||
|
@ -174,15 +174,15 @@ from .event import stop_propagation as stop_propagation
|
||||
from .event import upload_files as upload_files
|
||||
from .event import window_alert as window_alert
|
||||
from .experimental import _x as _x
|
||||
from .istate.storage import Cookie as Cookie
|
||||
from .istate.storage import LocalStorage as LocalStorage
|
||||
from .istate.storage import SessionStorage as SessionStorage
|
||||
from .middleware import Middleware as Middleware
|
||||
from .middleware import middleware as middleware
|
||||
from .model import Model as Model
|
||||
from .model import session as session
|
||||
from .page import page as page
|
||||
from .state import ComponentState as ComponentState
|
||||
from .state import Cookie as Cookie
|
||||
from .state import LocalStorage as LocalStorage
|
||||
from .state import SessionStorage as SessionStorage
|
||||
from .state import State as State
|
||||
from .state import var as var
|
||||
from .style import Style as Style
|
||||
|
@ -64,7 +64,7 @@ from reflex.components.core.client_side_routing import (
|
||||
)
|
||||
from reflex.components.core.upload import Upload, get_upload_dir
|
||||
from reflex.components.radix import themes
|
||||
from reflex.config import get_config
|
||||
from reflex.config import environment, get_config
|
||||
from reflex.event import Event, EventHandler, EventSpec, window_alert
|
||||
from reflex.model import Model, get_db_status
|
||||
from reflex.page import (
|
||||
@ -957,15 +957,16 @@ class App(MiddlewareMixin, LifespanMixin, Base):
|
||||
executor = None
|
||||
if (
|
||||
platform.system() in ("Linux", "Darwin")
|
||||
and os.environ.get("REFLEX_COMPILE_PROCESSES") is not None
|
||||
and (number_of_processes := environment.REFLEX_COMPILE_PROCESSES)
|
||||
is not None
|
||||
):
|
||||
executor = concurrent.futures.ProcessPoolExecutor(
|
||||
max_workers=int(os.environ.get("REFLEX_COMPILE_PROCESSES", 0)) or None,
|
||||
max_workers=number_of_processes,
|
||||
mp_context=multiprocessing.get_context("fork"),
|
||||
)
|
||||
else:
|
||||
executor = concurrent.futures.ThreadPoolExecutor(
|
||||
max_workers=int(os.environ.get("REFLEX_COMPILE_THREADS", 0)) or None,
|
||||
max_workers=environment.REFLEX_COMPILE_THREADS
|
||||
)
|
||||
|
||||
with executor:
|
||||
|
@ -2,7 +2,6 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
from typing import Dict, Iterable, Optional, Type, Union
|
||||
@ -16,7 +15,7 @@ from reflex.components.component import (
|
||||
CustomComponent,
|
||||
StatefulComponent,
|
||||
)
|
||||
from reflex.config import get_config
|
||||
from reflex.config import environment, get_config
|
||||
from reflex.state import BaseState
|
||||
from reflex.style import SYSTEM_COLOR_MODE
|
||||
from reflex.utils.exec import is_prod_mode
|
||||
@ -527,7 +526,7 @@ def remove_tailwind_from_postcss() -> tuple[str, str]:
|
||||
|
||||
def purge_web_pages_dir():
|
||||
"""Empty out .web/pages directory."""
|
||||
if not is_prod_mode() and os.environ.get("REFLEX_PERSIST_WEB_DIR"):
|
||||
if not is_prod_mode() and environment.REFLEX_PERSIST_WEB_DIR:
|
||||
# Skip purging the web directory in dev mode if REFLEX_PERSIST_WEB_DIR is set.
|
||||
return
|
||||
|
||||
|
@ -28,7 +28,8 @@ from reflex.components.base import (
|
||||
Title,
|
||||
)
|
||||
from reflex.components.component import Component, ComponentStyle, CustomComponent
|
||||
from reflex.state import BaseState, Cookie, LocalStorage, SessionStorage
|
||||
from reflex.istate.storage import Cookie, LocalStorage, SessionStorage
|
||||
from reflex.state import BaseState
|
||||
from reflex.style import Style
|
||||
from reflex.utils import console, format, imports, path_ops
|
||||
from reflex.utils.imports import ImportVar, ParsedImportDict
|
||||
|
@ -2,16 +2,32 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import List
|
||||
from typing import Dict, List, Tuple
|
||||
|
||||
from reflex.compiler.compiler import _compile_component
|
||||
from reflex.components.component import Component
|
||||
from reflex.components.el import div, p
|
||||
from reflex.constants import Hooks, Imports
|
||||
from reflex.event import EventChain, EventHandler
|
||||
from reflex.utils.imports import ImportVar
|
||||
from reflex.event import EventHandler
|
||||
from reflex.state import FrontendEventExceptionState
|
||||
from reflex.vars.base import Var
|
||||
from reflex.vars.function import FunctionVar
|
||||
|
||||
|
||||
def on_error_spec(
|
||||
error: Var[Dict[str, str]], info: Var[Dict[str, str]]
|
||||
) -> Tuple[Var[str], Var[str]]:
|
||||
"""The spec for the on_error event handler.
|
||||
|
||||
Args:
|
||||
error: The error message.
|
||||
info: Additional information about the error.
|
||||
|
||||
Returns:
|
||||
The arguments for the event handler.
|
||||
"""
|
||||
return (
|
||||
error.stack,
|
||||
info.componentStack,
|
||||
)
|
||||
|
||||
|
||||
class ErrorBoundary(Component):
|
||||
@ -21,31 +37,13 @@ class ErrorBoundary(Component):
|
||||
tag = "ErrorBoundary"
|
||||
|
||||
# Fired when the boundary catches an error.
|
||||
on_error: EventHandler[lambda error, info: [error, info]] = Var( # type: ignore
|
||||
"logFrontendError"
|
||||
).to(FunctionVar, EventChain)
|
||||
on_error: EventHandler[on_error_spec]
|
||||
|
||||
# Rendered instead of the children when an error is caught.
|
||||
Fallback_component: Var[Component] = Var(_js_expr="Fallback")._replace(
|
||||
_var_type=Component
|
||||
)
|
||||
|
||||
def add_imports(self) -> dict[str, list[ImportVar]]:
|
||||
"""Add imports for the component.
|
||||
|
||||
Returns:
|
||||
The imports to add.
|
||||
"""
|
||||
return Imports.EVENTS
|
||||
|
||||
def add_hooks(self) -> List[str | Var]:
|
||||
"""Add hooks for the component.
|
||||
|
||||
Returns:
|
||||
The hooks to add.
|
||||
"""
|
||||
return [Hooks.EVENTS, Hooks.FRONTEND_ERRORS]
|
||||
|
||||
def add_custom_code(self) -> List[str]:
|
||||
"""Add custom Javascript code into the page that contains this component.
|
||||
|
||||
@ -75,5 +73,20 @@ class ErrorBoundary(Component):
|
||||
"""
|
||||
]
|
||||
|
||||
@classmethod
|
||||
def create(cls, *children, **props):
|
||||
"""Create an ErrorBoundary component.
|
||||
|
||||
Args:
|
||||
*children: The children of the component.
|
||||
**props: The props of the component.
|
||||
|
||||
Returns:
|
||||
The ErrorBoundary component.
|
||||
"""
|
||||
if "on_error" not in props:
|
||||
props["on_error"] = FrontendEventExceptionState.handle_frontend_exception
|
||||
return super().create(*children, **props)
|
||||
|
||||
|
||||
error_boundary = ErrorBoundary.create
|
||||
|
@ -3,17 +3,18 @@
|
||||
# ------------------- DO NOT EDIT ----------------------
|
||||
# This file was generated by `reflex/utils/pyi_generator.py`!
|
||||
# ------------------------------------------------------
|
||||
from typing import Any, Dict, List, Optional, Union, overload
|
||||
from typing import Any, Dict, List, Optional, Tuple, Union, overload
|
||||
|
||||
from reflex.components.component import Component
|
||||
from reflex.event import EventType
|
||||
from reflex.style import Style
|
||||
from reflex.utils.imports import ImportVar
|
||||
from reflex.vars.base import Var
|
||||
|
||||
def on_error_spec(
|
||||
error: Var[Dict[str, str]], info: Var[Dict[str, str]]
|
||||
) -> Tuple[Var[str], Var[str]]: ...
|
||||
|
||||
class ErrorBoundary(Component):
|
||||
def add_imports(self) -> dict[str, list[ImportVar]]: ...
|
||||
def add_hooks(self) -> List[str | Var]: ...
|
||||
def add_custom_code(self) -> List[str]: ...
|
||||
@overload
|
||||
@classmethod
|
||||
@ -31,7 +32,7 @@ class ErrorBoundary(Component):
|
||||
on_click: Optional[EventType[[]]] = None,
|
||||
on_context_menu: Optional[EventType[[]]] = None,
|
||||
on_double_click: Optional[EventType[[]]] = None,
|
||||
on_error: Optional[EventType[[]]] = None,
|
||||
on_error: Optional[EventType[str, str]] = None,
|
||||
on_focus: Optional[EventType[[]]] = None,
|
||||
on_mount: Optional[EventType[[]]] = None,
|
||||
on_mouse_down: Optional[EventType[[]]] = None,
|
||||
@ -45,7 +46,7 @@ class ErrorBoundary(Component):
|
||||
on_unmount: Optional[EventType[[]]] = None,
|
||||
**props,
|
||||
) -> "ErrorBoundary":
|
||||
"""Create the component.
|
||||
"""Create an ErrorBoundary component.
|
||||
|
||||
Args:
|
||||
*children: The children of the component.
|
||||
@ -59,7 +60,7 @@ class ErrorBoundary(Component):
|
||||
**props: The props of the component.
|
||||
|
||||
Returns:
|
||||
The component.
|
||||
The ErrorBoundary component.
|
||||
"""
|
||||
...
|
||||
|
||||
|
@ -45,6 +45,7 @@ from reflex.event import (
|
||||
EventVar,
|
||||
call_event_fn,
|
||||
call_event_handler,
|
||||
empty_event,
|
||||
get_handler_args,
|
||||
)
|
||||
from reflex.style import Style, format_as_emotion
|
||||
@ -626,21 +627,21 @@ class Component(BaseComponent, ABC):
|
||||
|
||||
"""
|
||||
default_triggers = {
|
||||
EventTriggers.ON_FOCUS: lambda: [],
|
||||
EventTriggers.ON_BLUR: lambda: [],
|
||||
EventTriggers.ON_CLICK: lambda: [],
|
||||
EventTriggers.ON_CONTEXT_MENU: lambda: [],
|
||||
EventTriggers.ON_DOUBLE_CLICK: lambda: [],
|
||||
EventTriggers.ON_MOUSE_DOWN: lambda: [],
|
||||
EventTriggers.ON_MOUSE_ENTER: lambda: [],
|
||||
EventTriggers.ON_MOUSE_LEAVE: lambda: [],
|
||||
EventTriggers.ON_MOUSE_MOVE: lambda: [],
|
||||
EventTriggers.ON_MOUSE_OUT: lambda: [],
|
||||
EventTriggers.ON_MOUSE_OVER: lambda: [],
|
||||
EventTriggers.ON_MOUSE_UP: lambda: [],
|
||||
EventTriggers.ON_SCROLL: lambda: [],
|
||||
EventTriggers.ON_MOUNT: lambda: [],
|
||||
EventTriggers.ON_UNMOUNT: lambda: [],
|
||||
EventTriggers.ON_FOCUS: empty_event,
|
||||
EventTriggers.ON_BLUR: empty_event,
|
||||
EventTriggers.ON_CLICK: empty_event,
|
||||
EventTriggers.ON_CONTEXT_MENU: empty_event,
|
||||
EventTriggers.ON_DOUBLE_CLICK: empty_event,
|
||||
EventTriggers.ON_MOUSE_DOWN: empty_event,
|
||||
EventTriggers.ON_MOUSE_ENTER: empty_event,
|
||||
EventTriggers.ON_MOUSE_LEAVE: empty_event,
|
||||
EventTriggers.ON_MOUSE_MOVE: empty_event,
|
||||
EventTriggers.ON_MOUSE_OUT: empty_event,
|
||||
EventTriggers.ON_MOUSE_OVER: empty_event,
|
||||
EventTriggers.ON_MOUSE_UP: empty_event,
|
||||
EventTriggers.ON_SCROLL: empty_event,
|
||||
EventTriggers.ON_MOUNT: empty_event,
|
||||
EventTriggers.ON_UNMOUNT: empty_event,
|
||||
}
|
||||
|
||||
# Look for component specific triggers,
|
||||
@ -651,7 +652,7 @@ class Component(BaseComponent, ABC):
|
||||
annotation = field.annotation
|
||||
if (metadata := getattr(annotation, "__metadata__", None)) is not None:
|
||||
args_spec = metadata[0]
|
||||
default_triggers[field.name] = args_spec or (lambda: [])
|
||||
default_triggers[field.name] = args_spec or (empty_event) # type: ignore
|
||||
return default_triggers
|
||||
|
||||
def __repr__(self) -> str:
|
||||
@ -1708,7 +1709,7 @@ class CustomComponent(Component):
|
||||
value = self._create_event_chain(
|
||||
value=value,
|
||||
args_spec=event_triggers_in_component_declaration.get(
|
||||
key, lambda: []
|
||||
key, empty_event
|
||||
),
|
||||
key=key,
|
||||
)
|
||||
|
@ -40,7 +40,7 @@ class Clipboard(Fragment):
|
||||
on_mouse_out: Optional[EventType[[]]] = None,
|
||||
on_mouse_over: Optional[EventType[[]]] = None,
|
||||
on_mouse_up: Optional[EventType[[]]] = None,
|
||||
on_paste: Optional[EventType] = None,
|
||||
on_paste: Optional[EventType[list[tuple[str, str]]]] = None,
|
||||
on_scroll: Optional[EventType[[]]] = None,
|
||||
on_unmount: Optional[EventType[[]]] = None,
|
||||
**props,
|
||||
|
@ -2,13 +2,13 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
from pathlib import Path
|
||||
from typing import Any, Callable, ClassVar, Dict, List, Optional, Tuple
|
||||
|
||||
from reflex.components.component import Component, ComponentNamespace, MemoizationLeaf
|
||||
from reflex.components.el.elements.forms import Input
|
||||
from reflex.components.radix.themes.layout.box import Box
|
||||
from reflex.config import environment
|
||||
from reflex.constants import Dirs
|
||||
from reflex.event import (
|
||||
CallableEventSpec,
|
||||
@ -125,9 +125,7 @@ def get_upload_dir() -> Path:
|
||||
"""
|
||||
Upload.is_used = True
|
||||
|
||||
uploaded_files_dir = Path(
|
||||
os.environ.get("REFLEX_UPLOADED_FILES_DIR", "./uploaded_files")
|
||||
)
|
||||
uploaded_files_dir = environment.REFLEX_UPLOADED_FILES_DIR
|
||||
uploaded_files_dir.mkdir(parents=True, exist_ok=True)
|
||||
return uploaded_files_dir
|
||||
|
||||
@ -179,7 +177,7 @@ class UploadFilesProvider(Component):
|
||||
class Upload(MemoizationLeaf):
|
||||
"""A file upload component."""
|
||||
|
||||
library = "react-dropzone@14.2.9"
|
||||
library = "react-dropzone@14.2.10"
|
||||
|
||||
tag = "ReactDropzone"
|
||||
|
||||
|
@ -381,7 +381,7 @@ for theme_name in dir(Theme):
|
||||
class CodeBlock(Component):
|
||||
"""A code block."""
|
||||
|
||||
library = "react-syntax-highlighter@15.5.0"
|
||||
library = "react-syntax-highlighter@15.6.1"
|
||||
|
||||
tag = "PrismAsyncLight"
|
||||
|
||||
|
@ -5,6 +5,8 @@ from __future__ import annotations
|
||||
from enum import Enum
|
||||
from typing import Any, Dict, List, Literal, Optional, Tuple, Union
|
||||
|
||||
from typing_extensions import TypedDict
|
||||
|
||||
from reflex.base import Base
|
||||
from reflex.components.component import Component, NoSSRComponent
|
||||
from reflex.components.literals import LiteralRowMarker
|
||||
@ -107,17 +109,76 @@ class DataEditorTheme(Base):
|
||||
text_medium: Optional[str] = None
|
||||
|
||||
|
||||
def on_edit_spec(pos, data: dict[str, Any]):
|
||||
"""The on edit spec function.
|
||||
class Bounds(TypedDict):
|
||||
"""The bounds of the group header."""
|
||||
|
||||
Args:
|
||||
pos: The position of the edit event.
|
||||
data: The data of the edit event.
|
||||
x: int
|
||||
y: int
|
||||
width: int
|
||||
height: int
|
||||
|
||||
Returns:
|
||||
The position and data.
|
||||
"""
|
||||
return [pos, data]
|
||||
|
||||
class CompatSelection(TypedDict):
|
||||
"""The selection."""
|
||||
|
||||
items: list
|
||||
|
||||
|
||||
class Rectangle(TypedDict):
|
||||
"""The bounds of the group header."""
|
||||
|
||||
x: int
|
||||
y: int
|
||||
width: int
|
||||
height: int
|
||||
|
||||
|
||||
class GridSelectionCurrent(TypedDict):
|
||||
"""The current selection."""
|
||||
|
||||
cell: tuple[int, int]
|
||||
range: Rectangle
|
||||
rangeStack: list[Rectangle]
|
||||
|
||||
|
||||
class GridSelection(TypedDict):
|
||||
"""The grid selection."""
|
||||
|
||||
current: Optional[GridSelectionCurrent]
|
||||
columns: CompatSelection
|
||||
rows: CompatSelection
|
||||
|
||||
|
||||
class GroupHeaderClickedEventArgs(TypedDict):
|
||||
"""The arguments for the group header clicked event."""
|
||||
|
||||
kind: str
|
||||
group: str
|
||||
location: tuple[int, int]
|
||||
bounds: Bounds
|
||||
isEdge: bool
|
||||
shiftKey: bool
|
||||
ctrlKey: bool
|
||||
metaKey: bool
|
||||
isTouch: bool
|
||||
localEventX: int
|
||||
localEventY: int
|
||||
button: int
|
||||
buttons: int
|
||||
scrollEdge: tuple[int, int]
|
||||
|
||||
|
||||
class GridCell(TypedDict):
|
||||
"""The grid cell."""
|
||||
|
||||
span: Optional[List[int]]
|
||||
|
||||
|
||||
class GridColumn(TypedDict):
|
||||
"""The grid column."""
|
||||
|
||||
title: str
|
||||
group: Optional[str]
|
||||
|
||||
|
||||
class DataEditor(NoSSRComponent):
|
||||
@ -232,16 +293,18 @@ class DataEditor(NoSSRComponent):
|
||||
on_cell_context_menu: EventHandler[identity_event(Tuple[int, int])]
|
||||
|
||||
# Fired when a cell is edited.
|
||||
on_cell_edited: EventHandler[on_edit_spec]
|
||||
on_cell_edited: EventHandler[identity_event(Tuple[int, int], GridCell)]
|
||||
|
||||
# Fired when a group header is clicked.
|
||||
on_group_header_clicked: EventHandler[on_edit_spec]
|
||||
on_group_header_clicked: EventHandler[identity_event(Tuple[int, int], GridCell)]
|
||||
|
||||
# Fired when a group header is right-clicked.
|
||||
on_group_header_context_menu: EventHandler[lambda grp_idx, data: [grp_idx, data]]
|
||||
on_group_header_context_menu: EventHandler[
|
||||
identity_event(int, GroupHeaderClickedEventArgs)
|
||||
]
|
||||
|
||||
# Fired when a group header is renamed.
|
||||
on_group_header_renamed: EventHandler[lambda idx, val: [idx, val]]
|
||||
on_group_header_renamed: EventHandler[identity_event(str, str)]
|
||||
|
||||
# Fired when a header is clicked.
|
||||
on_header_clicked: EventHandler[identity_event(Tuple[int, int])]
|
||||
@ -250,16 +313,18 @@ class DataEditor(NoSSRComponent):
|
||||
on_header_context_menu: EventHandler[identity_event(Tuple[int, int])]
|
||||
|
||||
# Fired when a header menu item is clicked.
|
||||
on_header_menu_click: EventHandler[lambda col, pos: [col, pos]]
|
||||
on_header_menu_click: EventHandler[identity_event(int, Rectangle)]
|
||||
|
||||
# Fired when an item is hovered.
|
||||
on_item_hovered: EventHandler[identity_event(Tuple[int, int])]
|
||||
|
||||
# Fired when a selection is deleted.
|
||||
on_delete: EventHandler[lambda selection: [selection]]
|
||||
on_delete: EventHandler[identity_event(GridSelection)]
|
||||
|
||||
# Fired when editing is finished.
|
||||
on_finished_editing: EventHandler[lambda new_value, movement: [new_value, movement]]
|
||||
on_finished_editing: EventHandler[
|
||||
identity_event(Union[GridCell, None], tuple[int, int])
|
||||
]
|
||||
|
||||
# Fired when a row is appended.
|
||||
on_row_appended: EventHandler[empty_event]
|
||||
@ -268,7 +333,7 @@ class DataEditor(NoSSRComponent):
|
||||
on_selection_cleared: EventHandler[empty_event]
|
||||
|
||||
# Fired when a column is resized.
|
||||
on_column_resize: EventHandler[lambda col, width: [col, width]]
|
||||
on_column_resize: EventHandler[identity_event(GridColumn, int)]
|
||||
|
||||
def add_imports(self) -> ImportDict:
|
||||
"""Add imports for the component.
|
||||
|
@ -6,6 +6,8 @@
|
||||
from enum import Enum
|
||||
from typing import Any, Dict, List, Literal, Optional, Union, overload
|
||||
|
||||
from typing_extensions import TypedDict
|
||||
|
||||
from reflex.base import Base
|
||||
from reflex.components.component import NoSSRComponent
|
||||
from reflex.event import EventType
|
||||
@ -76,7 +78,53 @@ class DataEditorTheme(Base):
|
||||
text_light: Optional[str]
|
||||
text_medium: Optional[str]
|
||||
|
||||
def on_edit_spec(pos, data: dict[str, Any]): ...
|
||||
class Bounds(TypedDict):
|
||||
x: int
|
||||
y: int
|
||||
width: int
|
||||
height: int
|
||||
|
||||
class CompatSelection(TypedDict):
|
||||
items: list
|
||||
|
||||
class Rectangle(TypedDict):
|
||||
x: int
|
||||
y: int
|
||||
width: int
|
||||
height: int
|
||||
|
||||
class GridSelectionCurrent(TypedDict):
|
||||
cell: tuple[int, int]
|
||||
range: Rectangle
|
||||
rangeStack: list[Rectangle]
|
||||
|
||||
class GridSelection(TypedDict):
|
||||
current: Optional[GridSelectionCurrent]
|
||||
columns: CompatSelection
|
||||
rows: CompatSelection
|
||||
|
||||
class GroupHeaderClickedEventArgs(TypedDict):
|
||||
kind: str
|
||||
group: str
|
||||
location: tuple[int, int]
|
||||
bounds: Bounds
|
||||
isEdge: bool
|
||||
shiftKey: bool
|
||||
ctrlKey: bool
|
||||
metaKey: bool
|
||||
isTouch: bool
|
||||
localEventX: int
|
||||
localEventY: int
|
||||
button: int
|
||||
buttons: int
|
||||
scrollEdge: tuple[int, int]
|
||||
|
||||
class GridCell(TypedDict):
|
||||
span: Optional[List[int]]
|
||||
|
||||
class GridColumn(TypedDict):
|
||||
title: str
|
||||
group: Optional[str]
|
||||
|
||||
class DataEditor(NoSSRComponent):
|
||||
def add_imports(self) -> ImportDict: ...
|
||||
@ -136,24 +184,28 @@ class DataEditor(NoSSRComponent):
|
||||
autofocus: Optional[bool] = None,
|
||||
custom_attrs: Optional[Dict[str, Union[Var, str]]] = None,
|
||||
on_blur: Optional[EventType[[]]] = None,
|
||||
on_cell_activated: Optional[EventType] = None,
|
||||
on_cell_clicked: Optional[EventType] = None,
|
||||
on_cell_context_menu: Optional[EventType] = None,
|
||||
on_cell_edited: Optional[EventType[[]]] = None,
|
||||
on_cell_activated: Optional[EventType[tuple[int, int]]] = None,
|
||||
on_cell_clicked: Optional[EventType[tuple[int, int]]] = None,
|
||||
on_cell_context_menu: Optional[EventType[tuple[int, int]]] = None,
|
||||
on_cell_edited: Optional[EventType[tuple[int, int], GridCell]] = None,
|
||||
on_click: Optional[EventType[[]]] = None,
|
||||
on_column_resize: Optional[EventType[[]]] = None,
|
||||
on_column_resize: Optional[EventType[GridColumn, int]] = None,
|
||||
on_context_menu: Optional[EventType[[]]] = None,
|
||||
on_delete: Optional[EventType[[]]] = None,
|
||||
on_delete: Optional[EventType[GridSelection]] = None,
|
||||
on_double_click: Optional[EventType[[]]] = None,
|
||||
on_finished_editing: Optional[EventType[[]]] = None,
|
||||
on_finished_editing: Optional[
|
||||
EventType[Union[GridCell, None], tuple[int, int]]
|
||||
] = None,
|
||||
on_focus: Optional[EventType[[]]] = None,
|
||||
on_group_header_clicked: Optional[EventType[[]]] = None,
|
||||
on_group_header_context_menu: Optional[EventType[[]]] = None,
|
||||
on_group_header_renamed: Optional[EventType[[]]] = None,
|
||||
on_header_clicked: Optional[EventType] = None,
|
||||
on_header_context_menu: Optional[EventType] = None,
|
||||
on_header_menu_click: Optional[EventType[[]]] = None,
|
||||
on_item_hovered: Optional[EventType] = None,
|
||||
on_group_header_clicked: Optional[EventType[tuple[int, int], GridCell]] = None,
|
||||
on_group_header_context_menu: Optional[
|
||||
EventType[int, GroupHeaderClickedEventArgs]
|
||||
] = None,
|
||||
on_group_header_renamed: Optional[EventType[str, str]] = None,
|
||||
on_header_clicked: Optional[EventType[tuple[int, int]]] = None,
|
||||
on_header_context_menu: Optional[EventType[tuple[int, int]]] = None,
|
||||
on_header_menu_click: Optional[EventType[int, Rectangle]] = None,
|
||||
on_item_hovered: Optional[EventType[tuple[int, int]]] = None,
|
||||
on_mount: Optional[EventType[[]]] = None,
|
||||
on_mouse_down: Optional[EventType[[]]] = None,
|
||||
on_mouse_enter: Optional[EventType[[]]] = None,
|
||||
|
813
reflex/components/datadisplay/shiki_code_block.py
Normal file
813
reflex/components/datadisplay/shiki_code_block.py
Normal file
@ -0,0 +1,813 @@
|
||||
"""Shiki syntax hghlighter component."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
from collections import defaultdict
|
||||
from typing import Any, Literal, Optional, Union
|
||||
|
||||
from reflex.base import Base
|
||||
from reflex.components.component import Component, ComponentNamespace
|
||||
from reflex.components.core.colors import color
|
||||
from reflex.components.core.cond import color_mode_cond
|
||||
from reflex.components.el.elements.forms import Button
|
||||
from reflex.components.lucide.icon import Icon
|
||||
from reflex.components.radix.themes.layout.box import Box
|
||||
from reflex.event import call_script, set_clipboard
|
||||
from reflex.style import Style
|
||||
from reflex.utils.exceptions import VarTypeError
|
||||
from reflex.utils.imports import ImportVar
|
||||
from reflex.vars.base import LiteralVar, Var
|
||||
from reflex.vars.function import FunctionStringVar
|
||||
from reflex.vars.sequence import StringVar, string_replace_operation
|
||||
|
||||
|
||||
def copy_script() -> Any:
|
||||
"""Copy script for the code block and modify the child SVG element.
|
||||
|
||||
|
||||
Returns:
|
||||
Any: The result of calling the script.
|
||||
"""
|
||||
return call_script(
|
||||
f"""
|
||||
// Event listener for the parent click
|
||||
document.addEventListener('click', function(event) {{
|
||||
// Find the closest div (parent element)
|
||||
const parent = event.target.closest('div');
|
||||
// If the parent is found
|
||||
if (parent) {{
|
||||
// Find the SVG element within the parent
|
||||
const svgIcon = parent.querySelector('svg');
|
||||
// If the SVG exists, proceed with the script
|
||||
if (svgIcon) {{
|
||||
const originalPath = svgIcon.innerHTML;
|
||||
const checkmarkPath = '<polyline points="20 6 9 17 4 12"></polyline>'; // Checkmark SVG path
|
||||
function transition(element, scale, opacity) {{
|
||||
element.style.transform = `scale(${{scale}})`;
|
||||
element.style.opacity = opacity;
|
||||
}}
|
||||
// Animate the SVG
|
||||
transition(svgIcon, 0, '0');
|
||||
setTimeout(() => {{
|
||||
svgIcon.innerHTML = checkmarkPath; // Replace content with checkmark
|
||||
svgIcon.setAttribute('viewBox', '0 0 24 24'); // Adjust viewBox if necessary
|
||||
transition(svgIcon, 1, '1');
|
||||
setTimeout(() => {{
|
||||
transition(svgIcon, 0, '0');
|
||||
setTimeout(() => {{
|
||||
svgIcon.innerHTML = originalPath; // Restore original SVG content
|
||||
transition(svgIcon, 1, '1');
|
||||
}}, 125);
|
||||
}}, 600);
|
||||
}}, 125);
|
||||
}} else {{
|
||||
// console.error('SVG element not found within the parent.');
|
||||
}}
|
||||
}} else {{
|
||||
// console.error('Parent element not found.');
|
||||
}}
|
||||
}});
|
||||
"""
|
||||
)
|
||||
|
||||
|
||||
SHIKIJS_TRANSFORMER_FNS = {
|
||||
"transformerNotationDiff",
|
||||
"transformerNotationHighlight",
|
||||
"transformerNotationWordHighlight",
|
||||
"transformerNotationFocus",
|
||||
"transformerNotationErrorLevel",
|
||||
"transformerRenderWhitespace",
|
||||
"transformerMetaHighlight",
|
||||
"transformerMetaWordHighlight",
|
||||
"transformerCompactLineOptions",
|
||||
# TODO: this transformer when included adds a weird behavior which removes other code lines. Need to figure out why.
|
||||
# "transformerRemoveLineBreak",
|
||||
"transformerRemoveNotationEscape",
|
||||
}
|
||||
LINE_NUMBER_STYLING = {
|
||||
"code": {
|
||||
"counter-reset": "step",
|
||||
"counter-increment": "step 0",
|
||||
"display": "grid",
|
||||
"line-height": "1.7",
|
||||
"font-size": "0.875em",
|
||||
},
|
||||
"code .line::before": {
|
||||
"content": "counter(step)",
|
||||
"counter-increment": "step",
|
||||
"width": "1rem",
|
||||
"margin-right": "1.5rem",
|
||||
"display": "inline-block",
|
||||
"text-align": "right",
|
||||
"color": "rgba(115,138,148,.4)",
|
||||
},
|
||||
}
|
||||
BOX_PARENT_STYLING = {
|
||||
"pre": {
|
||||
"margin": "0",
|
||||
"padding": "24px",
|
||||
"background": "transparent",
|
||||
"overflow-x": "auto",
|
||||
"border-radius": "6px",
|
||||
},
|
||||
}
|
||||
|
||||
THEME_MAPPING = {
|
||||
"light": "one-light",
|
||||
"dark": "one-dark-pro",
|
||||
"a11y-dark": "github-dark",
|
||||
}
|
||||
LANGUAGE_MAPPING = {"bash": "shellscript"}
|
||||
LiteralCodeLanguage = Literal[
|
||||
"abap",
|
||||
"actionscript-3",
|
||||
"ada",
|
||||
"angular-html",
|
||||
"angular-ts",
|
||||
"apache",
|
||||
"apex",
|
||||
"apl",
|
||||
"applescript",
|
||||
"ara",
|
||||
"asciidoc",
|
||||
"asm",
|
||||
"astro",
|
||||
"awk",
|
||||
"ballerina",
|
||||
"bat",
|
||||
"beancount",
|
||||
"berry",
|
||||
"bibtex",
|
||||
"bicep",
|
||||
"blade",
|
||||
"c",
|
||||
"cadence",
|
||||
"clarity",
|
||||
"clojure",
|
||||
"cmake",
|
||||
"cobol",
|
||||
"codeowners",
|
||||
"codeql",
|
||||
"coffee",
|
||||
"common-lisp",
|
||||
"coq",
|
||||
"cpp",
|
||||
"crystal",
|
||||
"csharp",
|
||||
"css",
|
||||
"csv",
|
||||
"cue",
|
||||
"cypher",
|
||||
"d",
|
||||
"dart",
|
||||
"dax",
|
||||
"desktop",
|
||||
"diff",
|
||||
"docker",
|
||||
"dotenv",
|
||||
"dream-maker",
|
||||
"edge",
|
||||
"elixir",
|
||||
"elm",
|
||||
"emacs-lisp",
|
||||
"erb",
|
||||
"erlang",
|
||||
"fennel",
|
||||
"fish",
|
||||
"fluent",
|
||||
"fortran-fixed-form",
|
||||
"fortran-free-form",
|
||||
"fsharp",
|
||||
"gdresource",
|
||||
"gdscript",
|
||||
"gdshader",
|
||||
"genie",
|
||||
"gherkin",
|
||||
"git-commit",
|
||||
"git-rebase",
|
||||
"gleam",
|
||||
"glimmer-js",
|
||||
"glimmer-ts",
|
||||
"glsl",
|
||||
"gnuplot",
|
||||
"go",
|
||||
"graphql",
|
||||
"groovy",
|
||||
"hack",
|
||||
"haml",
|
||||
"handlebars",
|
||||
"haskell",
|
||||
"haxe",
|
||||
"hcl",
|
||||
"hjson",
|
||||
"hlsl",
|
||||
"html",
|
||||
"html-derivative",
|
||||
"http",
|
||||
"hxml",
|
||||
"hy",
|
||||
"imba",
|
||||
"ini",
|
||||
"java",
|
||||
"javascript",
|
||||
"jinja",
|
||||
"jison",
|
||||
"json",
|
||||
"json5",
|
||||
"jsonc",
|
||||
"jsonl",
|
||||
"jsonnet",
|
||||
"jssm",
|
||||
"jsx",
|
||||
"julia",
|
||||
"kotlin",
|
||||
"kusto",
|
||||
"latex",
|
||||
"lean",
|
||||
"less",
|
||||
"liquid",
|
||||
"log",
|
||||
"logo",
|
||||
"lua",
|
||||
"luau",
|
||||
"make",
|
||||
"markdown",
|
||||
"marko",
|
||||
"matlab",
|
||||
"mdc",
|
||||
"mdx",
|
||||
"mermaid",
|
||||
"mojo",
|
||||
"move",
|
||||
"narrat",
|
||||
"nextflow",
|
||||
"nginx",
|
||||
"nim",
|
||||
"nix",
|
||||
"nushell",
|
||||
"objective-c",
|
||||
"objective-cpp",
|
||||
"ocaml",
|
||||
"pascal",
|
||||
"perl",
|
||||
"php",
|
||||
"plsql",
|
||||
"po",
|
||||
"postcss",
|
||||
"powerquery",
|
||||
"powershell",
|
||||
"prisma",
|
||||
"prolog",
|
||||
"proto",
|
||||
"pug",
|
||||
"puppet",
|
||||
"purescript",
|
||||
"python",
|
||||
"qml",
|
||||
"qmldir",
|
||||
"qss",
|
||||
"r",
|
||||
"racket",
|
||||
"raku",
|
||||
"razor",
|
||||
"reg",
|
||||
"regexp",
|
||||
"rel",
|
||||
"riscv",
|
||||
"rst",
|
||||
"ruby",
|
||||
"rust",
|
||||
"sas",
|
||||
"sass",
|
||||
"scala",
|
||||
"scheme",
|
||||
"scss",
|
||||
"shaderlab",
|
||||
"shellscript",
|
||||
"shellsession",
|
||||
"smalltalk",
|
||||
"solidity",
|
||||
"soy",
|
||||
"sparql",
|
||||
"splunk",
|
||||
"sql",
|
||||
"ssh-config",
|
||||
"stata",
|
||||
"stylus",
|
||||
"svelte",
|
||||
"swift",
|
||||
"system-verilog",
|
||||
"systemd",
|
||||
"tasl",
|
||||
"tcl",
|
||||
"templ",
|
||||
"terraform",
|
||||
"tex",
|
||||
"toml",
|
||||
"ts-tags",
|
||||
"tsv",
|
||||
"tsx",
|
||||
"turtle",
|
||||
"twig",
|
||||
"typescript",
|
||||
"typespec",
|
||||
"typst",
|
||||
"v",
|
||||
"vala",
|
||||
"vb",
|
||||
"verilog",
|
||||
"vhdl",
|
||||
"viml",
|
||||
"vue",
|
||||
"vue-html",
|
||||
"vyper",
|
||||
"wasm",
|
||||
"wenyan",
|
||||
"wgsl",
|
||||
"wikitext",
|
||||
"wolfram",
|
||||
"xml",
|
||||
"xsl",
|
||||
"yaml",
|
||||
"zenscript",
|
||||
"zig",
|
||||
]
|
||||
LiteralCodeTheme = Literal[
|
||||
"andromeeda",
|
||||
"aurora-x",
|
||||
"ayu-dark",
|
||||
"catppuccin-frappe",
|
||||
"catppuccin-latte",
|
||||
"catppuccin-macchiato",
|
||||
"catppuccin-mocha",
|
||||
"dark-plus",
|
||||
"dracula",
|
||||
"dracula-soft",
|
||||
"everforest-dark",
|
||||
"everforest-light",
|
||||
"github-dark",
|
||||
"github-dark-default",
|
||||
"github-dark-dimmed",
|
||||
"github-dark-high-contrast",
|
||||
"github-light",
|
||||
"github-light-default",
|
||||
"github-light-high-contrast",
|
||||
"houston",
|
||||
"laserwave",
|
||||
"light-plus",
|
||||
"material-theme",
|
||||
"material-theme-darker",
|
||||
"material-theme-lighter",
|
||||
"material-theme-ocean",
|
||||
"material-theme-palenight",
|
||||
"min-dark",
|
||||
"min-light",
|
||||
"monokai",
|
||||
"night-owl",
|
||||
"nord",
|
||||
"one-dark-pro",
|
||||
"one-light",
|
||||
"plain",
|
||||
"plastic",
|
||||
"poimandres",
|
||||
"red",
|
||||
"rose-pine",
|
||||
"rose-pine-dawn",
|
||||
"rose-pine-moon",
|
||||
"slack-dark",
|
||||
"slack-ochin",
|
||||
"snazzy-light",
|
||||
"solarized-dark",
|
||||
"solarized-light",
|
||||
"synthwave-84",
|
||||
"tokyo-night",
|
||||
"vesper",
|
||||
"vitesse-black",
|
||||
"vitesse-dark",
|
||||
"vitesse-light",
|
||||
]
|
||||
|
||||
|
||||
class ShikiBaseTransformers(Base):
|
||||
"""Base for creating transformers."""
|
||||
|
||||
library: str
|
||||
fns: list[FunctionStringVar]
|
||||
style: Optional[Style]
|
||||
|
||||
|
||||
class ShikiJsTransformer(ShikiBaseTransformers):
|
||||
"""A Wrapped shikijs transformer."""
|
||||
|
||||
library: str = "@shikijs/transformers"
|
||||
fns: list[FunctionStringVar] = [
|
||||
FunctionStringVar.create(fn) for fn in SHIKIJS_TRANSFORMER_FNS
|
||||
]
|
||||
style: Optional[Style] = Style(
|
||||
{
|
||||
"code": {"line-height": "1.7", "font-size": "0.875em", "display": "grid"},
|
||||
# Diffs
|
||||
".diff": {
|
||||
"margin": "0 -24px",
|
||||
"padding": "0 24px",
|
||||
"width": "calc(100% + 48px)",
|
||||
"display": "inline-block",
|
||||
},
|
||||
".diff.add": {
|
||||
"background-color": "rgba(16, 185, 129, .14)",
|
||||
"position": "relative",
|
||||
},
|
||||
".diff.remove": {
|
||||
"background-color": "rgba(244, 63, 94, .14)",
|
||||
"opacity": "0.7",
|
||||
"position": "relative",
|
||||
},
|
||||
".diff.remove:after": {
|
||||
"position": "absolute",
|
||||
"left": "10px",
|
||||
"content": "'-'",
|
||||
"color": "#b34e52",
|
||||
},
|
||||
".diff.add:after": {
|
||||
"position": "absolute",
|
||||
"left": "10px",
|
||||
"content": "'+'",
|
||||
"color": "#18794e",
|
||||
},
|
||||
# Highlight
|
||||
".highlighted": {
|
||||
"background-color": "rgba(142, 150, 170, .14)",
|
||||
"margin": "0 -24px",
|
||||
"padding": "0 24px",
|
||||
"width": "calc(100% + 48px)",
|
||||
"display": "inline-block",
|
||||
},
|
||||
".highlighted.error": {
|
||||
"background-color": "rgba(244, 63, 94, .14)",
|
||||
},
|
||||
".highlighted.warning": {
|
||||
"background-color": "rgba(234, 179, 8, .14)",
|
||||
},
|
||||
# Highlighted Word
|
||||
".highlighted-word": {
|
||||
"background-color": color("gray", 2),
|
||||
"border": f"1px solid {color('gray', 5)}",
|
||||
"padding": "1px 3px",
|
||||
"margin": "-1px -3px",
|
||||
"border-radius": "4px",
|
||||
},
|
||||
# Focused Lines
|
||||
".has-focused .line:not(.focused)": {
|
||||
"opacity": "0.7",
|
||||
"filter": "blur(0.095rem)",
|
||||
"transition": "filter .35s, opacity .35s",
|
||||
},
|
||||
".has-focused:hover .line:not(.focused)": {
|
||||
"opacity": "1",
|
||||
"filter": "none",
|
||||
},
|
||||
# White Space
|
||||
# ".tab, .space": {
|
||||
# "position": "relative",
|
||||
# },
|
||||
# ".tab::before": {
|
||||
# "content": "'⇥'",
|
||||
# "position": "absolute",
|
||||
# "opacity": "0.3",
|
||||
# },
|
||||
# ".space::before": {
|
||||
# "content": "'·'",
|
||||
# "position": "absolute",
|
||||
# "opacity": "0.3",
|
||||
# },
|
||||
}
|
||||
)
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
"""Initialize the transformer.
|
||||
|
||||
Args:
|
||||
kwargs: Kwargs to initialize the props.
|
||||
|
||||
"""
|
||||
fns = kwargs.pop("fns", None)
|
||||
style = kwargs.pop("style", None)
|
||||
if fns:
|
||||
kwargs["fns"] = [
|
||||
(
|
||||
FunctionStringVar.create(x)
|
||||
if not isinstance(x, FunctionStringVar)
|
||||
else x
|
||||
)
|
||||
for x in fns
|
||||
]
|
||||
|
||||
if style:
|
||||
kwargs["style"] = Style(style)
|
||||
super().__init__(**kwargs)
|
||||
|
||||
|
||||
class ShikiCodeBlock(Component):
|
||||
"""A Code block."""
|
||||
|
||||
library = "/components/shiki/code"
|
||||
|
||||
tag = "Code"
|
||||
|
||||
alias = "ShikiCode"
|
||||
|
||||
lib_dependencies: list[str] = ["shiki"]
|
||||
|
||||
# The language to use.
|
||||
language: Var[LiteralCodeLanguage] = Var.create("python")
|
||||
|
||||
# The theme to use ("light" or "dark").
|
||||
theme: Var[LiteralCodeTheme] = Var.create("one-light")
|
||||
|
||||
# The set of themes to use for different modes.
|
||||
themes: Var[Union[list[dict[str, Any]], dict[str, str]]]
|
||||
|
||||
# The code to display.
|
||||
code: Var[str]
|
||||
|
||||
# The transformers to use for the syntax highlighter.
|
||||
transformers: Var[list[Union[ShikiBaseTransformers, dict[str, Any]]]] = Var.create(
|
||||
[]
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def create(
|
||||
cls,
|
||||
*children,
|
||||
**props,
|
||||
) -> Component:
|
||||
"""Create a code block component using [shiki syntax highlighter](https://shiki.matsu.io/).
|
||||
|
||||
Args:
|
||||
*children: The children of the component.
|
||||
**props: The props to pass to the component.
|
||||
|
||||
Returns:
|
||||
The code block component.
|
||||
"""
|
||||
# Separate props for the code block and the wrapper
|
||||
code_block_props = {}
|
||||
code_wrapper_props = {}
|
||||
|
||||
class_props = cls.get_props()
|
||||
|
||||
# Distribute props between the code block and wrapper
|
||||
for key, value in props.items():
|
||||
(code_block_props if key in class_props else code_wrapper_props)[key] = (
|
||||
value
|
||||
)
|
||||
|
||||
code_block_props["code"] = children[0]
|
||||
code_block = super().create(**code_block_props)
|
||||
|
||||
transformer_styles = {}
|
||||
# Collect styles from transformers and wrapper
|
||||
for transformer in code_block.transformers._var_value: # type: ignore
|
||||
if isinstance(transformer, ShikiBaseTransformers) and transformer.style:
|
||||
transformer_styles.update(transformer.style)
|
||||
transformer_styles.update(code_wrapper_props.pop("style", {}))
|
||||
|
||||
return Box.create(
|
||||
code_block,
|
||||
*children[1:],
|
||||
style=Style({**transformer_styles, **BOX_PARENT_STYLING}),
|
||||
**code_wrapper_props,
|
||||
)
|
||||
|
||||
def add_imports(self) -> dict[str, list[str]]:
|
||||
"""Add the necessary imports.
|
||||
We add all referenced transformer functions as imports from their corresponding
|
||||
libraries.
|
||||
|
||||
Returns:
|
||||
Imports for the component.
|
||||
"""
|
||||
imports = defaultdict(list)
|
||||
for transformer in self.transformers._var_value:
|
||||
if isinstance(transformer, ShikiBaseTransformers):
|
||||
imports[transformer.library].extend(
|
||||
[ImportVar(tag=str(fn)) for fn in transformer.fns]
|
||||
)
|
||||
(
|
||||
self.lib_dependencies.append(transformer.library)
|
||||
if transformer.library not in self.lib_dependencies
|
||||
else None
|
||||
)
|
||||
return imports
|
||||
|
||||
@classmethod
|
||||
def create_transformer(cls, library: str, fns: list[str]) -> ShikiBaseTransformers:
|
||||
"""Create a transformer from a third party library.
|
||||
|
||||
Args:
|
||||
library: The name of the library.
|
||||
fns: The str names of the functions/callables to invoke from the library.
|
||||
|
||||
Returns:
|
||||
A transformer for the specified library.
|
||||
|
||||
Raises:
|
||||
ValueError: If a supplied function name is not valid str.
|
||||
"""
|
||||
if any(not isinstance(fn_name, str) for fn_name in fns):
|
||||
raise ValueError(
|
||||
f"the function names should be str names of functions in the specified transformer: {library!r}"
|
||||
)
|
||||
return ShikiBaseTransformers( # type: ignore
|
||||
library=library, fns=[FunctionStringVar.create(fn) for fn in fns]
|
||||
)
|
||||
|
||||
def _render(self, props: dict[str, Any] | None = None):
|
||||
"""Renders the component with the given properties, processing transformers if present.
|
||||
|
||||
Args:
|
||||
props: Optional properties to pass to the render function.
|
||||
|
||||
Returns:
|
||||
Rendered component output.
|
||||
"""
|
||||
# Ensure props is initialized from class attributes if not provided
|
||||
props = props or {
|
||||
attr.rstrip("_"): getattr(self, attr) for attr in self.get_props()
|
||||
}
|
||||
|
||||
# Extract transformers and apply transformations
|
||||
transformers = props.get("transformers")
|
||||
if transformers is not None:
|
||||
transformed_values = self._process_transformers(transformers._var_value)
|
||||
props["transformers"] = LiteralVar.create(transformed_values)
|
||||
|
||||
return super()._render(props)
|
||||
|
||||
def _process_transformers(self, transformer_list: list) -> list:
|
||||
"""Processes a list of transformers, applying transformations where necessary.
|
||||
|
||||
Args:
|
||||
transformer_list: List of transformer objects or values.
|
||||
|
||||
Returns:
|
||||
list: A list of transformed values.
|
||||
"""
|
||||
processed = []
|
||||
|
||||
for transformer in transformer_list:
|
||||
if isinstance(transformer, ShikiBaseTransformers):
|
||||
processed.extend(fn.call() for fn in transformer.fns)
|
||||
else:
|
||||
processed.append(transformer)
|
||||
|
||||
return processed
|
||||
|
||||
|
||||
class ShikiHighLevelCodeBlock(ShikiCodeBlock):
|
||||
"""High level component for the shiki syntax highlighter."""
|
||||
|
||||
# If this is enabled, the default transformers(shikijs transformer) will be used.
|
||||
use_transformers: Var[bool]
|
||||
|
||||
# If this is enabled line numbers will be shown next to the code block.
|
||||
show_line_numbers: Var[bool]
|
||||
|
||||
# Whether a copy button should appear.
|
||||
can_copy: Var[bool] = Var.create(False)
|
||||
|
||||
# copy_button: A custom copy button to override the default one.
|
||||
copy_button: Var[Optional[Union[Component, bool]]] = Var.create(None)
|
||||
|
||||
@classmethod
|
||||
def create(
|
||||
cls,
|
||||
*children,
|
||||
**props,
|
||||
) -> Component:
|
||||
"""Create a code block component using [shiki syntax highlighter](https://shiki.matsu.io/).
|
||||
|
||||
Args:
|
||||
*children: The children of the component.
|
||||
**props: The props to pass to the component.
|
||||
|
||||
Returns:
|
||||
The code block component.
|
||||
"""
|
||||
use_transformers = props.pop("use_transformers", False)
|
||||
show_line_numbers = props.pop("show_line_numbers", False)
|
||||
language = props.pop("language", None)
|
||||
can_copy = props.pop("can_copy", False)
|
||||
copy_button = props.pop("copy_button", None)
|
||||
|
||||
if use_transformers:
|
||||
props["transformers"] = [ShikiJsTransformer()]
|
||||
|
||||
if language is not None:
|
||||
props["language"] = cls._map_languages(language)
|
||||
|
||||
# line numbers are generated via css
|
||||
if show_line_numbers:
|
||||
props["style"] = {**LINE_NUMBER_STYLING, **props.get("style", {})}
|
||||
|
||||
theme = props.pop("theme", None)
|
||||
props["theme"] = props["theme"] = (
|
||||
cls._map_themes(theme)
|
||||
if theme is not None
|
||||
else color_mode_cond( # Default color scheme responds to global color mode.
|
||||
light="one-light",
|
||||
dark="one-dark-pro",
|
||||
)
|
||||
)
|
||||
|
||||
if can_copy:
|
||||
code = children[0]
|
||||
copy_button = ( # type: ignore
|
||||
copy_button
|
||||
if copy_button is not None
|
||||
else Button.create(
|
||||
Icon.create(tag="copy", size=16, color=color("gray", 11)),
|
||||
on_click=[
|
||||
set_clipboard(cls._strip_transformer_triggers(code)), # type: ignore
|
||||
copy_script(),
|
||||
],
|
||||
style=Style(
|
||||
{
|
||||
"position": "absolute",
|
||||
"top": "4px",
|
||||
"right": "4px",
|
||||
"background": color("gray", 3),
|
||||
"border": "1px solid",
|
||||
"border-color": color("gray", 5),
|
||||
"border-radius": "6px",
|
||||
"padding": "5px",
|
||||
"opacity": "1",
|
||||
"cursor": "pointer",
|
||||
"_hover": {
|
||||
"background": color("gray", 4),
|
||||
},
|
||||
"transition": "background 0.250s ease-out",
|
||||
"&>svg": {
|
||||
"transition": "transform 0.250s ease-out, opacity 0.250s ease-out",
|
||||
},
|
||||
"_active": {
|
||||
"background": color("gray", 5),
|
||||
},
|
||||
}
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
if copy_button:
|
||||
return ShikiCodeBlock.create(
|
||||
children[0], copy_button, position="relative", **props
|
||||
)
|
||||
else:
|
||||
return ShikiCodeBlock.create(children[0], **props)
|
||||
|
||||
@staticmethod
|
||||
def _map_themes(theme: str) -> str:
|
||||
if isinstance(theme, str) and theme in THEME_MAPPING:
|
||||
return THEME_MAPPING[theme]
|
||||
return theme
|
||||
|
||||
@staticmethod
|
||||
def _map_languages(language: str) -> str:
|
||||
if isinstance(language, str) and language in LANGUAGE_MAPPING:
|
||||
return LANGUAGE_MAPPING[language]
|
||||
return language
|
||||
|
||||
@staticmethod
|
||||
def _strip_transformer_triggers(code: str | StringVar) -> StringVar | str:
|
||||
if not isinstance(code, (StringVar, str)):
|
||||
raise VarTypeError(
|
||||
f"code should be string literal or a StringVar type. Got {type(code)} instead."
|
||||
)
|
||||
regex_pattern = r"[\/#]+ *\[!code.*?\]"
|
||||
|
||||
if isinstance(code, Var):
|
||||
return string_replace_operation(
|
||||
code, StringVar(_js_expr=f"/{regex_pattern}/g", _var_type=str), ""
|
||||
)
|
||||
if isinstance(code, str):
|
||||
return re.sub(regex_pattern, "", code)
|
||||
|
||||
|
||||
class TransformerNamespace(ComponentNamespace):
|
||||
"""Namespace for the Transformers."""
|
||||
|
||||
shikijs = ShikiJsTransformer
|
||||
|
||||
|
||||
class CodeblockNamespace(ComponentNamespace):
|
||||
"""Namespace for the CodeBlock component."""
|
||||
|
||||
root = staticmethod(ShikiCodeBlock.create)
|
||||
create_transformer = staticmethod(ShikiCodeBlock.create_transformer)
|
||||
transformers = TransformerNamespace()
|
||||
__call__ = staticmethod(ShikiHighLevelCodeBlock.create)
|
||||
|
||||
|
||||
code_block = CodeblockNamespace()
|
2211
reflex/components/datadisplay/shiki_code_block.pyi
Normal file
2211
reflex/components/datadisplay/shiki_code_block.pyi
Normal file
File diff suppressed because it is too large
Load Diff
@ -20,6 +20,8 @@ from reflex.components.tags.tag import Tag
|
||||
from reflex.utils import types
|
||||
from reflex.utils.imports import ImportDict, ImportVar
|
||||
from reflex.vars.base import LiteralVar, Var
|
||||
from reflex.vars.function import ARRAY_ISARRAY
|
||||
from reflex.vars.number import ternary_operation
|
||||
|
||||
# Special vars used in the component map.
|
||||
_CHILDREN = Var(_js_expr="children", _var_type=str)
|
||||
@ -199,7 +201,16 @@ class Markdown(Component):
|
||||
raise ValueError(f"No markdown component found for tag: {tag}.")
|
||||
|
||||
special_props = [_PROPS_IN_TAG]
|
||||
children = [_CHILDREN]
|
||||
children = [
|
||||
_CHILDREN
|
||||
if tag != "codeblock"
|
||||
# For codeblock, the mapping for some cases returns an array of elements. Let's join them into a string.
|
||||
else ternary_operation(
|
||||
ARRAY_ISARRAY.call(_CHILDREN), # type: ignore
|
||||
_CHILDREN.to(list).join("\n"),
|
||||
_CHILDREN,
|
||||
).to(str)
|
||||
]
|
||||
|
||||
# For certain tags, the props from the markdown renderer are not actually valid for the component.
|
||||
if tag in NO_PROPS_TAGS:
|
||||
|
@ -58,7 +58,7 @@ class Moment(NoSSRComponent):
|
||||
autofocus: Optional[bool] = None,
|
||||
custom_attrs: Optional[Dict[str, Union[Var, str]]] = None,
|
||||
on_blur: Optional[EventType[[]]] = None,
|
||||
on_change: Optional[EventType] = None,
|
||||
on_change: Optional[EventType[str]] = None,
|
||||
on_click: Optional[EventType[[]]] = None,
|
||||
on_context_menu: Optional[EventType[[]]] = None,
|
||||
on_double_click: Optional[EventType[[]]] = None,
|
||||
|
@ -101,7 +101,7 @@ class DrawerRoot(DrawerComponent):
|
||||
on_mouse_out: Optional[EventType[[]]] = None,
|
||||
on_mouse_over: Optional[EventType[[]]] = None,
|
||||
on_mouse_up: Optional[EventType[[]]] = None,
|
||||
on_open_change: Optional[EventType] = None,
|
||||
on_open_change: Optional[EventType[bool]] = None,
|
||||
on_scroll: Optional[EventType[[]]] = None,
|
||||
on_unmount: Optional[EventType[[]]] = None,
|
||||
**props,
|
||||
@ -511,7 +511,7 @@ class Drawer(ComponentNamespace):
|
||||
on_mouse_out: Optional[EventType[[]]] = None,
|
||||
on_mouse_over: Optional[EventType[[]]] = None,
|
||||
on_mouse_up: Optional[EventType[[]]] = None,
|
||||
on_open_change: Optional[EventType] = None,
|
||||
on_open_change: Optional[EventType[bool]] = None,
|
||||
on_scroll: Optional[EventType[[]]] = None,
|
||||
on_unmount: Optional[EventType[[]]] = None,
|
||||
**props,
|
||||
|
@ -383,7 +383,7 @@ class ColorModeSwitch(Switch):
|
||||
autofocus: Optional[bool] = None,
|
||||
custom_attrs: Optional[Dict[str, Union[Var, str]]] = None,
|
||||
on_blur: Optional[EventType[[]]] = None,
|
||||
on_change: Optional[EventType] = None,
|
||||
on_change: Optional[EventType[bool]] = None,
|
||||
on_click: Optional[EventType[[]]] = None,
|
||||
on_context_menu: Optional[EventType[[]]] = None,
|
||||
on_double_click: Optional[EventType[[]]] = None,
|
||||
|
@ -42,7 +42,7 @@ class AlertDialogRoot(RadixThemesComponent):
|
||||
on_mouse_out: Optional[EventType[[]]] = None,
|
||||
on_mouse_over: Optional[EventType[[]]] = None,
|
||||
on_mouse_up: Optional[EventType[[]]] = None,
|
||||
on_open_change: Optional[EventType] = None,
|
||||
on_open_change: Optional[EventType[bool]] = None,
|
||||
on_scroll: Optional[EventType[[]]] = None,
|
||||
on_unmount: Optional[EventType[[]]] = None,
|
||||
**props,
|
||||
|
@ -116,7 +116,7 @@ class Checkbox(RadixThemesComponent):
|
||||
autofocus: Optional[bool] = None,
|
||||
custom_attrs: Optional[Dict[str, Union[Var, str]]] = None,
|
||||
on_blur: Optional[EventType[[]]] = None,
|
||||
on_change: Optional[EventType] = None,
|
||||
on_change: Optional[EventType[bool]] = None,
|
||||
on_click: Optional[EventType[[]]] = None,
|
||||
on_context_menu: Optional[EventType[[]]] = None,
|
||||
on_double_click: Optional[EventType[[]]] = None,
|
||||
@ -263,7 +263,7 @@ class HighLevelCheckbox(RadixThemesComponent):
|
||||
autofocus: Optional[bool] = None,
|
||||
custom_attrs: Optional[Dict[str, Union[Var, str]]] = None,
|
||||
on_blur: Optional[EventType[[]]] = None,
|
||||
on_change: Optional[EventType] = None,
|
||||
on_change: Optional[EventType[bool]] = None,
|
||||
on_click: Optional[EventType[[]]] = None,
|
||||
on_context_menu: Optional[EventType[[]]] = None,
|
||||
on_double_click: Optional[EventType[[]]] = None,
|
||||
@ -407,7 +407,7 @@ class CheckboxNamespace(ComponentNamespace):
|
||||
autofocus: Optional[bool] = None,
|
||||
custom_attrs: Optional[Dict[str, Union[Var, str]]] = None,
|
||||
on_blur: Optional[EventType[[]]] = None,
|
||||
on_change: Optional[EventType] = None,
|
||||
on_change: Optional[EventType[bool]] = None,
|
||||
on_click: Optional[EventType[[]]] = None,
|
||||
on_context_menu: Optional[EventType[[]]] = None,
|
||||
on_double_click: Optional[EventType[[]]] = None,
|
||||
|
@ -39,7 +39,7 @@ class ContextMenuRoot(RadixThemesComponent):
|
||||
on_mouse_out: Optional[EventType[[]]] = None,
|
||||
on_mouse_over: Optional[EventType[[]]] = None,
|
||||
on_mouse_up: Optional[EventType[[]]] = None,
|
||||
on_open_change: Optional[EventType] = None,
|
||||
on_open_change: Optional[EventType[bool]] = None,
|
||||
on_scroll: Optional[EventType[[]]] = None,
|
||||
on_unmount: Optional[EventType[[]]] = None,
|
||||
**props,
|
||||
|
@ -40,7 +40,7 @@ class DialogRoot(RadixThemesComponent):
|
||||
on_mouse_out: Optional[EventType[[]]] = None,
|
||||
on_mouse_over: Optional[EventType[[]]] = None,
|
||||
on_mouse_up: Optional[EventType[[]]] = None,
|
||||
on_open_change: Optional[EventType] = None,
|
||||
on_open_change: Optional[EventType[bool]] = None,
|
||||
on_scroll: Optional[EventType[[]]] = None,
|
||||
on_unmount: Optional[EventType[[]]] = None,
|
||||
**props,
|
||||
@ -382,7 +382,7 @@ class Dialog(ComponentNamespace):
|
||||
on_mouse_out: Optional[EventType[[]]] = None,
|
||||
on_mouse_over: Optional[EventType[[]]] = None,
|
||||
on_mouse_up: Optional[EventType[[]]] = None,
|
||||
on_open_change: Optional[EventType] = None,
|
||||
on_open_change: Optional[EventType[bool]] = None,
|
||||
on_scroll: Optional[EventType[[]]] = None,
|
||||
on_unmount: Optional[EventType[[]]] = None,
|
||||
**props,
|
||||
|
@ -49,7 +49,7 @@ class DropdownMenuRoot(RadixThemesComponent):
|
||||
on_mouse_out: Optional[EventType[[]]] = None,
|
||||
on_mouse_over: Optional[EventType[[]]] = None,
|
||||
on_mouse_up: Optional[EventType[[]]] = None,
|
||||
on_open_change: Optional[EventType] = None,
|
||||
on_open_change: Optional[EventType[bool]] = None,
|
||||
on_scroll: Optional[EventType[[]]] = None,
|
||||
on_unmount: Optional[EventType[[]]] = None,
|
||||
**props,
|
||||
@ -363,7 +363,7 @@ class DropdownMenuSub(RadixThemesComponent):
|
||||
on_mouse_out: Optional[EventType[[]]] = None,
|
||||
on_mouse_over: Optional[EventType[[]]] = None,
|
||||
on_mouse_up: Optional[EventType[[]]] = None,
|
||||
on_open_change: Optional[EventType] = None,
|
||||
on_open_change: Optional[EventType[bool]] = None,
|
||||
on_scroll: Optional[EventType[[]]] = None,
|
||||
on_unmount: Optional[EventType[[]]] = None,
|
||||
**props,
|
||||
|
@ -43,7 +43,7 @@ class HoverCardRoot(RadixThemesComponent):
|
||||
on_mouse_out: Optional[EventType[[]]] = None,
|
||||
on_mouse_over: Optional[EventType[[]]] = None,
|
||||
on_mouse_up: Optional[EventType[[]]] = None,
|
||||
on_open_change: Optional[EventType] = None,
|
||||
on_open_change: Optional[EventType[bool]] = None,
|
||||
on_scroll: Optional[EventType[[]]] = None,
|
||||
on_unmount: Optional[EventType[[]]] = None,
|
||||
**props,
|
||||
@ -256,7 +256,7 @@ class HoverCard(ComponentNamespace):
|
||||
on_mouse_out: Optional[EventType[[]]] = None,
|
||||
on_mouse_over: Optional[EventType[[]]] = None,
|
||||
on_mouse_up: Optional[EventType[[]]] = None,
|
||||
on_open_change: Optional[EventType] = None,
|
||||
on_open_change: Optional[EventType[bool]] = None,
|
||||
on_scroll: Optional[EventType[[]]] = None,
|
||||
on_unmount: Optional[EventType[[]]] = None,
|
||||
**props,
|
||||
|
@ -41,7 +41,7 @@ class PopoverRoot(RadixThemesComponent):
|
||||
on_mouse_out: Optional[EventType[[]]] = None,
|
||||
on_mouse_over: Optional[EventType[[]]] = None,
|
||||
on_mouse_up: Optional[EventType[[]]] = None,
|
||||
on_open_change: Optional[EventType] = None,
|
||||
on_open_change: Optional[EventType[bool]] = None,
|
||||
on_scroll: Optional[EventType[[]]] = None,
|
||||
on_unmount: Optional[EventType[[]]] = None,
|
||||
**props,
|
||||
|
@ -177,7 +177,7 @@ class RadioCardsRoot(RadixThemesComponent):
|
||||
on_mouse_up: Optional[EventType[[]]] = None,
|
||||
on_scroll: Optional[EventType[[]]] = None,
|
||||
on_unmount: Optional[EventType[[]]] = None,
|
||||
on_value_change: Optional[EventType] = None,
|
||||
on_value_change: Optional[EventType[str]] = None,
|
||||
**props,
|
||||
) -> "RadioCardsRoot":
|
||||
"""Create a new component instance.
|
||||
|
@ -113,7 +113,7 @@ class RadioGroupRoot(RadixThemesComponent):
|
||||
autofocus: Optional[bool] = None,
|
||||
custom_attrs: Optional[Dict[str, Union[Var, str]]] = None,
|
||||
on_blur: Optional[EventType[[]]] = None,
|
||||
on_change: Optional[EventType] = None,
|
||||
on_change: Optional[EventType[str]] = None,
|
||||
on_click: Optional[EventType[[]]] = None,
|
||||
on_context_menu: Optional[EventType[[]]] = None,
|
||||
on_double_click: Optional[EventType[[]]] = None,
|
||||
|
@ -44,7 +44,7 @@ class SelectRoot(RadixThemesComponent):
|
||||
autofocus: Optional[bool] = None,
|
||||
custom_attrs: Optional[Dict[str, Union[Var, str]]] = None,
|
||||
on_blur: Optional[EventType[[]]] = None,
|
||||
on_change: Optional[EventType] = None,
|
||||
on_change: Optional[EventType[str]] = None,
|
||||
on_click: Optional[EventType[[]]] = None,
|
||||
on_context_menu: Optional[EventType[[]]] = None,
|
||||
on_double_click: Optional[EventType[[]]] = None,
|
||||
@ -57,7 +57,7 @@ class SelectRoot(RadixThemesComponent):
|
||||
on_mouse_out: Optional[EventType[[]]] = None,
|
||||
on_mouse_over: Optional[EventType[[]]] = None,
|
||||
on_mouse_up: Optional[EventType[[]]] = None,
|
||||
on_open_change: Optional[EventType] = None,
|
||||
on_open_change: Optional[EventType[bool]] = None,
|
||||
on_scroll: Optional[EventType[[]]] = None,
|
||||
on_unmount: Optional[EventType[[]]] = None,
|
||||
**props,
|
||||
@ -680,7 +680,7 @@ class HighLevelSelect(SelectRoot):
|
||||
autofocus: Optional[bool] = None,
|
||||
custom_attrs: Optional[Dict[str, Union[Var, str]]] = None,
|
||||
on_blur: Optional[EventType[[]]] = None,
|
||||
on_change: Optional[EventType] = None,
|
||||
on_change: Optional[EventType[str]] = None,
|
||||
on_click: Optional[EventType[[]]] = None,
|
||||
on_context_menu: Optional[EventType[[]]] = None,
|
||||
on_double_click: Optional[EventType[[]]] = None,
|
||||
@ -693,7 +693,7 @@ class HighLevelSelect(SelectRoot):
|
||||
on_mouse_out: Optional[EventType[[]]] = None,
|
||||
on_mouse_over: Optional[EventType[[]]] = None,
|
||||
on_mouse_up: Optional[EventType[[]]] = None,
|
||||
on_open_change: Optional[EventType] = None,
|
||||
on_open_change: Optional[EventType[bool]] = None,
|
||||
on_scroll: Optional[EventType[[]]] = None,
|
||||
on_unmount: Optional[EventType[[]]] = None,
|
||||
**props,
|
||||
@ -854,7 +854,7 @@ class Select(ComponentNamespace):
|
||||
autofocus: Optional[bool] = None,
|
||||
custom_attrs: Optional[Dict[str, Union[Var, str]]] = None,
|
||||
on_blur: Optional[EventType[[]]] = None,
|
||||
on_change: Optional[EventType] = None,
|
||||
on_change: Optional[EventType[str]] = None,
|
||||
on_click: Optional[EventType[[]]] = None,
|
||||
on_context_menu: Optional[EventType[[]]] = None,
|
||||
on_double_click: Optional[EventType[[]]] = None,
|
||||
@ -867,7 +867,7 @@ class Select(ComponentNamespace):
|
||||
on_mouse_out: Optional[EventType[[]]] = None,
|
||||
on_mouse_over: Optional[EventType[[]]] = None,
|
||||
on_mouse_up: Optional[EventType[[]]] = None,
|
||||
on_open_change: Optional[EventType] = None,
|
||||
on_open_change: Optional[EventType[bool]] = None,
|
||||
on_scroll: Optional[EventType[[]]] = None,
|
||||
on_unmount: Optional[EventType[[]]] = None,
|
||||
**props,
|
||||
|
@ -119,7 +119,7 @@ class Switch(RadixThemesComponent):
|
||||
autofocus: Optional[bool] = None,
|
||||
custom_attrs: Optional[Dict[str, Union[Var, str]]] = None,
|
||||
on_blur: Optional[EventType[[]]] = None,
|
||||
on_change: Optional[EventType] = None,
|
||||
on_change: Optional[EventType[bool]] = None,
|
||||
on_click: Optional[EventType[[]]] = None,
|
||||
on_context_menu: Optional[EventType[[]]] = None,
|
||||
on_double_click: Optional[EventType[[]]] = None,
|
||||
|
@ -41,7 +41,7 @@ class TabsRoot(RadixThemesComponent):
|
||||
autofocus: Optional[bool] = None,
|
||||
custom_attrs: Optional[Dict[str, Union[Var, str]]] = None,
|
||||
on_blur: Optional[EventType[[]]] = None,
|
||||
on_change: Optional[EventType] = None,
|
||||
on_change: Optional[EventType[str]] = None,
|
||||
on_click: Optional[EventType[[]]] = None,
|
||||
on_context_menu: Optional[EventType[[]]] = None,
|
||||
on_double_click: Optional[EventType[[]]] = None,
|
||||
@ -340,7 +340,7 @@ class Tabs(ComponentNamespace):
|
||||
autofocus: Optional[bool] = None,
|
||||
custom_attrs: Optional[Dict[str, Union[Var, str]]] = None,
|
||||
on_blur: Optional[EventType[[]]] = None,
|
||||
on_change: Optional[EventType] = None,
|
||||
on_change: Optional[EventType[str]] = None,
|
||||
on_click: Optional[EventType[[]]] = None,
|
||||
on_context_menu: Optional[EventType[[]]] = None,
|
||||
on_double_click: Optional[EventType[[]]] = None,
|
||||
|
@ -5,7 +5,9 @@
|
||||
# ------------------------------------------------------
|
||||
from typing import Any, Dict, Literal, Optional, Union, overload
|
||||
|
||||
from reflex.event import EventType
|
||||
from reflex.event import (
|
||||
EventType,
|
||||
)
|
||||
from reflex.style import Style
|
||||
from reflex.vars.base import Var
|
||||
|
||||
@ -76,7 +78,7 @@ class Tooltip(RadixThemesComponent):
|
||||
on_mouse_out: Optional[EventType[[]]] = None,
|
||||
on_mouse_over: Optional[EventType[[]]] = None,
|
||||
on_mouse_up: Optional[EventType[[]]] = None,
|
||||
on_open_change: Optional[EventType] = None,
|
||||
on_open_change: Optional[EventType[bool]] = None,
|
||||
on_pointer_down_outside: Optional[EventType[[]]] = None,
|
||||
on_scroll: Optional[EventType[[]]] = None,
|
||||
on_unmount: Optional[EventType[[]]] = None,
|
||||
|
@ -1,5 +1,6 @@
|
||||
"""React Player component for audio and video."""
|
||||
|
||||
from . import react_player
|
||||
from .audio import Audio
|
||||
from .video import Video
|
||||
|
||||
|
@ -5,6 +5,7 @@
|
||||
# ------------------------------------------------------
|
||||
from typing import Any, Dict, Optional, Union, overload
|
||||
|
||||
import reflex
|
||||
from reflex.components.react_player.react_player import ReactPlayer
|
||||
from reflex.event import EventType
|
||||
from reflex.style import Style
|
||||
@ -41,7 +42,7 @@ class Audio(ReactPlayer):
|
||||
on_context_menu: Optional[EventType[[]]] = None,
|
||||
on_disable_pip: Optional[EventType[[]]] = None,
|
||||
on_double_click: Optional[EventType[[]]] = None,
|
||||
on_duration: Optional[EventType] = None,
|
||||
on_duration: Optional[EventType[float]] = None,
|
||||
on_enable_pip: Optional[EventType[[]]] = None,
|
||||
on_ended: Optional[EventType[[]]] = None,
|
||||
on_error: Optional[EventType[[]]] = None,
|
||||
@ -58,10 +59,12 @@ class Audio(ReactPlayer):
|
||||
on_play: Optional[EventType[[]]] = None,
|
||||
on_playback_quality_change: Optional[EventType[[]]] = None,
|
||||
on_playback_rate_change: Optional[EventType[[]]] = None,
|
||||
on_progress: Optional[EventType[[]]] = None,
|
||||
on_progress: Optional[
|
||||
EventType[reflex.components.react_player.react_player.Progress]
|
||||
] = None,
|
||||
on_ready: Optional[EventType[[]]] = None,
|
||||
on_scroll: Optional[EventType[[]]] = None,
|
||||
on_seek: Optional[EventType] = None,
|
||||
on_seek: Optional[EventType[float]] = None,
|
||||
on_start: Optional[EventType[[]]] = None,
|
||||
on_unmount: Optional[EventType[[]]] = None,
|
||||
**props,
|
||||
|
@ -2,11 +2,22 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing_extensions import TypedDict
|
||||
|
||||
from reflex.components.component import NoSSRComponent
|
||||
from reflex.event import EventHandler, empty_event, identity_event
|
||||
from reflex.vars.base import Var
|
||||
|
||||
|
||||
class Progress(TypedDict):
|
||||
"""Callback containing played and loaded progress as a fraction, and playedSeconds and loadedSeconds in seconds."""
|
||||
|
||||
played: float
|
||||
playedSeconds: float
|
||||
loaded: float
|
||||
loadedSeconds: float
|
||||
|
||||
|
||||
class ReactPlayer(NoSSRComponent):
|
||||
"""Using react-player and not implement all props and callback yet.
|
||||
reference: https://github.com/cookpete/react-player.
|
||||
@ -55,7 +66,7 @@ class ReactPlayer(NoSSRComponent):
|
||||
on_play: EventHandler[empty_event]
|
||||
|
||||
# Callback containing played and loaded progress as a fraction, and playedSeconds and loadedSeconds in seconds. eg { played: 0.12, playedSeconds: 11.3, loaded: 0.34, loadedSeconds: 16.7 }
|
||||
on_progress: EventHandler[lambda progress: [progress]]
|
||||
on_progress: EventHandler[identity_event(Progress)]
|
||||
|
||||
# Callback containing duration of the media, in seconds.
|
||||
on_duration: EventHandler[identity_event(float)]
|
||||
|
@ -5,11 +5,19 @@
|
||||
# ------------------------------------------------------
|
||||
from typing import Any, Dict, Optional, Union, overload
|
||||
|
||||
from typing_extensions import TypedDict
|
||||
|
||||
from reflex.components.component import NoSSRComponent
|
||||
from reflex.event import EventType
|
||||
from reflex.style import Style
|
||||
from reflex.vars.base import Var
|
||||
|
||||
class Progress(TypedDict):
|
||||
played: float
|
||||
playedSeconds: float
|
||||
loaded: float
|
||||
loadedSeconds: float
|
||||
|
||||
class ReactPlayer(NoSSRComponent):
|
||||
@overload
|
||||
@classmethod
|
||||
@ -39,7 +47,7 @@ class ReactPlayer(NoSSRComponent):
|
||||
on_context_menu: Optional[EventType[[]]] = None,
|
||||
on_disable_pip: Optional[EventType[[]]] = None,
|
||||
on_double_click: Optional[EventType[[]]] = None,
|
||||
on_duration: Optional[EventType] = None,
|
||||
on_duration: Optional[EventType[float]] = None,
|
||||
on_enable_pip: Optional[EventType[[]]] = None,
|
||||
on_ended: Optional[EventType[[]]] = None,
|
||||
on_error: Optional[EventType[[]]] = None,
|
||||
@ -56,10 +64,10 @@ class ReactPlayer(NoSSRComponent):
|
||||
on_play: Optional[EventType[[]]] = None,
|
||||
on_playback_quality_change: Optional[EventType[[]]] = None,
|
||||
on_playback_rate_change: Optional[EventType[[]]] = None,
|
||||
on_progress: Optional[EventType[[]]] = None,
|
||||
on_progress: Optional[EventType[Progress]] = None,
|
||||
on_ready: Optional[EventType[[]]] = None,
|
||||
on_scroll: Optional[EventType[[]]] = None,
|
||||
on_seek: Optional[EventType] = None,
|
||||
on_seek: Optional[EventType[float]] = None,
|
||||
on_start: Optional[EventType[[]]] = None,
|
||||
on_unmount: Optional[EventType[[]]] = None,
|
||||
**props,
|
||||
|
@ -5,6 +5,7 @@
|
||||
# ------------------------------------------------------
|
||||
from typing import Any, Dict, Optional, Union, overload
|
||||
|
||||
import reflex
|
||||
from reflex.components.react_player.react_player import ReactPlayer
|
||||
from reflex.event import EventType
|
||||
from reflex.style import Style
|
||||
@ -41,7 +42,7 @@ class Video(ReactPlayer):
|
||||
on_context_menu: Optional[EventType[[]]] = None,
|
||||
on_disable_pip: Optional[EventType[[]]] = None,
|
||||
on_double_click: Optional[EventType[[]]] = None,
|
||||
on_duration: Optional[EventType] = None,
|
||||
on_duration: Optional[EventType[float]] = None,
|
||||
on_enable_pip: Optional[EventType[[]]] = None,
|
||||
on_ended: Optional[EventType[[]]] = None,
|
||||
on_error: Optional[EventType[[]]] = None,
|
||||
@ -58,10 +59,12 @@ class Video(ReactPlayer):
|
||||
on_play: Optional[EventType[[]]] = None,
|
||||
on_playback_quality_change: Optional[EventType[[]]] = None,
|
||||
on_playback_rate_change: Optional[EventType[[]]] = None,
|
||||
on_progress: Optional[EventType[[]]] = None,
|
||||
on_progress: Optional[
|
||||
EventType[reflex.components.react_player.react_player.Progress]
|
||||
] = None,
|
||||
on_ready: Optional[EventType[[]]] = None,
|
||||
on_scroll: Optional[EventType[[]]] = None,
|
||||
on_seek: Optional[EventType] = None,
|
||||
on_seek: Optional[EventType[float]] = None,
|
||||
on_start: Optional[EventType[[]]] = None,
|
||||
on_unmount: Optional[EventType[[]]] = None,
|
||||
**props,
|
||||
|
@ -252,7 +252,7 @@ class Brush(Recharts):
|
||||
A dict mapping the event trigger to the var that is passed to the handler.
|
||||
"""
|
||||
return {
|
||||
EventTriggers.ON_CHANGE: lambda: [],
|
||||
EventTriggers.ON_CHANGE: empty_event,
|
||||
}
|
||||
|
||||
|
||||
@ -293,10 +293,10 @@ class Cartesian(Recharts):
|
||||
name: Var[Union[str, int]]
|
||||
|
||||
# The customized event handler of animation start
|
||||
on_animation_start: EventHandler[lambda: []]
|
||||
on_animation_start: EventHandler[empty_event]
|
||||
|
||||
# The customized event handler of animation end
|
||||
on_animation_end: EventHandler[lambda: []]
|
||||
on_animation_end: EventHandler[empty_event]
|
||||
|
||||
# The customized event handler of click on the component in this group
|
||||
on_click: EventHandler[empty_event]
|
||||
|
@ -330,9 +330,9 @@ class RadarChart(ChartBase):
|
||||
A dict mapping the event trigger to the var that is passed to the handler.
|
||||
"""
|
||||
return {
|
||||
EventTriggers.ON_CLICK: lambda: [],
|
||||
EventTriggers.ON_MOUSE_ENTER: lambda: [],
|
||||
EventTriggers.ON_MOUSE_LEAVE: lambda: [],
|
||||
EventTriggers.ON_CLICK: empty_event,
|
||||
EventTriggers.ON_MOUSE_ENTER: empty_event,
|
||||
EventTriggers.ON_MOUSE_LEAVE: empty_event,
|
||||
}
|
||||
|
||||
|
||||
@ -419,14 +419,14 @@ class ScatterChart(ChartBase):
|
||||
A dict mapping the event trigger to the var that is passed to the handler.
|
||||
"""
|
||||
return {
|
||||
EventTriggers.ON_CLICK: lambda: [],
|
||||
EventTriggers.ON_MOUSE_DOWN: lambda: [],
|
||||
EventTriggers.ON_MOUSE_UP: lambda: [],
|
||||
EventTriggers.ON_MOUSE_MOVE: lambda: [],
|
||||
EventTriggers.ON_MOUSE_OVER: lambda: [],
|
||||
EventTriggers.ON_MOUSE_OUT: lambda: [],
|
||||
EventTriggers.ON_MOUSE_ENTER: lambda: [],
|
||||
EventTriggers.ON_MOUSE_LEAVE: lambda: [],
|
||||
EventTriggers.ON_CLICK: empty_event,
|
||||
EventTriggers.ON_MOUSE_DOWN: empty_event,
|
||||
EventTriggers.ON_MOUSE_UP: empty_event,
|
||||
EventTriggers.ON_MOUSE_MOVE: empty_event,
|
||||
EventTriggers.ON_MOUSE_OVER: empty_event,
|
||||
EventTriggers.ON_MOUSE_OUT: empty_event,
|
||||
EventTriggers.ON_MOUSE_ENTER: empty_event,
|
||||
EventTriggers.ON_MOUSE_LEAVE: empty_event,
|
||||
}
|
||||
|
||||
|
||||
|
@ -103,14 +103,14 @@ class Pie(Recharts):
|
||||
A dict mapping the event trigger to the var that is passed to the handler.
|
||||
"""
|
||||
return {
|
||||
EventTriggers.ON_ANIMATION_START: lambda: [],
|
||||
EventTriggers.ON_ANIMATION_END: lambda: [],
|
||||
EventTriggers.ON_CLICK: lambda: [],
|
||||
EventTriggers.ON_MOUSE_MOVE: lambda: [],
|
||||
EventTriggers.ON_MOUSE_OVER: lambda: [],
|
||||
EventTriggers.ON_MOUSE_OUT: lambda: [],
|
||||
EventTriggers.ON_MOUSE_ENTER: lambda: [],
|
||||
EventTriggers.ON_MOUSE_LEAVE: lambda: [],
|
||||
EventTriggers.ON_ANIMATION_START: empty_event,
|
||||
EventTriggers.ON_ANIMATION_END: empty_event,
|
||||
EventTriggers.ON_CLICK: empty_event,
|
||||
EventTriggers.ON_MOUSE_MOVE: empty_event,
|
||||
EventTriggers.ON_MOUSE_OVER: empty_event,
|
||||
EventTriggers.ON_MOUSE_OUT: empty_event,
|
||||
EventTriggers.ON_MOUSE_ENTER: empty_event,
|
||||
EventTriggers.ON_MOUSE_LEAVE: empty_event,
|
||||
}
|
||||
|
||||
|
||||
@ -167,8 +167,8 @@ class Radar(Recharts):
|
||||
A dict mapping the event trigger to the var that is passed to the handler.
|
||||
"""
|
||||
return {
|
||||
EventTriggers.ON_ANIMATION_START: lambda: [],
|
||||
EventTriggers.ON_ANIMATION_END: lambda: [],
|
||||
EventTriggers.ON_ANIMATION_START: empty_event,
|
||||
EventTriggers.ON_ANIMATION_END: empty_event,
|
||||
}
|
||||
|
||||
|
||||
@ -219,14 +219,14 @@ class RadialBar(Recharts):
|
||||
A dict mapping the event trigger to the var that is passed to the handler.
|
||||
"""
|
||||
return {
|
||||
EventTriggers.ON_CLICK: lambda: [],
|
||||
EventTriggers.ON_MOUSE_MOVE: lambda: [],
|
||||
EventTriggers.ON_MOUSE_OVER: lambda: [],
|
||||
EventTriggers.ON_MOUSE_OUT: lambda: [],
|
||||
EventTriggers.ON_MOUSE_ENTER: lambda: [],
|
||||
EventTriggers.ON_MOUSE_LEAVE: lambda: [],
|
||||
EventTriggers.ON_ANIMATION_START: lambda: [],
|
||||
EventTriggers.ON_ANIMATION_END: lambda: [],
|
||||
EventTriggers.ON_CLICK: empty_event,
|
||||
EventTriggers.ON_MOUSE_MOVE: empty_event,
|
||||
EventTriggers.ON_MOUSE_OVER: empty_event,
|
||||
EventTriggers.ON_MOUSE_OUT: empty_event,
|
||||
EventTriggers.ON_MOUSE_ENTER: empty_event,
|
||||
EventTriggers.ON_MOUSE_LEAVE: empty_event,
|
||||
EventTriggers.ON_ANIMATION_START: empty_event,
|
||||
EventTriggers.ON_ANIMATION_END: empty_event,
|
||||
}
|
||||
|
||||
|
||||
@ -392,12 +392,12 @@ class PolarRadiusAxis(Recharts):
|
||||
A dict mapping the event trigger to the var that is passed to the handler.
|
||||
"""
|
||||
return {
|
||||
EventTriggers.ON_CLICK: lambda: [],
|
||||
EventTriggers.ON_MOUSE_MOVE: lambda: [],
|
||||
EventTriggers.ON_MOUSE_OVER: lambda: [],
|
||||
EventTriggers.ON_MOUSE_OUT: lambda: [],
|
||||
EventTriggers.ON_MOUSE_ENTER: lambda: [],
|
||||
EventTriggers.ON_MOUSE_LEAVE: lambda: [],
|
||||
EventTriggers.ON_CLICK: empty_event,
|
||||
EventTriggers.ON_MOUSE_MOVE: empty_event,
|
||||
EventTriggers.ON_MOUSE_OVER: empty_event,
|
||||
EventTriggers.ON_MOUSE_OUT: empty_event,
|
||||
EventTriggers.ON_MOUSE_ENTER: empty_event,
|
||||
EventTriggers.ON_MOUSE_LEAVE: empty_event,
|
||||
}
|
||||
|
||||
|
||||
|
@ -9,7 +9,7 @@ from reflex.utils import console
|
||||
class Recharts(Component):
|
||||
"""A component that wraps a recharts lib."""
|
||||
|
||||
library = "recharts@2.12.7"
|
||||
library = "recharts@2.13.0"
|
||||
|
||||
def render(self) -> Dict:
|
||||
"""Render the tag.
|
||||
@ -29,7 +29,7 @@ class Recharts(Component):
|
||||
class RechartsCharts(NoSSRComponent, MemoizationLeaf):
|
||||
"""A component that wraps a recharts lib."""
|
||||
|
||||
library = "recharts@2.12.7"
|
||||
library = "recharts@2.13.0"
|
||||
|
||||
|
||||
LiteralAnimationEasing = Literal["ease", "ease-in", "ease-out", "ease-in-out", "linear"]
|
||||
|
@ -3,11 +3,11 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import enum
|
||||
from typing import Dict, List, Literal, Optional, Union
|
||||
from typing import Dict, List, Literal, Optional, Tuple, Union
|
||||
|
||||
from reflex.base import Base
|
||||
from reflex.components.component import Component, NoSSRComponent
|
||||
from reflex.event import EventHandler
|
||||
from reflex.event import EventHandler, empty_event, identity_event
|
||||
from reflex.utils.format import to_camel_case
|
||||
from reflex.utils.imports import ImportDict, ImportVar
|
||||
from reflex.vars.base import Var
|
||||
@ -68,6 +68,35 @@ class EditorOptions(Base):
|
||||
button_list: Optional[List[Union[List[str], str]]]
|
||||
|
||||
|
||||
def on_blur_spec(e: Var, content: Var[str]) -> Tuple[Var[str]]:
|
||||
"""A helper function to specify the on_blur event handler.
|
||||
|
||||
Args:
|
||||
e: The event.
|
||||
content: The content of the editor.
|
||||
|
||||
Returns:
|
||||
A tuple containing the content of the editor.
|
||||
"""
|
||||
return (content,)
|
||||
|
||||
|
||||
def on_paste_spec(
|
||||
e: Var, clean_data: Var[str], max_char_count: Var[bool]
|
||||
) -> Tuple[Var[str], Var[bool]]:
|
||||
"""A helper function to specify the on_paste event handler.
|
||||
|
||||
Args:
|
||||
e: The event.
|
||||
clean_data: The clean data.
|
||||
max_char_count: The maximum character count.
|
||||
|
||||
Returns:
|
||||
A tuple containing the clean data and the maximum character count.
|
||||
"""
|
||||
return (clean_data, max_char_count)
|
||||
|
||||
|
||||
class Editor(NoSSRComponent):
|
||||
"""A Rich Text Editor component based on SunEditor.
|
||||
Not every JS prop is listed here (some are not easily usable from python),
|
||||
@ -178,36 +207,31 @@ class Editor(NoSSRComponent):
|
||||
disable_toolbar: Var[bool]
|
||||
|
||||
# Fired when the editor content changes.
|
||||
on_change: EventHandler[lambda content: [content]]
|
||||
on_change: EventHandler[identity_event(str)]
|
||||
|
||||
# Fired when the something is inputted in the editor.
|
||||
on_input: EventHandler[lambda e: [e]]
|
||||
on_input: EventHandler[empty_event]
|
||||
|
||||
# Fired when the editor loses focus.
|
||||
on_blur: EventHandler[lambda e, content: [content]]
|
||||
on_blur: EventHandler[on_blur_spec]
|
||||
|
||||
# Fired when the editor is loaded.
|
||||
on_load: EventHandler[lambda reload: [reload]]
|
||||
|
||||
# Fired when the editor is resized.
|
||||
on_resize_editor: EventHandler[lambda height, prev_height: [height, prev_height]]
|
||||
on_load: EventHandler[identity_event(bool)]
|
||||
|
||||
# Fired when the editor content is copied.
|
||||
on_copy: EventHandler[lambda e, clipboard_data: [clipboard_data]]
|
||||
on_copy: EventHandler[empty_event]
|
||||
|
||||
# Fired when the editor content is cut.
|
||||
on_cut: EventHandler[lambda e, clipboard_data: [clipboard_data]]
|
||||
on_cut: EventHandler[empty_event]
|
||||
|
||||
# Fired when the editor content is pasted.
|
||||
on_paste: EventHandler[
|
||||
lambda e, clean_data, max_char_count: [clean_data, max_char_count]
|
||||
]
|
||||
on_paste: EventHandler[on_paste_spec]
|
||||
|
||||
# Fired when the code view is toggled.
|
||||
toggle_code_view: EventHandler[lambda is_code_view: [is_code_view]]
|
||||
toggle_code_view: EventHandler[identity_event(bool)]
|
||||
|
||||
# Fired when the full screen mode is toggled.
|
||||
toggle_full_screen: EventHandler[lambda is_full_screen: [is_full_screen]]
|
||||
toggle_full_screen: EventHandler[identity_event(bool)]
|
||||
|
||||
def add_imports(self) -> ImportDict:
|
||||
"""Add imports for the Editor component.
|
||||
|
@ -4,7 +4,7 @@
|
||||
# This file was generated by `reflex/utils/pyi_generator.py`!
|
||||
# ------------------------------------------------------
|
||||
import enum
|
||||
from typing import Any, Dict, List, Literal, Optional, Union, overload
|
||||
from typing import Any, Dict, List, Literal, Optional, Tuple, Union, overload
|
||||
|
||||
from reflex.base import Base
|
||||
from reflex.components.component import NoSSRComponent
|
||||
@ -44,6 +44,11 @@ class EditorOptions(Base):
|
||||
rtl: Optional[bool]
|
||||
button_list: Optional[List[Union[List[str], str]]]
|
||||
|
||||
def on_blur_spec(e: Var, content: Var[str]) -> Tuple[Var[str]]: ...
|
||||
def on_paste_spec(
|
||||
e: Var, clean_data: Var[str], max_char_count: Var[bool]
|
||||
) -> Tuple[Var[str], Var[bool]]: ...
|
||||
|
||||
class Editor(NoSSRComponent):
|
||||
def add_imports(self) -> ImportDict: ...
|
||||
@overload
|
||||
@ -122,8 +127,8 @@ class Editor(NoSSRComponent):
|
||||
class_name: Optional[Any] = None,
|
||||
autofocus: Optional[bool] = None,
|
||||
custom_attrs: Optional[Dict[str, Union[Var, str]]] = None,
|
||||
on_blur: Optional[EventType[[]]] = None,
|
||||
on_change: Optional[EventType[[]]] = None,
|
||||
on_blur: Optional[EventType[str]] = None,
|
||||
on_change: Optional[EventType[str]] = None,
|
||||
on_click: Optional[EventType[[]]] = None,
|
||||
on_context_menu: Optional[EventType[[]]] = None,
|
||||
on_copy: Optional[EventType[[]]] = None,
|
||||
@ -131,7 +136,7 @@ class Editor(NoSSRComponent):
|
||||
on_double_click: Optional[EventType[[]]] = None,
|
||||
on_focus: Optional[EventType[[]]] = None,
|
||||
on_input: Optional[EventType[[]]] = None,
|
||||
on_load: Optional[EventType[[]]] = None,
|
||||
on_load: Optional[EventType[bool]] = None,
|
||||
on_mount: Optional[EventType[[]]] = None,
|
||||
on_mouse_down: Optional[EventType[[]]] = None,
|
||||
on_mouse_enter: Optional[EventType[[]]] = None,
|
||||
@ -140,12 +145,11 @@ class Editor(NoSSRComponent):
|
||||
on_mouse_out: Optional[EventType[[]]] = None,
|
||||
on_mouse_over: Optional[EventType[[]]] = None,
|
||||
on_mouse_up: Optional[EventType[[]]] = None,
|
||||
on_paste: Optional[EventType[[]]] = None,
|
||||
on_resize_editor: Optional[EventType[[]]] = None,
|
||||
on_paste: Optional[EventType[str, bool]] = None,
|
||||
on_scroll: Optional[EventType[[]]] = None,
|
||||
on_unmount: Optional[EventType[[]]] = None,
|
||||
toggle_code_view: Optional[EventType[[]]] = None,
|
||||
toggle_full_screen: Optional[EventType[[]]] = None,
|
||||
toggle_code_view: Optional[EventType[bool]] = None,
|
||||
toggle_full_screen: Optional[EventType[bool]] = None,
|
||||
**props,
|
||||
) -> "Editor":
|
||||
"""Create an instance of Editor. No children allowed.
|
||||
|
214
reflex/config.py
214
reflex/config.py
@ -2,6 +2,7 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import dataclasses
|
||||
import importlib
|
||||
import os
|
||||
import sys
|
||||
@ -9,7 +10,10 @@ import urllib.parse
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict, List, Optional, Set, Union
|
||||
|
||||
from reflex.utils.exceptions import ConfigError
|
||||
from typing_extensions import get_type_hints
|
||||
|
||||
from reflex.utils.exceptions import ConfigError, EnvironmentVarValueError
|
||||
from reflex.utils.types import value_inside_optional
|
||||
|
||||
try:
|
||||
import pydantic.v1 as pydantic
|
||||
@ -131,6 +135,198 @@ class DBConfig(Base):
|
||||
return f"{self.engine}://{path}/{self.database}"
|
||||
|
||||
|
||||
def get_default_value_for_field(field: dataclasses.Field) -> Any:
|
||||
"""Get the default value for a field.
|
||||
|
||||
Args:
|
||||
field: The field.
|
||||
|
||||
Returns:
|
||||
The default value.
|
||||
|
||||
Raises:
|
||||
ValueError: If no default value is found.
|
||||
"""
|
||||
if field.default != dataclasses.MISSING:
|
||||
return field.default
|
||||
elif field.default_factory != dataclasses.MISSING:
|
||||
return field.default_factory()
|
||||
else:
|
||||
raise ValueError(
|
||||
f"Missing value for environment variable {field.name} and no default value found"
|
||||
)
|
||||
|
||||
|
||||
def interpret_boolean_env(value: str) -> bool:
|
||||
"""Interpret a boolean environment variable value.
|
||||
|
||||
Args:
|
||||
value: The environment variable value.
|
||||
|
||||
Returns:
|
||||
The interpreted value.
|
||||
|
||||
Raises:
|
||||
EnvironmentVarValueError: If the value is invalid.
|
||||
"""
|
||||
true_values = ["true", "1", "yes", "y"]
|
||||
false_values = ["false", "0", "no", "n"]
|
||||
|
||||
if value.lower() in true_values:
|
||||
return True
|
||||
elif value.lower() in false_values:
|
||||
return False
|
||||
raise EnvironmentVarValueError(f"Invalid boolean value: {value}")
|
||||
|
||||
|
||||
def interpret_int_env(value: str) -> int:
|
||||
"""Interpret an integer environment variable value.
|
||||
|
||||
Args:
|
||||
value: The environment variable value.
|
||||
|
||||
Returns:
|
||||
The interpreted value.
|
||||
|
||||
Raises:
|
||||
EnvironmentVarValueError: If the value is invalid.
|
||||
"""
|
||||
try:
|
||||
return int(value)
|
||||
except ValueError as ve:
|
||||
raise EnvironmentVarValueError(f"Invalid integer value: {value}") from ve
|
||||
|
||||
|
||||
def interpret_path_env(value: str) -> Path:
|
||||
"""Interpret a path environment variable value.
|
||||
|
||||
Args:
|
||||
value: The environment variable value.
|
||||
|
||||
Returns:
|
||||
The interpreted value.
|
||||
|
||||
Raises:
|
||||
EnvironmentVarValueError: If the path does not exist.
|
||||
"""
|
||||
path = Path(value)
|
||||
if not path.exists():
|
||||
raise EnvironmentVarValueError(f"Path does not exist: {path}")
|
||||
return path
|
||||
|
||||
|
||||
def interpret_env_var_value(value: str, field: dataclasses.Field) -> Any:
|
||||
"""Interpret an environment variable value based on the field type.
|
||||
|
||||
Args:
|
||||
value: The environment variable value.
|
||||
field: The field.
|
||||
|
||||
Returns:
|
||||
The interpreted value.
|
||||
|
||||
Raises:
|
||||
ValueError: If the value is invalid.
|
||||
"""
|
||||
field_type = value_inside_optional(field.type)
|
||||
|
||||
if field_type is bool:
|
||||
return interpret_boolean_env(value)
|
||||
elif field_type is str:
|
||||
return value
|
||||
elif field_type is int:
|
||||
return interpret_int_env(value)
|
||||
elif field_type is Path:
|
||||
return interpret_path_env(value)
|
||||
|
||||
else:
|
||||
raise ValueError(
|
||||
f"Invalid type for environment variable {field.name}: {field_type}. This is probably an issue in Reflex."
|
||||
)
|
||||
|
||||
|
||||
@dataclasses.dataclass(init=False)
|
||||
class EnvironmentVariables:
|
||||
"""Environment variables class to instantiate environment variables."""
|
||||
|
||||
# Whether to use npm over bun to install frontend packages.
|
||||
REFLEX_USE_NPM: bool = False
|
||||
|
||||
# The npm registry to use.
|
||||
NPM_CONFIG_REGISTRY: Optional[str] = None
|
||||
|
||||
# Whether to use Granian for the backend. Otherwise, use Uvicorn.
|
||||
REFLEX_USE_GRANIAN: bool = False
|
||||
|
||||
# The username to use for authentication on python package repository. Username and password must both be provided.
|
||||
TWINE_USERNAME: Optional[str] = None
|
||||
|
||||
# The password to use for authentication on python package repository. Username and password must both be provided.
|
||||
TWINE_PASSWORD: Optional[str] = None
|
||||
|
||||
# Whether to use the system installed bun. If set to false, bun will be bundled with the app.
|
||||
REFLEX_USE_SYSTEM_BUN: bool = False
|
||||
|
||||
# Whether to use the system installed node and npm. If set to false, node and npm will be bundled with the app.
|
||||
REFLEX_USE_SYSTEM_NODE: bool = False
|
||||
|
||||
# The working directory for the next.js commands.
|
||||
REFLEX_WEB_WORKDIR: Path = Path(constants.Dirs.WEB)
|
||||
|
||||
# Path to the alembic config file
|
||||
ALEMBIC_CONFIG: Path = Path(constants.ALEMBIC_CONFIG)
|
||||
|
||||
# Disable SSL verification for HTTPX requests.
|
||||
SSL_NO_VERIFY: bool = False
|
||||
|
||||
# The directory to store uploaded files.
|
||||
REFLEX_UPLOADED_FILES_DIR: Path = Path(constants.Dirs.UPLOADED_FILES)
|
||||
|
||||
# Whether to use seperate processes to compile the frontend and how many. If not set, defaults to thread executor.
|
||||
REFLEX_COMPILE_PROCESSES: Optional[int] = None
|
||||
|
||||
# Whether to use seperate threads to compile the frontend and how many. Defaults to `min(32, os.cpu_count() + 4)`.
|
||||
REFLEX_COMPILE_THREADS: Optional[int] = None
|
||||
|
||||
# The directory to store reflex dependencies.
|
||||
REFLEX_DIR: Path = Path(constants.Reflex.DIR)
|
||||
|
||||
# Whether to print the SQL queries if the log level is INFO or lower.
|
||||
SQLALCHEMY_ECHO: bool = False
|
||||
|
||||
# Whether to ignore the redis config error. Some redis servers only allow out-of-band configuration.
|
||||
REFLEX_IGNORE_REDIS_CONFIG_ERROR: bool = False
|
||||
|
||||
# Whether to skip purging the web directory in dev mode.
|
||||
REFLEX_PERSIST_WEB_DIR: bool = False
|
||||
|
||||
# The reflex.build frontend host.
|
||||
REFLEX_BUILD_FRONTEND: str = constants.Templates.REFLEX_BUILD_FRONTEND
|
||||
|
||||
# The reflex.build backend host.
|
||||
REFLEX_BUILD_BACKEND: str = constants.Templates.REFLEX_BUILD_BACKEND
|
||||
|
||||
def __init__(self):
|
||||
"""Initialize the environment variables."""
|
||||
type_hints = get_type_hints(type(self))
|
||||
|
||||
for field in dataclasses.fields(self):
|
||||
raw_value = os.getenv(field.name, None)
|
||||
|
||||
field.type = type_hints.get(field.name) or field.type
|
||||
|
||||
value = (
|
||||
interpret_env_var_value(raw_value, field)
|
||||
if raw_value is not None
|
||||
else get_default_value_for_field(field)
|
||||
)
|
||||
|
||||
setattr(self, field.name, value)
|
||||
|
||||
|
||||
environment = EnvironmentVariables()
|
||||
|
||||
|
||||
class Config(Base):
|
||||
"""The config defines runtime settings for the app.
|
||||
|
||||
@ -222,6 +418,12 @@ class Config(Base):
|
||||
# Number of gunicorn workers from user
|
||||
gunicorn_workers: Optional[int] = None
|
||||
|
||||
# Number of requests before a worker is restarted
|
||||
gunicorn_max_requests: int = 100
|
||||
|
||||
# Variance limit for max requests; gunicorn only
|
||||
gunicorn_max_requests_jitter: int = 25
|
||||
|
||||
# Indicate which type of state manager to use
|
||||
state_manager_mode: constants.StateManagerMode = constants.StateManagerMode.DISK
|
||||
|
||||
@ -234,6 +436,9 @@ class Config(Base):
|
||||
# Attributes that were explicitly set by the user.
|
||||
_non_default_attributes: Set[str] = pydantic.PrivateAttr(set())
|
||||
|
||||
# Path to file containing key-values pairs to override in the environment; Dotenv format.
|
||||
env_file: Optional[str] = None
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
"""Initialize the config values.
|
||||
|
||||
@ -275,6 +480,7 @@ class Config(Base):
|
||||
|
||||
def update_from_env(self) -> dict[str, Any]:
|
||||
"""Update the config values based on set environment variables.
|
||||
If there is a set env_file, it is loaded first.
|
||||
|
||||
Returns:
|
||||
The updated config values.
|
||||
@ -284,6 +490,12 @@ class Config(Base):
|
||||
"""
|
||||
from reflex.utils.exceptions import EnvVarValueError
|
||||
|
||||
if self.env_file:
|
||||
from dotenv import load_dotenv
|
||||
|
||||
# load env file if exists
|
||||
load_dotenv(self.env_file, override=True)
|
||||
|
||||
updated_values = {}
|
||||
# Iterate over the fields.
|
||||
for key, field in self.__fields__.items():
|
||||
|
@ -2,6 +2,8 @@
|
||||
|
||||
from .base import (
|
||||
COOKIES,
|
||||
ENV_BACKEND_ONLY_ENV_VAR,
|
||||
ENV_FRONTEND_ONLY_ENV_VAR,
|
||||
ENV_MODE_ENV_VAR,
|
||||
IS_WINDOWS,
|
||||
LOCAL_STORAGE,
|
||||
|
@ -2,7 +2,6 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import platform
|
||||
from enum import Enum
|
||||
from importlib import metadata
|
||||
@ -11,6 +10,8 @@ from types import SimpleNamespace
|
||||
|
||||
from platformdirs import PlatformDirs
|
||||
|
||||
from .utils import classproperty
|
||||
|
||||
IS_WINDOWS = platform.system() == "Windows"
|
||||
|
||||
|
||||
@ -20,6 +21,8 @@ class Dirs(SimpleNamespace):
|
||||
# The frontend directories in a project.
|
||||
# The web folder where the NextJS app is compiled to.
|
||||
WEB = ".web"
|
||||
# The directory where uploaded files are stored.
|
||||
UPLOADED_FILES = "uploaded_files"
|
||||
# The name of the assets directory.
|
||||
APP_ASSETS = "assets"
|
||||
# The name of the assets directory for external ressource (a subfolder of APP_ASSETS).
|
||||
@ -64,21 +67,13 @@ class Reflex(SimpleNamespace):
|
||||
|
||||
# Files and directories used to init a new project.
|
||||
# The directory to store reflex dependencies.
|
||||
# Get directory value from enviroment variables if it exists.
|
||||
_dir = os.environ.get("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.
|
||||
# If user sets REFLEX_DIR envroment variable use that instead.
|
||||
DIR = PlatformDirs(MODULE_NAME, False).user_data_path
|
||||
|
||||
DIR = Path(
|
||||
_dir
|
||||
or (
|
||||
# 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.
|
||||
# If user sets REFLEX_DIR envroment variable use that instead.
|
||||
PlatformDirs(MODULE_NAME, False).user_data_dir
|
||||
)
|
||||
)
|
||||
# The root directory of the reflex library.
|
||||
|
||||
ROOT_DIR = Path(__file__).parents[2]
|
||||
|
||||
RELEASES_URL = f"https://api.github.com/repos/reflex-dev/templates/releases"
|
||||
@ -101,27 +96,51 @@ class Templates(SimpleNamespace):
|
||||
DEFAULT = "blank"
|
||||
|
||||
# The reflex.build frontend host
|
||||
REFLEX_BUILD_FRONTEND = os.environ.get(
|
||||
"REFLEX_BUILD_FRONTEND", "https://flexgen.reflex.run"
|
||||
)
|
||||
REFLEX_BUILD_FRONTEND = "https://flexgen.reflex.run"
|
||||
|
||||
# The reflex.build backend host
|
||||
REFLEX_BUILD_BACKEND = os.environ.get(
|
||||
"REFLEX_BUILD_BACKEND", "https://flexgen-prod-flexgen.fly.dev"
|
||||
)
|
||||
REFLEX_BUILD_BACKEND = "https://flexgen-prod-flexgen.fly.dev"
|
||||
|
||||
# The URL to redirect to reflex.build
|
||||
REFLEX_BUILD_URL = (
|
||||
REFLEX_BUILD_FRONTEND + "/gen?reflex_init_token={reflex_init_token}"
|
||||
)
|
||||
@classproperty
|
||||
@classmethod
|
||||
def REFLEX_BUILD_URL(cls):
|
||||
"""The URL to redirect to reflex.build.
|
||||
|
||||
# The URL to poll waiting for the user to select a generation.
|
||||
REFLEX_BUILD_POLL_URL = REFLEX_BUILD_BACKEND + "/api/init/{reflex_init_token}"
|
||||
Returns:
|
||||
The URL to redirect to reflex.build.
|
||||
"""
|
||||
from reflex.config import environment
|
||||
|
||||
# The URL to fetch the generation's reflex code
|
||||
REFLEX_BUILD_CODE_URL = (
|
||||
REFLEX_BUILD_BACKEND + "/api/gen/{generation_hash}/refactored"
|
||||
)
|
||||
return (
|
||||
environment.REFLEX_BUILD_FRONTEND
|
||||
+ "/gen?reflex_init_token={reflex_init_token}"
|
||||
)
|
||||
|
||||
@classproperty
|
||||
@classmethod
|
||||
def REFLEX_BUILD_POLL_URL(cls):
|
||||
"""The URL to poll waiting for the user to select a generation.
|
||||
|
||||
Returns:
|
||||
The URL to poll waiting for the user to select a generation.
|
||||
"""
|
||||
from reflex.config import environment
|
||||
|
||||
return environment.REFLEX_BUILD_BACKEND + "/api/init/{reflex_init_token}"
|
||||
|
||||
@classproperty
|
||||
@classmethod
|
||||
def REFLEX_BUILD_CODE_URL(cls):
|
||||
"""The URL to fetch the generation's reflex code.
|
||||
|
||||
Returns:
|
||||
The URL to fetch the generation's reflex code.
|
||||
"""
|
||||
from reflex.config import environment
|
||||
|
||||
return (
|
||||
environment.REFLEX_BUILD_BACKEND + "/api/gen/{generation_hash}/refactored"
|
||||
)
|
||||
|
||||
class Dirs(SimpleNamespace):
|
||||
"""Folders used by the template system of Reflex."""
|
||||
@ -226,6 +245,9 @@ SKIP_COMPILE_ENV_VAR = "__REFLEX_SKIP_COMPILE"
|
||||
# This env var stores the execution mode of the app
|
||||
ENV_MODE_ENV_VAR = "REFLEX_ENV_MODE"
|
||||
|
||||
ENV_BACKEND_ONLY_ENV_VAR = "REFLEX_BACKEND_ONLY"
|
||||
ENV_FRONTEND_ONLY_ENV_VAR = "REFLEX_FRONTEND_ONLY"
|
||||
|
||||
# Testing variables.
|
||||
# Testing os env set by pytest when running a test case.
|
||||
PYTEST_CURRENT_TEST = "PYTEST_CURRENT_TEST"
|
||||
|
@ -132,16 +132,6 @@ class Hooks(SimpleNamespace):
|
||||
}
|
||||
})"""
|
||||
|
||||
FRONTEND_ERRORS = f"""
|
||||
const logFrontendError = (error, info) => {{
|
||||
if (process.env.NODE_ENV === "production") {{
|
||||
addEvents([Event("{CompileVars.FRONTEND_EXCEPTION_STATE_FULL}.handle_frontend_exception", {{
|
||||
stack: error.stack,
|
||||
}})])
|
||||
}}
|
||||
}}
|
||||
"""
|
||||
|
||||
|
||||
class MemoizationDisposition(enum.Enum):
|
||||
"""The conditions under which a component should be memoized."""
|
||||
|
@ -1,6 +1,5 @@
|
||||
"""Config constants."""
|
||||
|
||||
import os
|
||||
from pathlib import Path
|
||||
from types import SimpleNamespace
|
||||
|
||||
@ -9,7 +8,7 @@ from reflex.constants.base import Dirs, Reflex
|
||||
from .compiler import Ext
|
||||
|
||||
# Alembic migrations
|
||||
ALEMBIC_CONFIG = os.environ.get("ALEMBIC_CONFIG", "alembic.ini")
|
||||
ALEMBIC_CONFIG = "alembic.ini"
|
||||
|
||||
|
||||
class Config(SimpleNamespace):
|
||||
|
@ -3,9 +3,11 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import platform
|
||||
from pathlib import Path
|
||||
from types import SimpleNamespace
|
||||
|
||||
from .base import IS_WINDOWS, Reflex
|
||||
from .base import IS_WINDOWS
|
||||
from .utils import classproperty
|
||||
|
||||
|
||||
def get_fnm_name() -> str | None:
|
||||
@ -36,12 +38,9 @@ class Bun(SimpleNamespace):
|
||||
|
||||
# The Bun version.
|
||||
VERSION = "1.1.29"
|
||||
|
||||
# Min Bun Version
|
||||
MIN_VERSION = "0.7.0"
|
||||
# The directory to store the bun.
|
||||
ROOT_PATH = Reflex.DIR / "bun"
|
||||
# Default bun path.
|
||||
DEFAULT_PATH = ROOT_PATH / "bin" / ("bun" if not IS_WINDOWS else "bun.exe")
|
||||
|
||||
# URL to bun install script.
|
||||
INSTALL_URL = "https://raw.githubusercontent.com/reflex-dev/reflex/main/scripts/bun_install.sh"
|
||||
@ -50,11 +49,31 @@ class Bun(SimpleNamespace):
|
||||
WINDOWS_INSTALL_URL = (
|
||||
"https://raw.githubusercontent.com/reflex-dev/reflex/main/scripts/install.ps1"
|
||||
)
|
||||
|
||||
# Path of the bunfig file
|
||||
CONFIG_PATH = "bunfig.toml"
|
||||
|
||||
# The environment variable to use the system installed bun.
|
||||
USE_SYSTEM_VAR = "REFLEX_USE_SYSTEM_BUN"
|
||||
@classproperty
|
||||
@classmethod
|
||||
def ROOT_PATH(cls):
|
||||
"""The directory to store the bun.
|
||||
|
||||
Returns:
|
||||
The directory to store the bun.
|
||||
"""
|
||||
from reflex.config import environment
|
||||
|
||||
return environment.REFLEX_DIR / "bun"
|
||||
|
||||
@classproperty
|
||||
@classmethod
|
||||
def DEFAULT_PATH(cls):
|
||||
"""Default bun path.
|
||||
|
||||
Returns:
|
||||
The default bun path.
|
||||
"""
|
||||
return cls.ROOT_PATH / "bin" / ("bun" if not IS_WINDOWS else "bun.exe")
|
||||
|
||||
|
||||
# FNM config.
|
||||
@ -63,44 +82,81 @@ class Fnm(SimpleNamespace):
|
||||
|
||||
# The FNM version.
|
||||
VERSION = "1.35.1"
|
||||
# The directory to store fnm.
|
||||
DIR = Reflex.DIR / "fnm"
|
||||
|
||||
FILENAME = get_fnm_name()
|
||||
# The fnm executable binary.
|
||||
EXE = 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"
|
||||
)
|
||||
|
||||
@classproperty
|
||||
@classmethod
|
||||
def DIR(cls) -> Path:
|
||||
"""The directory to store fnm.
|
||||
|
||||
Returns:
|
||||
The directory to store fnm.
|
||||
"""
|
||||
from reflex.config import environment
|
||||
|
||||
return environment.REFLEX_DIR / "fnm"
|
||||
|
||||
@classproperty
|
||||
@classmethod
|
||||
def EXE(cls):
|
||||
"""The fnm executable binary.
|
||||
|
||||
Returns:
|
||||
The fnm executable binary.
|
||||
"""
|
||||
return cls.DIR / ("fnm.exe" if IS_WINDOWS else "fnm")
|
||||
|
||||
|
||||
# Node / NPM config
|
||||
class Node(SimpleNamespace):
|
||||
"""Node/ NPM constants."""
|
||||
|
||||
# The Node version.
|
||||
VERSION = "20.18.0"
|
||||
VERSION = "22.10.0"
|
||||
# The minimum required node version.
|
||||
MIN_VERSION = "18.17.0"
|
||||
|
||||
# The node bin path.
|
||||
BIN_PATH = (
|
||||
Fnm.DIR
|
||||
/ "node-versions"
|
||||
/ f"v{VERSION}"
|
||||
/ "installation"
|
||||
/ ("bin" if not IS_WINDOWS else "")
|
||||
)
|
||||
@classproperty
|
||||
@classmethod
|
||||
def BIN_PATH(cls):
|
||||
"""The node bin path.
|
||||
|
||||
# The default path where node is installed.
|
||||
PATH = BIN_PATH / ("node.exe" if IS_WINDOWS else "node")
|
||||
Returns:
|
||||
The node bin path.
|
||||
"""
|
||||
return (
|
||||
Fnm.DIR
|
||||
/ "node-versions"
|
||||
/ f"v{cls.VERSION}"
|
||||
/ "installation"
|
||||
/ ("bin" if not IS_WINDOWS else "")
|
||||
)
|
||||
|
||||
# The default path where npm is installed.
|
||||
NPM_PATH = BIN_PATH / "npm"
|
||||
@classproperty
|
||||
@classmethod
|
||||
def PATH(cls):
|
||||
"""The default path where node is installed.
|
||||
|
||||
# The environment variable to use the system installed node.
|
||||
USE_SYSTEM_VAR = "REFLEX_USE_SYSTEM_NODE"
|
||||
Returns:
|
||||
The default path where node is installed.
|
||||
"""
|
||||
return cls.BIN_PATH / ("node.exe" if IS_WINDOWS else "node")
|
||||
|
||||
@classproperty
|
||||
@classmethod
|
||||
def NPM_PATH(cls):
|
||||
"""The default path where npm is installed.
|
||||
|
||||
Returns:
|
||||
The default path where npm is installed.
|
||||
"""
|
||||
return cls.BIN_PATH / "npm"
|
||||
|
||||
|
||||
class PackageJson(SimpleNamespace):
|
||||
@ -117,18 +173,18 @@ class PackageJson(SimpleNamespace):
|
||||
PATH = "package.json"
|
||||
|
||||
DEPENDENCIES = {
|
||||
"@babel/standalone": "7.25.7",
|
||||
"@babel/standalone": "7.25.8",
|
||||
"@emotion/react": "11.13.3",
|
||||
"axios": "1.7.7",
|
||||
"json5": "2.2.3",
|
||||
"next": "14.2.14",
|
||||
"next": "14.2.15",
|
||||
"next-sitemap": "4.2.3",
|
||||
"next-themes": "0.3.0",
|
||||
"react": "18.3.1",
|
||||
"react-dom": "18.3.1",
|
||||
"react-focus-lock": "2.13.2",
|
||||
"socket.io-client": "4.8.0",
|
||||
"universal-cookie": "7.2.0",
|
||||
"universal-cookie": "7.2.1",
|
||||
}
|
||||
DEV_DEPENDENCIES = {
|
||||
"autoprefixer": "10.4.20",
|
||||
|
@ -7,7 +7,7 @@ class Tailwind(SimpleNamespace):
|
||||
"""Tailwind constants."""
|
||||
|
||||
# The Tailwindcss version
|
||||
VERSION = "tailwindcss@3.4.13"
|
||||
VERSION = "tailwindcss@3.4.14"
|
||||
# The Tailwind config.
|
||||
CONFIG = "tailwind.config.js"
|
||||
# Default Tailwind content paths
|
||||
|
32
reflex/constants/utils.py
Normal file
32
reflex/constants/utils.py
Normal file
@ -0,0 +1,32 @@
|
||||
"""Utility functions for constants."""
|
||||
|
||||
from typing import Any, Callable, Generic, Type
|
||||
|
||||
from typing_extensions import TypeVar
|
||||
|
||||
T = TypeVar("T")
|
||||
V = TypeVar("V")
|
||||
|
||||
|
||||
class classproperty(Generic[T, V]):
|
||||
"""A class property decorator."""
|
||||
|
||||
def __init__(self, getter: Callable[[Type[T]], V]) -> None:
|
||||
"""Initialize the class property.
|
||||
|
||||
Args:
|
||||
getter: The getter function.
|
||||
"""
|
||||
self.getter = getattr(getter, "__func__", getter)
|
||||
|
||||
def __get__(self, instance: Any, owner: Type[T]) -> V:
|
||||
"""Get the value of the class property.
|
||||
|
||||
Args:
|
||||
instance: The instance of the class.
|
||||
owner: The class itself.
|
||||
|
||||
Returns:
|
||||
The value of the class property.
|
||||
"""
|
||||
return self.getter(owner)
|
@ -17,7 +17,7 @@ import typer
|
||||
from tomlkit.exceptions import TOMLKitError
|
||||
|
||||
from reflex import constants
|
||||
from reflex.config import get_config
|
||||
from reflex.config import environment, get_config
|
||||
from reflex.constants import CustomComponents
|
||||
from reflex.utils import console
|
||||
|
||||
@ -609,14 +609,14 @@ def publish(
|
||||
help="The API token to use for authentication on python package repository. If token is provided, no username/password should be provided at the same time",
|
||||
),
|
||||
username: Optional[str] = typer.Option(
|
||||
os.getenv("TWINE_USERNAME"),
|
||||
environment.TWINE_USERNAME,
|
||||
"-u",
|
||||
"--username",
|
||||
show_default="TWINE_USERNAME environment variable value if set",
|
||||
help="The username to use for authentication on python package repository. Username and password must both be provided.",
|
||||
),
|
||||
password: Optional[str] = typer.Option(
|
||||
os.getenv("TWINE_PASSWORD"),
|
||||
environment.TWINE_PASSWORD,
|
||||
"-p",
|
||||
"--password",
|
||||
show_default="TWINE_PASSWORD environment variable value if set",
|
||||
|
305
reflex/event.py
305
reflex/event.py
@ -12,7 +12,6 @@ from functools import partial
|
||||
from typing import (
|
||||
Any,
|
||||
Callable,
|
||||
ClassVar,
|
||||
Dict,
|
||||
Generic,
|
||||
List,
|
||||
@ -22,9 +21,10 @@ from typing import (
|
||||
TypeVar,
|
||||
Union,
|
||||
get_type_hints,
|
||||
overload,
|
||||
)
|
||||
|
||||
from typing_extensions import ParamSpec, get_args, get_origin
|
||||
from typing_extensions import ParamSpec, Protocol, get_args, get_origin
|
||||
|
||||
from reflex import constants
|
||||
from reflex.utils import console, format
|
||||
@ -36,14 +36,15 @@ from reflex.utils.exceptions import (
|
||||
from reflex.utils.types import ArgsSpec, GenericType
|
||||
from reflex.vars import VarData
|
||||
from reflex.vars.base import (
|
||||
CachedVarOperation,
|
||||
LiteralNoneVar,
|
||||
LiteralVar,
|
||||
ToOperation,
|
||||
Var,
|
||||
cached_property_no_lock,
|
||||
)
|
||||
from reflex.vars.function import ArgsFunctionOperation, FunctionStringVar, FunctionVar
|
||||
from reflex.vars.function import (
|
||||
ArgsFunctionOperation,
|
||||
FunctionStringVar,
|
||||
FunctionVar,
|
||||
VarOperationCall,
|
||||
)
|
||||
from reflex.vars.object import ObjectVar
|
||||
|
||||
try:
|
||||
@ -399,11 +400,6 @@ class EventChain(EventActionsMixin):
|
||||
invocation: Optional[Var] = dataclasses.field(default=None)
|
||||
|
||||
|
||||
# These chains can be used for their side effects when no other events are desired.
|
||||
stop_propagation = EventChain(events=[], args_spec=lambda: []).stop_propagation
|
||||
prevent_default = EventChain(events=[], args_spec=lambda: []).prevent_default
|
||||
|
||||
|
||||
@dataclasses.dataclass(
|
||||
init=True,
|
||||
frozen=True,
|
||||
@ -467,34 +463,103 @@ def empty_event() -> Tuple[()]:
|
||||
return tuple() # type: ignore
|
||||
|
||||
|
||||
# These chains can be used for their side effects when no other events are desired.
|
||||
stop_propagation = EventChain(events=[], args_spec=empty_event).stop_propagation
|
||||
prevent_default = EventChain(events=[], args_spec=empty_event).prevent_default
|
||||
|
||||
|
||||
T = TypeVar("T")
|
||||
U = TypeVar("U")
|
||||
|
||||
|
||||
def identity_event(event_type: Type[T]) -> Callable[[Var[T]], Tuple[Var[T]]]:
|
||||
# def identity_event(event_type: Type[T]) -> Callable[[Var[T]], Tuple[Var[T]]]:
|
||||
# """A helper function that returns the input event as output.
|
||||
|
||||
# Args:
|
||||
# event_type: The type of the event.
|
||||
|
||||
# Returns:
|
||||
# A function that returns the input event as output.
|
||||
# """
|
||||
|
||||
# def inner(ev: Var[T]) -> Tuple[Var[T]]:
|
||||
# return (ev,)
|
||||
|
||||
# inner.__signature__ = inspect.signature(inner).replace( # type: ignore
|
||||
# parameters=[
|
||||
# inspect.Parameter(
|
||||
# "ev",
|
||||
# kind=inspect.Parameter.POSITIONAL_OR_KEYWORD,
|
||||
# annotation=Var[event_type],
|
||||
# )
|
||||
# ],
|
||||
# return_annotation=Tuple[Var[event_type]],
|
||||
# )
|
||||
# inner.__annotations__["ev"] = Var[event_type]
|
||||
# inner.__annotations__["return"] = Tuple[Var[event_type]]
|
||||
|
||||
# return inner
|
||||
|
||||
|
||||
class IdentityEventReturn(Generic[T], Protocol):
|
||||
"""Protocol for an identity event return."""
|
||||
|
||||
def __call__(self, *values: Var[T]) -> Tuple[Var[T], ...]:
|
||||
"""Return the input values.
|
||||
|
||||
Args:
|
||||
*values: The values to return.
|
||||
|
||||
Returns:
|
||||
The input values.
|
||||
"""
|
||||
return values
|
||||
|
||||
|
||||
@overload
|
||||
def identity_event(event_type: Type[T], /) -> Callable[[Var[T]], Tuple[Var[T]]]: ... # type: ignore
|
||||
|
||||
|
||||
@overload
|
||||
def identity_event(
|
||||
event_type_1: Type[T], event_type2: Type[U], /
|
||||
) -> Callable[[Var[T], Var[U]], Tuple[Var[T], Var[U]]]: ...
|
||||
|
||||
|
||||
@overload
|
||||
def identity_event(*event_types: Type[T]) -> IdentityEventReturn[T]: ...
|
||||
|
||||
|
||||
def identity_event(*event_types: Type[T]) -> IdentityEventReturn[T]: # type: ignore
|
||||
"""A helper function that returns the input event as output.
|
||||
|
||||
Args:
|
||||
event_type: The type of the event.
|
||||
*event_types: The types of the events.
|
||||
|
||||
Returns:
|
||||
A function that returns the input event as output.
|
||||
"""
|
||||
|
||||
def inner(ev: Var[T]) -> Tuple[Var[T]]:
|
||||
return (ev,)
|
||||
def inner(*values: Var[T]) -> Tuple[Var[T], ...]:
|
||||
return values
|
||||
|
||||
inner_type = tuple(Var[event_type] for event_type in event_types)
|
||||
return_annotation = Tuple[inner_type] # type: ignore
|
||||
|
||||
inner.__signature__ = inspect.signature(inner).replace( # type: ignore
|
||||
parameters=[
|
||||
inspect.Parameter(
|
||||
"ev",
|
||||
f"ev_{i}",
|
||||
kind=inspect.Parameter.POSITIONAL_OR_KEYWORD,
|
||||
annotation=Var[event_type],
|
||||
)
|
||||
for i, event_type in enumerate(event_types)
|
||||
],
|
||||
return_annotation=Tuple[Var[event_type]],
|
||||
return_annotation=return_annotation,
|
||||
)
|
||||
inner.__annotations__["ev"] = Var[event_type]
|
||||
inner.__annotations__["return"] = Tuple[Var[event_type]]
|
||||
for i, event_type in enumerate(event_types):
|
||||
inner.__annotations__[f"ev_{i}"] = Var[event_type]
|
||||
inner.__annotations__["return"] = return_annotation
|
||||
|
||||
return inner
|
||||
|
||||
@ -1116,7 +1181,8 @@ def resolve_annotation(annotations: dict[str, Any], arg_name: str):
|
||||
deprecation_version="0.6.3",
|
||||
removal_version="0.7.0",
|
||||
)
|
||||
return JavascriptInputEvent
|
||||
# Allow arbitrary attribute access two levels deep until removed.
|
||||
return Dict[str, dict]
|
||||
return annotation
|
||||
|
||||
|
||||
@ -1334,7 +1400,7 @@ def get_fn_signature(fn: Callable) -> inspect.Signature:
|
||||
return signature.replace(parameters=(new_param, *signature.parameters.values()))
|
||||
|
||||
|
||||
class EventVar(ObjectVar):
|
||||
class EventVar(ObjectVar, python_types=EventSpec):
|
||||
"""Base class for event vars."""
|
||||
|
||||
|
||||
@ -1343,7 +1409,7 @@ class EventVar(ObjectVar):
|
||||
frozen=True,
|
||||
**{"slots": True} if sys.version_info >= (3, 10) else {},
|
||||
)
|
||||
class LiteralEventVar(CachedVarOperation, LiteralVar, EventVar):
|
||||
class LiteralEventVar(VarOperationCall, LiteralVar, EventVar):
|
||||
"""A literal event var."""
|
||||
|
||||
_var_value: EventSpec = dataclasses.field(default=None) # type: ignore
|
||||
@ -1356,35 +1422,6 @@ class LiteralEventVar(CachedVarOperation, LiteralVar, EventVar):
|
||||
"""
|
||||
return hash((self.__class__.__name__, self._js_expr))
|
||||
|
||||
@cached_property_no_lock
|
||||
def _cached_var_name(self) -> str:
|
||||
"""The name of the var.
|
||||
|
||||
Returns:
|
||||
The name of the var.
|
||||
"""
|
||||
return str(
|
||||
FunctionStringVar("Event").call(
|
||||
# event handler name
|
||||
".".join(
|
||||
filter(
|
||||
None,
|
||||
format.get_event_handler_parts(self._var_value.handler),
|
||||
)
|
||||
),
|
||||
# event handler args
|
||||
{str(name): value for name, value in self._var_value.args},
|
||||
# event actions
|
||||
self._var_value.event_actions,
|
||||
# client handler name
|
||||
*(
|
||||
[self._var_value.client_handler_name]
|
||||
if self._var_value.client_handler_name
|
||||
else []
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def create(
|
||||
cls,
|
||||
@ -1405,10 +1442,26 @@ class LiteralEventVar(CachedVarOperation, LiteralVar, EventVar):
|
||||
_var_type=EventSpec,
|
||||
_var_data=_var_data,
|
||||
_var_value=value,
|
||||
_func=FunctionStringVar("Event"),
|
||||
_args=(
|
||||
# event handler name
|
||||
".".join(
|
||||
filter(
|
||||
None,
|
||||
format.get_event_handler_parts(value.handler),
|
||||
)
|
||||
),
|
||||
# event handler args
|
||||
{str(name): value for name, value in value.args},
|
||||
# event actions
|
||||
value.event_actions,
|
||||
# client handler name
|
||||
*([value.client_handler_name] if value.client_handler_name else []),
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
class EventChainVar(FunctionVar):
|
||||
class EventChainVar(FunctionVar, python_types=EventChain):
|
||||
"""Base class for event chain vars."""
|
||||
|
||||
|
||||
@ -1417,7 +1470,10 @@ class EventChainVar(FunctionVar):
|
||||
frozen=True,
|
||||
**{"slots": True} if sys.version_info >= (3, 10) else {},
|
||||
)
|
||||
class LiteralEventChainVar(CachedVarOperation, LiteralVar, EventChainVar):
|
||||
# Note: LiteralVar is second in the inheritance list allowing it act like a
|
||||
# CachedVarOperation (ArgsFunctionOperation) and get the _js_expr from the
|
||||
# _cached_var_name property.
|
||||
class LiteralEventChainVar(ArgsFunctionOperation, LiteralVar, EventChainVar):
|
||||
"""A literal event chain var."""
|
||||
|
||||
_var_value: EventChain = dataclasses.field(default=None) # type: ignore
|
||||
@ -1430,41 +1486,6 @@ class LiteralEventChainVar(CachedVarOperation, LiteralVar, EventChainVar):
|
||||
"""
|
||||
return hash((self.__class__.__name__, self._js_expr))
|
||||
|
||||
@cached_property_no_lock
|
||||
def _cached_var_name(self) -> str:
|
||||
"""The name of the var.
|
||||
|
||||
Returns:
|
||||
The name of the var.
|
||||
"""
|
||||
sig = inspect.signature(self._var_value.args_spec) # type: ignore
|
||||
if sig.parameters:
|
||||
arg_def = tuple((f"_{p}" for p in sig.parameters))
|
||||
arg_def_expr = LiteralVar.create([Var(_js_expr=arg) for arg in arg_def])
|
||||
else:
|
||||
# add a default argument for addEvents if none were specified in value.args_spec
|
||||
# used to trigger the preventDefault() on the event.
|
||||
arg_def = ("...args",)
|
||||
arg_def_expr = Var(_js_expr="args")
|
||||
|
||||
if self._var_value.invocation is None:
|
||||
invocation = FunctionStringVar.create("addEvents")
|
||||
else:
|
||||
invocation = self._var_value.invocation
|
||||
|
||||
return str(
|
||||
ArgsFunctionOperation.create(
|
||||
arg_def,
|
||||
invocation.call(
|
||||
LiteralVar.create(
|
||||
[LiteralVar.create(event) for event in self._var_value.events]
|
||||
),
|
||||
arg_def_expr,
|
||||
self._var_value.event_actions,
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def create(
|
||||
cls,
|
||||
@ -1480,48 +1501,48 @@ class LiteralEventChainVar(CachedVarOperation, LiteralVar, EventChainVar):
|
||||
Returns:
|
||||
The created LiteralEventChainVar instance.
|
||||
"""
|
||||
sig = inspect.signature(value.args_spec) # type: ignore
|
||||
if sig.parameters:
|
||||
arg_def = tuple((f"_{p}" for p in sig.parameters))
|
||||
arg_def_expr = LiteralVar.create([Var(_js_expr=arg) for arg in arg_def])
|
||||
else:
|
||||
# add a default argument for addEvents if none were specified in value.args_spec
|
||||
# used to trigger the preventDefault() on the event.
|
||||
arg_def = ("...args",)
|
||||
arg_def_expr = Var(_js_expr="args")
|
||||
|
||||
if value.invocation is None:
|
||||
invocation = FunctionStringVar.create("addEvents")
|
||||
else:
|
||||
invocation = value.invocation
|
||||
|
||||
return cls(
|
||||
_js_expr="",
|
||||
_var_type=EventChain,
|
||||
_var_data=_var_data,
|
||||
_args_names=arg_def,
|
||||
_return_expr=invocation.call(
|
||||
LiteralVar.create([LiteralVar.create(event) for event in value.events]),
|
||||
arg_def_expr,
|
||||
value.event_actions,
|
||||
),
|
||||
_var_value=value,
|
||||
)
|
||||
|
||||
|
||||
@dataclasses.dataclass(
|
||||
eq=False,
|
||||
frozen=True,
|
||||
**{"slots": True} if sys.version_info >= (3, 10) else {},
|
||||
)
|
||||
class ToEventVarOperation(ToOperation, EventVar):
|
||||
"""Result of a cast to an event var."""
|
||||
|
||||
_original: Var = dataclasses.field(default_factory=lambda: LiteralNoneVar.create())
|
||||
|
||||
_default_var_type: ClassVar[Type] = EventSpec
|
||||
|
||||
|
||||
@dataclasses.dataclass(
|
||||
eq=False,
|
||||
frozen=True,
|
||||
**{"slots": True} if sys.version_info >= (3, 10) else {},
|
||||
)
|
||||
class ToEventChainVarOperation(ToOperation, EventChainVar):
|
||||
"""Result of a cast to an event chain var."""
|
||||
|
||||
_original: Var = dataclasses.field(default_factory=lambda: LiteralNoneVar.create())
|
||||
|
||||
_default_var_type: ClassVar[Type] = EventChain
|
||||
|
||||
|
||||
G = ParamSpec("G")
|
||||
|
||||
IndividualEventType = Union[EventSpec, EventHandler, Callable[G, Any], Var]
|
||||
IndividualEventType = Union[EventSpec, EventHandler, Callable[G, Any], Var[Any]]
|
||||
|
||||
EventType = Union[IndividualEventType[G], List[IndividualEventType[G]]]
|
||||
|
||||
P = ParamSpec("P")
|
||||
T = TypeVar("T")
|
||||
V = TypeVar("V")
|
||||
V2 = TypeVar("V2")
|
||||
V3 = TypeVar("V3")
|
||||
V4 = TypeVar("V4")
|
||||
V5 = TypeVar("V5")
|
||||
|
||||
if sys.version_info >= (3, 10):
|
||||
from typing import Concatenate
|
||||
@ -1537,7 +1558,55 @@ if sys.version_info >= (3, 10):
|
||||
"""
|
||||
self.func = func
|
||||
|
||||
def __get__(self, instance, owner) -> Callable[P, T]:
|
||||
@overload
|
||||
def __get__(
|
||||
self: EventCallback[[V], T], instance: None, owner
|
||||
) -> Callable[[Union[Var[V], V]], EventSpec]: ...
|
||||
|
||||
@overload
|
||||
def __get__(
|
||||
self: EventCallback[[V, V2], T], instance: None, owner
|
||||
) -> Callable[[Union[Var[V], V], Union[Var[V2], V2]], EventSpec]: ...
|
||||
|
||||
@overload
|
||||
def __get__(
|
||||
self: EventCallback[[V, V2, V3], T], instance: None, owner
|
||||
) -> Callable[
|
||||
[Union[Var[V], V], Union[Var[V2], V2], Union[Var[V3], V3]],
|
||||
EventSpec,
|
||||
]: ...
|
||||
|
||||
@overload
|
||||
def __get__(
|
||||
self: EventCallback[[V, V2, V3, V4], T], instance: None, owner
|
||||
) -> Callable[
|
||||
[
|
||||
Union[Var[V], V],
|
||||
Union[Var[V2], V2],
|
||||
Union[Var[V3], V3],
|
||||
Union[Var[V4], V4],
|
||||
],
|
||||
EventSpec,
|
||||
]: ...
|
||||
|
||||
@overload
|
||||
def __get__(
|
||||
self: EventCallback[[V, V2, V3, V4, V5], T], instance: None, owner
|
||||
) -> Callable[
|
||||
[
|
||||
Union[Var[V], V],
|
||||
Union[Var[V2], V2],
|
||||
Union[Var[V3], V3],
|
||||
Union[Var[V4], V4],
|
||||
Union[Var[V5], V5],
|
||||
],
|
||||
EventSpec,
|
||||
]: ...
|
||||
|
||||
@overload
|
||||
def __get__(self, instance, owner) -> Callable[P, T]: ...
|
||||
|
||||
def __get__(self, instance, owner) -> Callable:
|
||||
"""Get the function with the instance bound to it.
|
||||
|
||||
Args:
|
||||
@ -1588,8 +1657,6 @@ class EventNamespace(types.SimpleNamespace):
|
||||
LiteralEventVar = LiteralEventVar
|
||||
EventChainVar = EventChainVar
|
||||
LiteralEventChainVar = LiteralEventChainVar
|
||||
ToEventVarOperation = ToEventVarOperation
|
||||
ToEventChainVarOperation = ToEventChainVarOperation
|
||||
EventType = EventType
|
||||
|
||||
__call__ = staticmethod(event_handler)
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
from types import SimpleNamespace
|
||||
|
||||
from reflex.components.datadisplay.shiki_code_block import code_block as code_block
|
||||
from reflex.components.props import PropsBase
|
||||
from reflex.components.radix.themes.components.progress import progress as progress
|
||||
from reflex.components.sonner.toast import toast as toast
|
||||
@ -67,4 +68,5 @@ _x = ExperimentalNamespace(
|
||||
layout=layout,
|
||||
PropsBase=PropsBase,
|
||||
run_in_thread=run_in_thread,
|
||||
code_block=code_block,
|
||||
)
|
||||
|
@ -129,7 +129,7 @@ class DrawerSidebar(DrawerRoot):
|
||||
on_mouse_out: Optional[EventType[[]]] = None,
|
||||
on_mouse_over: Optional[EventType[[]]] = None,
|
||||
on_mouse_up: Optional[EventType[[]]] = None,
|
||||
on_open_change: Optional[EventType] = None,
|
||||
on_open_change: Optional[EventType[bool]] = None,
|
||||
on_scroll: Optional[EventType[[]]] = None,
|
||||
on_unmount: Optional[EventType[[]]] = None,
|
||||
**props,
|
||||
|
144
reflex/istate/storage.py
Normal file
144
reflex/istate/storage.py
Normal file
@ -0,0 +1,144 @@
|
||||
"""Client-side storage classes for reflex state variables."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
from reflex.utils import format
|
||||
|
||||
|
||||
class ClientStorageBase:
|
||||
"""Base class for client-side storage."""
|
||||
|
||||
def options(self) -> dict[str, Any]:
|
||||
"""Get the options for the storage.
|
||||
|
||||
Returns:
|
||||
All set options for the storage (not None).
|
||||
"""
|
||||
return {
|
||||
format.to_camel_case(k): v for k, v in vars(self).items() if v is not None
|
||||
}
|
||||
|
||||
|
||||
class Cookie(ClientStorageBase, str):
|
||||
"""Represents a state Var that is stored as a cookie in the browser."""
|
||||
|
||||
name: str | None
|
||||
path: str
|
||||
max_age: int | None
|
||||
domain: str | None
|
||||
secure: bool | None
|
||||
same_site: str
|
||||
|
||||
def __new__(
|
||||
cls,
|
||||
object: Any = "",
|
||||
encoding: str | None = None,
|
||||
errors: str | None = None,
|
||||
/,
|
||||
name: str | None = None,
|
||||
path: str = "/",
|
||||
max_age: int | None = None,
|
||||
domain: str | None = None,
|
||||
secure: bool | None = None,
|
||||
same_site: str = "lax",
|
||||
):
|
||||
"""Create a client-side Cookie (str).
|
||||
|
||||
Args:
|
||||
object: The initial object.
|
||||
encoding: The encoding to use.
|
||||
errors: The error handling scheme to use.
|
||||
name: The name of the cookie on the client side.
|
||||
path: Cookie path. Use / as the path if the cookie should be accessible on all pages.
|
||||
max_age: Relative max age of the cookie in seconds from when the client receives it.
|
||||
domain: Domain for the cookie (sub.domain.com or .allsubdomains.com).
|
||||
secure: Is the cookie only accessible through HTTPS?
|
||||
same_site: Whether the cookie is sent with third party requests.
|
||||
One of (true|false|none|lax|strict)
|
||||
|
||||
Returns:
|
||||
The client-side Cookie object.
|
||||
|
||||
Note: expires (absolute Date) is not supported at this time.
|
||||
"""
|
||||
if encoding or errors:
|
||||
inst = super().__new__(cls, object, encoding or "utf-8", errors or "strict")
|
||||
else:
|
||||
inst = super().__new__(cls, object)
|
||||
inst.name = name
|
||||
inst.path = path
|
||||
inst.max_age = max_age
|
||||
inst.domain = domain
|
||||
inst.secure = secure
|
||||
inst.same_site = same_site
|
||||
return inst
|
||||
|
||||
|
||||
class LocalStorage(ClientStorageBase, str):
|
||||
"""Represents a state Var that is stored in localStorage in the browser."""
|
||||
|
||||
name: str | None
|
||||
sync: bool = False
|
||||
|
||||
def __new__(
|
||||
cls,
|
||||
object: Any = "",
|
||||
encoding: str | None = None,
|
||||
errors: str | None = None,
|
||||
/,
|
||||
name: str | None = None,
|
||||
sync: bool = False,
|
||||
) -> "LocalStorage":
|
||||
"""Create a client-side localStorage (str).
|
||||
|
||||
Args:
|
||||
object: The initial object.
|
||||
encoding: The encoding to use.
|
||||
errors: The error handling scheme to use.
|
||||
name: The name of the storage key on the client side.
|
||||
sync: Whether changes should be propagated to other tabs.
|
||||
|
||||
Returns:
|
||||
The client-side localStorage object.
|
||||
"""
|
||||
if encoding or errors:
|
||||
inst = super().__new__(cls, object, encoding or "utf-8", errors or "strict")
|
||||
else:
|
||||
inst = super().__new__(cls, object)
|
||||
inst.name = name
|
||||
inst.sync = sync
|
||||
return inst
|
||||
|
||||
|
||||
class SessionStorage(ClientStorageBase, str):
|
||||
"""Represents a state Var that is stored in sessionStorage in the browser."""
|
||||
|
||||
name: str | None
|
||||
|
||||
def __new__(
|
||||
cls,
|
||||
object: Any = "",
|
||||
encoding: str | None = None,
|
||||
errors: str | None = None,
|
||||
/,
|
||||
name: str | None = None,
|
||||
) -> "SessionStorage":
|
||||
"""Create a client-side sessionStorage (str).
|
||||
|
||||
Args:
|
||||
object: The initial object.
|
||||
encoding: The encoding to use.
|
||||
errors: The error handling scheme to use
|
||||
name: The name of the storage on the client side
|
||||
|
||||
Returns:
|
||||
The client-side sessionStorage object.
|
||||
"""
|
||||
if encoding or errors:
|
||||
inst = super().__new__(cls, object, encoding or "utf-8", errors or "strict")
|
||||
else:
|
||||
inst = super().__new__(cls, object)
|
||||
inst.name = name
|
||||
return inst
|
@ -2,9 +2,7 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
from collections import defaultdict
|
||||
from pathlib import Path
|
||||
from typing import Any, ClassVar, Optional, Type, Union
|
||||
|
||||
import alembic.autogenerate
|
||||
@ -18,9 +16,8 @@ import sqlalchemy
|
||||
import sqlalchemy.exc
|
||||
import sqlalchemy.orm
|
||||
|
||||
from reflex import constants
|
||||
from reflex.base import Base
|
||||
from reflex.config import get_config
|
||||
from reflex.config import environment, get_config
|
||||
from reflex.utils import console
|
||||
from reflex.utils.compat import sqlmodel, sqlmodel_field_has_primary_key
|
||||
|
||||
@ -41,12 +38,12 @@ def get_engine(url: str | None = None) -> sqlalchemy.engine.Engine:
|
||||
url = url or conf.db_url
|
||||
if url is None:
|
||||
raise ValueError("No database url configured")
|
||||
if not Path(constants.ALEMBIC_CONFIG).exists():
|
||||
if environment.ALEMBIC_CONFIG.exists():
|
||||
console.warn(
|
||||
"Database is not initialized, run [bold]reflex db init[/bold] first."
|
||||
)
|
||||
# Print the SQL queries if the log level is INFO or lower.
|
||||
echo_db_query = os.environ.get("SQLALCHEMY_ECHO") == "True"
|
||||
echo_db_query = environment.SQLALCHEMY_ECHO
|
||||
# Needed for the admin dash on sqlite.
|
||||
connect_args = {"check_same_thread": False} if url.startswith("sqlite") else {}
|
||||
return sqlmodel.create_engine(url, echo=echo_db_query, connect_args=connect_args)
|
||||
@ -234,7 +231,7 @@ class Model(Base, sqlmodel.SQLModel): # pyright: ignore [reportGeneralTypeIssue
|
||||
Returns:
|
||||
tuple of (config, script_directory)
|
||||
"""
|
||||
config = alembic.config.Config(constants.ALEMBIC_CONFIG)
|
||||
config = alembic.config.Config(environment.ALEMBIC_CONFIG)
|
||||
return config, alembic.script.ScriptDirectory(
|
||||
config.get_main_option("script_location", default="version"),
|
||||
)
|
||||
@ -269,8 +266,8 @@ class Model(Base, sqlmodel.SQLModel): # pyright: ignore [reportGeneralTypeIssue
|
||||
def alembic_init(cls):
|
||||
"""Initialize alembic for the project."""
|
||||
alembic.command.init(
|
||||
config=alembic.config.Config(constants.ALEMBIC_CONFIG),
|
||||
directory=str(Path(constants.ALEMBIC_CONFIG).parent / "alembic"),
|
||||
config=alembic.config.Config(environment.ALEMBIC_CONFIG),
|
||||
directory=str(environment.ALEMBIC_CONFIG.parent / "alembic"),
|
||||
)
|
||||
|
||||
@classmethod
|
||||
@ -290,7 +287,7 @@ class Model(Base, sqlmodel.SQLModel): # pyright: ignore [reportGeneralTypeIssue
|
||||
Returns:
|
||||
True when changes have been detected.
|
||||
"""
|
||||
if not Path(constants.ALEMBIC_CONFIG).exists():
|
||||
if not environment.ALEMBIC_CONFIG.exists():
|
||||
return False
|
||||
|
||||
config, script_directory = cls._alembic_config()
|
||||
@ -391,7 +388,7 @@ class Model(Base, sqlmodel.SQLModel): # pyright: ignore [reportGeneralTypeIssue
|
||||
True - indicating the process was successful.
|
||||
None - indicating the process was skipped.
|
||||
"""
|
||||
if not Path(constants.ALEMBIC_CONFIG).exists():
|
||||
if not environment.ALEMBIC_CONFIG.exists():
|
||||
return
|
||||
|
||||
with cls.get_db_engine().connect() as connection:
|
||||
|
@ -4,7 +4,6 @@ from __future__ import annotations
|
||||
|
||||
import atexit
|
||||
import os
|
||||
import webbrowser
|
||||
from pathlib import Path
|
||||
from typing import List, Optional
|
||||
|
||||
@ -14,7 +13,7 @@ from reflex_cli.deployments import deployments_cli
|
||||
from reflex_cli.utils import dependency
|
||||
|
||||
from reflex import constants
|
||||
from reflex.config import get_config
|
||||
from reflex.config import environment, get_config
|
||||
from reflex.custom_components.custom_components import custom_components_cli
|
||||
from reflex.state import reset_disk_state_manager
|
||||
from reflex.utils import console, redir, telemetry
|
||||
@ -275,9 +274,17 @@ def run(
|
||||
constants.Env.DEV, help="The environment to run the app in."
|
||||
),
|
||||
frontend: bool = typer.Option(
|
||||
False, "--frontend-only", help="Execute only frontend."
|
||||
False,
|
||||
"--frontend-only",
|
||||
help="Execute only frontend.",
|
||||
envvar=constants.ENV_FRONTEND_ONLY_ENV_VAR,
|
||||
),
|
||||
backend: bool = typer.Option(
|
||||
False,
|
||||
"--backend-only",
|
||||
help="Execute only backend.",
|
||||
envvar=constants.ENV_BACKEND_ONLY_ENV_VAR,
|
||||
),
|
||||
backend: bool = typer.Option(False, "--backend-only", help="Execute only backend."),
|
||||
frontend_port: str = typer.Option(
|
||||
config.frontend_port, help="Specify a different frontend port."
|
||||
),
|
||||
@ -292,6 +299,12 @@ def run(
|
||||
),
|
||||
):
|
||||
"""Run the app in the current directory."""
|
||||
if frontend and backend:
|
||||
console.error("Cannot use both --frontend-only and --backend-only options.")
|
||||
raise typer.Exit(1)
|
||||
os.environ[constants.ENV_BACKEND_ONLY_ENV_VAR] = str(backend).lower()
|
||||
os.environ[constants.ENV_FRONTEND_ONLY_ENV_VAR] = str(frontend).lower()
|
||||
|
||||
_run(env, frontend, backend, frontend_port, backend_port, backend_host, loglevel)
|
||||
|
||||
|
||||
@ -407,7 +420,7 @@ def db_init():
|
||||
return
|
||||
|
||||
# Check the alembic config.
|
||||
if Path(constants.ALEMBIC_CONFIG).exists():
|
||||
if environment.ALEMBIC_CONFIG.exists():
|
||||
console.error(
|
||||
"Database is already initialized. Use "
|
||||
"[bold]reflex db makemigrations[/bold] to create schema change "
|
||||
@ -586,18 +599,6 @@ def deploy(
|
||||
)
|
||||
|
||||
|
||||
@cli.command()
|
||||
def demo(
|
||||
frontend_port: str = typer.Option(
|
||||
"3001", help="Specify a different frontend port."
|
||||
),
|
||||
backend_port: str = typer.Option("8001", help="Specify a different backend port."),
|
||||
):
|
||||
"""Run the demo app."""
|
||||
# Open the demo app in a terminal.
|
||||
webbrowser.open("https://demo.reflex.run")
|
||||
|
||||
|
||||
cli.add_typer(db_cli, name="db", help="Subcommands for managing the database schema.")
|
||||
cli.add_typer(script_cli, name="script", help="Subcommands running helper scripts.")
|
||||
cli.add_typer(
|
||||
|
159
reflex/state.py
159
reflex/state.py
@ -8,7 +8,6 @@ import copy
|
||||
import dataclasses
|
||||
import functools
|
||||
import inspect
|
||||
import os
|
||||
import pickle
|
||||
import sys
|
||||
import uuid
|
||||
@ -40,8 +39,12 @@ from typing import (
|
||||
from sqlalchemy.orm import DeclarativeBase
|
||||
from typing_extensions import Self
|
||||
|
||||
from reflex import event
|
||||
from reflex.config import get_config
|
||||
from reflex.istate.data import RouterData
|
||||
from reflex.istate.storage import (
|
||||
ClientStorageBase,
|
||||
)
|
||||
from reflex.vars.base import (
|
||||
ComputedVar,
|
||||
DynamicRouteVar,
|
||||
@ -64,6 +67,7 @@ from redis.exceptions import ResponseError
|
||||
import reflex.istate.dynamic
|
||||
from reflex import constants
|
||||
from reflex.base import Base
|
||||
from reflex.config import environment
|
||||
from reflex.event import (
|
||||
BACKGROUND_TASK_MARKER,
|
||||
Event,
|
||||
@ -2094,7 +2098,8 @@ class State(BaseState):
|
||||
class FrontendEventExceptionState(State):
|
||||
"""Substate for handling frontend exceptions."""
|
||||
|
||||
def handle_frontend_exception(self, stack: str) -> None:
|
||||
@event
|
||||
def handle_frontend_exception(self, stack: str, component_stack: str) -> None:
|
||||
"""Handle frontend exceptions.
|
||||
|
||||
If a frontend exception handler is provided, it will be called.
|
||||
@ -2102,6 +2107,7 @@ class FrontendEventExceptionState(State):
|
||||
|
||||
Args:
|
||||
stack: The stack trace of the exception.
|
||||
component_stack: The stack trace of the component where the exception occurred.
|
||||
|
||||
"""
|
||||
app_instance = getattr(prerequisites.get_app(), constants.CompileVars.APP)
|
||||
@ -2566,9 +2572,11 @@ class StateManager(Base, ABC):
|
||||
The state manager (either disk, memory or redis).
|
||||
"""
|
||||
config = get_config()
|
||||
if config.state_manager_mode == constants.StateManagerMode.DISK:
|
||||
return StateManagerMemory(state=state)
|
||||
if prerequisites.parse_redis_url() is not None:
|
||||
config.state_manager_mode = constants.StateManagerMode.REDIS
|
||||
if config.state_manager_mode == constants.StateManagerMode.MEMORY:
|
||||
return StateManagerMemory(state=state)
|
||||
if config.state_manager_mode == constants.StateManagerMode.DISK:
|
||||
return StateManagerDisk(state=state)
|
||||
if config.state_manager_mode == constants.StateManagerMode.REDIS:
|
||||
redis = prerequisites.get_redis()
|
||||
@ -3272,11 +3280,7 @@ class StateManagerRedis(StateManager):
|
||||
)
|
||||
except ResponseError:
|
||||
# Some redis servers only allow out-of-band configuration, so ignore errors here.
|
||||
ignore_config_error = os.environ.get(
|
||||
"REFLEX_IGNORE_REDIS_CONFIG_ERROR",
|
||||
None,
|
||||
)
|
||||
if not ignore_config_error:
|
||||
if not environment.REFLEX_IGNORE_REDIS_CONFIG_ERROR:
|
||||
raise
|
||||
async with self.redis.pubsub() as pubsub:
|
||||
await pubsub.psubscribe(lock_key_channel)
|
||||
@ -3348,143 +3352,6 @@ def get_state_manager() -> StateManager:
|
||||
return app.state_manager
|
||||
|
||||
|
||||
class ClientStorageBase:
|
||||
"""Base class for client-side storage."""
|
||||
|
||||
def options(self) -> dict[str, Any]:
|
||||
"""Get the options for the storage.
|
||||
|
||||
Returns:
|
||||
All set options for the storage (not None).
|
||||
"""
|
||||
return {
|
||||
format.to_camel_case(k): v for k, v in vars(self).items() if v is not None
|
||||
}
|
||||
|
||||
|
||||
class Cookie(ClientStorageBase, str):
|
||||
"""Represents a state Var that is stored as a cookie in the browser."""
|
||||
|
||||
name: str | None
|
||||
path: str
|
||||
max_age: int | None
|
||||
domain: str | None
|
||||
secure: bool | None
|
||||
same_site: str
|
||||
|
||||
def __new__(
|
||||
cls,
|
||||
object: Any = "",
|
||||
encoding: str | None = None,
|
||||
errors: str | None = None,
|
||||
/,
|
||||
name: str | None = None,
|
||||
path: str = "/",
|
||||
max_age: int | None = None,
|
||||
domain: str | None = None,
|
||||
secure: bool | None = None,
|
||||
same_site: str = "lax",
|
||||
):
|
||||
"""Create a client-side Cookie (str).
|
||||
|
||||
Args:
|
||||
object: The initial object.
|
||||
encoding: The encoding to use.
|
||||
errors: The error handling scheme to use.
|
||||
name: The name of the cookie on the client side.
|
||||
path: Cookie path. Use / as the path if the cookie should be accessible on all pages.
|
||||
max_age: Relative max age of the cookie in seconds from when the client receives it.
|
||||
domain: Domain for the cookie (sub.domain.com or .allsubdomains.com).
|
||||
secure: Is the cookie only accessible through HTTPS?
|
||||
same_site: Whether the cookie is sent with third party requests.
|
||||
One of (true|false|none|lax|strict)
|
||||
|
||||
Returns:
|
||||
The client-side Cookie object.
|
||||
|
||||
Note: expires (absolute Date) is not supported at this time.
|
||||
"""
|
||||
if encoding or errors:
|
||||
inst = super().__new__(cls, object, encoding or "utf-8", errors or "strict")
|
||||
else:
|
||||
inst = super().__new__(cls, object)
|
||||
inst.name = name
|
||||
inst.path = path
|
||||
inst.max_age = max_age
|
||||
inst.domain = domain
|
||||
inst.secure = secure
|
||||
inst.same_site = same_site
|
||||
return inst
|
||||
|
||||
|
||||
class LocalStorage(ClientStorageBase, str):
|
||||
"""Represents a state Var that is stored in localStorage in the browser."""
|
||||
|
||||
name: str | None
|
||||
sync: bool = False
|
||||
|
||||
def __new__(
|
||||
cls,
|
||||
object: Any = "",
|
||||
encoding: str | None = None,
|
||||
errors: str | None = None,
|
||||
/,
|
||||
name: str | None = None,
|
||||
sync: bool = False,
|
||||
) -> "LocalStorage":
|
||||
"""Create a client-side localStorage (str).
|
||||
|
||||
Args:
|
||||
object: The initial object.
|
||||
encoding: The encoding to use.
|
||||
errors: The error handling scheme to use.
|
||||
name: The name of the storage key on the client side.
|
||||
sync: Whether changes should be propagated to other tabs.
|
||||
|
||||
Returns:
|
||||
The client-side localStorage object.
|
||||
"""
|
||||
if encoding or errors:
|
||||
inst = super().__new__(cls, object, encoding or "utf-8", errors or "strict")
|
||||
else:
|
||||
inst = super().__new__(cls, object)
|
||||
inst.name = name
|
||||
inst.sync = sync
|
||||
return inst
|
||||
|
||||
|
||||
class SessionStorage(ClientStorageBase, str):
|
||||
"""Represents a state Var that is stored in sessionStorage in the browser."""
|
||||
|
||||
name: str | None
|
||||
|
||||
def __new__(
|
||||
cls,
|
||||
object: Any = "",
|
||||
encoding: str | None = None,
|
||||
errors: str | None = None,
|
||||
/,
|
||||
name: str | None = None,
|
||||
) -> "SessionStorage":
|
||||
"""Create a client-side sessionStorage (str).
|
||||
|
||||
Args:
|
||||
object: The initial object.
|
||||
encoding: The encoding to use.
|
||||
errors: The error handling scheme to use
|
||||
name: The name of the storage on the client side
|
||||
|
||||
Returns:
|
||||
The client-side sessionStorage object.
|
||||
"""
|
||||
if encoding or errors:
|
||||
inst = super().__new__(cls, object, encoding or "utf-8", errors or "strict")
|
||||
else:
|
||||
inst = super().__new__(cls, object)
|
||||
inst.name = name
|
||||
return inst
|
||||
|
||||
|
||||
class MutableProxy(wrapt.ObjectProxy):
|
||||
"""A proxy for a mutable object that tracks changes."""
|
||||
|
||||
|
@ -10,6 +10,7 @@ from reflex.event import EventChain, EventHandler
|
||||
from reflex.utils import format
|
||||
from reflex.utils.exceptions import ReflexError
|
||||
from reflex.utils.imports import ImportVar
|
||||
from reflex.utils.types import get_origin
|
||||
from reflex.vars import VarData
|
||||
from reflex.vars.base import CallableVar, LiteralVar, Var
|
||||
from reflex.vars.function import FunctionVar
|
||||
@ -196,6 +197,10 @@ def convert(
|
||||
isinstance(value, Breakpoints)
|
||||
and all(not isinstance(v, dict) for v in value.values())
|
||||
)
|
||||
or (
|
||||
isinstance(value, ObjectVar)
|
||||
and not issubclass(get_origin(value._var_type) or value._var_type, dict)
|
||||
)
|
||||
else (key,)
|
||||
)
|
||||
|
||||
|
@ -23,18 +23,6 @@ def set_env_json():
|
||||
)
|
||||
|
||||
|
||||
def set_os_env(**kwargs):
|
||||
"""Set os environment variables.
|
||||
|
||||
Args:
|
||||
kwargs: env key word args.
|
||||
"""
|
||||
for key, value in kwargs.items():
|
||||
if not value:
|
||||
continue
|
||||
os.environ[key.upper()] = value
|
||||
|
||||
|
||||
def generate_sitemap_config(deploy_url: str, export=False):
|
||||
"""Generate the sitemap config file.
|
||||
|
||||
|
@ -139,3 +139,7 @@ class SetUndefinedStateVarError(ReflexError, AttributeError):
|
||||
|
||||
class StateSchemaMismatchError(ReflexError, TypeError):
|
||||
"""Raised when the serialized schema of a state class does not match the current schema."""
|
||||
|
||||
|
||||
class EnvironmentVarValueError(ReflexError, ValueError):
|
||||
"""Raised when an environment variable is set to an invalid value."""
|
||||
|
@ -15,7 +15,7 @@ from urllib.parse import urljoin
|
||||
import psutil
|
||||
|
||||
from reflex import constants
|
||||
from reflex.config import get_config
|
||||
from reflex.config import environment, get_config
|
||||
from reflex.constants.base import LogLevel
|
||||
from reflex.utils import console, path_ops
|
||||
from reflex.utils.prerequisites import get_web_dir
|
||||
@ -184,7 +184,7 @@ def should_use_granian():
|
||||
Returns:
|
||||
True if Granian should be used.
|
||||
"""
|
||||
return os.getenv("REFLEX_USE_GRANIAN", "0") == "1"
|
||||
return environment.REFLEX_USE_GRANIAN
|
||||
|
||||
|
||||
def get_app_module():
|
||||
@ -337,8 +337,8 @@ def run_uvicorn_backend_prod(host, port, loglevel):
|
||||
|
||||
app_module = get_app_module()
|
||||
|
||||
RUN_BACKEND_PROD = f"gunicorn --worker-class {config.gunicorn_worker_class} --preload --timeout {config.timeout} --log-level critical".split()
|
||||
RUN_BACKEND_PROD_WINDOWS = f"uvicorn --timeout-keep-alive {config.timeout}".split()
|
||||
RUN_BACKEND_PROD = f"gunicorn --worker-class {config.gunicorn_worker_class} --max-requests {config.gunicorn_max_requests} --max-requests-jitter {config.gunicorn_max_requests_jitter} --preload --timeout {config.timeout} --log-level critical".split()
|
||||
RUN_BACKEND_PROD_WINDOWS = f"uvicorn --limit-max-requests {config.gunicorn_max_requests} --timeout-keep-alive {config.timeout}".split()
|
||||
command = (
|
||||
[
|
||||
*RUN_BACKEND_PROD_WINDOWS,
|
||||
@ -496,6 +496,24 @@ def is_prod_mode() -> bool:
|
||||
return current_mode == constants.Env.PROD.value
|
||||
|
||||
|
||||
def is_frontend_only() -> bool:
|
||||
"""Check if the app is running in frontend-only mode.
|
||||
|
||||
Returns:
|
||||
True if the app is running in frontend-only mode.
|
||||
"""
|
||||
return os.environ.get(constants.ENV_FRONTEND_ONLY_ENV_VAR, "").lower() == "true"
|
||||
|
||||
|
||||
def is_backend_only() -> bool:
|
||||
"""Check if the app is running in backend-only mode.
|
||||
|
||||
Returns:
|
||||
True if the app is running in backend-only mode.
|
||||
"""
|
||||
return os.environ.get(constants.ENV_BACKEND_ONLY_ENV_VAR, "").lower() == "true"
|
||||
|
||||
|
||||
def should_skip_compile() -> bool:
|
||||
"""Whether the app should skip compile.
|
||||
|
||||
|
@ -1,9 +1,8 @@
|
||||
"""Helpers for downloading files from the network."""
|
||||
|
||||
import os
|
||||
|
||||
import httpx
|
||||
|
||||
from ..config import environment
|
||||
from . import console
|
||||
|
||||
|
||||
@ -13,8 +12,7 @@ def _httpx_verify_kwarg() -> bool:
|
||||
Returns:
|
||||
True if SSL verification is enabled, False otherwise
|
||||
"""
|
||||
ssl_no_verify = os.environ.get("SSL_NO_VERIFY", "").lower() in ["true", "1", "yes"]
|
||||
return not ssl_no_verify
|
||||
return not environment.SSL_NO_VERIFY
|
||||
|
||||
|
||||
def get(url: str, **kwargs) -> httpx.Response:
|
||||
|
@ -9,6 +9,7 @@ import shutil
|
||||
from pathlib import Path
|
||||
|
||||
from reflex import constants
|
||||
from reflex.config import environment
|
||||
|
||||
# Shorthand for join.
|
||||
join = os.linesep.join
|
||||
@ -129,30 +130,13 @@ def which(program: str | Path) -> str | Path | None:
|
||||
return shutil.which(str(program))
|
||||
|
||||
|
||||
def use_system_install(var_name: str) -> bool:
|
||||
"""Check if the system install should be used.
|
||||
|
||||
Args:
|
||||
var_name: The name of the environment variable.
|
||||
|
||||
Raises:
|
||||
ValueError: If the variable name is invalid.
|
||||
|
||||
Returns:
|
||||
Whether the associated env var should use the system install.
|
||||
"""
|
||||
if not var_name.startswith("REFLEX_USE_SYSTEM_"):
|
||||
raise ValueError("Invalid system install variable name.")
|
||||
return os.getenv(var_name, "").lower() in ["true", "1", "yes"]
|
||||
|
||||
|
||||
def use_system_node() -> bool:
|
||||
"""Check if the system node should be used.
|
||||
|
||||
Returns:
|
||||
Whether the system node should be used.
|
||||
"""
|
||||
return use_system_install(constants.Node.USE_SYSTEM_VAR)
|
||||
return environment.REFLEX_USE_SYSTEM_NODE
|
||||
|
||||
|
||||
def use_system_bun() -> bool:
|
||||
@ -161,7 +145,7 @@ def use_system_bun() -> bool:
|
||||
Returns:
|
||||
Whether the system bun should be used.
|
||||
"""
|
||||
return use_system_install(constants.Bun.USE_SYSTEM_VAR)
|
||||
return environment.REFLEX_USE_SYSTEM_BUN
|
||||
|
||||
|
||||
def get_node_bin_path() -> Path | None:
|
||||
@ -185,7 +169,8 @@ def get_node_path() -> str | None:
|
||||
"""
|
||||
node_path = Path(constants.Node.PATH)
|
||||
if use_system_node() or not node_path.exists():
|
||||
return str(which("node"))
|
||||
system_node_path = which("node")
|
||||
return str(system_node_path) if system_node_path else None
|
||||
return str(node_path)
|
||||
|
||||
|
||||
@ -197,7 +182,8 @@ def get_npm_path() -> str | None:
|
||||
"""
|
||||
npm_path = Path(constants.Node.NPM_PATH)
|
||||
if use_system_node() or not npm_path.exists():
|
||||
return str(which("npm"))
|
||||
system_npm_path = which("npm")
|
||||
return str(system_npm_path) if system_npm_path else None
|
||||
return str(npm_path)
|
||||
|
||||
|
||||
|
@ -33,7 +33,7 @@ from redis.asyncio import Redis
|
||||
|
||||
from reflex import constants, model
|
||||
from reflex.compiler import templates
|
||||
from reflex.config import Config, get_config
|
||||
from reflex.config import Config, environment, get_config
|
||||
from reflex.utils import console, net, path_ops, processes
|
||||
from reflex.utils.exceptions import GeneratedCodeHasNoFunctionDefs
|
||||
from reflex.utils.format import format_library_name
|
||||
@ -69,8 +69,7 @@ def get_web_dir() -> Path:
|
||||
Returns:
|
||||
The working directory.
|
||||
"""
|
||||
workdir = Path(os.getenv("REFLEX_WEB_WORKDIR", constants.Dirs.WEB))
|
||||
return workdir
|
||||
return environment.REFLEX_WEB_WORKDIR
|
||||
|
||||
|
||||
def _python_version_check():
|
||||
@ -146,14 +145,9 @@ def check_node_version() -> bool:
|
||||
Whether the version of Node.js is valid.
|
||||
"""
|
||||
current_version = get_node_version()
|
||||
if current_version:
|
||||
# Compare the version numbers
|
||||
return (
|
||||
current_version >= version.parse(constants.Node.MIN_VERSION)
|
||||
if constants.IS_WINDOWS or path_ops.use_system_node()
|
||||
else current_version == version.parse(constants.Node.VERSION)
|
||||
)
|
||||
return False
|
||||
return current_version is not None and current_version >= version.parse(
|
||||
constants.Node.MIN_VERSION
|
||||
)
|
||||
|
||||
|
||||
def get_node_version() -> version.Version | None:
|
||||
@ -255,7 +249,7 @@ def windows_npm_escape_hatch() -> bool:
|
||||
Returns:
|
||||
If the user has set REFLEX_USE_NPM.
|
||||
"""
|
||||
return os.environ.get("REFLEX_USE_NPM", "").lower() in ["true", "1", "yes"]
|
||||
return environment.REFLEX_USE_NPM
|
||||
|
||||
|
||||
def get_app(reload: bool = False) -> ModuleType:
|
||||
@ -997,7 +991,7 @@ def needs_reinit(frontend: bool = True) -> bool:
|
||||
return False
|
||||
|
||||
# Make sure the .reflex directory exists.
|
||||
if not constants.Reflex.DIR.exists():
|
||||
if not environment.REFLEX_DIR.exists():
|
||||
return True
|
||||
|
||||
# Make sure the .web directory exists in frontend mode.
|
||||
@ -1102,7 +1096,7 @@ def ensure_reflex_installation_id() -> Optional[int]:
|
||||
"""
|
||||
try:
|
||||
initialize_reflex_user_directory()
|
||||
installation_id_file = constants.Reflex.DIR / "installation_id"
|
||||
installation_id_file = environment.REFLEX_DIR / "installation_id"
|
||||
|
||||
installation_id = None
|
||||
if installation_id_file.exists():
|
||||
@ -1127,7 +1121,7 @@ def ensure_reflex_installation_id() -> Optional[int]:
|
||||
def initialize_reflex_user_directory():
|
||||
"""Initialize the reflex user directory."""
|
||||
# Create the reflex directory.
|
||||
path_ops.mkdir(constants.Reflex.DIR)
|
||||
path_ops.mkdir(environment.REFLEX_DIR)
|
||||
|
||||
|
||||
def initialize_frontend_dependencies():
|
||||
@ -1150,7 +1144,7 @@ def check_db_initialized() -> bool:
|
||||
Returns:
|
||||
True if alembic is initialized (or if database is not used).
|
||||
"""
|
||||
if get_config().db_url is not None and not Path(constants.ALEMBIC_CONFIG).exists():
|
||||
if get_config().db_url is not None and not environment.ALEMBIC_CONFIG.exists():
|
||||
console.error(
|
||||
"Database is not initialized. Run [bold]reflex db init[/bold] first."
|
||||
)
|
||||
@ -1160,7 +1154,7 @@ def check_db_initialized() -> bool:
|
||||
|
||||
def check_schema_up_to_date():
|
||||
"""Check if the sqlmodel metadata matches the current database schema."""
|
||||
if get_config().db_url is None or not Path(constants.ALEMBIC_CONFIG).exists():
|
||||
if get_config().db_url is None or not environment.ALEMBIC_CONFIG.exists():
|
||||
return
|
||||
with model.Model.get_db_engine().connect() as connection:
|
||||
try:
|
||||
|
@ -16,7 +16,7 @@ from itertools import chain
|
||||
from multiprocessing import Pool, cpu_count
|
||||
from pathlib import Path
|
||||
from types import ModuleType, SimpleNamespace
|
||||
from typing import Any, Callable, Iterable, Type, get_args
|
||||
from typing import Any, Callable, Iterable, Type, get_args, get_origin
|
||||
|
||||
from reflex.components.component import Component
|
||||
from reflex.utils import types as rx_types
|
||||
@ -214,7 +214,9 @@ def _get_type_hint(value, type_hint_globals, is_optional=True) -> str:
|
||||
return res
|
||||
|
||||
|
||||
def _generate_imports(typing_imports: Iterable[str]) -> list[ast.ImportFrom]:
|
||||
def _generate_imports(
|
||||
typing_imports: Iterable[str],
|
||||
) -> list[ast.ImportFrom | ast.Import]:
|
||||
"""Generate the import statements for the stub file.
|
||||
|
||||
Args:
|
||||
@ -228,6 +230,7 @@ def _generate_imports(typing_imports: Iterable[str]) -> list[ast.ImportFrom]:
|
||||
ast.ImportFrom(module=name, names=[ast.alias(name=val) for val in values])
|
||||
for name, values in DEFAULT_IMPORTS.items()
|
||||
],
|
||||
ast.Import([ast.alias("reflex")]),
|
||||
]
|
||||
|
||||
|
||||
@ -372,6 +375,64 @@ def _extract_class_props_as_ast_nodes(
|
||||
return kwargs
|
||||
|
||||
|
||||
def type_to_ast(typ, cls: type) -> ast.AST:
|
||||
"""Converts any type annotation into its AST representation.
|
||||
Handles nested generic types, unions, etc.
|
||||
|
||||
Args:
|
||||
typ: The type annotation to convert.
|
||||
cls: The class where the type annotation is used.
|
||||
|
||||
Returns:
|
||||
The AST representation of the type annotation.
|
||||
"""
|
||||
if typ is type(None):
|
||||
return ast.Name(id="None")
|
||||
|
||||
origin = get_origin(typ)
|
||||
|
||||
# Handle plain types (int, str, custom classes, etc.)
|
||||
if origin is None:
|
||||
if hasattr(typ, "__name__"):
|
||||
if typ.__module__.startswith("reflex."):
|
||||
typ_parts = typ.__module__.split(".")
|
||||
cls_parts = cls.__module__.split(".")
|
||||
|
||||
zipped = list(zip(typ_parts, cls_parts, strict=False))
|
||||
|
||||
if all(a == b for a, b in zipped) and len(typ_parts) == len(cls_parts):
|
||||
return ast.Name(id=typ.__name__)
|
||||
|
||||
return ast.Name(id=typ.__module__ + "." + typ.__name__)
|
||||
return ast.Name(id=typ.__name__)
|
||||
elif hasattr(typ, "_name"):
|
||||
return ast.Name(id=typ._name)
|
||||
return ast.Name(id=str(typ))
|
||||
|
||||
# Get the base type name (List, Dict, Optional, etc.)
|
||||
base_name = origin._name if hasattr(origin, "_name") else origin.__name__
|
||||
|
||||
# Get type arguments
|
||||
args = get_args(typ)
|
||||
|
||||
# Handle empty type arguments
|
||||
if not args:
|
||||
return ast.Name(id=base_name)
|
||||
|
||||
# Convert all type arguments recursively
|
||||
arg_nodes = [type_to_ast(arg, cls) for arg in args]
|
||||
|
||||
# Special case for single-argument types (like List[T] or Optional[T])
|
||||
if len(arg_nodes) == 1:
|
||||
slice_value = arg_nodes[0]
|
||||
else:
|
||||
slice_value = ast.Tuple(elts=arg_nodes, ctx=ast.Load())
|
||||
|
||||
return ast.Subscript(
|
||||
value=ast.Name(id=base_name), slice=ast.Index(value=slice_value), ctx=ast.Load()
|
||||
)
|
||||
|
||||
|
||||
def _get_parent_imports(func):
|
||||
_imports = {"reflex.vars": ["Var"]}
|
||||
for type_hint in inspect.get_annotations(func).values():
|
||||
@ -429,14 +490,41 @@ def _generate_component_create_functiondef(
|
||||
|
||||
def figure_out_return_type(annotation: Any):
|
||||
if inspect.isclass(annotation) and issubclass(annotation, inspect._empty):
|
||||
return ast.Name(id="Optional[EventType[[]]]")
|
||||
return ast.Name(id="Optional[EventType]")
|
||||
|
||||
if not isinstance(annotation, str) and get_origin(annotation) is tuple:
|
||||
arguments = get_args(annotation)
|
||||
|
||||
arguments_without_var = [
|
||||
get_args(argument)[0] if get_origin(argument) == Var else argument
|
||||
for argument in arguments
|
||||
]
|
||||
|
||||
# Convert each argument type to its AST representation
|
||||
type_args = [type_to_ast(arg, cls=clz) for arg in arguments_without_var]
|
||||
|
||||
# Join the type arguments with commas for EventType
|
||||
args_str = ", ".join(ast.unparse(arg) for arg in type_args)
|
||||
|
||||
# Create EventType using the joined string
|
||||
event_type = ast.Name(id=f"EventType[{args_str}]")
|
||||
|
||||
# Wrap in Optional
|
||||
optional_type = ast.Subscript(
|
||||
value=ast.Name(id="Optional"),
|
||||
slice=ast.Index(value=event_type),
|
||||
ctx=ast.Load(),
|
||||
)
|
||||
|
||||
return ast.Name(id=ast.unparse(optional_type))
|
||||
|
||||
if isinstance(annotation, str) and annotation.startswith("Tuple["):
|
||||
inside_of_tuple = annotation.removeprefix("Tuple[").removesuffix("]")
|
||||
|
||||
if inside_of_tuple == "()":
|
||||
return ast.Name(id="Optional[EventType[[]]]")
|
||||
|
||||
arguments: list[str] = [""]
|
||||
arguments = [""]
|
||||
|
||||
bracket_count = 0
|
||||
|
||||
|
@ -1,9 +1,8 @@
|
||||
"""Utilities for working with registries."""
|
||||
|
||||
import os
|
||||
|
||||
import httpx
|
||||
|
||||
from reflex.config import environment
|
||||
from reflex.utils import console, net
|
||||
|
||||
|
||||
@ -56,7 +55,4 @@ def _get_npm_registry() -> str:
|
||||
Returns:
|
||||
str:
|
||||
"""
|
||||
if npm_registry := os.environ.get("NPM_CONFIG_REGISTRY", ""):
|
||||
return npm_registry
|
||||
else:
|
||||
return get_best_registry()
|
||||
return environment.NPM_CONFIG_REGISTRY or get_best_registry()
|
||||
|
@ -274,6 +274,20 @@ def is_optional(cls: GenericType) -> bool:
|
||||
return is_union(cls) and type(None) in get_args(cls)
|
||||
|
||||
|
||||
def value_inside_optional(cls: GenericType) -> GenericType:
|
||||
"""Get the value inside an Optional type or the original type.
|
||||
|
||||
Args:
|
||||
cls: The class to check.
|
||||
|
||||
Returns:
|
||||
The value inside the Optional type or the original type.
|
||||
"""
|
||||
if is_union(cls) and len(args := get_args(cls)) >= 2 and type(None) in args:
|
||||
return unionize(*[arg for arg in args if arg is not type(None)])
|
||||
return cls
|
||||
|
||||
|
||||
def get_property_hint(attr: Any | None) -> GenericType | None:
|
||||
"""Check if an attribute is a property and return its type hint.
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -4,21 +4,20 @@ from __future__ import annotations
|
||||
|
||||
import dataclasses
|
||||
import sys
|
||||
from typing import Any, Callable, ClassVar, Optional, Tuple, Type, Union
|
||||
from typing import Any, Callable, Optional, Tuple, Type, Union
|
||||
|
||||
from reflex.utils.types import GenericType
|
||||
|
||||
from .base import (
|
||||
CachedVarOperation,
|
||||
LiteralVar,
|
||||
ToOperation,
|
||||
Var,
|
||||
VarData,
|
||||
cached_property_no_lock,
|
||||
)
|
||||
|
||||
|
||||
class FunctionVar(Var[Callable]):
|
||||
class FunctionVar(Var[Callable], python_types=Callable):
|
||||
"""Base class for immutable function vars."""
|
||||
|
||||
def __call__(self, *args: Var | Any) -> ArgsFunctionOperation:
|
||||
@ -180,17 +179,8 @@ class ArgsFunctionOperation(CachedVarOperation, FunctionVar):
|
||||
)
|
||||
|
||||
|
||||
@dataclasses.dataclass(
|
||||
eq=False,
|
||||
frozen=True,
|
||||
**{"slots": True} if sys.version_info >= (3, 10) else {},
|
||||
)
|
||||
class ToFunctionOperation(ToOperation, FunctionVar):
|
||||
"""Base class of converting a var to a function."""
|
||||
|
||||
_original: Var = dataclasses.field(default_factory=lambda: LiteralVar.create(None))
|
||||
|
||||
_default_var_type: ClassVar[GenericType] = Callable
|
||||
|
||||
|
||||
JSON_STRINGIFY = FunctionStringVar.create("JSON.stringify")
|
||||
ARRAY_ISARRAY = FunctionStringVar.create("Array.isArray")
|
||||
PROTOTYPE_TO_STRING = FunctionStringVar.create(
|
||||
"((__to_string) => __to_string.toString())"
|
||||
)
|
||||
|
@ -10,7 +10,6 @@ from typing import (
|
||||
TYPE_CHECKING,
|
||||
Any,
|
||||
Callable,
|
||||
ClassVar,
|
||||
NoReturn,
|
||||
Type,
|
||||
TypeVar,
|
||||
@ -25,9 +24,7 @@ from reflex.utils.types import is_optional
|
||||
|
||||
from .base import (
|
||||
CustomVarOperationReturn,
|
||||
LiteralNoneVar,
|
||||
LiteralVar,
|
||||
ToOperation,
|
||||
Var,
|
||||
VarData,
|
||||
unionize,
|
||||
@ -58,7 +55,7 @@ def raise_unsupported_operand_types(
|
||||
)
|
||||
|
||||
|
||||
class NumberVar(Var[NUMBER_T]):
|
||||
class NumberVar(Var[NUMBER_T], python_types=(int, float)):
|
||||
"""Base class for immutable number vars."""
|
||||
|
||||
@overload
|
||||
@ -760,7 +757,7 @@ def number_trunc_operation(value: NumberVar):
|
||||
return var_operation_return(js_expression=f"Math.trunc({value})", var_type=int)
|
||||
|
||||
|
||||
class BooleanVar(NumberVar[bool]):
|
||||
class BooleanVar(NumberVar[bool], python_types=bool):
|
||||
"""Base class for immutable boolean vars."""
|
||||
|
||||
def __invert__(self):
|
||||
@ -984,51 +981,6 @@ def boolean_not_operation(value: BooleanVar):
|
||||
return var_operation_return(js_expression=f"!({value})", var_type=bool)
|
||||
|
||||
|
||||
@dataclasses.dataclass(
|
||||
eq=False,
|
||||
frozen=True,
|
||||
**{"slots": True} if sys.version_info >= (3, 10) else {},
|
||||
)
|
||||
class LiteralBooleanVar(LiteralVar, BooleanVar):
|
||||
"""Base class for immutable literal boolean vars."""
|
||||
|
||||
_var_value: bool = dataclasses.field(default=False)
|
||||
|
||||
def json(self) -> str:
|
||||
"""Get the JSON representation of the var.
|
||||
|
||||
Returns:
|
||||
The JSON representation of the var.
|
||||
"""
|
||||
return "true" if self._var_value else "false"
|
||||
|
||||
def __hash__(self) -> int:
|
||||
"""Calculate the hash value of the object.
|
||||
|
||||
Returns:
|
||||
int: The hash value of the object.
|
||||
"""
|
||||
return hash((self.__class__.__name__, self._var_value))
|
||||
|
||||
@classmethod
|
||||
def create(cls, value: bool, _var_data: VarData | None = None):
|
||||
"""Create the boolean var.
|
||||
|
||||
Args:
|
||||
value: The value of the var.
|
||||
_var_data: Additional hooks and imports associated with the Var.
|
||||
|
||||
Returns:
|
||||
The boolean var.
|
||||
"""
|
||||
return cls(
|
||||
_js_expr="true" if value else "false",
|
||||
_var_type=bool,
|
||||
_var_data=_var_data,
|
||||
_var_value=value,
|
||||
)
|
||||
|
||||
|
||||
@dataclasses.dataclass(
|
||||
eq=False,
|
||||
frozen=True,
|
||||
@ -1088,36 +1040,55 @@ class LiteralNumberVar(LiteralVar, NumberVar):
|
||||
)
|
||||
|
||||
|
||||
@dataclasses.dataclass(
|
||||
eq=False,
|
||||
frozen=True,
|
||||
**{"slots": True} if sys.version_info >= (3, 10) else {},
|
||||
)
|
||||
class LiteralBooleanVar(LiteralVar, BooleanVar):
|
||||
"""Base class for immutable literal boolean vars."""
|
||||
|
||||
_var_value: bool = dataclasses.field(default=False)
|
||||
|
||||
def json(self) -> str:
|
||||
"""Get the JSON representation of the var.
|
||||
|
||||
Returns:
|
||||
The JSON representation of the var.
|
||||
"""
|
||||
return "true" if self._var_value else "false"
|
||||
|
||||
def __hash__(self) -> int:
|
||||
"""Calculate the hash value of the object.
|
||||
|
||||
Returns:
|
||||
int: The hash value of the object.
|
||||
"""
|
||||
return hash((self.__class__.__name__, self._var_value))
|
||||
|
||||
@classmethod
|
||||
def create(cls, value: bool, _var_data: VarData | None = None):
|
||||
"""Create the boolean var.
|
||||
|
||||
Args:
|
||||
value: The value of the var.
|
||||
_var_data: Additional hooks and imports associated with the Var.
|
||||
|
||||
Returns:
|
||||
The boolean var.
|
||||
"""
|
||||
return cls(
|
||||
_js_expr="true" if value else "false",
|
||||
_var_type=bool,
|
||||
_var_data=_var_data,
|
||||
_var_value=value,
|
||||
)
|
||||
|
||||
|
||||
number_types = Union[NumberVar, int, float]
|
||||
boolean_types = Union[BooleanVar, bool]
|
||||
|
||||
|
||||
@dataclasses.dataclass(
|
||||
eq=False,
|
||||
frozen=True,
|
||||
**{"slots": True} if sys.version_info >= (3, 10) else {},
|
||||
)
|
||||
class ToNumberVarOperation(ToOperation, NumberVar):
|
||||
"""Base class for immutable number vars that are the result of a number operation."""
|
||||
|
||||
_original: Var = dataclasses.field(default_factory=lambda: LiteralNoneVar.create())
|
||||
|
||||
_default_var_type: ClassVar[Type] = float
|
||||
|
||||
|
||||
@dataclasses.dataclass(
|
||||
eq=False,
|
||||
frozen=True,
|
||||
**{"slots": True} if sys.version_info >= (3, 10) else {},
|
||||
)
|
||||
class ToBooleanVarOperation(ToOperation, BooleanVar):
|
||||
"""Base class for immutable boolean vars that are the result of a boolean operation."""
|
||||
|
||||
_original: Var = dataclasses.field(default_factory=lambda: LiteralNoneVar.create())
|
||||
|
||||
_default_var_type: ClassVar[Type] = bool
|
||||
|
||||
|
||||
_IS_TRUE_IMPORT: ImportDict = {
|
||||
f"/{Dirs.STATE_PATH}": [ImportVar(tag="isTrue")],
|
||||
}
|
||||
@ -1140,8 +1111,12 @@ def boolify(value: Var):
|
||||
)
|
||||
|
||||
|
||||
T = TypeVar("T")
|
||||
U = TypeVar("U")
|
||||
|
||||
|
||||
@var_operation
|
||||
def ternary_operation(condition: BooleanVar, if_true: Var, if_false: Var):
|
||||
def ternary_operation(condition: BooleanVar, if_true: Var[T], if_false: Var[U]):
|
||||
"""Create a ternary operation.
|
||||
|
||||
Args:
|
||||
@ -1152,10 +1127,14 @@ def ternary_operation(condition: BooleanVar, if_true: Var, if_false: Var):
|
||||
Returns:
|
||||
The ternary operation.
|
||||
"""
|
||||
return var_operation_return(
|
||||
js_expression=f"({condition} ? {if_true} : {if_false})",
|
||||
var_type=unionize(if_true._var_type, if_false._var_type),
|
||||
type_value: Union[Type[T], Type[U]] = unionize(
|
||||
if_true._var_type, if_false._var_type
|
||||
)
|
||||
value: CustomVarOperationReturn[Union[T, U]] = var_operation_return(
|
||||
js_expression=f"({condition} ? {if_true} : {if_false})",
|
||||
var_type=type_value,
|
||||
)
|
||||
return value
|
||||
|
||||
|
||||
NUMBER_TYPES = (int, float, NumberVar)
|
||||
|
@ -8,7 +8,6 @@ import typing
|
||||
from inspect import isclass
|
||||
from typing import (
|
||||
Any,
|
||||
ClassVar,
|
||||
Dict,
|
||||
List,
|
||||
NoReturn,
|
||||
@ -27,7 +26,6 @@ from reflex.utils.types import GenericType, get_attribute_access_type, get_origi
|
||||
from .base import (
|
||||
CachedVarOperation,
|
||||
LiteralVar,
|
||||
ToOperation,
|
||||
Var,
|
||||
VarData,
|
||||
cached_property_no_lock,
|
||||
@ -48,7 +46,7 @@ ARRAY_INNER_TYPE = TypeVar("ARRAY_INNER_TYPE")
|
||||
OTHER_KEY_TYPE = TypeVar("OTHER_KEY_TYPE")
|
||||
|
||||
|
||||
class ObjectVar(Var[OBJECT_TYPE]):
|
||||
class ObjectVar(Var[OBJECT_TYPE], python_types=dict):
|
||||
"""Base class for immutable object vars."""
|
||||
|
||||
def _key_type(self) -> Type:
|
||||
@ -521,34 +519,6 @@ class ObjectItemOperation(CachedVarOperation, Var):
|
||||
)
|
||||
|
||||
|
||||
@dataclasses.dataclass(
|
||||
eq=False,
|
||||
frozen=True,
|
||||
**{"slots": True} if sys.version_info >= (3, 10) else {},
|
||||
)
|
||||
class ToObjectOperation(ToOperation, ObjectVar):
|
||||
"""Operation to convert a var to an object."""
|
||||
|
||||
_original: Var = dataclasses.field(
|
||||
default_factory=lambda: LiteralObjectVar.create({})
|
||||
)
|
||||
|
||||
_default_var_type: ClassVar[GenericType] = dict
|
||||
|
||||
def __getattr__(self, name: str) -> Any:
|
||||
"""Get an attribute of the var.
|
||||
|
||||
Args:
|
||||
name: The name of the attribute.
|
||||
|
||||
Returns:
|
||||
The attribute of the var.
|
||||
"""
|
||||
if name == "_js_expr":
|
||||
return self._original._js_expr
|
||||
return ObjectVar.__getattr__(self, name)
|
||||
|
||||
|
||||
@var_operation
|
||||
def object_has_own_property_operation(object: ObjectVar, key: Var):
|
||||
"""Check if an object has a key.
|
||||
|
@ -11,7 +11,6 @@ import typing
|
||||
from typing import (
|
||||
TYPE_CHECKING,
|
||||
Any,
|
||||
ClassVar,
|
||||
Dict,
|
||||
List,
|
||||
Literal,
|
||||
@ -19,27 +18,28 @@ from typing import (
|
||||
Set,
|
||||
Tuple,
|
||||
Type,
|
||||
TypeVar,
|
||||
Union,
|
||||
overload,
|
||||
)
|
||||
|
||||
from typing_extensions import TypeVar
|
||||
|
||||
from reflex import constants
|
||||
from reflex.constants.base import REFLEX_VAR_OPENING_TAG
|
||||
from reflex.constants.colors import Color
|
||||
from reflex.utils.exceptions import VarTypeError
|
||||
from reflex.utils.types import GenericType, get_origin
|
||||
|
||||
from .base import (
|
||||
CachedVarOperation,
|
||||
CustomVarOperationReturn,
|
||||
LiteralNoneVar,
|
||||
LiteralVar,
|
||||
ToOperation,
|
||||
Var,
|
||||
VarData,
|
||||
_global_vars,
|
||||
cached_property_no_lock,
|
||||
figure_out_type,
|
||||
get_python_literal,
|
||||
get_unique_variable_name,
|
||||
unionize,
|
||||
var_operation,
|
||||
@ -50,13 +50,16 @@ from .number import (
|
||||
LiteralNumberVar,
|
||||
NumberVar,
|
||||
raise_unsupported_operand_types,
|
||||
ternary_operation,
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .object import ObjectVar
|
||||
|
||||
STRING_TYPE = TypeVar("STRING_TYPE", default=str)
|
||||
|
||||
class StringVar(Var[str]):
|
||||
|
||||
class StringVar(Var[STRING_TYPE], python_types=str):
|
||||
"""Base class for immutable string vars."""
|
||||
|
||||
@overload
|
||||
@ -350,7 +353,7 @@ class StringVar(Var[str]):
|
||||
|
||||
|
||||
@var_operation
|
||||
def string_lt_operation(lhs: StringVar | str, rhs: StringVar | str):
|
||||
def string_lt_operation(lhs: StringVar[Any] | str, rhs: StringVar[Any] | str):
|
||||
"""Check if a string is less than another string.
|
||||
|
||||
Args:
|
||||
@ -364,7 +367,7 @@ def string_lt_operation(lhs: StringVar | str, rhs: StringVar | str):
|
||||
|
||||
|
||||
@var_operation
|
||||
def string_gt_operation(lhs: StringVar | str, rhs: StringVar | str):
|
||||
def string_gt_operation(lhs: StringVar[Any] | str, rhs: StringVar[Any] | str):
|
||||
"""Check if a string is greater than another string.
|
||||
|
||||
Args:
|
||||
@ -378,7 +381,7 @@ def string_gt_operation(lhs: StringVar | str, rhs: StringVar | str):
|
||||
|
||||
|
||||
@var_operation
|
||||
def string_le_operation(lhs: StringVar | str, rhs: StringVar | str):
|
||||
def string_le_operation(lhs: StringVar[Any] | str, rhs: StringVar[Any] | str):
|
||||
"""Check if a string is less than or equal to another string.
|
||||
|
||||
Args:
|
||||
@ -392,7 +395,7 @@ def string_le_operation(lhs: StringVar | str, rhs: StringVar | str):
|
||||
|
||||
|
||||
@var_operation
|
||||
def string_ge_operation(lhs: StringVar | str, rhs: StringVar | str):
|
||||
def string_ge_operation(lhs: StringVar[Any] | str, rhs: StringVar[Any] | str):
|
||||
"""Check if a string is greater than or equal to another string.
|
||||
|
||||
Args:
|
||||
@ -406,7 +409,7 @@ def string_ge_operation(lhs: StringVar | str, rhs: StringVar | str):
|
||||
|
||||
|
||||
@var_operation
|
||||
def string_lower_operation(string: StringVar):
|
||||
def string_lower_operation(string: StringVar[Any]):
|
||||
"""Convert a string to lowercase.
|
||||
|
||||
Args:
|
||||
@ -419,7 +422,7 @@ def string_lower_operation(string: StringVar):
|
||||
|
||||
|
||||
@var_operation
|
||||
def string_upper_operation(string: StringVar):
|
||||
def string_upper_operation(string: StringVar[Any]):
|
||||
"""Convert a string to uppercase.
|
||||
|
||||
Args:
|
||||
@ -432,7 +435,7 @@ def string_upper_operation(string: StringVar):
|
||||
|
||||
|
||||
@var_operation
|
||||
def string_strip_operation(string: StringVar):
|
||||
def string_strip_operation(string: StringVar[Any]):
|
||||
"""Strip a string.
|
||||
|
||||
Args:
|
||||
@ -446,7 +449,7 @@ def string_strip_operation(string: StringVar):
|
||||
|
||||
@var_operation
|
||||
def string_contains_field_operation(
|
||||
haystack: StringVar, needle: StringVar | str, field: StringVar | str
|
||||
haystack: StringVar[Any], needle: StringVar[Any] | str, field: StringVar[Any] | str
|
||||
):
|
||||
"""Check if a string contains another string.
|
||||
|
||||
@ -465,7 +468,7 @@ def string_contains_field_operation(
|
||||
|
||||
|
||||
@var_operation
|
||||
def string_contains_operation(haystack: StringVar, needle: StringVar | str):
|
||||
def string_contains_operation(haystack: StringVar[Any], needle: StringVar[Any] | str):
|
||||
"""Check if a string contains another string.
|
||||
|
||||
Args:
|
||||
@ -481,7 +484,9 @@ def string_contains_operation(haystack: StringVar, needle: StringVar | str):
|
||||
|
||||
|
||||
@var_operation
|
||||
def string_starts_with_operation(full_string: StringVar, prefix: StringVar | str):
|
||||
def string_starts_with_operation(
|
||||
full_string: StringVar[Any], prefix: StringVar[Any] | str
|
||||
):
|
||||
"""Check if a string starts with a prefix.
|
||||
|
||||
Args:
|
||||
@ -497,7 +502,7 @@ def string_starts_with_operation(full_string: StringVar, prefix: StringVar | str
|
||||
|
||||
|
||||
@var_operation
|
||||
def string_item_operation(string: StringVar, index: NumberVar | int):
|
||||
def string_item_operation(string: StringVar[Any], index: NumberVar | int):
|
||||
"""Get an item from a string.
|
||||
|
||||
Args:
|
||||
@ -511,7 +516,7 @@ def string_item_operation(string: StringVar, index: NumberVar | int):
|
||||
|
||||
|
||||
@var_operation
|
||||
def array_join_operation(array: ArrayVar, sep: StringVar | str = ""):
|
||||
def array_join_operation(array: ArrayVar, sep: StringVar[Any] | str = ""):
|
||||
"""Join the elements of an array.
|
||||
|
||||
Args:
|
||||
@ -524,6 +529,26 @@ def array_join_operation(array: ArrayVar, sep: StringVar | str = ""):
|
||||
return var_operation_return(js_expression=f"{array}.join({sep})", var_type=str)
|
||||
|
||||
|
||||
@var_operation
|
||||
def string_replace_operation(
|
||||
string: StringVar, search_value: StringVar | str, new_value: StringVar | str
|
||||
):
|
||||
"""Replace a string with a value.
|
||||
|
||||
Args:
|
||||
string: The string.
|
||||
search_value: The string to search.
|
||||
new_value: The value to be replaced with.
|
||||
|
||||
Returns:
|
||||
The string replace operation.
|
||||
"""
|
||||
return var_operation_return(
|
||||
js_expression=f"{string}.replace({search_value}, {new_value})",
|
||||
var_type=str,
|
||||
)
|
||||
|
||||
|
||||
# Compile regex for finding reflex var tags.
|
||||
_decode_var_pattern_re = (
|
||||
rf"{constants.REFLEX_VAR_OPENING_TAG}(.*?){constants.REFLEX_VAR_CLOSING_TAG}"
|
||||
@ -536,7 +561,7 @@ _decode_var_pattern = re.compile(_decode_var_pattern_re, flags=re.DOTALL)
|
||||
frozen=True,
|
||||
**{"slots": True} if sys.version_info >= (3, 10) else {},
|
||||
)
|
||||
class LiteralStringVar(LiteralVar, StringVar):
|
||||
class LiteralStringVar(LiteralVar, StringVar[str]):
|
||||
"""Base class for immutable literal string vars."""
|
||||
|
||||
_var_value: str = dataclasses.field(default="")
|
||||
@ -658,7 +683,7 @@ class LiteralStringVar(LiteralVar, StringVar):
|
||||
frozen=True,
|
||||
**{"slots": True} if sys.version_info >= (3, 10) else {},
|
||||
)
|
||||
class ConcatVarOperation(CachedVarOperation, StringVar):
|
||||
class ConcatVarOperation(CachedVarOperation, StringVar[str]):
|
||||
"""Representing a concatenation of literal string vars."""
|
||||
|
||||
_var_value: Tuple[Var, ...] = dataclasses.field(default_factory=tuple)
|
||||
@ -742,7 +767,7 @@ KEY_TYPE = TypeVar("KEY_TYPE")
|
||||
VALUE_TYPE = TypeVar("VALUE_TYPE")
|
||||
|
||||
|
||||
class ArrayVar(Var[ARRAY_VAR_TYPE]):
|
||||
class ArrayVar(Var[ARRAY_VAR_TYPE], python_types=(list, tuple, set)):
|
||||
"""Base class for immutable array vars."""
|
||||
|
||||
@overload
|
||||
@ -1155,7 +1180,7 @@ class ArrayVar(Var[ARRAY_VAR_TYPE]):
|
||||
function_var = ArgsFunctionOperation.create(tuple(), return_value)
|
||||
else:
|
||||
# generic number var
|
||||
number_var = Var("").to(NumberVar)
|
||||
number_var = Var("").to(NumberVar, int)
|
||||
|
||||
first_arg_type = self[number_var]._var_type
|
||||
|
||||
@ -1167,7 +1192,10 @@ class ArrayVar(Var[ARRAY_VAR_TYPE]):
|
||||
_var_type=first_arg_type,
|
||||
).guess_type()
|
||||
|
||||
function_var = ArgsFunctionOperation.create((arg_name,), fn(first_arg))
|
||||
function_var = ArgsFunctionOperation.create(
|
||||
(arg_name,),
|
||||
Var.create(fn(first_arg)),
|
||||
)
|
||||
|
||||
return map_array_operation(self, function_var)
|
||||
|
||||
@ -1272,7 +1300,7 @@ class LiteralArrayVar(CachedVarOperation, LiteralVar, ArrayVar[ARRAY_VAR_TYPE]):
|
||||
|
||||
|
||||
@var_operation
|
||||
def string_split_operation(string: StringVar, sep: StringVar | str = ""):
|
||||
def string_split_operation(string: StringVar[Any], sep: StringVar | str = ""):
|
||||
"""Split a string.
|
||||
|
||||
Args:
|
||||
@ -1569,32 +1597,6 @@ def array_contains_operation(haystack: ArrayVar, needle: Any | Var):
|
||||
)
|
||||
|
||||
|
||||
@dataclasses.dataclass(
|
||||
eq=False,
|
||||
frozen=True,
|
||||
**{"slots": True} if sys.version_info >= (3, 10) else {},
|
||||
)
|
||||
class ToStringOperation(ToOperation, StringVar):
|
||||
"""Base class for immutable string vars that are the result of a to string operation."""
|
||||
|
||||
_original: Var = dataclasses.field(default_factory=lambda: LiteralNoneVar.create())
|
||||
|
||||
_default_var_type: ClassVar[Type] = str
|
||||
|
||||
|
||||
@dataclasses.dataclass(
|
||||
eq=False,
|
||||
frozen=True,
|
||||
**{"slots": True} if sys.version_info >= (3, 10) else {},
|
||||
)
|
||||
class ToArrayOperation(ToOperation, ArrayVar):
|
||||
"""Base class for immutable array vars that are the result of a to array operation."""
|
||||
|
||||
_original: Var = dataclasses.field(default_factory=lambda: LiteralNoneVar.create())
|
||||
|
||||
_default_var_type: ClassVar[Type] = List[Any]
|
||||
|
||||
|
||||
@var_operation
|
||||
def repeat_array_operation(
|
||||
array: ArrayVar[ARRAY_VAR_TYPE], count: NumberVar | int
|
||||
@ -1654,3 +1656,134 @@ def array_concat_operation(
|
||||
js_expression=f"[...{lhs}, ...{rhs}]",
|
||||
var_type=Union[lhs._var_type, rhs._var_type],
|
||||
)
|
||||
|
||||
|
||||
class ColorVar(StringVar[Color], python_types=Color):
|
||||
"""Base class for immutable color vars."""
|
||||
|
||||
|
||||
@dataclasses.dataclass(
|
||||
eq=False,
|
||||
frozen=True,
|
||||
**{"slots": True} if sys.version_info >= (3, 10) else {},
|
||||
)
|
||||
class LiteralColorVar(CachedVarOperation, LiteralVar, ColorVar):
|
||||
"""Base class for immutable literal color vars."""
|
||||
|
||||
_var_value: Color = dataclasses.field(default_factory=lambda: Color(color="black"))
|
||||
|
||||
@classmethod
|
||||
def create(
|
||||
cls,
|
||||
value: Color,
|
||||
_var_type: Type[Color] | None = None,
|
||||
_var_data: VarData | None = None,
|
||||
) -> ColorVar:
|
||||
"""Create a var from a string value.
|
||||
|
||||
Args:
|
||||
value: The value to create the var from.
|
||||
_var_type: The type of the var.
|
||||
_var_data: Additional hooks and imports associated with the Var.
|
||||
|
||||
Returns:
|
||||
The var.
|
||||
"""
|
||||
return cls(
|
||||
_js_expr="",
|
||||
_var_type=_var_type or Color,
|
||||
_var_data=_var_data,
|
||||
_var_value=value,
|
||||
)
|
||||
|
||||
def __hash__(self) -> int:
|
||||
"""Get the hash of the var.
|
||||
|
||||
Returns:
|
||||
The hash of the var.
|
||||
"""
|
||||
return hash(
|
||||
(
|
||||
self.__class__.__name__,
|
||||
self._var_value.color,
|
||||
self._var_value.alpha,
|
||||
self._var_value.shade,
|
||||
)
|
||||
)
|
||||
|
||||
@cached_property_no_lock
|
||||
def _cached_var_name(self) -> str:
|
||||
"""The name of the var.
|
||||
|
||||
Returns:
|
||||
The name of the var.
|
||||
"""
|
||||
alpha = self._var_value.alpha
|
||||
alpha = (
|
||||
ternary_operation(
|
||||
alpha,
|
||||
LiteralStringVar.create("a"),
|
||||
LiteralStringVar.create(""),
|
||||
)
|
||||
if isinstance(alpha, Var)
|
||||
else LiteralStringVar.create("a" if alpha else "")
|
||||
)
|
||||
|
||||
shade = self._var_value.shade
|
||||
shade = (
|
||||
shade.to_string(use_json=False)
|
||||
if isinstance(shade, Var)
|
||||
else LiteralStringVar.create(str(shade))
|
||||
)
|
||||
return str(
|
||||
ConcatVarOperation.create(
|
||||
LiteralStringVar.create("var(--"),
|
||||
self._var_value.color,
|
||||
LiteralStringVar.create("-"),
|
||||
alpha,
|
||||
shade,
|
||||
LiteralStringVar.create(")"),
|
||||
)
|
||||
)
|
||||
|
||||
@cached_property_no_lock
|
||||
def _cached_get_all_var_data(self) -> VarData | None:
|
||||
"""Get all the var data.
|
||||
|
||||
Returns:
|
||||
The var data.
|
||||
"""
|
||||
return VarData.merge(
|
||||
*[
|
||||
LiteralVar.create(var)._get_all_var_data()
|
||||
for var in (
|
||||
self._var_value.color,
|
||||
self._var_value.alpha,
|
||||
self._var_value.shade,
|
||||
)
|
||||
],
|
||||
self._var_data,
|
||||
)
|
||||
|
||||
def json(self) -> str:
|
||||
"""Get the JSON representation of the var.
|
||||
|
||||
Returns:
|
||||
The JSON representation of the var.
|
||||
|
||||
Raises:
|
||||
TypeError: If the color is not a valid color.
|
||||
"""
|
||||
color, alpha, shade = map(
|
||||
get_python_literal,
|
||||
(self._var_value.color, self._var_value.alpha, self._var_value.shade),
|
||||
)
|
||||
if color is None or alpha is None or shade is None:
|
||||
raise TypeError("Cannot serialize color that contains non-literal vars.")
|
||||
if (
|
||||
not isinstance(color, str)
|
||||
or not isinstance(alpha, bool)
|
||||
or not isinstance(shade, int)
|
||||
):
|
||||
raise TypeError("Color is not a valid color.")
|
||||
return f"var(--{color}-{'a' if alpha else ''}{shade})"
|
||||
|
@ -51,6 +51,7 @@ def LifespanApp():
|
||||
def context_global(self) -> int:
|
||||
return lifespan_context_global
|
||||
|
||||
@rx.event
|
||||
def tick(self, date):
|
||||
pass
|
||||
|
||||
|
@ -14,6 +14,7 @@ class ColorState(rx.State):
|
||||
color: str = "mint"
|
||||
color_part: str = "tom"
|
||||
shade: int = 4
|
||||
alpha: bool = False
|
||||
|
||||
|
||||
color_state_name = ColorState.get_full_name().replace(".", "__")
|
||||
@ -31,7 +32,14 @@ def create_color_var(color):
|
||||
(create_color_var(rx.color("mint", 3, True)), '"var(--mint-a3)"', Color),
|
||||
(
|
||||
create_color_var(rx.color(ColorState.color, ColorState.shade)), # type: ignore
|
||||
f'("var(--"+{str(color_state_name)}.color+"-"+{str(color_state_name)}.shade+")")',
|
||||
f'("var(--"+{str(color_state_name)}.color+"-"+(((__to_string) => __to_string.toString())({str(color_state_name)}.shade))+")")',
|
||||
Color,
|
||||
),
|
||||
(
|
||||
create_color_var(
|
||||
rx.color(ColorState.color, ColorState.shade, ColorState.alpha) # type: ignore
|
||||
),
|
||||
f'("var(--"+{str(color_state_name)}.color+"-"+({str(color_state_name)}.alpha ? "a" : "")+(((__to_string) => __to_string.toString())({str(color_state_name)}.shade))+")")',
|
||||
Color,
|
||||
),
|
||||
(
|
||||
@ -43,7 +51,7 @@ def create_color_var(color):
|
||||
create_color_var(
|
||||
rx.color(f"{ColorState.color_part}ato", f"{ColorState.shade}") # type: ignore
|
||||
),
|
||||
f'("var(--"+{str(color_state_name)}.color_part+"ato-"+{str(color_state_name)}.shade+")")',
|
||||
f'("var(--"+({str(color_state_name)}.color_part+"ato")+"-"+{str(color_state_name)}.shade+")")',
|
||||
Color,
|
||||
),
|
||||
(
|
||||
|
172
tests/units/components/datadisplay/test_shiki_code.py
Normal file
172
tests/units/components/datadisplay/test_shiki_code.py
Normal file
@ -0,0 +1,172 @@
|
||||
import pytest
|
||||
|
||||
from reflex.components.datadisplay.shiki_code_block import (
|
||||
ShikiBaseTransformers,
|
||||
ShikiCodeBlock,
|
||||
ShikiHighLevelCodeBlock,
|
||||
ShikiJsTransformer,
|
||||
)
|
||||
from reflex.components.el.elements.forms import Button
|
||||
from reflex.components.lucide.icon import Icon
|
||||
from reflex.components.radix.themes.layout.box import Box
|
||||
from reflex.style import Style
|
||||
from reflex.vars import Var
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"library, fns, expected_output, raises_exception",
|
||||
[
|
||||
("some_library", ["function_one"], ["function_one"], False),
|
||||
("some_library", [123], None, True),
|
||||
("some_library", [], [], False),
|
||||
(
|
||||
"some_library",
|
||||
["function_one", "function_two"],
|
||||
["function_one", "function_two"],
|
||||
False,
|
||||
),
|
||||
("", ["function_one"], ["function_one"], False),
|
||||
("some_library", ["function_one", 789], None, True),
|
||||
("", [], [], False),
|
||||
],
|
||||
)
|
||||
def test_create_transformer(library, fns, expected_output, raises_exception):
|
||||
if raises_exception:
|
||||
# Ensure ValueError is raised for invalid cases
|
||||
with pytest.raises(ValueError):
|
||||
ShikiCodeBlock.create_transformer(library, fns)
|
||||
else:
|
||||
transformer = ShikiCodeBlock.create_transformer(library, fns)
|
||||
assert isinstance(transformer, ShikiBaseTransformers)
|
||||
assert transformer.library == library
|
||||
|
||||
# Verify that the functions are correctly wrapped in FunctionStringVar
|
||||
function_names = [str(fn) for fn in transformer.fns]
|
||||
assert function_names == expected_output
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"code_block, children, props, expected_first_child, expected_styles",
|
||||
[
|
||||
("print('Hello')", ["print('Hello')"], {}, "print('Hello')", {}),
|
||||
(
|
||||
"print('Hello')",
|
||||
["print('Hello')", "More content"],
|
||||
{},
|
||||
"print('Hello')",
|
||||
{},
|
||||
),
|
||||
(
|
||||
"print('Hello')",
|
||||
["print('Hello')"],
|
||||
{
|
||||
"transformers": [
|
||||
ShikiBaseTransformers(
|
||||
library="lib", fns=[], style=Style({"color": "red"})
|
||||
)
|
||||
]
|
||||
},
|
||||
"print('Hello')",
|
||||
{"color": "red"},
|
||||
),
|
||||
(
|
||||
"print('Hello')",
|
||||
["print('Hello')"],
|
||||
{
|
||||
"transformers": [
|
||||
ShikiBaseTransformers(
|
||||
library="lib", fns=[], style=Style({"color": "red"})
|
||||
)
|
||||
],
|
||||
"style": {"background": "blue"},
|
||||
},
|
||||
"print('Hello')",
|
||||
{"color": "red", "background": "blue"},
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_create_shiki_code_block(
|
||||
code_block, children, props, expected_first_child, expected_styles
|
||||
):
|
||||
component = ShikiCodeBlock.create(code_block, *children, **props)
|
||||
|
||||
# Test that the created component is a Box
|
||||
assert isinstance(component, Box)
|
||||
|
||||
# Test that the first child is the code
|
||||
code_block_component = component.children[0]
|
||||
assert code_block_component.code._var_value == expected_first_child # type: ignore
|
||||
|
||||
applied_styles = component.style
|
||||
for key, value in expected_styles.items():
|
||||
assert Var.create(applied_styles[key])._var_value == value
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"children, props, expected_transformers, expected_button_type",
|
||||
[
|
||||
(["print('Hello')"], {"use_transformers": True}, [ShikiJsTransformer], None),
|
||||
(["print('Hello')"], {"can_copy": True}, None, Button),
|
||||
(
|
||||
["print('Hello')"],
|
||||
{
|
||||
"can_copy": True,
|
||||
"copy_button": Button.create(Icon.create(tag="a_arrow_down")),
|
||||
},
|
||||
None,
|
||||
Button,
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_create_shiki_high_level_code_block(
|
||||
children, props, expected_transformers, expected_button_type
|
||||
):
|
||||
component = ShikiHighLevelCodeBlock.create(*children, **props)
|
||||
|
||||
# Test that the created component is a Box
|
||||
assert isinstance(component, Box)
|
||||
|
||||
# Test that the first child is the code block component
|
||||
code_block_component = component.children[0]
|
||||
assert code_block_component.code._var_value == children[0] # type: ignore
|
||||
|
||||
# Check if the transformer is set correctly if expected
|
||||
if expected_transformers:
|
||||
exp_trans_names = [t.__name__ for t in expected_transformers]
|
||||
for transformer in code_block_component.transformers._var_value: # type: ignore
|
||||
assert type(transformer).__name__ in exp_trans_names
|
||||
|
||||
# Check if the second child is the copy button if can_copy is True
|
||||
if props.get("can_copy", False):
|
||||
if props.get("copy_button"):
|
||||
assert isinstance(component.children[1], expected_button_type)
|
||||
assert component.children[1] == props["copy_button"]
|
||||
else:
|
||||
assert isinstance(component.children[1], expected_button_type)
|
||||
else:
|
||||
assert len(component.children) == 1
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"children, props",
|
||||
[
|
||||
(["print('Hello')"], {"theme": "dark"}),
|
||||
(["print('Hello')"], {"language": "javascript"}),
|
||||
],
|
||||
)
|
||||
def test_shiki_high_level_code_block_theme_language_mapping(children, props):
|
||||
component = ShikiHighLevelCodeBlock.create(*children, **props)
|
||||
|
||||
# Test that the theme is mapped correctly
|
||||
if "theme" in props:
|
||||
assert component.children[
|
||||
0
|
||||
].theme._var_value == ShikiHighLevelCodeBlock._map_themes(props["theme"]) # type: ignore
|
||||
|
||||
# Test that the language is mapped correctly
|
||||
if "language" in props:
|
||||
assert component.children[
|
||||
0
|
||||
].language._var_value == ShikiHighLevelCodeBlock._map_languages( # type: ignore
|
||||
props["language"]
|
||||
)
|
@ -5,6 +5,7 @@ import pytest
|
||||
|
||||
import reflex as rx
|
||||
import reflex.config
|
||||
from reflex.config import environment
|
||||
from reflex.constants import Endpoint
|
||||
|
||||
|
||||
@ -178,7 +179,7 @@ def test_replace_defaults(
|
||||
|
||||
|
||||
def reflex_dir_constant():
|
||||
return rx.constants.Reflex.DIR
|
||||
return environment.REFLEX_DIR
|
||||
|
||||
|
||||
def test_reflex_dir_env_var(monkeypatch, tmp_path):
|
||||
|
@ -2,11 +2,18 @@ from typing import List
|
||||
|
||||
import pytest
|
||||
|
||||
from reflex import event
|
||||
from reflex.event import Event, EventHandler, EventSpec, call_event_handler, fix_events
|
||||
from reflex.event import (
|
||||
Event,
|
||||
EventChain,
|
||||
EventHandler,
|
||||
EventSpec,
|
||||
call_event_handler,
|
||||
event,
|
||||
fix_events,
|
||||
)
|
||||
from reflex.state import BaseState
|
||||
from reflex.utils import format
|
||||
from reflex.vars.base import LiteralVar, Var
|
||||
from reflex.vars.base import Field, LiteralVar, Var, field
|
||||
|
||||
|
||||
def make_var(value) -> Var:
|
||||
@ -388,3 +395,28 @@ def test_event_actions_on_state():
|
||||
assert sp_handler.event_actions == {"stopPropagation": True}
|
||||
# should NOT affect other references to the handler
|
||||
assert not handler.event_actions
|
||||
|
||||
|
||||
def test_event_var_data():
|
||||
class S(BaseState):
|
||||
x: Field[int] = field(0)
|
||||
|
||||
@event
|
||||
def s(self, value: int):
|
||||
pass
|
||||
|
||||
# Handler doesn't have any _var_data because it's just a str
|
||||
handler_var = Var.create(S.s)
|
||||
assert handler_var._get_all_var_data() is None
|
||||
|
||||
# Ensure spec carries _var_data
|
||||
spec_var = Var.create(S.s(S.x))
|
||||
assert spec_var._get_all_var_data() == S.x._get_all_var_data()
|
||||
|
||||
# Needed to instantiate the EventChain
|
||||
def _args_spec(value: Var[int]) -> tuple[Var[int]]:
|
||||
return (value,)
|
||||
|
||||
# Ensure chain carries _var_data
|
||||
chain_var = Var.create(EventChain(events=[S.s(S.x)], args_spec=_args_spec))
|
||||
assert chain_var._get_all_var_data() == S.x._get_all_var_data()
|
||||
|
@ -519,8 +519,8 @@ def test_var_indexing_types(var, type_):
|
||||
type_ : The type on indexed object.
|
||||
|
||||
"""
|
||||
assert var[2]._var_type == type_[0]
|
||||
assert var[3]._var_type == type_[1]
|
||||
assert var[0]._var_type == type_[0]
|
||||
assert var[1]._var_type == type_[1]
|
||||
|
||||
|
||||
def test_var_indexing_str():
|
||||
|
Loading…
Reference in New Issue
Block a user