diff --git a/reflex/compiler/compiler.py b/reflex/compiler/compiler.py index f333c3779de..7e78b36bf85 100644 --- a/reflex/compiler/compiler.py +++ b/reflex/compiler/compiler.py @@ -161,7 +161,7 @@ def _compile_page(component: BaseComponent) -> str: # Compile the code to render the component. return templates.page_template( imports=imports, - dynamic_imports=component._get_all_dynamic_imports(), + dynamic_imports=sorted(component._get_all_dynamic_imports()), custom_codes=component._get_all_custom_code(), hooks=component._get_all_hooks(), render=component.render(), @@ -379,7 +379,7 @@ def _compile_components( templates.custom_component_template( imports=utils.compile_imports(imports), components=component_renders, - dynamic_imports=dynamic_imports, + dynamic_imports=sorted(dynamic_imports), custom_codes=custom_codes, ), imports, @@ -435,9 +435,7 @@ def get_shared_components_recursive(component: BaseComponent): rendered_components.update(dict.fromkeys(dynamic_imports)) # Include custom code in the shared component. - rendered_components.update( - dict.fromkeys(component._get_all_custom_code(export=True)), - ) + rendered_components.update(component._get_all_custom_code(export=True)) # Include all imports in the shared component. all_import_dicts.append(component._get_all_imports()) diff --git a/reflex/compiler/templates.py b/reflex/compiler/templates.py index d47d89696b4..bf525b2fa51 100644 --- a/reflex/compiler/templates.py +++ b/reflex/compiler/templates.py @@ -164,7 +164,7 @@ def document_root_template(*, imports: list[_ImportDict], document: dict[str, An def app_root_template( *, imports: list[_ImportDict], - custom_codes: set[str], + custom_codes: Iterable[str], hooks: dict[str, VarData | None], window_libraries: list[tuple[str, str]], render: dict[str, Any], diff --git a/reflex/components/base/bare.py b/reflex/components/base/bare.py index 0bf53857a7d..4182d0666ba 100644 --- a/reflex/components/base/bare.py +++ b/reflex/components/base/bare.py @@ -129,7 +129,7 @@ def _get_all_dynamic_imports(self) -> set[str]: dynamic_imports |= component._get_all_dynamic_imports() return dynamic_imports - def _get_all_custom_code(self) -> set[str]: + def _get_all_custom_code(self) -> dict[str, None]: """Get custom code for the component. Returns: @@ -166,7 +166,7 @@ def _get_all_app_wrap_components( ) return app_wrap_components - def _get_all_refs(self) -> set[str]: + def _get_all_refs(self) -> dict[str, None]: """Get the refs for the children of the component. Returns: diff --git a/reflex/components/component.py b/reflex/components/component.py index 0bae03ca7ca..383ea75b560 100644 --- a/reflex/components/component.py +++ b/reflex/components/component.py @@ -10,7 +10,7 @@ import inspect import typing from abc import ABC, ABCMeta, abstractmethod -from collections.abc import Callable, Iterator, Mapping, Sequence +from collections.abc import Callable, Iterable, Iterator, Mapping, Sequence from dataclasses import _MISSING_TYPE, MISSING from functools import wraps from hashlib import md5 @@ -374,7 +374,7 @@ def _get_all_dynamic_imports(self) -> set[str]: """ @abstractmethod - def _get_all_custom_code(self) -> set[str]: + def _get_all_custom_code(self) -> dict[str, None]: """Get custom code for the component. Returns: @@ -382,7 +382,7 @@ def _get_all_custom_code(self) -> set[str]: """ @abstractmethod - def _get_all_refs(self) -> set[str]: + def _get_all_refs(self) -> dict[str, None]: """Get the refs for the children of the component. Returns: @@ -1003,13 +1003,13 @@ def _render(self, props: dict[str, Any] | None = None) -> Tag: @classmethod @functools.cache - def get_props(cls) -> set[str]: + def get_props(cls) -> Iterable[str]: """Get the unique fields for the component. Returns: The unique fields. """ - return set(cls.get_js_fields()) + return cls.get_js_fields() @classmethod @functools.cache @@ -1509,19 +1509,19 @@ def _get_custom_code(self) -> str | None: """ return None - def _get_all_custom_code(self) -> set[str]: + def _get_all_custom_code(self) -> dict[str, None]: """Get custom code for the component and its children. Returns: The custom code. """ # Store the code in a set to avoid duplicates. - code = set() + code: dict[str, None] = {} # Add the custom code for this component. custom_code = self._get_custom_code() if custom_code is not None: - code.add(custom_code) + code[custom_code] = None for component in self._get_components_in_props(): code |= component._get_all_custom_code() @@ -1529,7 +1529,7 @@ def _get_all_custom_code(self) -> set[str]: # Add the custom code from add_custom_code method. for clz in self._iter_parent_classes_with_method("add_custom_code"): for item in clz.add_custom_code(self): - code.add(item) + code[item] = None # Add the custom code for the children. for child in self.children: @@ -1814,7 +1814,7 @@ def _get_all_hooks_internal(self) -> dict[str, VarData | None]: # Add the hook code for the children. for child in self.children: - code = {**code, **child._get_all_hooks_internal()} + code.update(child._get_all_hooks_internal()) return code @@ -1838,7 +1838,7 @@ def _get_all_hooks(self) -> dict[str, VarData | None]: # Add the hook code for the children. for child in self.children: - code = {**code, **child._get_all_hooks()} + code.update(child._get_all_hooks()) return code @@ -1853,16 +1853,16 @@ def get_ref(self) -> str | None: return None return format.format_ref(self.id) - def _get_all_refs(self) -> set[str]: + def _get_all_refs(self) -> dict[str, None]: """Get the refs for the children of the component. Returns: The refs for the children. """ - refs = set() + refs = {} ref = self.get_ref() if ref is not None: - refs.add(ref) + refs[ref] = None for child in self.children: refs |= child._get_all_refs() for component in self._get_components_in_props(): @@ -1994,7 +1994,7 @@ def get_args_spec(key: str) -> types.ArgsSpec | Sequence[types.ArgsSpec]: ) to_camel_cased_props = { - format.to_camel_case(key + MEMO_MARKER) + format.to_camel_case(key + MEMO_MARKER): None for key in props if key not in event_types } @@ -2048,13 +2048,13 @@ def __hash__(self) -> int: return hash(self.tag) @classmethod - def get_props(cls) -> set[str]: + def get_props(cls) -> Iterable[str]: """Get the props for the component. Returns: The set of component props. """ - return set() + return () @staticmethod def _get_event_spec_from_args_spec(name: str, event: EventChain) -> Callable: @@ -2656,7 +2656,7 @@ def _get_all_dynamic_imports(self) -> set[str]: return set() return self.component._get_all_dynamic_imports() - def _get_all_custom_code(self, export: bool = False) -> set[str]: + def _get_all_custom_code(self, export: bool = False) -> dict[str, None]: """Get custom code for the component. Args: @@ -2666,19 +2666,19 @@ def _get_all_custom_code(self, export: bool = False) -> set[str]: The custom code. """ if self.rendered_as_shared: - return set() - return self.component._get_all_custom_code().union( - {self._render_stateful_code(export=export)} + return {} + return self.component._get_all_custom_code() | ( + {self._render_stateful_code(export=export): None} ) - def _get_all_refs(self) -> set[str]: + def _get_all_refs(self) -> dict[str, None]: """Get the refs for the children of the component. Returns: The refs for the children. """ if self.rendered_as_shared: - return set() + return {} return self.component._get_all_refs() def render(self) -> dict: diff --git a/reflex/components/core/debounce.py b/reflex/components/core/debounce.py index c539ecc0554..ff9129fc38d 100644 --- a/reflex/components/core/debounce.py +++ b/reflex/components/core/debounce.py @@ -132,7 +132,7 @@ def create(cls, *children: Component, **props: Any) -> Component: component.children = child.children component._rename_props = child._rename_props # pyright: ignore[reportAttributeAccessIssue] outer_get_all_custom_code = component._get_all_custom_code - component._get_all_custom_code = lambda: outer_get_all_custom_code().union( + component._get_all_custom_code = lambda: outer_get_all_custom_code() | ( child._get_all_custom_code() ) return component diff --git a/reflex/components/core/foreach.py b/reflex/components/core/foreach.py index 3ec03473d77..67dd3d83bbf 100644 --- a/reflex/components/core/foreach.py +++ b/reflex/components/core/foreach.py @@ -5,6 +5,7 @@ import functools import inspect from collections.abc import Callable, Iterable +from hashlib import md5 from typing import Any from reflex.components.base.fragment import Fragment @@ -141,25 +142,12 @@ def _render(self) -> IterTag: else: render_fn = self.render_fn # Otherwise, use a deterministic index, based on the render function bytecode. - code_hash = ( - hash( - getattr( - render_fn, - "__code__", - ( - repr(self.render_fn) - if not isinstance(render_fn, functools.partial) - else render_fn.func.__code__ - ), - ) - ) - .to_bytes( - length=8, - byteorder="big", - signed=True, - ) - .hex() - ) + if (render_fn_code := getattr(render_fn, "__code__", None)) is not None: + code_hash = md5(render_fn_code.co_code).hexdigest() + elif isinstance(render_fn, functools.partial): + code_hash = md5(render_fn.func.__code__.co_code).hexdigest() + else: + code_hash = md5(repr(render_fn).encode()).hexdigest() props["index_var_name"] = f"index_{code_hash}" return IterTag( diff --git a/reflex/components/core/upload.py b/reflex/components/core/upload.py index b81f8595bf0..a3d8cf06292 100644 --- a/reflex/components/core/upload.py +++ b/reflex/components/core/upload.py @@ -260,7 +260,7 @@ def create(cls, *children, **props) -> Component: props["class_name"] = ["rx-Upload", *given_class_name] # get only upload component props - supported_props = cls.get_props().union({"on_drop"}) + supported_props = set(cls.get_props()) | {"on_drop"} upload_props = { key: value for key, value in props.items() if key in supported_props } diff --git a/reflex/components/dynamic.py b/reflex/components/dynamic.py index 6ad9d0fe7de..79f87765a4a 100644 --- a/reflex/components/dynamic.py +++ b/reflex/components/dynamic.py @@ -81,9 +81,7 @@ def make_component(component: Component) -> str: rendered_components.update(dict.fromkeys(dynamic_imports)) # Include custom code in the shared component. - rendered_components.update( - dict.fromkeys(component._get_all_custom_code()), - ) + rendered_components.update(component._get_all_custom_code()) rendered_components[ templates.stateful_component_template( diff --git a/reflex/components/el/elements/forms.py b/reflex/components/el/elements/forms.py index a96e0670010..6548c29816f 100644 --- a/reflex/components/el/elements/forms.py +++ b/reflex/components/el/elements/forms.py @@ -770,7 +770,7 @@ def _exclude_props(self) -> list[str]: "enter_key_submit", ] - def _get_all_custom_code(self) -> set[str]: + def _get_all_custom_code(self) -> dict[str, None]: """Include the custom code for auto_height and enter_key_submit functionality. Returns: @@ -778,9 +778,9 @@ def _get_all_custom_code(self) -> set[str]: """ custom_code = super()._get_all_custom_code() if self.auto_height is not None: - custom_code.add(AUTO_HEIGHT_JS) + custom_code[AUTO_HEIGHT_JS] = None if self.enter_key_submit is not None: - custom_code.add(ENTER_KEY_SUBMIT_JS) + custom_code[ENTER_KEY_SUBMIT_JS] = None return custom_code diff --git a/reflex/vars/base.py b/reflex/vars/base.py index a007f1d1253..d28e98ae72f 100644 --- a/reflex/vars/base.py +++ b/reflex/vars/base.py @@ -9,7 +9,6 @@ import functools import inspect import json -import random import re import string import uuid @@ -50,6 +49,7 @@ from reflex.constants.compiler import Hooks from reflex.constants.state import FIELD_MARKER from reflex.utils import console, exceptions, imports, serializers, types +from reflex.utils.decorator import once from reflex.utils.exceptions import ( ComputedVarSignatureError, UntypedComputedVarError, @@ -3033,13 +3033,20 @@ def get_uuid_string_var() -> Var: USED_VARIABLES = set() +@once +def _rng(): + import random + + return random.Random(42) + + def get_unique_variable_name() -> str: """Get a unique variable name. Returns: The unique variable name. """ - name = "".join([random.choice(string.ascii_lowercase) for _ in range(8)]) + name = "".join([_rng().choice(string.ascii_lowercase) for _ in range(8)]) if name not in USED_VARIABLES: USED_VARIABLES.add(name) return name diff --git a/tests/units/components/test_component.py b/tests/units/components/test_component.py index 8d236d0f17a..616a5a238bf 100644 --- a/tests/units/components/test_component.py +++ b/tests/units/components/test_component.py @@ -501,7 +501,7 @@ def test_get_imports(component1, component2): } -def test_get_custom_code(component1, component2): +def test_get_custom_code(component1: Component, component2: Component): """Test getting the custom code of a component. Args: @@ -511,21 +511,21 @@ def test_get_custom_code(component1, component2): # Check that the code gets compiled correctly. c1 = component1.create() c2 = component2.create() - assert c1._get_all_custom_code() == {"console.log('component1')"} - assert c2._get_all_custom_code() == {"console.log('component2')"} + assert c1._get_all_custom_code() == {"console.log('component1')": None} + assert c2._get_all_custom_code() == {"console.log('component2')": None} # Check that nesting components compiles both codes. c1 = component1.create(c2) assert c1._get_all_custom_code() == { - "console.log('component1')", - "console.log('component2')", + "console.log('component1')": None, + "console.log('component2')": None, } # Check that code is not duplicated. c1 = component1.create(c2, c2, c1, c1) assert c1._get_all_custom_code() == { - "console.log('component1')", - "console.log('component2')", + "console.log('component1')": None, + "console.log('component2')": None, } @@ -536,8 +536,8 @@ def test_get_props(component1, component2): component1: A test component. component2: A test component. """ - assert component1.get_props() == {"text", "number", "text_or_number"} - assert component2.get_props() == {"arr", "on_prop_event"} + assert set(component1.get_props()) == {"text", "number", "text_or_number"} + assert set(component2.get_props()) == {"arr", "on_prop_event"} @pytest.mark.parametrize( @@ -825,7 +825,7 @@ def test_create_custom_component(my_component): """ component = rx.memo(my_component)(prop1="test", prop2=1) assert component.tag == "MyComponent" - assert component.get_props() == {"prop1RxMemo", "prop2RxMemo"} + assert set(component.get_props()) == {"prop1RxMemo", "prop2RxMemo"} assert component.tag in CUSTOM_COMPONENTS @@ -2017,50 +2017,52 @@ def add_custom_code(self): "const custom_code6 = 47", ] - assert BaseComponent.create()._get_all_custom_code() == {"const custom_code1 = 42"} + assert BaseComponent.create()._get_all_custom_code() == { + "const custom_code1 = 42": None + } assert ChildComponent1.create()._get_all_custom_code() == { - "const custom_code1 = 42" + "const custom_code1 = 42": None } assert GrandchildComponent1.create()._get_all_custom_code() == { - "const custom_code1 = 42", - "const custom_code2 = 43", - "const custom_code3 = 44", + "const custom_code1 = 42": None, + "const custom_code2 = 43": None, + "const custom_code3 = 44": None, } assert GreatGrandchildComponent1.create()._get_all_custom_code() == { - "const custom_code1 = 42", - "const custom_code2 = 43", - "const custom_code3 = 44", - "const custom_code4 = 45", + "const custom_code1 = 42": None, + "const custom_code2 = 43": None, + "const custom_code3 = 44": None, + "const custom_code4 = 45": None, } assert GrandchildComponent2.create()._get_all_custom_code() == { - "const custom_code5 = 46" + "const custom_code5 = 46": None } assert GreatGrandchildComponent2.create()._get_all_custom_code() == { - "const custom_code2 = 43", - "const custom_code5 = 46", - "const custom_code6 = 47", + "const custom_code2 = 43": None, + "const custom_code5 = 46": None, + "const custom_code6 = 47": None, } assert BaseComponent.create( GrandchildComponent1.create(GreatGrandchildComponent2.create()), GreatGrandchildComponent1.create(), )._get_all_custom_code() == { - "const custom_code1 = 42", - "const custom_code2 = 43", - "const custom_code3 = 44", - "const custom_code4 = 45", - "const custom_code5 = 46", - "const custom_code6 = 47", + "const custom_code1 = 42": None, + "const custom_code2 = 43": None, + "const custom_code3 = 44": None, + "const custom_code4 = 45": None, + "const custom_code5 = 46": None, + "const custom_code6 = 47": None, } assert Fragment.create( GreatGrandchildComponent2.create(), GreatGrandchildComponent1.create(), )._get_all_custom_code() == { - "const custom_code1 = 42", - "const custom_code2 = 43", - "const custom_code3 = 44", - "const custom_code4 = 45", - "const custom_code5 = 46", - "const custom_code6 = 47", + "const custom_code1 = 42": None, + "const custom_code2 = 43": None, + "const custom_code3 = 44": None, + "const custom_code4 = 45": None, + "const custom_code5 = 46": None, + "const custom_code6 = 47": None, }