Merge branch 'main' into use-lowercase-datatypes-typing
This commit is contained in:
commit
192646af9f
@ -96,6 +96,7 @@ from reflex.state import (
|
|||||||
StateManager,
|
StateManager,
|
||||||
StateUpdate,
|
StateUpdate,
|
||||||
_substate_key,
|
_substate_key,
|
||||||
|
all_base_state_classes,
|
||||||
code_uses_state_contexts,
|
code_uses_state_contexts,
|
||||||
)
|
)
|
||||||
from reflex.utils import (
|
from reflex.utils import (
|
||||||
@ -107,12 +108,13 @@ from reflex.utils import (
|
|||||||
prerequisites,
|
prerequisites,
|
||||||
types,
|
types,
|
||||||
)
|
)
|
||||||
from reflex.utils.exec import is_prod_mode, is_testing_env
|
from reflex.utils.exec import get_compile_context, is_prod_mode, is_testing_env
|
||||||
from reflex.utils.imports import ImportVar
|
from reflex.utils.imports import ImportVar
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from reflex.vars import Var
|
from reflex.vars import Var
|
||||||
|
|
||||||
|
|
||||||
# Define custom types.
|
# Define custom types.
|
||||||
ComponentCallable = Callable[[], Component]
|
ComponentCallable = Callable[[], Component]
|
||||||
Reducer = Callable[[Event], Coroutine[Any, Any, StateUpdate]]
|
Reducer = Callable[[Event], Coroutine[Any, Any, StateUpdate]]
|
||||||
@ -197,14 +199,17 @@ def default_overlay_component() -> Component:
|
|||||||
Returns:
|
Returns:
|
||||||
The default overlay_component, which is a connection_modal.
|
The default overlay_component, which is a connection_modal.
|
||||||
"""
|
"""
|
||||||
config = get_config()
|
|
||||||
from reflex.components.component import memo
|
from reflex.components.component import memo
|
||||||
|
|
||||||
def default_overlay_components():
|
def default_overlay_components():
|
||||||
return Fragment.create(
|
return Fragment.create(
|
||||||
connection_pulser(),
|
connection_pulser(),
|
||||||
connection_toaster(),
|
connection_toaster(),
|
||||||
*([backend_disabled()] if config.is_reflex_cloud else []),
|
*(
|
||||||
|
[backend_disabled()]
|
||||||
|
if get_compile_context() == constants.CompileContext.DEPLOY
|
||||||
|
else []
|
||||||
|
),
|
||||||
*codespaces.codespaces_auto_redirect(),
|
*codespaces.codespaces_auto_redirect(),
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -368,6 +373,9 @@ class App(MiddlewareMixin, LifespanMixin):
|
|||||||
# A map from a page route to the component to render. Users should use `add_page`.
|
# A map from a page route to the component to render. Users should use `add_page`.
|
||||||
_pages: dict[str, Component] = dataclasses.field(default_factory=dict)
|
_pages: dict[str, Component] = dataclasses.field(default_factory=dict)
|
||||||
|
|
||||||
|
# A mapping of pages which created states as they were being evaluated.
|
||||||
|
_stateful_pages: Dict[str, None] = dataclasses.field(default_factory=dict)
|
||||||
|
|
||||||
# The backend API object.
|
# The backend API object.
|
||||||
_api: FastAPI | None = None
|
_api: FastAPI | None = None
|
||||||
|
|
||||||
@ -585,8 +593,10 @@ class App(MiddlewareMixin, LifespanMixin):
|
|||||||
"""Add optional api endpoints (_upload)."""
|
"""Add optional api endpoints (_upload)."""
|
||||||
if not self.api:
|
if not self.api:
|
||||||
return
|
return
|
||||||
|
upload_is_used_marker = (
|
||||||
if Upload.is_used:
|
prerequisites.get_backend_dir() / constants.Dirs.UPLOAD_IS_USED
|
||||||
|
)
|
||||||
|
if Upload.is_used or upload_is_used_marker.exists():
|
||||||
# To upload files.
|
# To upload files.
|
||||||
self.api.post(str(constants.Endpoint.UPLOAD))(upload(self))
|
self.api.post(str(constants.Endpoint.UPLOAD))(upload(self))
|
||||||
|
|
||||||
@ -596,10 +606,15 @@ class App(MiddlewareMixin, LifespanMixin):
|
|||||||
StaticFiles(directory=get_upload_dir()),
|
StaticFiles(directory=get_upload_dir()),
|
||||||
name="uploaded_files",
|
name="uploaded_files",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
upload_is_used_marker.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
upload_is_used_marker.touch()
|
||||||
if codespaces.is_running_in_codespaces():
|
if codespaces.is_running_in_codespaces():
|
||||||
self.api.get(str(constants.Endpoint.AUTH_CODESPACE))(
|
self.api.get(str(constants.Endpoint.AUTH_CODESPACE))(
|
||||||
codespaces.auth_codespace
|
codespaces.auth_codespace
|
||||||
)
|
)
|
||||||
|
if environment.REFLEX_ADD_ALL_ROUTES_ENDPOINT.get():
|
||||||
|
self.add_all_routes_endpoint()
|
||||||
|
|
||||||
def _add_cors(self):
|
def _add_cors(self):
|
||||||
"""Add CORS middleware to the app."""
|
"""Add CORS middleware to the app."""
|
||||||
@ -740,13 +755,19 @@ class App(MiddlewareMixin, LifespanMixin):
|
|||||||
route: The route of the page to compile.
|
route: The route of the page to compile.
|
||||||
save_page: If True, the compiled page is saved to self._pages.
|
save_page: If True, the compiled page is saved to self._pages.
|
||||||
"""
|
"""
|
||||||
|
n_states_before = len(all_base_state_classes)
|
||||||
component, enable_state = compiler.compile_unevaluated_page(
|
component, enable_state = compiler.compile_unevaluated_page(
|
||||||
route, self._unevaluated_pages[route], self._state, self.style, self.theme
|
route, self._unevaluated_pages[route], self._state, self.style, self.theme
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Indicate that the app should use state.
|
||||||
if enable_state:
|
if enable_state:
|
||||||
self._enable_state()
|
self._enable_state()
|
||||||
|
|
||||||
|
# Indicate that evaluating this page creates one or more state classes.
|
||||||
|
if len(all_base_state_classes) > n_states_before:
|
||||||
|
self._stateful_pages[route] = None
|
||||||
|
|
||||||
# Add the page.
|
# Add the page.
|
||||||
self._check_routes_conflict(route)
|
self._check_routes_conflict(route)
|
||||||
if save_page:
|
if save_page:
|
||||||
@ -1033,6 +1054,20 @@ class App(MiddlewareMixin, LifespanMixin):
|
|||||||
def get_compilation_time() -> str:
|
def get_compilation_time() -> str:
|
||||||
return str(datetime.now().time()).split(".")[0]
|
return str(datetime.now().time()).split(".")[0]
|
||||||
|
|
||||||
|
should_compile = self._should_compile()
|
||||||
|
backend_dir = prerequisites.get_backend_dir()
|
||||||
|
if not should_compile and backend_dir.exists():
|
||||||
|
stateful_pages_marker = backend_dir / constants.Dirs.STATEFUL_PAGES
|
||||||
|
if stateful_pages_marker.exists():
|
||||||
|
with stateful_pages_marker.open("r") as f:
|
||||||
|
stateful_pages = json.load(f)
|
||||||
|
for route in stateful_pages:
|
||||||
|
console.info(f"BE Evaluating stateful page: {route}")
|
||||||
|
self._compile_page(route, save_page=False)
|
||||||
|
self._enable_state()
|
||||||
|
self._add_optional_endpoints()
|
||||||
|
return
|
||||||
|
|
||||||
# Render a default 404 page if the user didn't supply one
|
# Render a default 404 page if the user didn't supply one
|
||||||
if constants.Page404.SLUG not in self._unevaluated_pages:
|
if constants.Page404.SLUG not in self._unevaluated_pages:
|
||||||
self.add_page(route=constants.Page404.SLUG)
|
self.add_page(route=constants.Page404.SLUG)
|
||||||
@ -1130,6 +1165,16 @@ class App(MiddlewareMixin, LifespanMixin):
|
|||||||
|
|
||||||
self._validate_var_dependencies()
|
self._validate_var_dependencies()
|
||||||
self._setup_overlay_component()
|
self._setup_overlay_component()
|
||||||
|
|
||||||
|
if config.show_built_with_reflex is None:
|
||||||
|
if (
|
||||||
|
get_compile_context() == constants.CompileContext.DEPLOY
|
||||||
|
and prerequisites.get_user_tier() in ["pro", "team", "enterprise"]
|
||||||
|
):
|
||||||
|
config.show_built_with_reflex = False
|
||||||
|
else:
|
||||||
|
config.show_built_with_reflex = True
|
||||||
|
|
||||||
if is_prod_mode() and config.show_built_with_reflex:
|
if is_prod_mode() and config.show_built_with_reflex:
|
||||||
self._setup_sticky_badge()
|
self._setup_sticky_badge()
|
||||||
|
|
||||||
@ -1324,6 +1369,24 @@ class App(MiddlewareMixin, LifespanMixin):
|
|||||||
for output_path, code in compile_results:
|
for output_path, code in compile_results:
|
||||||
compiler_utils.write_page(output_path, code)
|
compiler_utils.write_page(output_path, code)
|
||||||
|
|
||||||
|
# Write list of routes that create dynamic states for backend to use.
|
||||||
|
if self._state is not None:
|
||||||
|
stateful_pages_marker = (
|
||||||
|
prerequisites.get_backend_dir() / constants.Dirs.STATEFUL_PAGES
|
||||||
|
)
|
||||||
|
stateful_pages_marker.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
with stateful_pages_marker.open("w") as f:
|
||||||
|
json.dump(list(self._stateful_pages), f)
|
||||||
|
|
||||||
|
def add_all_routes_endpoint(self):
|
||||||
|
"""Add an endpoint to the app that returns all the routes."""
|
||||||
|
if not self.api:
|
||||||
|
return
|
||||||
|
|
||||||
|
@self.api.get(str(constants.Endpoint.ALL_ROUTES))
|
||||||
|
async def all_routes():
|
||||||
|
return list(self._unevaluated_pages.keys())
|
||||||
|
|
||||||
@contextlib.asynccontextmanager
|
@contextlib.asynccontextmanager
|
||||||
async def modify_state(self, token: str) -> AsyncIterator[BaseState]:
|
async def modify_state(self, token: str) -> AsyncIterator[BaseState]:
|
||||||
"""Modify the state out of band.
|
"""Modify the state out of band.
|
||||||
|
@ -2,9 +2,9 @@
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from typing import Any, Iterator
|
from typing import Any, Iterator, Sequence
|
||||||
|
|
||||||
from reflex.components.component import Component, LiteralComponentVar
|
from reflex.components.component import BaseComponent, Component, ComponentStyle
|
||||||
from reflex.components.tags import Tag
|
from reflex.components.tags import Tag
|
||||||
from reflex.components.tags.tagless import Tagless
|
from reflex.components.tags.tagless import Tagless
|
||||||
from reflex.config import PerformanceMode, environment
|
from reflex.config import PerformanceMode, environment
|
||||||
@ -12,7 +12,7 @@ from reflex.utils import console
|
|||||||
from reflex.utils.decorator import once
|
from reflex.utils.decorator import once
|
||||||
from reflex.utils.imports import ParsedImportDict
|
from reflex.utils.imports import ParsedImportDict
|
||||||
from reflex.vars import BooleanVar, ObjectVar, Var
|
from reflex.vars import BooleanVar, ObjectVar, Var
|
||||||
from reflex.vars.base import VarData
|
from reflex.vars.base import GLOBAL_CACHE, VarData
|
||||||
from reflex.vars.sequence import LiteralStringVar
|
from reflex.vars.sequence import LiteralStringVar
|
||||||
|
|
||||||
|
|
||||||
@ -47,6 +47,11 @@ def validate_str(value: str):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _components_from_var(var: Var) -> Sequence[BaseComponent]:
|
||||||
|
var_data = var._get_all_var_data()
|
||||||
|
return var_data.components if var_data else ()
|
||||||
|
|
||||||
|
|
||||||
class Bare(Component):
|
class Bare(Component):
|
||||||
"""A component with no tag."""
|
"""A component with no tag."""
|
||||||
|
|
||||||
@ -80,8 +85,9 @@ class Bare(Component):
|
|||||||
The hooks for the component.
|
The hooks for the component.
|
||||||
"""
|
"""
|
||||||
hooks = super()._get_all_hooks_internal()
|
hooks = super()._get_all_hooks_internal()
|
||||||
if isinstance(self.contents, LiteralComponentVar):
|
if isinstance(self.contents, Var):
|
||||||
hooks |= self.contents._var_value._get_all_hooks_internal()
|
for component in _components_from_var(self.contents):
|
||||||
|
hooks |= component._get_all_hooks_internal()
|
||||||
return hooks
|
return hooks
|
||||||
|
|
||||||
def _get_all_hooks(self) -> dict[str, VarData | None]:
|
def _get_all_hooks(self) -> dict[str, VarData | None]:
|
||||||
@ -91,18 +97,22 @@ class Bare(Component):
|
|||||||
The hooks for the component.
|
The hooks for the component.
|
||||||
"""
|
"""
|
||||||
hooks = super()._get_all_hooks()
|
hooks = super()._get_all_hooks()
|
||||||
if isinstance(self.contents, LiteralComponentVar):
|
if isinstance(self.contents, Var):
|
||||||
hooks |= self.contents._var_value._get_all_hooks()
|
for component in _components_from_var(self.contents):
|
||||||
|
hooks |= component._get_all_hooks()
|
||||||
return hooks
|
return hooks
|
||||||
|
|
||||||
def _get_all_imports(self) -> ParsedImportDict:
|
def _get_all_imports(self, collapse: bool = False) -> ParsedImportDict:
|
||||||
"""Include the imports for the component.
|
"""Include the imports for the component.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
collapse: Whether to collapse the imports.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
The imports for the component.
|
The imports for the component.
|
||||||
"""
|
"""
|
||||||
imports = super()._get_all_imports()
|
imports = super()._get_all_imports(collapse=collapse)
|
||||||
if isinstance(self.contents, LiteralComponentVar):
|
if isinstance(self.contents, Var):
|
||||||
var_data = self.contents._get_all_var_data()
|
var_data = self.contents._get_all_var_data()
|
||||||
if var_data:
|
if var_data:
|
||||||
imports |= {k: list(v) for k, v in var_data.imports}
|
imports |= {k: list(v) for k, v in var_data.imports}
|
||||||
@ -115,8 +125,9 @@ class Bare(Component):
|
|||||||
The dynamic imports.
|
The dynamic imports.
|
||||||
"""
|
"""
|
||||||
dynamic_imports = super()._get_all_dynamic_imports()
|
dynamic_imports = super()._get_all_dynamic_imports()
|
||||||
if isinstance(self.contents, LiteralComponentVar):
|
if isinstance(self.contents, Var):
|
||||||
dynamic_imports |= self.contents._var_value._get_all_dynamic_imports()
|
for component in _components_from_var(self.contents):
|
||||||
|
dynamic_imports |= component._get_all_dynamic_imports()
|
||||||
return dynamic_imports
|
return dynamic_imports
|
||||||
|
|
||||||
def _get_all_custom_code(self) -> set[str]:
|
def _get_all_custom_code(self) -> set[str]:
|
||||||
@ -126,10 +137,24 @@ class Bare(Component):
|
|||||||
The custom code.
|
The custom code.
|
||||||
"""
|
"""
|
||||||
custom_code = super()._get_all_custom_code()
|
custom_code = super()._get_all_custom_code()
|
||||||
if isinstance(self.contents, LiteralComponentVar):
|
if isinstance(self.contents, Var):
|
||||||
custom_code |= self.contents._var_value._get_all_custom_code()
|
for component in _components_from_var(self.contents):
|
||||||
|
custom_code |= component._get_all_custom_code()
|
||||||
return custom_code
|
return custom_code
|
||||||
|
|
||||||
|
def _get_all_app_wrap_components(self) -> dict[tuple[int, str], Component]:
|
||||||
|
"""Get the components that should be wrapped in the app.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The components that should be wrapped in the app.
|
||||||
|
"""
|
||||||
|
app_wrap_components = super()._get_all_app_wrap_components()
|
||||||
|
if isinstance(self.contents, Var):
|
||||||
|
for component in _components_from_var(self.contents):
|
||||||
|
if isinstance(component, Component):
|
||||||
|
app_wrap_components |= component._get_all_app_wrap_components()
|
||||||
|
return app_wrap_components
|
||||||
|
|
||||||
def _get_all_refs(self) -> set[str]:
|
def _get_all_refs(self) -> set[str]:
|
||||||
"""Get the refs for the children of the component.
|
"""Get the refs for the children of the component.
|
||||||
|
|
||||||
@ -137,8 +162,9 @@ class Bare(Component):
|
|||||||
The refs for the children.
|
The refs for the children.
|
||||||
"""
|
"""
|
||||||
refs = super()._get_all_refs()
|
refs = super()._get_all_refs()
|
||||||
if isinstance(self.contents, LiteralComponentVar):
|
if isinstance(self.contents, Var):
|
||||||
refs |= self.contents._var_value._get_all_refs()
|
for component in _components_from_var(self.contents):
|
||||||
|
refs |= component._get_all_refs()
|
||||||
return refs
|
return refs
|
||||||
|
|
||||||
def _render(self) -> Tag:
|
def _render(self) -> Tag:
|
||||||
@ -148,6 +174,33 @@ class Bare(Component):
|
|||||||
return Tagless(contents=f"{{{self.contents!s}}}")
|
return Tagless(contents=f"{{{self.contents!s}}}")
|
||||||
return Tagless(contents=str(self.contents))
|
return Tagless(contents=str(self.contents))
|
||||||
|
|
||||||
|
def _add_style_recursive(
|
||||||
|
self, style: ComponentStyle, theme: Component | None = None
|
||||||
|
) -> Component:
|
||||||
|
"""Add style to the component and its children.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
style: The style to add.
|
||||||
|
theme: The theme to add.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The component with the style added.
|
||||||
|
"""
|
||||||
|
new_self = super()._add_style_recursive(style, theme)
|
||||||
|
|
||||||
|
are_components_touched = False
|
||||||
|
|
||||||
|
if isinstance(self.contents, Var):
|
||||||
|
for component in _components_from_var(self.contents):
|
||||||
|
if isinstance(component, Component):
|
||||||
|
component._add_style_recursive(style, theme)
|
||||||
|
are_components_touched = True
|
||||||
|
|
||||||
|
if are_components_touched:
|
||||||
|
GLOBAL_CACHE.clear()
|
||||||
|
|
||||||
|
return new_self
|
||||||
|
|
||||||
def _get_vars(
|
def _get_vars(
|
||||||
self, include_children: bool = False, ignore_ids: set[int] | None = None
|
self, include_children: bool = False, ignore_ids: set[int] | None = None
|
||||||
) -> Iterator[Var]:
|
) -> Iterator[Var]:
|
||||||
|
@ -4,12 +4,27 @@ from __future__ import annotations
|
|||||||
|
|
||||||
import copy
|
import copy
|
||||||
import dataclasses
|
import dataclasses
|
||||||
|
import inspect
|
||||||
import typing
|
import typing
|
||||||
from abc import ABC, abstractmethod
|
from abc import ABC, abstractmethod
|
||||||
from functools import lru_cache, wraps
|
from functools import lru_cache, wraps
|
||||||
from hashlib import md5
|
from hashlib import md5
|
||||||
from types import SimpleNamespace
|
from types import SimpleNamespace
|
||||||
from typing import Any, Callable, ClassVar, Iterator, List, Sequence, Type
|
from typing import (
|
||||||
|
Any,
|
||||||
|
Callable,
|
||||||
|
ClassVar,
|
||||||
|
Dict,
|
||||||
|
Iterator,
|
||||||
|
List,
|
||||||
|
Optional,
|
||||||
|
Sequence,
|
||||||
|
Set,
|
||||||
|
Type,
|
||||||
|
Union,
|
||||||
|
get_args,
|
||||||
|
get_origin,
|
||||||
|
)
|
||||||
|
|
||||||
from typing_extensions import Self
|
from typing_extensions import Self
|
||||||
|
|
||||||
@ -31,6 +46,7 @@ from reflex.constants import (
|
|||||||
from reflex.constants.compiler import SpecialAttributes
|
from reflex.constants.compiler import SpecialAttributes
|
||||||
from reflex.constants.state import FRONTEND_EVENT_STATE
|
from reflex.constants.state import FRONTEND_EVENT_STATE
|
||||||
from reflex.event import (
|
from reflex.event import (
|
||||||
|
EventActionsMixin,
|
||||||
EventCallback,
|
EventCallback,
|
||||||
EventChain,
|
EventChain,
|
||||||
EventHandler,
|
EventHandler,
|
||||||
@ -177,6 +193,25 @@ def satisfies_type_hint(obj: Any, type_hint: Any) -> bool:
|
|||||||
return types._isinstance(obj, type_hint, nested=1)
|
return types._isinstance(obj, type_hint, nested=1)
|
||||||
|
|
||||||
|
|
||||||
|
def _components_from(
|
||||||
|
component_or_var: Union[BaseComponent, Var],
|
||||||
|
) -> tuple[BaseComponent, ...]:
|
||||||
|
"""Get the components from a component or Var.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
component_or_var: The component or Var to get the components from.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The components.
|
||||||
|
"""
|
||||||
|
if isinstance(component_or_var, Var):
|
||||||
|
var_data = component_or_var._get_all_var_data()
|
||||||
|
return var_data.components if var_data else ()
|
||||||
|
if isinstance(component_or_var, BaseComponent):
|
||||||
|
return (component_or_var,)
|
||||||
|
return ()
|
||||||
|
|
||||||
|
|
||||||
class Component(BaseComponent, ABC):
|
class Component(BaseComponent, ABC):
|
||||||
"""A component with style, event trigger and other props."""
|
"""A component with style, event trigger and other props."""
|
||||||
|
|
||||||
@ -475,7 +510,7 @@ class Component(BaseComponent, ABC):
|
|||||||
|
|
||||||
# Remove any keys that were added as events.
|
# Remove any keys that were added as events.
|
||||||
for key in kwargs["event_triggers"]:
|
for key in kwargs["event_triggers"]:
|
||||||
del kwargs[key]
|
kwargs.pop(key, None)
|
||||||
|
|
||||||
# Place data_ and aria_ attributes into custom_attrs
|
# Place data_ and aria_ attributes into custom_attrs
|
||||||
special_attributes = tuple(
|
special_attributes = tuple(
|
||||||
@ -652,12 +687,21 @@ class Component(BaseComponent, ABC):
|
|||||||
return set()
|
return set()
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@lru_cache(maxsize=None)
|
def _are_fields_known(cls) -> bool:
|
||||||
def get_component_props(cls) -> set[str]:
|
"""Check if all fields are known at compile time. True for most components.
|
||||||
"""Get the props that expected a component as value.
|
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
The components props.
|
Whether all fields are known at compile time.
|
||||||
|
"""
|
||||||
|
return True
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
@lru_cache(maxsize=None)
|
||||||
|
def _get_component_prop_names(cls) -> Set[str]:
|
||||||
|
"""Get the names of the component props. NOTE: This assumes all fields are known.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The names of the component props.
|
||||||
"""
|
"""
|
||||||
return {
|
return {
|
||||||
name
|
name
|
||||||
@ -666,6 +710,26 @@ class Component(BaseComponent, ABC):
|
|||||||
and types._issubclass(field.outer_type_, Component)
|
and types._issubclass(field.outer_type_, Component)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def _get_components_in_props(self) -> Sequence[BaseComponent]:
|
||||||
|
"""Get the components in the props.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The components in the props
|
||||||
|
"""
|
||||||
|
if self._are_fields_known():
|
||||||
|
return [
|
||||||
|
component
|
||||||
|
for name in self._get_component_prop_names()
|
||||||
|
for component in _components_from(getattr(self, name))
|
||||||
|
]
|
||||||
|
return [
|
||||||
|
component
|
||||||
|
for prop in self.get_props()
|
||||||
|
if (value := getattr(self, prop)) is not None
|
||||||
|
and isinstance(value, (BaseComponent, Var))
|
||||||
|
for component in _components_from(value)
|
||||||
|
]
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def create(cls, *children, **props) -> Self:
|
def create(cls, *children, **props) -> Self:
|
||||||
"""Create the component.
|
"""Create the component.
|
||||||
@ -1122,6 +1186,9 @@ class Component(BaseComponent, ABC):
|
|||||||
if custom_code is not None:
|
if custom_code is not None:
|
||||||
code.add(custom_code)
|
code.add(custom_code)
|
||||||
|
|
||||||
|
for component in self._get_components_in_props():
|
||||||
|
code |= component._get_all_custom_code()
|
||||||
|
|
||||||
# Add the custom code from add_custom_code method.
|
# Add the custom code from add_custom_code method.
|
||||||
for clz in self._iter_parent_classes_with_method("add_custom_code"):
|
for clz in self._iter_parent_classes_with_method("add_custom_code"):
|
||||||
for item in clz.add_custom_code(self):
|
for item in clz.add_custom_code(self):
|
||||||
@ -1149,7 +1216,7 @@ class Component(BaseComponent, ABC):
|
|||||||
The dynamic imports.
|
The dynamic imports.
|
||||||
"""
|
"""
|
||||||
# Store the import in a set to avoid duplicates.
|
# Store the import in a set to avoid duplicates.
|
||||||
dynamic_imports = set()
|
dynamic_imports: set[str] = set()
|
||||||
|
|
||||||
# Get dynamic import for this component.
|
# Get dynamic import for this component.
|
||||||
dynamic_import = self._get_dynamic_imports()
|
dynamic_import = self._get_dynamic_imports()
|
||||||
@ -1160,25 +1227,12 @@ class Component(BaseComponent, ABC):
|
|||||||
for child in self.children:
|
for child in self.children:
|
||||||
dynamic_imports |= child._get_all_dynamic_imports()
|
dynamic_imports |= child._get_all_dynamic_imports()
|
||||||
|
|
||||||
for prop in self.get_component_props():
|
for component in self._get_components_in_props():
|
||||||
if getattr(self, prop) is not None:
|
dynamic_imports |= component._get_all_dynamic_imports()
|
||||||
dynamic_imports |= getattr(self, prop)._get_all_dynamic_imports()
|
|
||||||
|
|
||||||
# Return the dynamic imports
|
# Return the dynamic imports
|
||||||
return dynamic_imports
|
return dynamic_imports
|
||||||
|
|
||||||
def _get_props_imports(self) -> list[ParsedImportDict]:
|
|
||||||
"""Get the imports needed for components props.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
The imports for the components props of the component.
|
|
||||||
"""
|
|
||||||
return [
|
|
||||||
getattr(self, prop)._get_all_imports()
|
|
||||||
for prop in self.get_component_props()
|
|
||||||
if getattr(self, prop) is not None
|
|
||||||
]
|
|
||||||
|
|
||||||
def _should_transpile(self, dep: str | None) -> bool:
|
def _should_transpile(self, dep: str | None) -> bool:
|
||||||
"""Check if a dependency should be transpiled.
|
"""Check if a dependency should be transpiled.
|
||||||
|
|
||||||
@ -1289,7 +1343,6 @@ class Component(BaseComponent, ABC):
|
|||||||
)
|
)
|
||||||
|
|
||||||
return imports.merge_imports(
|
return imports.merge_imports(
|
||||||
*self._get_props_imports(),
|
|
||||||
self._get_dependencies_imports(),
|
self._get_dependencies_imports(),
|
||||||
self._get_hooks_imports(),
|
self._get_hooks_imports(),
|
||||||
_imports,
|
_imports,
|
||||||
@ -1366,6 +1419,8 @@ class Component(BaseComponent, ABC):
|
|||||||
for k in var_data.hooks
|
for k in var_data.hooks
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
for component in var_data.components:
|
||||||
|
vars_hooks.update(component._get_all_hooks())
|
||||||
return vars_hooks
|
return vars_hooks
|
||||||
|
|
||||||
def _get_events_hooks(self) -> dict[str, VarData | None]:
|
def _get_events_hooks(self) -> dict[str, VarData | None]:
|
||||||
@ -1514,6 +1569,9 @@ class Component(BaseComponent, ABC):
|
|||||||
refs.add(ref)
|
refs.add(ref)
|
||||||
for child in self.children:
|
for child in self.children:
|
||||||
refs |= child._get_all_refs()
|
refs |= child._get_all_refs()
|
||||||
|
for component in self._get_components_in_props():
|
||||||
|
refs |= component._get_all_refs()
|
||||||
|
|
||||||
return refs
|
return refs
|
||||||
|
|
||||||
def _get_all_custom_components(
|
def _get_all_custom_components(
|
||||||
@ -1537,6 +1595,9 @@ class Component(BaseComponent, ABC):
|
|||||||
if not isinstance(child, Component):
|
if not isinstance(child, Component):
|
||||||
continue
|
continue
|
||||||
custom_components |= child._get_all_custom_components(seen=seen)
|
custom_components |= child._get_all_custom_components(seen=seen)
|
||||||
|
for component in self._get_components_in_props():
|
||||||
|
if isinstance(component, Component) and component.tag is not None:
|
||||||
|
custom_components |= component._get_all_custom_components(seen=seen)
|
||||||
return custom_components
|
return custom_components
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@ -1600,17 +1661,65 @@ class CustomComponent(Component):
|
|||||||
# The props of the component.
|
# The props of the component.
|
||||||
props: dict[str, Any] = {}
|
props: dict[str, Any] = {}
|
||||||
|
|
||||||
# Props that reference other components.
|
def __init__(self, **kwargs):
|
||||||
component_props: dict[str, Component] = {}
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
"""Initialize the custom component.
|
"""Initialize the custom component.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
*args: The args to pass to the component.
|
|
||||||
**kwargs: The kwargs to pass to the component.
|
**kwargs: The kwargs to pass to the component.
|
||||||
"""
|
"""
|
||||||
super().__init__(*args, **kwargs)
|
component_fn = kwargs.get("component_fn")
|
||||||
|
|
||||||
|
# Set the props.
|
||||||
|
props_types = typing.get_type_hints(component_fn) if component_fn else {}
|
||||||
|
props = {key: value for key, value in kwargs.items() if key in props_types}
|
||||||
|
kwargs = {key: value for key, value in kwargs.items() if key not in props_types}
|
||||||
|
|
||||||
|
event_types = {
|
||||||
|
key
|
||||||
|
for key in props
|
||||||
|
if (
|
||||||
|
(get_origin((annotation := props_types.get(key))) or annotation)
|
||||||
|
== EventHandler
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
def get_args_spec(key: str) -> types.ArgsSpec | Sequence[types.ArgsSpec]:
|
||||||
|
type_ = props_types[key]
|
||||||
|
|
||||||
|
return (
|
||||||
|
args[0]
|
||||||
|
if (args := get_args(type_))
|
||||||
|
else (
|
||||||
|
annotation_args[1]
|
||||||
|
if get_origin(
|
||||||
|
(
|
||||||
|
annotation := inspect.getfullargspec(
|
||||||
|
component_fn
|
||||||
|
).annotations[key]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
is typing.Annotated
|
||||||
|
and (annotation_args := get_args(annotation))
|
||||||
|
else no_args_event_spec
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
super().__init__(
|
||||||
|
event_triggers={
|
||||||
|
key: EventChain.create(
|
||||||
|
value=props[key],
|
||||||
|
args_spec=get_args_spec(key),
|
||||||
|
key=key,
|
||||||
|
)
|
||||||
|
for key in event_types
|
||||||
|
},
|
||||||
|
**kwargs,
|
||||||
|
)
|
||||||
|
|
||||||
|
to_camel_cased_props = {
|
||||||
|
format.to_camel_case(key) for key in props if key not in event_types
|
||||||
|
}
|
||||||
|
self.get_props = lambda: to_camel_cased_props # pyright: ignore [reportIncompatibleVariableOverride]
|
||||||
|
|
||||||
# Unset the style.
|
# Unset the style.
|
||||||
self.style = Style()
|
self.style = Style()
|
||||||
@ -1618,51 +1727,36 @@ class CustomComponent(Component):
|
|||||||
# Set the tag to the name of the function.
|
# Set the tag to the name of the function.
|
||||||
self.tag = format.to_title_case(self.component_fn.__name__)
|
self.tag = format.to_title_case(self.component_fn.__name__)
|
||||||
|
|
||||||
# Get the event triggers defined in the component declaration.
|
for key, value in props.items():
|
||||||
event_triggers_in_component_declaration = self.get_event_triggers()
|
|
||||||
|
|
||||||
# Set the props.
|
|
||||||
props = typing.get_type_hints(self.component_fn)
|
|
||||||
for key, value in kwargs.items():
|
|
||||||
# Skip kwargs that are not props.
|
# Skip kwargs that are not props.
|
||||||
if key not in props:
|
if key not in props_types:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
camel_cased_key = format.to_camel_case(key)
|
||||||
|
|
||||||
# Get the type based on the annotation.
|
# Get the type based on the annotation.
|
||||||
type_ = props[key]
|
type_ = props_types[key]
|
||||||
|
|
||||||
# Handle event chains.
|
# Handle event chains.
|
||||||
if types._issubclass(type_, EventChain):
|
if types._issubclass(type_, EventActionsMixin):
|
||||||
value = EventChain.create(
|
inspect.getfullargspec(component_fn).annotations[key]
|
||||||
value=value,
|
self.props[camel_cased_key] = EventChain.create(
|
||||||
args_spec=event_triggers_in_component_declaration.get(
|
value=value, args_spec=get_args_spec(key), key=key
|
||||||
key, no_args_event_spec
|
|
||||||
),
|
|
||||||
key=key,
|
|
||||||
)
|
)
|
||||||
self.props[format.to_camel_case(key)] = value
|
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Handle subclasses of Base.
|
value = LiteralVar.create(value)
|
||||||
if isinstance(value, Base):
|
self.props[camel_cased_key] = value
|
||||||
base_value = LiteralVar.create(value)
|
setattr(self, camel_cased_key, value)
|
||||||
|
|
||||||
# Track hooks and imports associated with Component instances.
|
@classmethod
|
||||||
if base_value is not None and isinstance(value, Component):
|
def _are_fields_known(cls) -> bool:
|
||||||
self.component_props[key] = value
|
"""Check if the fields are known.
|
||||||
value = base_value._replace(
|
|
||||||
merge_var_data=VarData(
|
|
||||||
imports=value._get_all_imports(),
|
|
||||||
hooks=value._get_all_hooks(),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
value = base_value
|
|
||||||
else:
|
|
||||||
value = LiteralVar.create(value)
|
|
||||||
|
|
||||||
# Set the prop.
|
Returns:
|
||||||
self.props[format.to_camel_case(key)] = value
|
Whether the fields are known.
|
||||||
|
"""
|
||||||
|
return False
|
||||||
|
|
||||||
def __eq__(self, other: Any) -> bool:
|
def __eq__(self, other: Any) -> bool:
|
||||||
"""Check if the component is equal to another.
|
"""Check if the component is equal to another.
|
||||||
@ -1684,7 +1778,7 @@ class CustomComponent(Component):
|
|||||||
return hash(self.tag)
|
return hash(self.tag)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_props(cls) -> set[str]: # pyright: ignore [reportIncompatibleVariableOverride]
|
def get_props(cls) -> set[str]:
|
||||||
"""Get the props for the component.
|
"""Get the props for the component.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
@ -1721,28 +1815,9 @@ class CustomComponent(Component):
|
|||||||
seen=seen
|
seen=seen
|
||||||
)
|
)
|
||||||
|
|
||||||
# Fetch custom components from props as well.
|
|
||||||
for child_component in self.component_props.values():
|
|
||||||
if child_component.tag is None:
|
|
||||||
continue
|
|
||||||
if child_component.tag not in seen:
|
|
||||||
seen.add(child_component.tag)
|
|
||||||
if isinstance(child_component, CustomComponent):
|
|
||||||
custom_components |= {child_component}
|
|
||||||
custom_components |= child_component._get_all_custom_components(
|
|
||||||
seen=seen
|
|
||||||
)
|
|
||||||
return custom_components
|
return custom_components
|
||||||
|
|
||||||
def _render(self) -> Tag:
|
def get_prop_vars(self) -> List[Var]:
|
||||||
"""Define how to render the component in React.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
The tag to render.
|
|
||||||
"""
|
|
||||||
return super()._render(props=self.props)
|
|
||||||
|
|
||||||
def get_prop_vars(self) -> list[Var]:
|
|
||||||
"""Get the prop vars.
|
"""Get the prop vars.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
@ -1751,29 +1826,19 @@ class CustomComponent(Component):
|
|||||||
return [
|
return [
|
||||||
Var(
|
Var(
|
||||||
_js_expr=name,
|
_js_expr=name,
|
||||||
_var_type=(prop._var_type if isinstance(prop, Var) else type(prop)),
|
_var_type=(
|
||||||
|
prop._var_type
|
||||||
|
if isinstance(prop, Var)
|
||||||
|
else (
|
||||||
|
type(prop)
|
||||||
|
if not isinstance(prop, EventActionsMixin)
|
||||||
|
else EventChain
|
||||||
|
)
|
||||||
|
),
|
||||||
).guess_type()
|
).guess_type()
|
||||||
for name, prop in self.props.items()
|
for name, prop in self.props.items()
|
||||||
]
|
]
|
||||||
|
|
||||||
def _get_vars(
|
|
||||||
self, include_children: bool = False, ignore_ids: set[int] | None = None
|
|
||||||
) -> Iterator[Var]:
|
|
||||||
"""Walk all Vars used in this component.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
include_children: Whether to include Vars from children.
|
|
||||||
ignore_ids: The ids to ignore.
|
|
||||||
|
|
||||||
Yields:
|
|
||||||
Each var referenced by the component (props, styles, event handlers).
|
|
||||||
"""
|
|
||||||
ignore_ids = ignore_ids or set()
|
|
||||||
yield from super()._get_vars(
|
|
||||||
include_children=include_children, ignore_ids=ignore_ids
|
|
||||||
)
|
|
||||||
yield from filter(lambda prop: isinstance(prop, Var), self.props.values())
|
|
||||||
|
|
||||||
@lru_cache(maxsize=None) # noqa: B019
|
@lru_cache(maxsize=None) # noqa: B019
|
||||||
def get_component(self) -> Component:
|
def get_component(self) -> Component:
|
||||||
"""Render the component.
|
"""Render the component.
|
||||||
@ -2461,6 +2526,7 @@ class LiteralComponentVar(CachedVarOperation, LiteralVar, ComponentVar):
|
|||||||
The VarData for the var.
|
The VarData for the var.
|
||||||
"""
|
"""
|
||||||
return VarData.merge(
|
return VarData.merge(
|
||||||
|
self._var_data,
|
||||||
VarData(
|
VarData(
|
||||||
imports={
|
imports={
|
||||||
"@emotion/react": [
|
"@emotion/react": [
|
||||||
@ -2503,9 +2569,21 @@ class LiteralComponentVar(CachedVarOperation, LiteralVar, ComponentVar):
|
|||||||
Returns:
|
Returns:
|
||||||
The var.
|
The var.
|
||||||
"""
|
"""
|
||||||
|
var_datas = [
|
||||||
|
var_data
|
||||||
|
for var in value._get_vars(include_children=True)
|
||||||
|
if (var_data := var._get_all_var_data())
|
||||||
|
]
|
||||||
|
|
||||||
return LiteralComponentVar(
|
return LiteralComponentVar(
|
||||||
_js_expr="",
|
_js_expr="",
|
||||||
_var_type=type(value),
|
_var_type=type(value),
|
||||||
_var_data=_var_data,
|
_var_data=VarData.merge(
|
||||||
|
_var_data,
|
||||||
|
*var_datas,
|
||||||
|
VarData(
|
||||||
|
components=(value,),
|
||||||
|
),
|
||||||
|
),
|
||||||
_var_value=value,
|
_var_value=value,
|
||||||
)
|
)
|
||||||
|
@ -61,14 +61,6 @@ class Cond(MemoizationLeaf):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
def _get_props_imports(self):
|
|
||||||
"""Get the imports needed for component's props.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
The imports for the component's props of the component.
|
|
||||||
"""
|
|
||||||
return []
|
|
||||||
|
|
||||||
def _render(self) -> Tag:
|
def _render(self) -> Tag:
|
||||||
return CondTag(
|
return CondTag(
|
||||||
cond=self.cond,
|
cond=self.cond,
|
||||||
|
@ -10,6 +10,7 @@ from reflex.vars.base import Var
|
|||||||
|
|
||||||
from ..base import LiteralAccentColor, RadixThemesComponent
|
from ..base import LiteralAccentColor, RadixThemesComponent
|
||||||
from .checkbox import Checkbox
|
from .checkbox import Checkbox
|
||||||
|
from .radio_group import HighLevelRadioGroup
|
||||||
|
|
||||||
LiteralDirType = Literal["ltr", "rtl"]
|
LiteralDirType = Literal["ltr", "rtl"]
|
||||||
|
|
||||||
@ -226,7 +227,11 @@ class ContextMenuItem(RadixThemesComponent):
|
|||||||
# Optional text used for typeahead purposes. By default the typeahead behavior will use the content of the item. Use this when the content is complex, or you have non-textual content inside.
|
# Optional text used for typeahead purposes. By default the typeahead behavior will use the content of the item. Use this when the content is complex, or you have non-textual content inside.
|
||||||
text_value: Var[str]
|
text_value: Var[str]
|
||||||
|
|
||||||
_valid_parents: list[str] = ["ContextMenuContent", "ContextMenuSubContent"]
|
_valid_parents: List[str] = [
|
||||||
|
"ContextMenuContent",
|
||||||
|
"ContextMenuSubContent",
|
||||||
|
"ContextMenuGroup",
|
||||||
|
]
|
||||||
|
|
||||||
# Fired when the item is selected.
|
# Fired when the item is selected.
|
||||||
on_select: EventHandler[no_args_event_spec]
|
on_select: EventHandler[no_args_event_spec]
|
||||||
@ -247,6 +252,75 @@ class ContextMenuCheckbox(Checkbox):
|
|||||||
shortcut: Var[str]
|
shortcut: Var[str]
|
||||||
|
|
||||||
|
|
||||||
|
class ContextMenuLabel(RadixThemesComponent):
|
||||||
|
"""The component that contains the label."""
|
||||||
|
|
||||||
|
tag = "ContextMenu.Label"
|
||||||
|
|
||||||
|
# Change the default rendered element for the one passed as a child, merging their props and behavior. Defaults to False.
|
||||||
|
as_child: Var[bool]
|
||||||
|
|
||||||
|
|
||||||
|
class ContextMenuGroup(RadixThemesComponent):
|
||||||
|
"""The component that contains the group."""
|
||||||
|
|
||||||
|
tag = "ContextMenu.Group"
|
||||||
|
|
||||||
|
# Change the default rendered element for the one passed as a child, merging their props and behavior. Defaults to False.
|
||||||
|
as_child: Var[bool]
|
||||||
|
|
||||||
|
_valid_parents: List[str] = ["ContextMenuContent", "ContextMenuSubContent"]
|
||||||
|
|
||||||
|
|
||||||
|
class ContextMenuRadioGroup(RadixThemesComponent):
|
||||||
|
"""The component that contains context menu radio items."""
|
||||||
|
|
||||||
|
tag = "ContextMenu.RadioGroup"
|
||||||
|
|
||||||
|
# Change the default rendered element for the one passed as a child, merging their props and behavior. Defaults to False.
|
||||||
|
as_child: Var[bool]
|
||||||
|
|
||||||
|
# The value of the selected item in the group.
|
||||||
|
value: Var[str]
|
||||||
|
|
||||||
|
# Props to rename
|
||||||
|
_rename_props = {"onChange": "onValueChange"}
|
||||||
|
|
||||||
|
# Fired when the value of the radio group changes.
|
||||||
|
on_change: EventHandler[passthrough_event_spec(str)]
|
||||||
|
|
||||||
|
_valid_parents: List[str] = [
|
||||||
|
"ContextMenuRadioItem",
|
||||||
|
"ContextMenuSubContent",
|
||||||
|
"ContextMenuContent",
|
||||||
|
"ContextMenuSub",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class ContextMenuRadioItem(HighLevelRadioGroup):
|
||||||
|
"""The component that contains context menu radio items."""
|
||||||
|
|
||||||
|
tag = "ContextMenu.RadioItem"
|
||||||
|
|
||||||
|
# Override theme color for Dropdown Menu Content
|
||||||
|
color_scheme: Var[LiteralAccentColor]
|
||||||
|
|
||||||
|
# Change the default rendered element for the one passed as a child, merging their props and behavior. Defaults to False.
|
||||||
|
as_child: Var[bool]
|
||||||
|
|
||||||
|
# The unique value of the item.
|
||||||
|
value: Var[str]
|
||||||
|
|
||||||
|
# When true, prevents the user from interacting with the item.
|
||||||
|
disabled: Var[bool]
|
||||||
|
|
||||||
|
# Event handler called when the user selects an item (via mouse or keyboard). Calling event.preventDefault in this handler will prevent the context menu from closing when selecting that item.
|
||||||
|
on_select: EventHandler[no_args_event_spec]
|
||||||
|
|
||||||
|
# Optional text used for typeahead purposes. By default the typeahead behavior will use the .textContent of the item. Use this when the content is complex, or you have non-textual content inside.
|
||||||
|
text_value: Var[str]
|
||||||
|
|
||||||
|
|
||||||
class ContextMenu(ComponentNamespace):
|
class ContextMenu(ComponentNamespace):
|
||||||
"""Menu representing a set of actions, displayed at the origin of a pointer right-click or long-press."""
|
"""Menu representing a set of actions, displayed at the origin of a pointer right-click or long-press."""
|
||||||
|
|
||||||
@ -259,6 +333,10 @@ class ContextMenu(ComponentNamespace):
|
|||||||
item = staticmethod(ContextMenuItem.create)
|
item = staticmethod(ContextMenuItem.create)
|
||||||
separator = staticmethod(ContextMenuSeparator.create)
|
separator = staticmethod(ContextMenuSeparator.create)
|
||||||
checkbox = staticmethod(ContextMenuCheckbox.create)
|
checkbox = staticmethod(ContextMenuCheckbox.create)
|
||||||
|
label = staticmethod(ContextMenuLabel.create)
|
||||||
|
group = staticmethod(ContextMenuGroup.create)
|
||||||
|
radio_group = staticmethod(ContextMenuRadioGroup.create)
|
||||||
|
radio = staticmethod(ContextMenuRadioItem.create)
|
||||||
|
|
||||||
|
|
||||||
context_menu = ContextMenu()
|
context_menu = ContextMenu()
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
# ------------------- DO NOT EDIT ----------------------
|
# ------------------- DO NOT EDIT ----------------------
|
||||||
# This file was generated by `reflex/utils/pyi_generator.py`!
|
# This file was generated by `reflex/utils/pyi_generator.py`!
|
||||||
# ------------------------------------------------------
|
# ------------------------------------------------------
|
||||||
from typing import Any, Literal, Optional, overload
|
from typing import Any, Dict, List, Literal, Optional, Union, overload
|
||||||
|
|
||||||
from reflex.components.component import ComponentNamespace
|
from reflex.components.component import ComponentNamespace
|
||||||
from reflex.components.core.breakpoints import Breakpoints
|
from reflex.components.core.breakpoints import Breakpoints
|
||||||
@ -13,6 +13,7 @@ from reflex.vars.base import Var
|
|||||||
|
|
||||||
from ..base import RadixThemesComponent
|
from ..base import RadixThemesComponent
|
||||||
from .checkbox import Checkbox
|
from .checkbox import Checkbox
|
||||||
|
from .radio_group import HighLevelRadioGroup
|
||||||
|
|
||||||
LiteralDirType = Literal["ltr", "rtl"]
|
LiteralDirType = Literal["ltr", "rtl"]
|
||||||
LiteralSizeType = Literal["1", "2"]
|
LiteralSizeType = Literal["1", "2"]
|
||||||
@ -784,6 +785,320 @@ class ContextMenuCheckbox(Checkbox):
|
|||||||
"""
|
"""
|
||||||
...
|
...
|
||||||
|
|
||||||
|
class ContextMenuLabel(RadixThemesComponent):
|
||||||
|
@overload
|
||||||
|
@classmethod
|
||||||
|
def create( # type: ignore
|
||||||
|
cls,
|
||||||
|
*children,
|
||||||
|
as_child: Optional[Union[Var[bool], bool]] = None,
|
||||||
|
style: Optional[Style] = None,
|
||||||
|
key: Optional[Any] = None,
|
||||||
|
id: Optional[Any] = None,
|
||||||
|
class_name: Optional[Any] = None,
|
||||||
|
autofocus: Optional[bool] = None,
|
||||||
|
custom_attrs: Optional[Dict[str, Union[Var, Any]]] = None,
|
||||||
|
on_blur: Optional[EventType[()]] = None,
|
||||||
|
on_click: Optional[EventType[()]] = None,
|
||||||
|
on_context_menu: Optional[EventType[()]] = None,
|
||||||
|
on_double_click: Optional[EventType[()]] = None,
|
||||||
|
on_focus: Optional[EventType[()]] = None,
|
||||||
|
on_mount: Optional[EventType[()]] = None,
|
||||||
|
on_mouse_down: Optional[EventType[()]] = None,
|
||||||
|
on_mouse_enter: Optional[EventType[()]] = None,
|
||||||
|
on_mouse_leave: Optional[EventType[()]] = None,
|
||||||
|
on_mouse_move: Optional[EventType[()]] = None,
|
||||||
|
on_mouse_out: Optional[EventType[()]] = None,
|
||||||
|
on_mouse_over: Optional[EventType[()]] = None,
|
||||||
|
on_mouse_up: Optional[EventType[()]] = None,
|
||||||
|
on_scroll: Optional[EventType[()]] = None,
|
||||||
|
on_unmount: Optional[EventType[()]] = None,
|
||||||
|
**props,
|
||||||
|
) -> "ContextMenuLabel":
|
||||||
|
"""Create a new component instance.
|
||||||
|
|
||||||
|
Will prepend "RadixThemes" to the component tag to avoid conflicts with
|
||||||
|
other UI libraries for common names, like Text and Button.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
*children: Child components.
|
||||||
|
as_child: Change the default rendered element for the one passed as a child, merging their props and behavior. Defaults to False.
|
||||||
|
style: The style of the component.
|
||||||
|
key: A unique key for the component.
|
||||||
|
id: The id for the component.
|
||||||
|
class_name: The class name for the component.
|
||||||
|
autofocus: Whether the component should take the focus once the page is loaded
|
||||||
|
custom_attrs: custom attribute
|
||||||
|
**props: Component properties.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
A new component instance.
|
||||||
|
"""
|
||||||
|
...
|
||||||
|
|
||||||
|
class ContextMenuGroup(RadixThemesComponent):
|
||||||
|
@overload
|
||||||
|
@classmethod
|
||||||
|
def create( # type: ignore
|
||||||
|
cls,
|
||||||
|
*children,
|
||||||
|
as_child: Optional[Union[Var[bool], bool]] = None,
|
||||||
|
style: Optional[Style] = None,
|
||||||
|
key: Optional[Any] = None,
|
||||||
|
id: Optional[Any] = None,
|
||||||
|
class_name: Optional[Any] = None,
|
||||||
|
autofocus: Optional[bool] = None,
|
||||||
|
custom_attrs: Optional[Dict[str, Union[Var, Any]]] = None,
|
||||||
|
on_blur: Optional[EventType[()]] = None,
|
||||||
|
on_click: Optional[EventType[()]] = None,
|
||||||
|
on_context_menu: Optional[EventType[()]] = None,
|
||||||
|
on_double_click: Optional[EventType[()]] = None,
|
||||||
|
on_focus: Optional[EventType[()]] = None,
|
||||||
|
on_mount: Optional[EventType[()]] = None,
|
||||||
|
on_mouse_down: Optional[EventType[()]] = None,
|
||||||
|
on_mouse_enter: Optional[EventType[()]] = None,
|
||||||
|
on_mouse_leave: Optional[EventType[()]] = None,
|
||||||
|
on_mouse_move: Optional[EventType[()]] = None,
|
||||||
|
on_mouse_out: Optional[EventType[()]] = None,
|
||||||
|
on_mouse_over: Optional[EventType[()]] = None,
|
||||||
|
on_mouse_up: Optional[EventType[()]] = None,
|
||||||
|
on_scroll: Optional[EventType[()]] = None,
|
||||||
|
on_unmount: Optional[EventType[()]] = None,
|
||||||
|
**props,
|
||||||
|
) -> "ContextMenuGroup":
|
||||||
|
"""Create a new component instance.
|
||||||
|
|
||||||
|
Will prepend "RadixThemes" to the component tag to avoid conflicts with
|
||||||
|
other UI libraries for common names, like Text and Button.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
*children: Child components.
|
||||||
|
as_child: Change the default rendered element for the one passed as a child, merging their props and behavior. Defaults to False.
|
||||||
|
style: The style of the component.
|
||||||
|
key: A unique key for the component.
|
||||||
|
id: The id for the component.
|
||||||
|
class_name: The class name for the component.
|
||||||
|
autofocus: Whether the component should take the focus once the page is loaded
|
||||||
|
custom_attrs: custom attribute
|
||||||
|
**props: Component properties.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
A new component instance.
|
||||||
|
"""
|
||||||
|
...
|
||||||
|
|
||||||
|
class ContextMenuRadioGroup(RadixThemesComponent):
|
||||||
|
@overload
|
||||||
|
@classmethod
|
||||||
|
def create( # type: ignore
|
||||||
|
cls,
|
||||||
|
*children,
|
||||||
|
as_child: Optional[Union[Var[bool], bool]] = None,
|
||||||
|
value: Optional[Union[Var[str], str]] = None,
|
||||||
|
style: Optional[Style] = None,
|
||||||
|
key: Optional[Any] = None,
|
||||||
|
id: Optional[Any] = None,
|
||||||
|
class_name: Optional[Any] = None,
|
||||||
|
autofocus: Optional[bool] = None,
|
||||||
|
custom_attrs: Optional[Dict[str, Union[Var, Any]]] = None,
|
||||||
|
on_blur: Optional[EventType[()]] = None,
|
||||||
|
on_change: Optional[Union[EventType[()], EventType[str]]] = None,
|
||||||
|
on_click: Optional[EventType[()]] = None,
|
||||||
|
on_context_menu: Optional[EventType[()]] = None,
|
||||||
|
on_double_click: Optional[EventType[()]] = None,
|
||||||
|
on_focus: Optional[EventType[()]] = None,
|
||||||
|
on_mount: Optional[EventType[()]] = None,
|
||||||
|
on_mouse_down: Optional[EventType[()]] = None,
|
||||||
|
on_mouse_enter: Optional[EventType[()]] = None,
|
||||||
|
on_mouse_leave: Optional[EventType[()]] = None,
|
||||||
|
on_mouse_move: Optional[EventType[()]] = None,
|
||||||
|
on_mouse_out: Optional[EventType[()]] = None,
|
||||||
|
on_mouse_over: Optional[EventType[()]] = None,
|
||||||
|
on_mouse_up: Optional[EventType[()]] = None,
|
||||||
|
on_scroll: Optional[EventType[()]] = None,
|
||||||
|
on_unmount: Optional[EventType[()]] = None,
|
||||||
|
**props,
|
||||||
|
) -> "ContextMenuRadioGroup":
|
||||||
|
"""Create a new component instance.
|
||||||
|
|
||||||
|
Will prepend "RadixThemes" to the component tag to avoid conflicts with
|
||||||
|
other UI libraries for common names, like Text and Button.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
*children: Child components.
|
||||||
|
as_child: Change the default rendered element for the one passed as a child, merging their props and behavior. Defaults to False.
|
||||||
|
value: The value of the selected item in the group.
|
||||||
|
on_change: Fired when the value of the radio group changes.
|
||||||
|
style: The style of the component.
|
||||||
|
key: A unique key for the component.
|
||||||
|
id: The id for the component.
|
||||||
|
class_name: The class name for the component.
|
||||||
|
autofocus: Whether the component should take the focus once the page is loaded
|
||||||
|
custom_attrs: custom attribute
|
||||||
|
**props: Component properties.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
A new component instance.
|
||||||
|
"""
|
||||||
|
...
|
||||||
|
|
||||||
|
class ContextMenuRadioItem(HighLevelRadioGroup):
|
||||||
|
@overload
|
||||||
|
@classmethod
|
||||||
|
def create( # type: ignore
|
||||||
|
cls,
|
||||||
|
*children,
|
||||||
|
color_scheme: Optional[
|
||||||
|
Union[
|
||||||
|
Literal[
|
||||||
|
"amber",
|
||||||
|
"blue",
|
||||||
|
"bronze",
|
||||||
|
"brown",
|
||||||
|
"crimson",
|
||||||
|
"cyan",
|
||||||
|
"gold",
|
||||||
|
"grass",
|
||||||
|
"gray",
|
||||||
|
"green",
|
||||||
|
"indigo",
|
||||||
|
"iris",
|
||||||
|
"jade",
|
||||||
|
"lime",
|
||||||
|
"mint",
|
||||||
|
"orange",
|
||||||
|
"pink",
|
||||||
|
"plum",
|
||||||
|
"purple",
|
||||||
|
"red",
|
||||||
|
"ruby",
|
||||||
|
"sky",
|
||||||
|
"teal",
|
||||||
|
"tomato",
|
||||||
|
"violet",
|
||||||
|
"yellow",
|
||||||
|
],
|
||||||
|
Var[
|
||||||
|
Literal[
|
||||||
|
"amber",
|
||||||
|
"blue",
|
||||||
|
"bronze",
|
||||||
|
"brown",
|
||||||
|
"crimson",
|
||||||
|
"cyan",
|
||||||
|
"gold",
|
||||||
|
"grass",
|
||||||
|
"gray",
|
||||||
|
"green",
|
||||||
|
"indigo",
|
||||||
|
"iris",
|
||||||
|
"jade",
|
||||||
|
"lime",
|
||||||
|
"mint",
|
||||||
|
"orange",
|
||||||
|
"pink",
|
||||||
|
"plum",
|
||||||
|
"purple",
|
||||||
|
"red",
|
||||||
|
"ruby",
|
||||||
|
"sky",
|
||||||
|
"teal",
|
||||||
|
"tomato",
|
||||||
|
"violet",
|
||||||
|
"yellow",
|
||||||
|
]
|
||||||
|
],
|
||||||
|
]
|
||||||
|
] = None,
|
||||||
|
as_child: Optional[Union[Var[bool], bool]] = None,
|
||||||
|
value: Optional[Union[Var[str], str]] = None,
|
||||||
|
disabled: Optional[Union[Var[bool], bool]] = None,
|
||||||
|
text_value: Optional[Union[Var[str], str]] = None,
|
||||||
|
items: Optional[Union[List[str], Var[List[str]]]] = None,
|
||||||
|
direction: Optional[
|
||||||
|
Union[
|
||||||
|
Literal["column", "column-reverse", "row", "row-reverse"],
|
||||||
|
Var[Literal["column", "column-reverse", "row", "row-reverse"]],
|
||||||
|
]
|
||||||
|
] = None,
|
||||||
|
spacing: Optional[
|
||||||
|
Union[
|
||||||
|
Literal["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"],
|
||||||
|
Var[Literal["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"]],
|
||||||
|
]
|
||||||
|
] = None,
|
||||||
|
size: Optional[
|
||||||
|
Union[Literal["1", "2", "3"], Var[Literal["1", "2", "3"]]]
|
||||||
|
] = None,
|
||||||
|
variant: Optional[
|
||||||
|
Union[
|
||||||
|
Literal["classic", "soft", "surface"],
|
||||||
|
Var[Literal["classic", "soft", "surface"]],
|
||||||
|
]
|
||||||
|
] = None,
|
||||||
|
high_contrast: Optional[Union[Var[bool], bool]] = None,
|
||||||
|
default_value: Optional[Union[Var[str], str]] = None,
|
||||||
|
name: Optional[Union[Var[str], str]] = None,
|
||||||
|
required: Optional[Union[Var[bool], bool]] = None,
|
||||||
|
style: Optional[Style] = None,
|
||||||
|
key: Optional[Any] = None,
|
||||||
|
id: Optional[Any] = None,
|
||||||
|
class_name: Optional[Any] = None,
|
||||||
|
autofocus: Optional[bool] = None,
|
||||||
|
custom_attrs: Optional[Dict[str, Union[Var, Any]]] = None,
|
||||||
|
on_blur: Optional[EventType[()]] = None,
|
||||||
|
on_click: Optional[EventType[()]] = None,
|
||||||
|
on_context_menu: Optional[EventType[()]] = None,
|
||||||
|
on_double_click: Optional[EventType[()]] = None,
|
||||||
|
on_focus: Optional[EventType[()]] = None,
|
||||||
|
on_mount: Optional[EventType[()]] = None,
|
||||||
|
on_mouse_down: Optional[EventType[()]] = None,
|
||||||
|
on_mouse_enter: Optional[EventType[()]] = None,
|
||||||
|
on_mouse_leave: Optional[EventType[()]] = None,
|
||||||
|
on_mouse_move: Optional[EventType[()]] = None,
|
||||||
|
on_mouse_out: Optional[EventType[()]] = None,
|
||||||
|
on_mouse_over: Optional[EventType[()]] = None,
|
||||||
|
on_mouse_up: Optional[EventType[()]] = None,
|
||||||
|
on_scroll: Optional[EventType[()]] = None,
|
||||||
|
on_select: Optional[EventType[()]] = None,
|
||||||
|
on_unmount: Optional[EventType[()]] = None,
|
||||||
|
**props,
|
||||||
|
) -> "ContextMenuRadioItem":
|
||||||
|
"""Create a radio group component.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
items: The items of the radio group.
|
||||||
|
color_scheme: The color of the radio group
|
||||||
|
as_child: Change the default rendered element for the one passed as a child, merging their props and behavior. Defaults to False.
|
||||||
|
value: The controlled value of the radio item to check. Should be used in conjunction with on_change.
|
||||||
|
disabled: Whether the radio group is disabled
|
||||||
|
on_select: Event handler called when the user selects an item (via mouse or keyboard). Calling event.preventDefault in this handler will prevent the context menu from closing when selecting that item.
|
||||||
|
text_value: Optional text used for typeahead purposes. By default the typeahead behavior will use the .textContent of the item. Use this when the content is complex, or you have non-textual content inside.
|
||||||
|
items: The items of the radio group.
|
||||||
|
direction: The direction of the radio group.
|
||||||
|
spacing: The gap between the items of the radio group.
|
||||||
|
size: The size of the radio group.
|
||||||
|
variant: The variant of the radio group
|
||||||
|
high_contrast: Whether to render the radio group with higher contrast color against background
|
||||||
|
default_value: The initial value of checked radio item. Should be used in conjunction with on_change.
|
||||||
|
name: The name of the group. Submitted with its owning form as part of a name/value pair.
|
||||||
|
required: Whether the radio group is required
|
||||||
|
style: The style of the component.
|
||||||
|
key: A unique key for the component.
|
||||||
|
id: The id for the component.
|
||||||
|
class_name: The class name for the component.
|
||||||
|
autofocus: Whether the component should take the focus once the page is loaded
|
||||||
|
custom_attrs: custom attribute
|
||||||
|
**props: Additional properties to apply to the accordion item.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The created radio group component.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
TypeError: If the type of items is invalid.
|
||||||
|
"""
|
||||||
|
...
|
||||||
|
|
||||||
class ContextMenu(ComponentNamespace):
|
class ContextMenu(ComponentNamespace):
|
||||||
root = staticmethod(ContextMenuRoot.create)
|
root = staticmethod(ContextMenuRoot.create)
|
||||||
trigger = staticmethod(ContextMenuTrigger.create)
|
trigger = staticmethod(ContextMenuTrigger.create)
|
||||||
@ -794,5 +1109,9 @@ class ContextMenu(ComponentNamespace):
|
|||||||
item = staticmethod(ContextMenuItem.create)
|
item = staticmethod(ContextMenuItem.create)
|
||||||
separator = staticmethod(ContextMenuSeparator.create)
|
separator = staticmethod(ContextMenuSeparator.create)
|
||||||
checkbox = staticmethod(ContextMenuCheckbox.create)
|
checkbox = staticmethod(ContextMenuCheckbox.create)
|
||||||
|
label = staticmethod(ContextMenuLabel.create)
|
||||||
|
group = staticmethod(ContextMenuGroup.create)
|
||||||
|
radio_group = staticmethod(ContextMenuRadioGroup.create)
|
||||||
|
radio = staticmethod(ContextMenuRadioItem.create)
|
||||||
|
|
||||||
context_menu = ContextMenu()
|
context_menu = ContextMenu()
|
||||||
|
@ -101,7 +101,7 @@ class Tag:
|
|||||||
"""
|
"""
|
||||||
self.props.update(
|
self.props.update(
|
||||||
{
|
{
|
||||||
format.to_camel_case(name, allow_hyphens=True): (
|
format.to_camel_case(name, treat_hyphens_as_underscores=False): (
|
||||||
prop
|
prop
|
||||||
if types._isinstance(prop, (EventChain, Mapping))
|
if types._isinstance(prop, (EventChain, Mapping))
|
||||||
else LiteralVar.create(prop)
|
else LiteralVar.create(prop)
|
||||||
|
@ -585,6 +585,11 @@ class ExecutorType(enum.Enum):
|
|||||||
class EnvironmentVariables:
|
class EnvironmentVariables:
|
||||||
"""Environment variables class to instantiate environment variables."""
|
"""Environment variables class to instantiate environment variables."""
|
||||||
|
|
||||||
|
# Indicate the current command that was invoked in the reflex CLI.
|
||||||
|
REFLEX_COMPILE_CONTEXT: EnvVar[constants.CompileContext] = env_var(
|
||||||
|
constants.CompileContext.UNDEFINED, internal=True
|
||||||
|
)
|
||||||
|
|
||||||
# Whether to use npm over bun to install frontend packages.
|
# Whether to use npm over bun to install frontend packages.
|
||||||
REFLEX_USE_NPM: EnvVar[bool] = env_var(False)
|
REFLEX_USE_NPM: EnvVar[bool] = env_var(False)
|
||||||
|
|
||||||
@ -632,7 +637,7 @@ class EnvironmentVariables:
|
|||||||
REFLEX_COMPILE_THREADS: EnvVar[int | None] = env_var(None)
|
REFLEX_COMPILE_THREADS: EnvVar[int | None] = env_var(None)
|
||||||
|
|
||||||
# The directory to store reflex dependencies.
|
# The directory to store reflex dependencies.
|
||||||
REFLEX_DIR: EnvVar[Path] = env_var(Path(constants.Reflex.DIR))
|
REFLEX_DIR: EnvVar[Path] = env_var(constants.Reflex.DIR)
|
||||||
|
|
||||||
# Whether to print the SQL queries if the log level is INFO or lower.
|
# Whether to print the SQL queries if the log level is INFO or lower.
|
||||||
SQLALCHEMY_ECHO: EnvVar[bool] = env_var(False)
|
SQLALCHEMY_ECHO: EnvVar[bool] = env_var(False)
|
||||||
@ -704,6 +709,9 @@ class EnvironmentVariables:
|
|||||||
# Paths to exclude from the hot reload. Takes precedence over include paths. Separated by a colon.
|
# Paths to exclude from the hot reload. Takes precedence over include paths. Separated by a colon.
|
||||||
REFLEX_HOT_RELOAD_EXCLUDE_PATHS: EnvVar[list[Path]] = env_var([])
|
REFLEX_HOT_RELOAD_EXCLUDE_PATHS: EnvVar[list[Path]] = env_var([])
|
||||||
|
|
||||||
|
# Used by flexgen to enumerate the pages.
|
||||||
|
REFLEX_ADD_ALL_ROUTES_ENDPOINT: EnvVar[bool] = env_var(False)
|
||||||
|
|
||||||
|
|
||||||
environment = EnvironmentVariables()
|
environment = EnvironmentVariables()
|
||||||
|
|
||||||
@ -838,7 +846,7 @@ class Config(Base):
|
|||||||
env_file: str | None = None
|
env_file: str | None = None
|
||||||
|
|
||||||
# Whether to display the sticky "Built with Reflex" badge on all pages.
|
# Whether to display the sticky "Built with Reflex" badge on all pages.
|
||||||
show_built_with_reflex: bool = True
|
show_built_with_reflex: Optional[bool] = None
|
||||||
|
|
||||||
# Whether the app is running in the reflex cloud environment.
|
# Whether the app is running in the reflex cloud environment.
|
||||||
is_reflex_cloud: bool = False
|
is_reflex_cloud: bool = False
|
||||||
|
@ -25,6 +25,7 @@ from .base import (
|
|||||||
from .compiler import (
|
from .compiler import (
|
||||||
NOCOMPILE_FILE,
|
NOCOMPILE_FILE,
|
||||||
SETTER_PREFIX,
|
SETTER_PREFIX,
|
||||||
|
CompileContext,
|
||||||
CompileVars,
|
CompileVars,
|
||||||
ComponentName,
|
ComponentName,
|
||||||
Ext,
|
Ext,
|
||||||
@ -65,6 +66,7 @@ __ALL__ = [
|
|||||||
ColorMode,
|
ColorMode,
|
||||||
Config,
|
Config,
|
||||||
COOKIES,
|
COOKIES,
|
||||||
|
CompileContext,
|
||||||
ComponentName,
|
ComponentName,
|
||||||
CustomComponents,
|
CustomComponents,
|
||||||
DefaultPage,
|
DefaultPage,
|
||||||
|
@ -53,6 +53,12 @@ class Dirs(SimpleNamespace):
|
|||||||
POSTCSS_JS = "postcss.config.js"
|
POSTCSS_JS = "postcss.config.js"
|
||||||
# The name of the states directory.
|
# The name of the states directory.
|
||||||
STATES = ".states"
|
STATES = ".states"
|
||||||
|
# Where compilation artifacts for the backend are stored.
|
||||||
|
BACKEND = "backend"
|
||||||
|
# JSON-encoded list of page routes that need to be evaluated on the backend.
|
||||||
|
STATEFUL_PAGES = "stateful_pages.json"
|
||||||
|
# Marker file indicating that upload component was used in the frontend.
|
||||||
|
UPLOAD_IS_USED = "upload_is_used"
|
||||||
|
|
||||||
|
|
||||||
class Reflex(SimpleNamespace):
|
class Reflex(SimpleNamespace):
|
||||||
|
@ -111,6 +111,15 @@ class ComponentName(Enum):
|
|||||||
return self.value.lower() + Ext.ZIP
|
return self.value.lower() + Ext.ZIP
|
||||||
|
|
||||||
|
|
||||||
|
class CompileContext(str, Enum):
|
||||||
|
"""The context in which the compiler is running."""
|
||||||
|
|
||||||
|
RUN = "run"
|
||||||
|
EXPORT = "export"
|
||||||
|
DEPLOY = "deploy"
|
||||||
|
UNDEFINED = "undefined"
|
||||||
|
|
||||||
|
|
||||||
class Imports(SimpleNamespace):
|
class Imports(SimpleNamespace):
|
||||||
"""Common sets of import vars."""
|
"""Common sets of import vars."""
|
||||||
|
|
||||||
|
@ -12,6 +12,7 @@ class Endpoint(Enum):
|
|||||||
UPLOAD = "_upload"
|
UPLOAD = "_upload"
|
||||||
AUTH_CODESPACE = "auth-codespace"
|
AUTH_CODESPACE = "auth-codespace"
|
||||||
HEALTH = "_health"
|
HEALTH = "_health"
|
||||||
|
ALL_ROUTES = "_all_routes"
|
||||||
|
|
||||||
def __str__(self) -> str:
|
def __str__(self) -> str:
|
||||||
"""Get the string representation of the endpoint.
|
"""Get the string representation of the endpoint.
|
||||||
|
@ -22,8 +22,6 @@ typer.core.rich = None # pyright: ignore [reportPrivateImportUsage]
|
|||||||
cli = typer.Typer(add_completion=False, pretty_exceptions_enable=False)
|
cli = typer.Typer(add_completion=False, pretty_exceptions_enable=False)
|
||||||
|
|
||||||
|
|
||||||
SHOW_BUILT_WITH_REFLEX_INFO = "https://reflex.dev/docs/hosting/reflex-branding/"
|
|
||||||
|
|
||||||
# Get the config.
|
# Get the config.
|
||||||
config = get_config()
|
config = get_config()
|
||||||
|
|
||||||
@ -192,15 +190,6 @@ def _run(
|
|||||||
prerequisites.check_latest_package_version(constants.Reflex.MODULE_NAME)
|
prerequisites.check_latest_package_version(constants.Reflex.MODULE_NAME)
|
||||||
|
|
||||||
if frontend:
|
if frontend:
|
||||||
if not config.show_built_with_reflex:
|
|
||||||
# The sticky badge may be disabled at runtime for team/enterprise tiers.
|
|
||||||
prerequisites.check_config_option_in_tier(
|
|
||||||
option_name="show_built_with_reflex",
|
|
||||||
allowed_tiers=["team", "enterprise"],
|
|
||||||
fallback_value=True,
|
|
||||||
help_link=SHOW_BUILT_WITH_REFLEX_INFO,
|
|
||||||
)
|
|
||||||
|
|
||||||
# Get the app module.
|
# Get the app module.
|
||||||
prerequisites.get_compiled_app()
|
prerequisites.get_compiled_app()
|
||||||
|
|
||||||
@ -302,6 +291,8 @@ def run(
|
|||||||
if frontend and backend:
|
if frontend and backend:
|
||||||
console.error("Cannot use both --frontend-only and --backend-only options.")
|
console.error("Cannot use both --frontend-only and --backend-only options.")
|
||||||
raise typer.Exit(1)
|
raise typer.Exit(1)
|
||||||
|
|
||||||
|
environment.REFLEX_COMPILE_CONTEXT.set(constants.CompileContext.RUN)
|
||||||
environment.REFLEX_BACKEND_ONLY.set(backend)
|
environment.REFLEX_BACKEND_ONLY.set(backend)
|
||||||
environment.REFLEX_FRONTEND_ONLY.set(frontend)
|
environment.REFLEX_FRONTEND_ONLY.set(frontend)
|
||||||
|
|
||||||
@ -348,20 +339,13 @@ def export(
|
|||||||
from reflex.utils import export as export_utils
|
from reflex.utils import export as export_utils
|
||||||
from reflex.utils import prerequisites
|
from reflex.utils import prerequisites
|
||||||
|
|
||||||
|
environment.REFLEX_COMPILE_CONTEXT.set(constants.CompileContext.EXPORT)
|
||||||
|
|
||||||
frontend, backend = prerequisites.check_running_mode(frontend, backend)
|
frontend, backend = prerequisites.check_running_mode(frontend, backend)
|
||||||
|
|
||||||
if prerequisites.needs_reinit(frontend=frontend or not backend):
|
if prerequisites.needs_reinit(frontend=frontend or not backend):
|
||||||
_init(name=config.app_name, loglevel=loglevel)
|
_init(name=config.app_name, loglevel=loglevel)
|
||||||
|
|
||||||
if frontend and not config.show_built_with_reflex:
|
|
||||||
# The sticky badge may be disabled on export for team/enterprise tiers.
|
|
||||||
prerequisites.check_config_option_in_tier(
|
|
||||||
option_name="show_built_with_reflex",
|
|
||||||
allowed_tiers=["team", "enterprise"],
|
|
||||||
fallback_value=False,
|
|
||||||
help_link=SHOW_BUILT_WITH_REFLEX_INFO,
|
|
||||||
)
|
|
||||||
|
|
||||||
export_utils.export(
|
export_utils.export(
|
||||||
zipping=zipping,
|
zipping=zipping,
|
||||||
frontend=frontend,
|
frontend=frontend,
|
||||||
@ -556,14 +540,7 @@ def deploy(
|
|||||||
|
|
||||||
check_version()
|
check_version()
|
||||||
|
|
||||||
if not config.show_built_with_reflex:
|
environment.REFLEX_COMPILE_CONTEXT.set(constants.CompileContext.DEPLOY)
|
||||||
# The sticky badge may be disabled on deploy for pro/team/enterprise tiers.
|
|
||||||
prerequisites.check_config_option_in_tier(
|
|
||||||
option_name="show_built_with_reflex",
|
|
||||||
allowed_tiers=["pro", "team", "enterprise"],
|
|
||||||
fallback_value=True,
|
|
||||||
help_link=SHOW_BUILT_WITH_REFLEX_INFO,
|
|
||||||
)
|
|
||||||
|
|
||||||
# Set the log level.
|
# Set the log level.
|
||||||
console.set_log_level(loglevel)
|
console.set_log_level(loglevel)
|
||||||
|
@ -325,6 +325,9 @@ async def _resolve_delta(delta: Delta) -> Delta:
|
|||||||
return delta
|
return delta
|
||||||
|
|
||||||
|
|
||||||
|
all_base_state_classes: dict[str, None] = {}
|
||||||
|
|
||||||
|
|
||||||
class BaseState(Base, ABC, extra=pydantic.Extra.allow):
|
class BaseState(Base, ABC, extra=pydantic.Extra.allow):
|
||||||
"""The state of the app."""
|
"""The state of the app."""
|
||||||
|
|
||||||
@ -622,6 +625,8 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
|
|||||||
cls._var_dependencies = {}
|
cls._var_dependencies = {}
|
||||||
cls._init_var_dependency_dicts()
|
cls._init_var_dependency_dicts()
|
||||||
|
|
||||||
|
all_base_state_classes[cls.get_full_name()] = None
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _copy_fn(fn: Callable) -> Callable:
|
def _copy_fn(fn: Callable) -> Callable:
|
||||||
"""Copy a function. Used to copy ComputedVars and EventHandlers from mixins.
|
"""Copy a function. Used to copy ComputedVars and EventHandlers from mixins.
|
||||||
@ -4083,6 +4088,7 @@ def reload_state_module(
|
|||||||
for subclass in tuple(state.class_subclasses):
|
for subclass in tuple(state.class_subclasses):
|
||||||
reload_state_module(module=module, state=subclass)
|
reload_state_module(module=module, state=subclass)
|
||||||
if subclass.__module__ == module and module is not None:
|
if subclass.__module__ == module and module is not None:
|
||||||
|
all_base_state_classes.pop(subclass.get_full_name(), None)
|
||||||
state.class_subclasses.remove(subclass)
|
state.class_subclasses.remove(subclass)
|
||||||
state._always_dirty_substates.discard(subclass.get_name())
|
state._always_dirty_substates.discard(subclass.get_name())
|
||||||
state._var_dependencies = {}
|
state._var_dependencies = {}
|
||||||
|
@ -190,11 +190,12 @@ def convert(
|
|||||||
for key, value in style_dict.items():
|
for key, value in style_dict.items():
|
||||||
keys = (
|
keys = (
|
||||||
format_style_key(key)
|
format_style_key(key)
|
||||||
if not isinstance(value, (dict, ObjectVar))
|
if not isinstance(value, (dict, ObjectVar, list))
|
||||||
or (
|
or (
|
||||||
isinstance(value, Breakpoints)
|
isinstance(value, Breakpoints)
|
||||||
and all(not isinstance(v, dict) for v in value.values())
|
and all(not isinstance(v, dict) for v in value.values())
|
||||||
)
|
)
|
||||||
|
or (isinstance(value, list) and all(not isinstance(v, dict) for v in value))
|
||||||
or (
|
or (
|
||||||
isinstance(value, ObjectVar)
|
isinstance(value, ObjectVar)
|
||||||
and not issubclass(get_origin(value._var_type) or value._var_type, dict)
|
and not issubclass(get_origin(value._var_type) or value._var_type, dict)
|
||||||
@ -236,7 +237,9 @@ def format_style_key(key: str) -> tuple[str, ...]:
|
|||||||
Returns:
|
Returns:
|
||||||
Tuple of css style names corresponding to the key provided.
|
Tuple of css style names corresponding to the key provided.
|
||||||
"""
|
"""
|
||||||
key = format.to_camel_case(key, allow_hyphens=True)
|
if key.startswith("--"):
|
||||||
|
return (key,)
|
||||||
|
key = format.to_camel_case(key)
|
||||||
return STYLE_PROP_SHORTHAND_MAPPING.get(key, (key,))
|
return STYLE_PROP_SHORTHAND_MAPPING.get(key, (key,))
|
||||||
|
|
||||||
|
|
||||||
|
@ -60,6 +60,7 @@ def _zip(
|
|||||||
dirs_to_exclude: set[str] | None = None,
|
dirs_to_exclude: set[str] | None = None,
|
||||||
files_to_exclude: set[str] | None = None,
|
files_to_exclude: set[str] | None = None,
|
||||||
top_level_dirs_to_exclude: set[str] | None = None,
|
top_level_dirs_to_exclude: set[str] | None = None,
|
||||||
|
globs_to_include: list[str] | None = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Zip utility function.
|
"""Zip utility function.
|
||||||
|
|
||||||
@ -72,6 +73,7 @@ def _zip(
|
|||||||
dirs_to_exclude: The directories to exclude.
|
dirs_to_exclude: The directories to exclude.
|
||||||
files_to_exclude: The files to exclude.
|
files_to_exclude: The files to exclude.
|
||||||
top_level_dirs_to_exclude: The top level directory names immediately under root_dir to exclude. Do not exclude folders by these names further in the sub-directories.
|
top_level_dirs_to_exclude: The top level directory names immediately under root_dir to exclude. Do not exclude folders by these names further in the sub-directories.
|
||||||
|
globs_to_include: Apply these globs from the root_dir and always include them in the zip.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
target = Path(target)
|
target = Path(target)
|
||||||
@ -103,6 +105,13 @@ def _zip(
|
|||||||
files_to_zip += [
|
files_to_zip += [
|
||||||
str(root / file) for file in files if file not in files_to_exclude
|
str(root / file) for file in files if file not in files_to_exclude
|
||||||
]
|
]
|
||||||
|
if globs_to_include:
|
||||||
|
for glob in globs_to_include:
|
||||||
|
files_to_zip += [
|
||||||
|
str(file)
|
||||||
|
for file in root_dir.glob(glob)
|
||||||
|
if file.name not in files_to_exclude
|
||||||
|
]
|
||||||
|
|
||||||
# Create a progress bar for zipping the component.
|
# Create a progress bar for zipping the component.
|
||||||
progress = Progress(
|
progress = Progress(
|
||||||
@ -160,6 +169,9 @@ def zip_app(
|
|||||||
top_level_dirs_to_exclude={"assets"},
|
top_level_dirs_to_exclude={"assets"},
|
||||||
exclude_venv_dirs=True,
|
exclude_venv_dirs=True,
|
||||||
upload_db_file=upload_db_file,
|
upload_db_file=upload_db_file,
|
||||||
|
globs_to_include=[
|
||||||
|
str(Path(constants.Dirs.WEB) / constants.Dirs.BACKEND / "*")
|
||||||
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -584,3 +584,12 @@ def is_prod_mode() -> bool:
|
|||||||
"""
|
"""
|
||||||
current_mode = environment.REFLEX_ENV_MODE.get()
|
current_mode = environment.REFLEX_ENV_MODE.get()
|
||||||
return current_mode == constants.Env.PROD
|
return current_mode == constants.Env.PROD
|
||||||
|
|
||||||
|
|
||||||
|
def get_compile_context() -> constants.CompileContext:
|
||||||
|
"""Check if the app is compiled for deploy.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Whether the app is being compiled for deploy.
|
||||||
|
"""
|
||||||
|
return environment.REFLEX_COMPILE_CONTEXT.get()
|
||||||
|
@ -168,7 +168,7 @@ def to_snake_case(text: str) -> str:
|
|||||||
return re.sub("([a-z0-9])([A-Z])", r"\1_\2", s1).lower().replace("-", "_")
|
return re.sub("([a-z0-9])([A-Z])", r"\1_\2", s1).lower().replace("-", "_")
|
||||||
|
|
||||||
|
|
||||||
def to_camel_case(text: str, allow_hyphens: bool = False) -> str:
|
def to_camel_case(text: str, treat_hyphens_as_underscores: bool = True) -> str:
|
||||||
"""Convert a string to camel case.
|
"""Convert a string to camel case.
|
||||||
|
|
||||||
The first word in the text is converted to lowercase and
|
The first word in the text is converted to lowercase and
|
||||||
@ -176,17 +176,16 @@ def to_camel_case(text: str, allow_hyphens: bool = False) -> str:
|
|||||||
|
|
||||||
Args:
|
Args:
|
||||||
text: The string to convert.
|
text: The string to convert.
|
||||||
allow_hyphens: Whether to allow hyphens in the string.
|
treat_hyphens_as_underscores: Whether to allow hyphens in the string.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
The camel case string.
|
The camel case string.
|
||||||
"""
|
"""
|
||||||
char = "_" if allow_hyphens else "-_"
|
char = "_" if not treat_hyphens_as_underscores else "-_"
|
||||||
words = re.split(f"[{char}]", text.lstrip(char))
|
words = re.split(f"[{char}]", text)
|
||||||
leading_underscores_or_hyphens = "".join(re.findall(rf"^[{char}]+", text))
|
|
||||||
# Capitalize the first letter of each word except the first one
|
# Capitalize the first letter of each word except the first one
|
||||||
converted_word = words[0] + "".join(x.capitalize() for x in words[1:])
|
converted_word = words[0] + "".join(x.capitalize() for x in words[1:])
|
||||||
return leading_underscores_or_hyphens + converted_word
|
return converted_word
|
||||||
|
|
||||||
|
|
||||||
def to_title_case(text: str, sep: str = "") -> str:
|
def to_title_case(text: str, sep: str = "") -> str:
|
||||||
|
@ -99,6 +99,15 @@ def get_states_dir() -> Path:
|
|||||||
return environment.REFLEX_STATES_WORKDIR.get()
|
return environment.REFLEX_STATES_WORKDIR.get()
|
||||||
|
|
||||||
|
|
||||||
|
def get_backend_dir() -> Path:
|
||||||
|
"""Get the working directory for the backend.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The working directory.
|
||||||
|
"""
|
||||||
|
return get_web_dir() / constants.Dirs.BACKEND
|
||||||
|
|
||||||
|
|
||||||
def check_latest_package_version(package_name: str):
|
def check_latest_package_version(package_name: str):
|
||||||
"""Check if the latest version of the package is installed.
|
"""Check if the latest version of the package is installed.
|
||||||
|
|
||||||
@ -2001,6 +2010,22 @@ def is_generation_hash(template: str) -> bool:
|
|||||||
return re.match(r"^[0-9a-f]{32,}$", template) is not None
|
return re.match(r"^[0-9a-f]{32,}$", template) is not None
|
||||||
|
|
||||||
|
|
||||||
|
def get_user_tier():
|
||||||
|
"""Get the current user's tier.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The current user's tier.
|
||||||
|
"""
|
||||||
|
from reflex_cli.v2.utils import hosting
|
||||||
|
|
||||||
|
authenticated_token = hosting.authenticated_token()
|
||||||
|
return (
|
||||||
|
authenticated_token[1].get("tier", "").lower()
|
||||||
|
if authenticated_token[0]
|
||||||
|
else "anonymous"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def check_config_option_in_tier(
|
def check_config_option_in_tier(
|
||||||
option_name: str,
|
option_name: str,
|
||||||
allowed_tiers: list[str],
|
allowed_tiers: list[str],
|
||||||
@ -2015,23 +2040,21 @@ def check_config_option_in_tier(
|
|||||||
fallback_value: The fallback value if the option is not allowed.
|
fallback_value: The fallback value if the option is not allowed.
|
||||||
help_link: The help link to show to a user that is authenticated.
|
help_link: The help link to show to a user that is authenticated.
|
||||||
"""
|
"""
|
||||||
from reflex_cli.v2.utils import hosting
|
|
||||||
|
|
||||||
config = get_config()
|
config = get_config()
|
||||||
authenticated_token = hosting.authenticated_token()
|
current_tier = get_user_tier()
|
||||||
if not authenticated_token[0]:
|
|
||||||
|
if current_tier == "anonymous":
|
||||||
the_remedy = (
|
the_remedy = (
|
||||||
"You are currently logged out. Run `reflex login` to access this option."
|
"You are currently logged out. Run `reflex login` to access this option."
|
||||||
)
|
)
|
||||||
current_tier = "anonymous"
|
|
||||||
else:
|
else:
|
||||||
current_tier = authenticated_token[1].get("tier", "").lower()
|
|
||||||
the_remedy = (
|
the_remedy = (
|
||||||
f"Your current subscription tier is `{current_tier}`. "
|
f"Your current subscription tier is `{current_tier}`. "
|
||||||
f"Please upgrade to {allowed_tiers} to access this option. "
|
f"Please upgrade to {allowed_tiers} to access this option. "
|
||||||
)
|
)
|
||||||
if help_link:
|
if help_link:
|
||||||
the_remedy += f"See {help_link} for more information."
|
the_remedy += f"See {help_link} for more information."
|
||||||
|
|
||||||
if current_tier not in allowed_tiers:
|
if current_tier not in allowed_tiers:
|
||||||
console.warn(f"Config option `{option_name}` is restricted. {the_remedy}")
|
console.warn(f"Config option `{option_name}` is restricted. {the_remedy}")
|
||||||
setattr(config, option_name, fallback_value)
|
setattr(config, option_name, fallback_value)
|
||||||
|
@ -75,6 +75,7 @@ from reflex.utils.types import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
|
from reflex.components.component import BaseComponent
|
||||||
from reflex.state import BaseState
|
from reflex.state import BaseState
|
||||||
|
|
||||||
from .number import BooleanVar, LiteralBooleanVar, LiteralNumberVar, NumberVar
|
from .number import BooleanVar, LiteralBooleanVar, LiteralNumberVar, NumberVar
|
||||||
@ -131,6 +132,9 @@ class VarData:
|
|||||||
# Position of the hook in the component
|
# Position of the hook in the component
|
||||||
position: Hooks.HookPosition | None = None
|
position: Hooks.HookPosition | None = None
|
||||||
|
|
||||||
|
# Components that are part of this var
|
||||||
|
components: Tuple[BaseComponent, ...] = dataclasses.field(default_factory=tuple)
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
state: str = "",
|
state: str = "",
|
||||||
@ -139,6 +143,7 @@ class VarData:
|
|||||||
hooks: Mapping[str, VarData | None] | Sequence[str] | str | None = None,
|
hooks: Mapping[str, VarData | None] | Sequence[str] | str | None = None,
|
||||||
deps: list[Var] | None = None,
|
deps: list[Var] | None = None,
|
||||||
position: Hooks.HookPosition | None = None,
|
position: Hooks.HookPosition | None = None,
|
||||||
|
components: Iterable[BaseComponent] | None = None,
|
||||||
):
|
):
|
||||||
"""Initialize the var data.
|
"""Initialize the var data.
|
||||||
|
|
||||||
@ -149,6 +154,7 @@ class VarData:
|
|||||||
hooks: Hooks that need to be present in the component to render this var.
|
hooks: Hooks that need to be present in the component to render this var.
|
||||||
deps: Dependencies of the var for useCallback.
|
deps: Dependencies of the var for useCallback.
|
||||||
position: Position of the hook in the component.
|
position: Position of the hook in the component.
|
||||||
|
components: Components that are part of this var.
|
||||||
"""
|
"""
|
||||||
if isinstance(hooks, str):
|
if isinstance(hooks, str):
|
||||||
hooks = [hooks]
|
hooks = [hooks]
|
||||||
@ -163,6 +169,7 @@ class VarData:
|
|||||||
object.__setattr__(self, "hooks", tuple(hooks or {}))
|
object.__setattr__(self, "hooks", tuple(hooks or {}))
|
||||||
object.__setattr__(self, "deps", tuple(deps or []))
|
object.__setattr__(self, "deps", tuple(deps or []))
|
||||||
object.__setattr__(self, "position", position or None)
|
object.__setattr__(self, "position", position or None)
|
||||||
|
object.__setattr__(self, "components", tuple(components or []))
|
||||||
|
|
||||||
if hooks and any(hooks.values()):
|
if hooks and any(hooks.values()):
|
||||||
merged_var_data = VarData.merge(self, *hooks.values())
|
merged_var_data = VarData.merge(self, *hooks.values())
|
||||||
@ -173,6 +180,7 @@ class VarData:
|
|||||||
object.__setattr__(self, "hooks", merged_var_data.hooks)
|
object.__setattr__(self, "hooks", merged_var_data.hooks)
|
||||||
object.__setattr__(self, "deps", merged_var_data.deps)
|
object.__setattr__(self, "deps", merged_var_data.deps)
|
||||||
object.__setattr__(self, "position", merged_var_data.position)
|
object.__setattr__(self, "position", merged_var_data.position)
|
||||||
|
object.__setattr__(self, "components", merged_var_data.components)
|
||||||
|
|
||||||
def old_school_imports(self) -> ImportDict:
|
def old_school_imports(self) -> ImportDict:
|
||||||
"""Return the imports as a mutable dict.
|
"""Return the imports as a mutable dict.
|
||||||
@ -241,17 +249,19 @@ class VarData:
|
|||||||
else:
|
else:
|
||||||
position = None
|
position = None
|
||||||
|
|
||||||
if state or _imports or hooks or field_name or deps or position:
|
components = tuple(
|
||||||
return VarData(
|
component for var_data in all_var_datas for component in var_data.components
|
||||||
state=state,
|
)
|
||||||
field_name=field_name,
|
|
||||||
imports=_imports,
|
|
||||||
hooks=hooks,
|
|
||||||
deps=deps,
|
|
||||||
position=position,
|
|
||||||
)
|
|
||||||
|
|
||||||
return None
|
return VarData(
|
||||||
|
state=state,
|
||||||
|
field_name=field_name,
|
||||||
|
imports=_imports,
|
||||||
|
hooks=hooks,
|
||||||
|
deps=deps,
|
||||||
|
position=position,
|
||||||
|
components=components,
|
||||||
|
)
|
||||||
|
|
||||||
def __bool__(self) -> bool:
|
def __bool__(self) -> bool:
|
||||||
"""Check if the var data is non-empty.
|
"""Check if the var data is non-empty.
|
||||||
@ -266,6 +276,7 @@ class VarData:
|
|||||||
or self.field_name
|
or self.field_name
|
||||||
or self.deps
|
or self.deps
|
||||||
or self.position
|
or self.position
|
||||||
|
or self.components
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
@ -7,24 +7,19 @@ import pytest
|
|||||||
from selenium.common.exceptions import NoSuchElementException
|
from selenium.common.exceptions import NoSuchElementException
|
||||||
from selenium.webdriver.common.by import By
|
from selenium.webdriver.common.by import By
|
||||||
|
|
||||||
|
from reflex import constants
|
||||||
|
from reflex.config import environment
|
||||||
from reflex.testing import AppHarness, WebDriver
|
from reflex.testing import AppHarness, WebDriver
|
||||||
|
|
||||||
from .utils import SessionStorage
|
from .utils import SessionStorage
|
||||||
|
|
||||||
|
|
||||||
def ConnectionBanner(is_reflex_cloud: bool = False):
|
def ConnectionBanner():
|
||||||
"""App with a connection banner.
|
"""App with a connection banner."""
|
||||||
|
|
||||||
Args:
|
|
||||||
is_reflex_cloud: The value for config.is_reflex_cloud.
|
|
||||||
"""
|
|
||||||
import asyncio
|
import asyncio
|
||||||
|
|
||||||
import reflex as rx
|
import reflex as rx
|
||||||
|
|
||||||
# Simulate reflex cloud deploy
|
|
||||||
rx.config.get_config().is_reflex_cloud = is_reflex_cloud
|
|
||||||
|
|
||||||
class State(rx.State):
|
class State(rx.State):
|
||||||
foo: int = 0
|
foo: int = 0
|
||||||
|
|
||||||
@ -49,16 +44,17 @@ def ConnectionBanner(is_reflex_cloud: bool = False):
|
|||||||
|
|
||||||
|
|
||||||
@pytest.fixture(
|
@pytest.fixture(
|
||||||
params=[False, True], ids=["reflex_cloud_disabled", "reflex_cloud_enabled"]
|
params=[constants.CompileContext.RUN, constants.CompileContext.DEPLOY],
|
||||||
|
ids=["compile_context_run", "compile_context_deploy"],
|
||||||
)
|
)
|
||||||
def simulate_is_reflex_cloud(request) -> bool:
|
def simulate_compile_context(request) -> constants.CompileContext:
|
||||||
"""Fixture to simulate reflex cloud deployment.
|
"""Fixture to simulate reflex cloud deployment.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
request: pytest request fixture.
|
request: pytest request fixture.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
True if reflex cloud is enabled, False otherwise.
|
The context to run the app with.
|
||||||
"""
|
"""
|
||||||
return request.param
|
return request.param
|
||||||
|
|
||||||
@ -66,25 +62,27 @@ def simulate_is_reflex_cloud(request) -> bool:
|
|||||||
@pytest.fixture()
|
@pytest.fixture()
|
||||||
def connection_banner(
|
def connection_banner(
|
||||||
tmp_path,
|
tmp_path,
|
||||||
simulate_is_reflex_cloud: bool,
|
simulate_compile_context: constants.CompileContext,
|
||||||
) -> Generator[AppHarness, None, None]:
|
) -> Generator[AppHarness, None, None]:
|
||||||
"""Start ConnectionBanner app at tmp_path via AppHarness.
|
"""Start ConnectionBanner app at tmp_path via AppHarness.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
tmp_path: pytest tmp_path fixture
|
tmp_path: pytest tmp_path fixture
|
||||||
simulate_is_reflex_cloud: Whether is_reflex_cloud is set for the app.
|
simulate_compile_context: Which context to run the app with.
|
||||||
|
|
||||||
Yields:
|
Yields:
|
||||||
running AppHarness instance
|
running AppHarness instance
|
||||||
"""
|
"""
|
||||||
|
environment.REFLEX_COMPILE_CONTEXT.set(simulate_compile_context)
|
||||||
|
|
||||||
with AppHarness.create(
|
with AppHarness.create(
|
||||||
root=tmp_path,
|
root=tmp_path,
|
||||||
app_source=functools.partial(
|
app_source=functools.partial(ConnectionBanner),
|
||||||
ConnectionBanner, is_reflex_cloud=simulate_is_reflex_cloud
|
app_name=(
|
||||||
|
"connection_banner_reflex_cloud"
|
||||||
|
if simulate_compile_context == constants.CompileContext.DEPLOY
|
||||||
|
else "connection_banner"
|
||||||
),
|
),
|
||||||
app_name="connection_banner_reflex_cloud"
|
|
||||||
if simulate_is_reflex_cloud
|
|
||||||
else "connection_banner",
|
|
||||||
) as harness:
|
) as harness:
|
||||||
yield harness
|
yield harness
|
||||||
|
|
||||||
@ -194,13 +192,13 @@ async def test_connection_banner(connection_banner: AppHarness):
|
|||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_cloud_banner(
|
async def test_cloud_banner(
|
||||||
connection_banner: AppHarness, simulate_is_reflex_cloud: bool
|
connection_banner: AppHarness, simulate_compile_context: constants.CompileContext
|
||||||
):
|
):
|
||||||
"""Test that the connection banner is displayed when the websocket drops.
|
"""Test that the connection banner is displayed when the websocket drops.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
connection_banner: AppHarness instance.
|
connection_banner: AppHarness instance.
|
||||||
simulate_is_reflex_cloud: Whether is_reflex_cloud is set for the app.
|
simulate_compile_context: Which context to set for the app.
|
||||||
"""
|
"""
|
||||||
assert connection_banner.app_instance is not None
|
assert connection_banner.app_instance is not None
|
||||||
assert connection_banner.backend is not None
|
assert connection_banner.backend is not None
|
||||||
@ -213,7 +211,7 @@ async def test_cloud_banner(
|
|||||||
|
|
||||||
driver.add_cookie({"name": "backend-enabled", "value": "false"})
|
driver.add_cookie({"name": "backend-enabled", "value": "false"})
|
||||||
driver.refresh()
|
driver.refresh()
|
||||||
if simulate_is_reflex_cloud:
|
if simulate_compile_context == constants.CompileContext.DEPLOY:
|
||||||
assert connection_banner._poll_for(lambda: has_cloud_banner(driver))
|
assert connection_banner._poll_for(lambda: has_cloud_banner(driver))
|
||||||
else:
|
else:
|
||||||
_assert_token(connection_banner, driver)
|
_assert_token(connection_banner, driver)
|
||||||
|
@ -157,7 +157,7 @@ def test_create_map_fn_var_subclass(cls, fn_body, fn_args, explicit_return, expe
|
|||||||
value, **props
|
value, **props
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
r"""(({node, inline, className, children, ...props}) => { const match = (className || '').match(/language-(?<lang>.*)/); let _language = match ? match[1] : ''; ; return inline ? ( <RadixThemesCode {...props}>{children}</RadixThemesCode> ) : ( <RadixThemesBox css={({ ["pre"] : ({ ["margin"] : "0", ["padding"] : "24px", ["background"] : "transparent", ["overflow-x"] : "auto", ["border-radius"] : "6px" }) })} {...props}><ShikiCode code={((Array.isArray(children)) ? children.join("\n") : children)} decorations={[]} language={_language} theme={((resolvedColorMode === "light") ? "one-light" : "one-dark-pro")} transformers={[]}/></RadixThemesBox> ); })""",
|
r"""(({node, inline, className, children, ...props}) => { const match = (className || '').match(/language-(?<lang>.*)/); let _language = match ? match[1] : ''; ; return inline ? ( <RadixThemesCode {...props}>{children}</RadixThemesCode> ) : ( <RadixThemesBox css={({ ["pre"] : ({ ["margin"] : "0", ["padding"] : "24px", ["background"] : "transparent", ["overflowX"] : "auto", ["borderRadius"] : "6px" }) })} {...props}><ShikiCode code={((Array.isArray(children)) ? children.join("\n") : children)} decorations={[]} language={_language} theme={((resolvedColorMode === "light") ? "one-light" : "one-dark-pro")} transformers={[]}/></RadixThemesBox> ); })""",
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"h1",
|
"h1",
|
||||||
|
@ -651,7 +651,7 @@ def test_create_filters_none_props(test_component):
|
|||||||
|
|
||||||
# Assert that the style prop is present in the component's props
|
# Assert that the style prop is present in the component's props
|
||||||
assert str(component.style["color"]) == '"white"'
|
assert str(component.style["color"]) == '"white"'
|
||||||
assert str(component.style["text-align"]) == '"center"'
|
assert str(component.style["textAlign"]) == '"center"'
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
@ -871,7 +871,7 @@ def test_create_custom_component(my_component):
|
|||||||
"""
|
"""
|
||||||
component = CustomComponent(component_fn=my_component, prop1="test", prop2=1)
|
component = CustomComponent(component_fn=my_component, prop1="test", prop2=1)
|
||||||
assert component.tag == "MyComponent"
|
assert component.tag == "MyComponent"
|
||||||
assert component.get_props() == set()
|
assert component.get_props() == {"prop1", "prop2"}
|
||||||
assert component._get_all_custom_components() == {component}
|
assert component._get_all_custom_components() == {component}
|
||||||
|
|
||||||
|
|
||||||
|
@ -189,11 +189,11 @@ def test_to_snake_case(input: str, output: str):
|
|||||||
("kebab-case", "kebabCase"),
|
("kebab-case", "kebabCase"),
|
||||||
("kebab-case-two", "kebabCaseTwo"),
|
("kebab-case-two", "kebabCaseTwo"),
|
||||||
("snake_kebab-case", "snakeKebabCase"),
|
("snake_kebab-case", "snakeKebabCase"),
|
||||||
("_hover", "_hover"),
|
("_hover", "Hover"),
|
||||||
("-starts-with-hyphen", "-startsWithHyphen"),
|
("-starts-with-hyphen", "StartsWithHyphen"),
|
||||||
("--starts-with-double-hyphen", "--startsWithDoubleHyphen"),
|
("--starts-with-double-hyphen", "StartsWithDoubleHyphen"),
|
||||||
("_starts_with_underscore", "_startsWithUnderscore"),
|
("_starts_with_underscore", "StartsWithUnderscore"),
|
||||||
("__starts_with_double_underscore", "__startsWithDoubleUnderscore"),
|
("__starts_with_double_underscore", "StartsWithDoubleUnderscore"),
|
||||||
(":start-with-colon", ":startWithColon"),
|
(":start-with-colon", ":startWithColon"),
|
||||||
(":-start-with-colon-dash", ":StartWithColonDash"),
|
(":-start-with-colon-dash", ":StartWithColonDash"),
|
||||||
],
|
],
|
||||||
|
Loading…
Reference in New Issue
Block a user