From 1651289485d194118eafd6fa987608dbaaa100c6 Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Thu, 6 Feb 2025 10:09:05 -0800 Subject: [PATCH 01/10] use getattr when given str in getitem (#4761) * use getattr when given str in getitem * stronger checking and tests * switch ordering * use safe issubclass * calculate origin differently --- reflex/vars/object.py | 17 +++++++++++++---- tests/integration/test_var_operations.py | 19 +++++++++++++++++++ 2 files changed, 32 insertions(+), 4 deletions(-) diff --git a/reflex/vars/object.py b/reflex/vars/object.py index cb29cabfb..89479bbc4 100644 --- a/reflex/vars/object.py +++ b/reflex/vars/object.py @@ -22,7 +22,12 @@ from typing_extensions import is_typeddict from reflex.utils import types from reflex.utils.exceptions import VarAttributeError -from reflex.utils.types import GenericType, get_attribute_access_type, get_origin +from reflex.utils.types import ( + GenericType, + get_attribute_access_type, + get_origin, + safe_issubclass, +) from .base import ( CachedVarOperation, @@ -187,10 +192,14 @@ class ObjectVar(Var[OBJECT_TYPE], python_types=Mapping): Returns: The item from the object. """ + from .sequence import LiteralStringVar + if not isinstance(key, (StringVar, str, int, NumberVar)) or ( isinstance(key, NumberVar) and key._is_strict_float() ): raise_unsupported_operand_types("[]", (type(self), type(key))) + if isinstance(key, str) and isinstance(Var.create(key), LiteralStringVar): + return self.__getattr__(key) return ObjectItemOperation.create(self, key).guess_type() # NoReturn is used here to catch when key value is Any @@ -260,12 +269,12 @@ class ObjectVar(Var[OBJECT_TYPE], python_types=Mapping): if types.is_optional(var_type): var_type = get_args(var_type)[0] - fixed_type = var_type if isclass(var_type) else get_origin(var_type) + fixed_type = get_origin(var_type) or var_type if ( - (isclass(fixed_type) and not issubclass(fixed_type, Mapping)) + is_typeddict(fixed_type) + or (isclass(fixed_type) and not safe_issubclass(fixed_type, Mapping)) or (fixed_type in types.UnionTypes) - or is_typeddict(fixed_type) ): attribute_type = get_attribute_access_type(var_type, name) if attribute_type is None: diff --git a/tests/integration/test_var_operations.py b/tests/integration/test_var_operations.py index a5a74c9ee..16885cd06 100644 --- a/tests/integration/test_var_operations.py +++ b/tests/integration/test_var_operations.py @@ -10,6 +10,8 @@ from reflex.testing import AppHarness def VarOperations(): """App with var operations.""" + from typing import TypedDict + import reflex as rx from reflex.vars.base import LiteralVar from reflex.vars.sequence import ArrayVar @@ -17,6 +19,10 @@ def VarOperations(): class Object(rx.Base): name: str = "hello" + class Person(TypedDict): + name: str + age: int + class VarOperationState(rx.State): int_var1: rx.Field[int] = rx.field(10) int_var2: rx.Field[int] = rx.field(5) @@ -34,6 +40,9 @@ def VarOperations(): dict1: rx.Field[dict[int, int]] = rx.field({1: 2}) dict2: rx.Field[dict[int, int]] = rx.field({3: 4}) html_str: rx.Field[str] = rx.field("
hello
") + people: rx.Field[list[Person]] = rx.field( + [{"name": "Alice", "age": 30}, {"name": "Bob", "age": 25}] + ) app = rx.App(_state=rx.State) @@ -619,6 +628,15 @@ def VarOperations(): ), id="dict_in_foreach3", ), + rx.box( + rx.foreach( + VarOperationState.people, + lambda person: rx.text.span( + "Hello " + person["name"], person["age"] + 3 + ), + ), + id="typed_dict_in_foreach", + ), ) @@ -826,6 +844,7 @@ def test_var_operations(driver, var_operations: AppHarness): ("dict_in_foreach1", "a1b2"), ("dict_in_foreach2", "12"), ("dict_in_foreach3", "1234"), + ("typed_dict_in_foreach", "Hello Alice33Hello Bob28"), ] for tag, expected in tests: From 9d23271c14a136df85475c465aad94a46fe90052 Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Thu, 6 Feb 2025 10:09:26 -0800 Subject: [PATCH 02/10] improve behavior on missing language with markdown code blocks (#4750) * improve behavior on missing language with markdown code blocks * special case on literal var * fix tests * missing f * remove extra throw --- reflex/components/datadisplay/code.py | 46 +++++++++++++++---- reflex/components/datadisplay/code.pyi | 2 +- reflex/components/markdown/markdown.py | 35 +++++++++++--- reflex/components/markdown/markdown.pyi | 5 +- .../components/markdown/test_markdown.py | 9 ++-- 5 files changed, 73 insertions(+), 24 deletions(-) diff --git a/reflex/components/datadisplay/code.py b/reflex/components/datadisplay/code.py index 4f1eb493e..3e4794482 100644 --- a/reflex/components/datadisplay/code.py +++ b/reflex/components/datadisplay/code.py @@ -3,6 +3,7 @@ from __future__ import annotations import dataclasses +import typing from typing import ClassVar, Dict, Literal, Optional, Union from reflex.components.component import Component, ComponentNamespace @@ -503,7 +504,7 @@ class CodeBlock(Component, MarkdownComponentMap): return ["can_copy", "copy_button"] @classmethod - def _get_language_registration_hook(cls, language_var: Var = _LANGUAGE) -> str: + def _get_language_registration_hook(cls, language_var: Var = _LANGUAGE) -> Var: """Get the hook to register the language. Args: @@ -514,21 +515,46 @@ class CodeBlock(Component, MarkdownComponentMap): Returns: The hook to register the language. """ - return f""" - if ({language_var!s}) {{ - (async () => {{ - try {{ + language_in_there = Var.create(typing.get_args(LiteralCodeLanguage)).contains( + language_var + ) + async_load = f""" +(async () => {{ + try {{ const module = await import(`react-syntax-highlighter/dist/cjs/languages/prism/${{{language_var!s}}}`); SyntaxHighlighter.registerLanguage({language_var!s}, module.default); - }} catch (error) {{ - console.error(`Error importing language module for ${{{language_var!s}}}:`, error); - }} - }})(); + }} catch (error) {{ + console.error(`Language ${{{language_var!s}}} is not supported for code blocks inside of markdown: `, error); + }} +}})(); +""" + return Var( + f""" + if ({language_var!s}) {{ + if (!{language_in_there!s}) {{ + console.warn(`Language \\`${{{language_var!s}}}\\` is not supported for code blocks inside of markdown.`); + {language_var!s} = ''; + }} else {{ + {async_load!s} + }} }} """ + if not isinstance(language_var, LiteralVar) + else f""" +if ({language_var!s}) {{ + {async_load!s} +}}""", + _var_data=VarData( + imports={ + cls.__fields__["library"].default: [ + ImportVar(tag="PrismAsyncLight", alias="SyntaxHighlighter") + ] + }, + ), + ) @classmethod - def get_component_map_custom_code(cls) -> str: + def get_component_map_custom_code(cls) -> Var: """Get the custom code for the component. Returns: diff --git a/reflex/components/datadisplay/code.pyi b/reflex/components/datadisplay/code.pyi index fc35092fe..fda92a974 100644 --- a/reflex/components/datadisplay/code.pyi +++ b/reflex/components/datadisplay/code.pyi @@ -984,7 +984,7 @@ class CodeBlock(Component, MarkdownComponentMap): def add_style(self): ... @classmethod - def get_component_map_custom_code(cls) -> str: ... + def get_component_map_custom_code(cls) -> Var: ... def add_hooks(self) -> list[str | Var]: ... class CodeblockNamespace(ComponentNamespace): diff --git a/reflex/components/markdown/markdown.py b/reflex/components/markdown/markdown.py index 27bd5bd62..91d34ea9b 100644 --- a/reflex/components/markdown/markdown.py +++ b/reflex/components/markdown/markdown.py @@ -12,7 +12,7 @@ from reflex.components.component import BaseComponent, Component, CustomComponen from reflex.components.tags.tag import Tag from reflex.utils import types from reflex.utils.imports import ImportDict, ImportVar -from reflex.vars.base import LiteralVar, Var +from reflex.vars.base import LiteralVar, Var, VarData from reflex.vars.function import ARRAY_ISARRAY, ArgsFunctionOperation, DestructuredArg from reflex.vars.number import ternary_operation @@ -83,13 +83,13 @@ class MarkdownComponentMap: _explicit_return: bool = dataclasses.field(default=False) @classmethod - def get_component_map_custom_code(cls) -> str: + def get_component_map_custom_code(cls) -> Var: """Get the custom code for the component map. Returns: The custom code for the component map. """ - return "" + return Var("") @classmethod def create_map_fn_var( @@ -97,6 +97,7 @@ class MarkdownComponentMap: fn_body: Var | None = None, fn_args: Sequence[str] | None = None, explicit_return: bool | None = None, + var_data: VarData | None = None, ) -> Var: """Create a function Var for the component map. @@ -104,6 +105,7 @@ class MarkdownComponentMap: fn_body: The formatted component as a string. fn_args: The function arguments. explicit_return: Whether to use explicit return syntax. + var_data: The var data for the function. Returns: The function Var for the component map. @@ -116,6 +118,7 @@ class MarkdownComponentMap: args_names=(DestructuredArg(fields=tuple(fn_args)),), return_expr=fn_body, explicit_return=explicit_return, + _var_data=var_data, ) @classmethod @@ -239,6 +242,15 @@ class Markdown(Component): component(_MOCK_ARG)._get_all_imports() for component in self.component_map.values() ], + *( + [inline_code_var_data.old_school_imports()] + if ( + inline_code_var_data + := self._get_inline_code_fn_var()._get_all_var_data() + ) + is not None + else [] + ), ] def _get_tag_map_fn_var(self, tag: str) -> Var: @@ -278,12 +290,20 @@ class Markdown(Component): self._get_map_fn_custom_code_from_children(self.get_component("code")) ) - codeblock_custom_code = "\n".join(custom_code_list) + var_data = VarData.merge( + *[ + code._get_all_var_data() + for code in custom_code_list + if isinstance(code, Var) + ] + ) + + codeblock_custom_code = "\n".join(map(str, custom_code_list)) # Format the code to handle inline and block code. formatted_code = f""" const match = (className || '').match(/language-(?.*)/); -const {_LANGUAGE!s} = match ? match[1] : ''; +let {_LANGUAGE!s} = match ? match[1] : ''; {codeblock_custom_code}; return inline ? ( {self.format_component("code")} @@ -302,6 +322,7 @@ const {_LANGUAGE!s} = match ? match[1] : ''; ), fn_body=Var(_js_expr=formatted_code), explicit_return=True, + var_data=var_data, ) def get_component(self, tag: str, **props) -> Component: @@ -381,7 +402,7 @@ const {_LANGUAGE!s} = match ? match[1] : ''; def _get_map_fn_custom_code_from_children( self, component: BaseComponent - ) -> list[str]: + ) -> list[str | Var]: """Recursively get markdown custom code from children components. Args: @@ -390,7 +411,7 @@ const {_LANGUAGE!s} = match ? match[1] : ''; Returns: A list of markdown custom code strings. """ - custom_code_list = [] + custom_code_list: list[str | Var] = [] if isinstance(component, MarkdownComponentMap): custom_code_list.append(component.get_component_map_custom_code()) diff --git a/reflex/components/markdown/markdown.pyi b/reflex/components/markdown/markdown.pyi index 606780a7a..61ddee094 100644 --- a/reflex/components/markdown/markdown.pyi +++ b/reflex/components/markdown/markdown.pyi @@ -11,7 +11,7 @@ from reflex.components.component import Component from reflex.event import EventType from reflex.style import Style from reflex.utils.imports import ImportDict -from reflex.vars.base import LiteralVar, Var +from reflex.vars.base import LiteralVar, Var, VarData _CHILDREN = Var(_js_expr="children", _var_type=str) _PROPS = Var(_js_expr="...props") @@ -32,13 +32,14 @@ def get_base_component_map() -> dict[str, Callable]: ... @dataclasses.dataclass() class MarkdownComponentMap: @classmethod - def get_component_map_custom_code(cls) -> str: ... + def get_component_map_custom_code(cls) -> Var: ... @classmethod def create_map_fn_var( cls, fn_body: Var | None = None, fn_args: Sequence[str] | None = None, explicit_return: bool | None = None, + var_data: VarData | None = None, ) -> Var: ... @classmethod def get_fn_args(cls) -> Sequence[str]: ... diff --git a/tests/units/components/markdown/test_markdown.py b/tests/units/components/markdown/test_markdown.py index 866f32ae1..c6d395eb1 100644 --- a/tests/units/components/markdown/test_markdown.py +++ b/tests/units/components/markdown/test_markdown.py @@ -148,7 +148,7 @@ def test_create_map_fn_var_subclass(cls, fn_body, fn_args, explicit_return, expe ( "code", {}, - """(({node, inline, className, children, ...props}) => { const match = (className || '').match(/language-(?.*)/); const _language = match ? match[1] : ''; if (_language) { (async () => { try { const module = await import(`react-syntax-highlighter/dist/cjs/languages/prism/${_language}`); SyntaxHighlighter.registerLanguage(_language, module.default); } catch (error) { console.error(`Error importing language module for ${_language}:`, error); } })(); } ; return inline ? ( {children} ) : ( ); })""", + r"""(({node, inline, className, children, ...props}) => { const match = (className || '').match(/language-(?.*)/); let _language = match ? match[1] : ''; if (_language) { if (!["abap", "abnf", "actionscript", "ada", "agda", "al", "antlr4", "apacheconf", "apex", "apl", "applescript", "aql", "arduino", "arff", "asciidoc", "asm6502", "asmatmel", "aspnet", "autohotkey", "autoit", "avisynth", "avro-idl", "bash", "basic", "batch", "bbcode", "bicep", "birb", "bison", "bnf", "brainfuck", "brightscript", "bro", "bsl", "c", "cfscript", "chaiscript", "cil", "clike", "clojure", "cmake", "cobol", "coffeescript", "concurnas", "coq", "core", "cpp", "crystal", "csharp", "cshtml", "csp", "css", "css-extras", "csv", "cypher", "d", "dart", "dataweave", "dax", "dhall", "diff", "django", "dns-zone-file", "docker", "dot", "ebnf", "editorconfig", "eiffel", "ejs", "elixir", "elm", "erb", "erlang", "etlua", "excel-formula", "factor", "false", "firestore-security-rules", "flow", "fortran", "fsharp", "ftl", "gap", "gcode", "gdscript", "gedcom", "gherkin", "git", "glsl", "gml", "gn", "go", "go-module", "graphql", "groovy", "haml", "handlebars", "haskell", "haxe", "hcl", "hlsl", "hoon", "hpkp", "hsts", "http", "ichigojam", "icon", "icu-message-format", "idris", "iecst", "ignore", "index", "inform7", "ini", "io", "j", "java", "javadoc", "javadoclike", "javascript", "javastacktrace", "jexl", "jolie", "jq", "js-extras", "js-templates", "jsdoc", "json", "json5", "jsonp", "jsstacktrace", "jsx", "julia", "keepalived", "keyman", "kotlin", "kumir", "kusto", "latex", "latte", "less", "lilypond", "liquid", "lisp", "livescript", "llvm", "log", "lolcode", "lua", "magma", "makefile", "markdown", "markup", "markup-templating", "matlab", "maxscript", "mel", "mermaid", "mizar", "mongodb", "monkey", "moonscript", "n1ql", "n4js", "nand2tetris-hdl", "naniscript", "nasm", "neon", "nevod", "nginx", "nim", "nix", "nsis", "objectivec", "ocaml", "opencl", "openqasm", "oz", "parigp", "parser", "pascal", "pascaligo", "pcaxis", "peoplecode", "perl", "php", "php-extras", "phpdoc", "plsql", "powerquery", "powershell", "processing", "prolog", "promql", "properties", "protobuf", "psl", "pug", "puppet", "pure", "purebasic", "purescript", "python", "q", "qml", "qore", "qsharp", "r", "racket", "reason", "regex", "rego", "renpy", "rest", "rip", "roboconf", "robotframework", "ruby", "rust", "sas", "sass", "scala", "scheme", "scss", "shell-session", "smali", "smalltalk", "smarty", "sml", "solidity", "solution-file", "soy", "sparql", "splunk-spl", "sqf", "sql", "squirrel", "stan", "stylus", "swift", "systemd", "t4-cs", "t4-templating", "t4-vb", "tap", "tcl", "textile", "toml", "tremor", "tsx", "tt2", "turtle", "twig", "typescript", "typoscript", "unrealscript", "uorazor", "uri", "v", "vala", "vbnet", "velocity", "verilog", "vhdl", "vim", "visual-basic", "warpscript", "wasm", "web-idl", "wiki", "wolfram", "wren", "xeora", "xml-doc", "xojo", "xquery", "yaml", "yang", "zig"].includes(_language)) { console.warn(`Language \`${_language}\` is not supported for code blocks inside of markdown.`); _language = ''; } else { (async () => { try { const module = await import(`react-syntax-highlighter/dist/cjs/languages/prism/${_language}`); SyntaxHighlighter.registerLanguage(_language, module.default); } catch (error) { console.error(`Language ${_language} is not supported for code blocks inside of markdown: `, error); } })(); } } ; return inline ? ( {children} ) : ( ); })""", ), ( "code", @@ -157,7 +157,7 @@ def test_create_map_fn_var_subclass(cls, fn_body, fn_args, explicit_return, expe value, **props ) }, - """(({node, inline, className, children, ...props}) => { const match = (className || '').match(/language-(?.*)/); const _language = match ? match[1] : ''; ; return inline ? ( {children} ) : ( ); })""", + r"""(({node, inline, className, children, ...props}) => { const match = (className || '').match(/language-(?.*)/); let _language = match ? match[1] : ''; ; return inline ? ( {children} ) : ( ); })""", ), ( "h1", @@ -171,7 +171,7 @@ def test_create_map_fn_var_subclass(cls, fn_body, fn_args, explicit_return, expe ( "code", {"codeblock": syntax_highlighter_memoized_component(CodeBlock)}, - """(({node, inline, className, children, ...props}) => { const match = (className || '').match(/language-(?.*)/); const _language = match ? match[1] : ''; if (_language) { (async () => { try { const module = await import(`react-syntax-highlighter/dist/cjs/languages/prism/${_language}`); SyntaxHighlighter.registerLanguage(_language, module.default); } catch (error) { console.error(`Error importing language module for ${_language}:`, error); } })(); } ; return inline ? ( {children} ) : ( ); })""", + r"""(({node, inline, className, children, ...props}) => { const match = (className || '').match(/language-(?.*)/); let _language = match ? match[1] : ''; if (_language) { if (!["abap", "abnf", "actionscript", "ada", "agda", "al", "antlr4", "apacheconf", "apex", "apl", "applescript", "aql", "arduino", "arff", "asciidoc", "asm6502", "asmatmel", "aspnet", "autohotkey", "autoit", "avisynth", "avro-idl", "bash", "basic", "batch", "bbcode", "bicep", "birb", "bison", "bnf", "brainfuck", "brightscript", "bro", "bsl", "c", "cfscript", "chaiscript", "cil", "clike", "clojure", "cmake", "cobol", "coffeescript", "concurnas", "coq", "core", "cpp", "crystal", "csharp", "cshtml", "csp", "css", "css-extras", "csv", "cypher", "d", "dart", "dataweave", "dax", "dhall", "diff", "django", "dns-zone-file", "docker", "dot", "ebnf", "editorconfig", "eiffel", "ejs", "elixir", "elm", "erb", "erlang", "etlua", "excel-formula", "factor", "false", "firestore-security-rules", "flow", "fortran", "fsharp", "ftl", "gap", "gcode", "gdscript", "gedcom", "gherkin", "git", "glsl", "gml", "gn", "go", "go-module", "graphql", "groovy", "haml", "handlebars", "haskell", "haxe", "hcl", "hlsl", "hoon", "hpkp", "hsts", "http", "ichigojam", "icon", "icu-message-format", "idris", "iecst", "ignore", "index", "inform7", "ini", "io", "j", "java", "javadoc", "javadoclike", "javascript", "javastacktrace", "jexl", "jolie", "jq", "js-extras", "js-templates", "jsdoc", "json", "json5", "jsonp", "jsstacktrace", "jsx", "julia", "keepalived", "keyman", "kotlin", "kumir", "kusto", "latex", "latte", "less", "lilypond", "liquid", "lisp", "livescript", "llvm", "log", "lolcode", "lua", "magma", "makefile", "markdown", "markup", "markup-templating", "matlab", "maxscript", "mel", "mermaid", "mizar", "mongodb", "monkey", "moonscript", "n1ql", "n4js", "nand2tetris-hdl", "naniscript", "nasm", "neon", "nevod", "nginx", "nim", "nix", "nsis", "objectivec", "ocaml", "opencl", "openqasm", "oz", "parigp", "parser", "pascal", "pascaligo", "pcaxis", "peoplecode", "perl", "php", "php-extras", "phpdoc", "plsql", "powerquery", "powershell", "processing", "prolog", "promql", "properties", "protobuf", "psl", "pug", "puppet", "pure", "purebasic", "purescript", "python", "q", "qml", "qore", "qsharp", "r", "racket", "reason", "regex", "rego", "renpy", "rest", "rip", "roboconf", "robotframework", "ruby", "rust", "sas", "sass", "scala", "scheme", "scss", "shell-session", "smali", "smalltalk", "smarty", "sml", "solidity", "solution-file", "soy", "sparql", "splunk-spl", "sqf", "sql", "squirrel", "stan", "stylus", "swift", "systemd", "t4-cs", "t4-templating", "t4-vb", "tap", "tcl", "textile", "toml", "tremor", "tsx", "tt2", "turtle", "twig", "typescript", "typoscript", "unrealscript", "uorazor", "uri", "v", "vala", "vbnet", "velocity", "verilog", "vhdl", "vim", "visual-basic", "warpscript", "wasm", "web-idl", "wiki", "wolfram", "wren", "xeora", "xml-doc", "xojo", "xquery", "yaml", "yang", "zig"].includes(_language)) { console.warn(`Language \`${_language}\` is not supported for code blocks inside of markdown.`); _language = ''; } else { (async () => { try { const module = await import(`react-syntax-highlighter/dist/cjs/languages/prism/${_language}`); SyntaxHighlighter.registerLanguage(_language, module.default); } catch (error) { console.error(`Language ${_language} is not supported for code blocks inside of markdown: `, error); } })(); } } ; return inline ? ( {children} ) : ( ); })""", ), ( "code", @@ -180,11 +180,12 @@ def test_create_map_fn_var_subclass(cls, fn_body, fn_args, explicit_return, expe ShikiHighLevelCodeBlock ) }, - """(({node, inline, className, children, ...props}) => { const match = (className || '').match(/language-(?.*)/); const _language = match ? match[1] : ''; ; return inline ? ( {children} ) : ( ); })""", + r"""(({node, inline, className, children, ...props}) => { const match = (className || '').match(/language-(?.*)/); let _language = match ? match[1] : ''; ; return inline ? ( {children} ) : ( ); })""", ), ], ) def test_markdown_format_component(key, component_map, expected): markdown = Markdown.create("# header", component_map=component_map) result = markdown.format_component_map() + print(str(result[key])) assert str(result[key]) == expected From ab558ce17285a30ccf88d479277f2b2ebea2760c Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Thu, 6 Feb 2025 10:09:40 -0800 Subject: [PATCH 03/10] increase nested type checking for component var types (#4756) * increase nested type checking for component var types * handle mapping as dict in type hint * fix weird cases of using _isinstance instead of isinstance * test out nested=0 * move union below * don't use _instance for simple unions --- reflex/components/component.py | 15 +++------ reflex/components/core/match.py | 8 ++--- reflex/components/markdown/markdown.py | 5 ++- reflex/components/tags/tag.py | 4 +-- reflex/utils/types.py | 42 +++++++++++++++++--------- 5 files changed, 40 insertions(+), 34 deletions(-) diff --git a/reflex/components/component.py b/reflex/components/component.py index 6d1264f4d..6e4c6c37f 100644 --- a/reflex/components/component.py +++ b/reflex/components/component.py @@ -179,6 +179,7 @@ ComponentStyle = Dict[ Union[str, Type[BaseComponent], Callable, ComponentNamespace], Any ] ComponentChild = Union[types.PrimitiveType, Var, BaseComponent] +ComponentChildTypes = (*types.PrimitiveTypes, Var, BaseComponent) def satisfies_type_hint(obj: Any, type_hint: Any) -> bool: @@ -191,11 +192,7 @@ def satisfies_type_hint(obj: Any, type_hint: Any) -> bool: Returns: Whether the object satisfies the type hint. """ - if isinstance(obj, LiteralVar): - return types._isinstance(obj._var_value, type_hint) - if isinstance(obj, Var): - return types._issubclass(obj._var_type, type_hint) - return types._isinstance(obj, type_hint) + return types._isinstance(obj, type_hint, nested=1) class Component(BaseComponent, ABC): @@ -712,8 +709,8 @@ class Component(BaseComponent, ABC): validate_children(child) # Make sure the child is a valid type. - if isinstance(child, dict) or not types._isinstance( - child, ComponentChild + if isinstance(child, dict) or not isinstance( + child, ComponentChildTypes ): raise ChildrenTypeError(component=cls.__name__, child=child) @@ -1771,9 +1768,7 @@ class CustomComponent(Component): return [ Var( _js_expr=name, - _var_type=( - prop._var_type if types._isinstance(prop, Var) else type(prop) - ), + _var_type=(prop._var_type if isinstance(prop, Var) else type(prop)), ).guess_type() for name, prop in self.props.items() ] diff --git a/reflex/components/core/match.py b/reflex/components/core/match.py index 5c31669a1..2d936544a 100644 --- a/reflex/components/core/match.py +++ b/reflex/components/core/match.py @@ -178,9 +178,9 @@ class Match(MemoizationLeaf): first_case_return = match_cases[0][-1] return_type = type(first_case_return) - if types._isinstance(first_case_return, BaseComponent): + if isinstance(first_case_return, BaseComponent): return_type = BaseComponent - elif types._isinstance(first_case_return, Var): + elif isinstance(first_case_return, Var): return_type = Var for index, case in enumerate(match_cases): @@ -228,8 +228,8 @@ class Match(MemoizationLeaf): # Validate the match cases (as well as the default case) to have Var return types. if any( - case for case in match_cases if not types._isinstance(case[-1], Var) - ) or not types._isinstance(default, Var): + case for case in match_cases if not isinstance(case[-1], Var) + ) or not isinstance(default, Var): raise ValueError("Return types of match cases should be Vars.") return Var( diff --git a/reflex/components/markdown/markdown.py b/reflex/components/markdown/markdown.py index 91d34ea9b..51d3dd3dd 100644 --- a/reflex/components/markdown/markdown.py +++ b/reflex/components/markdown/markdown.py @@ -6,11 +6,10 @@ import dataclasses import textwrap from functools import lru_cache from hashlib import md5 -from typing import Any, Callable, Dict, Sequence, Union +from typing import Any, Callable, Dict, Sequence from reflex.components.component import BaseComponent, Component, CustomComponent from reflex.components.tags.tag import Tag -from reflex.utils import types from reflex.utils.imports import ImportDict, ImportVar from reflex.vars.base import LiteralVar, Var, VarData from reflex.vars.function import ARRAY_ISARRAY, ArgsFunctionOperation, DestructuredArg @@ -169,7 +168,7 @@ class Markdown(Component): Returns: The markdown component. """ - if len(children) != 1 or not types._isinstance(children[0], Union[str, Var]): + if len(children) != 1 or not isinstance(children[0], (str, Var)): raise ValueError( "Markdown component must have exactly one child containing the markdown source." ) diff --git a/reflex/components/tags/tag.py b/reflex/components/tags/tag.py index 983726e56..515d9e05f 100644 --- a/reflex/components/tags/tag.py +++ b/reflex/components/tags/tag.py @@ -3,7 +3,7 @@ from __future__ import annotations import dataclasses -from typing import Any, Dict, List, Optional, Sequence, Union +from typing import Any, Dict, List, Mapping, Optional, Sequence from reflex.event import EventChain from reflex.utils import format, types @@ -103,7 +103,7 @@ class Tag: { format.to_camel_case(name, allow_hyphens=True): ( prop - if types._isinstance(prop, Union[EventChain, dict]) + if types._isinstance(prop, (EventChain, Mapping)) else LiteralVar.create(prop) ) # rx.color is always a string for name, prop in kwargs.items() diff --git a/reflex/utils/types.py b/reflex/utils/types.py index 58fec8f3b..b432319e0 100644 --- a/reflex/utils/types.py +++ b/reflex/utils/types.py @@ -95,6 +95,7 @@ GenericType = Union[Type, _GenericAlias] # Valid state var types. JSONType = {str, int, float, bool} PrimitiveType = Union[int, float, bool, str, list, dict, set, tuple] +PrimitiveTypes = (int, float, bool, str, list, dict, set, tuple) StateVar = Union[PrimitiveType, Base, None] StateIterVar = Union[list, set, tuple] @@ -551,13 +552,13 @@ def does_obj_satisfy_typed_dict(obj: Any, cls: GenericType) -> bool: return required_keys.issubset(required_keys) -def _isinstance(obj: Any, cls: GenericType, nested: bool = False) -> bool: +def _isinstance(obj: Any, cls: GenericType, nested: int = 0) -> bool: """Check if an object is an instance of a class. Args: obj: The object to check. cls: The class to check against. - nested: Whether the check is nested. + nested: How many levels deep to check. Returns: Whether the object is an instance of the class. @@ -565,15 +566,24 @@ def _isinstance(obj: Any, cls: GenericType, nested: bool = False) -> bool: if cls is Any: return True + from reflex.vars import LiteralVar, Var + + if cls is Var: + return isinstance(obj, Var) + if isinstance(obj, LiteralVar): + return _isinstance(obj._var_value, cls, nested=nested) + if isinstance(obj, Var): + return _issubclass(obj._var_type, cls) + if cls is None or cls is type(None): return obj is None + if cls and is_union(cls): + return any(_isinstance(obj, arg, nested=nested) for arg in get_args(cls)) + if is_literal(cls): return obj in get_args(cls) - if is_union(cls): - return any(_isinstance(obj, arg) for arg in get_args(cls)) - origin = get_origin(cls) if origin is None: @@ -596,38 +606,40 @@ def _isinstance(obj: Any, cls: GenericType, nested: bool = False) -> bool: # cls is a simple generic class return isinstance(obj, origin) - if nested and args: + if nested > 0 and args: if origin is list: return isinstance(obj, list) and all( - _isinstance(item, args[0]) for item in obj + _isinstance(item, args[0], nested=nested - 1) for item in obj ) if origin is tuple: if args[-1] is Ellipsis: return isinstance(obj, tuple) and all( - _isinstance(item, args[0]) for item in obj + _isinstance(item, args[0], nested=nested - 1) for item in obj ) return ( isinstance(obj, tuple) and len(obj) == len(args) and all( - _isinstance(item, arg) for item, arg in zip(obj, args, strict=True) + _isinstance(item, arg, nested=nested - 1) + for item, arg in zip(obj, args, strict=True) ) ) - if origin in (dict, Breakpoints): - return isinstance(obj, dict) and all( - _isinstance(key, args[0]) and _isinstance(value, args[1]) + if origin in (dict, Mapping, Breakpoints): + return isinstance(obj, Mapping) and all( + _isinstance(key, args[0], nested=nested - 1) + and _isinstance(value, args[1], nested=nested - 1) for key, value in obj.items() ) if origin is set: return isinstance(obj, set) and all( - _isinstance(item, args[0]) for item in obj + _isinstance(item, args[0], nested=nested - 1) for item in obj ) if args: from reflex.vars import Field if origin is Field: - return _isinstance(obj, args[0]) + return _isinstance(obj, args[0], nested=nested) return isinstance(obj, get_base_class(cls)) @@ -749,7 +761,7 @@ def check_prop_in_allowed_types(prop: Any, allowed_types: Iterable) -> bool: """ from reflex.vars import Var - type_ = prop._var_type if _isinstance(prop, Var) else type(prop) + type_ = prop._var_type if isinstance(prop, Var) else type(prop) return type_ in allowed_types From b3b79a652d3e1b7f85740f0e6210f536236f7041 Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Thu, 6 Feb 2025 10:41:38 -0800 Subject: [PATCH 04/10] improve foreach behavior with strings (#4751) * improve foreach behavior with strings * add a defensive guard before giving up * add integration tests --- reflex/components/core/foreach.py | 14 ++++++++++++-- tests/integration/test_var_operations.py | 10 ++++++++++ 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/reflex/components/core/foreach.py b/reflex/components/core/foreach.py index e9222b200..13db48575 100644 --- a/reflex/components/core/foreach.py +++ b/reflex/components/core/foreach.py @@ -54,9 +54,10 @@ class Foreach(Component): TypeError: If the render function is a ComponentState. UntypedVarError: If the iterable is of type Any without a type annotation. """ - from reflex.vars.object import ObjectVar + from reflex.vars import ArrayVar, ObjectVar, StringVar + + iterable = LiteralVar.create(iterable).guess_type() - iterable = LiteralVar.create(iterable) if iterable._var_type == Any: raise ForeachVarError( f"Could not foreach over var `{iterable!s}` of type Any. " @@ -75,6 +76,15 @@ class Foreach(Component): if isinstance(iterable, ObjectVar): iterable = iterable.entries() + if isinstance(iterable, StringVar): + iterable = iterable.split() + + if not isinstance(iterable, ArrayVar): + raise ForeachVarError( + f"Could not foreach over var `{iterable!s}` of type {iterable._var_type}. " + "See https://reflex.dev/docs/library/dynamic-rendering/foreach/" + ) + component = cls( iterable=iterable, render_fn=render_fn, diff --git a/tests/integration/test_var_operations.py b/tests/integration/test_var_operations.py index 16885cd06..35763556a 100644 --- a/tests/integration/test_var_operations.py +++ b/tests/integration/test_var_operations.py @@ -628,6 +628,14 @@ def VarOperations(): ), id="dict_in_foreach3", ), + rx.box( + rx.foreach("abcdef", lambda x: rx.text.span(x + " ")), + id="str_in_foreach", + ), + rx.box( + rx.foreach(VarOperationState.str_var1, lambda x: rx.text.span(x + " ")), + id="str_var_in_foreach", + ), rx.box( rx.foreach( VarOperationState.people, @@ -844,6 +852,8 @@ def test_var_operations(driver, var_operations: AppHarness): ("dict_in_foreach1", "a1b2"), ("dict_in_foreach2", "12"), ("dict_in_foreach3", "1234"), + ("str_in_foreach", "a b c d e f"), + ("str_var_in_foreach", "f i r s t"), ("typed_dict_in_foreach", "Hello Alice33Hello Bob28"), ] From f3220470e8141a2017edb3a2183a78b30e0a903e Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Fri, 7 Feb 2025 14:20:29 -0800 Subject: [PATCH 05/10] fix dynamic icons for underscore and positional argument (#4767) * fix dynamic icons for underscore and positional argument * use no return --- reflex/components/core/sticky.py | 4 +-- reflex/components/lucide/icon.py | 14 ++++++-- reflex/vars/base.py | 34 ++++++++++++++----- reflex/vars/number.py | 2 +- reflex/vars/sequence.py | 31 +++++++++++++++-- .../components/datadisplay/test_shiki_code.py | 5 ++- tests/units/vars/test_object.py | 8 ++--- 7 files changed, 75 insertions(+), 23 deletions(-) diff --git a/reflex/components/core/sticky.py b/reflex/components/core/sticky.py index 162bab3cd..cbcec00a9 100644 --- a/reflex/components/core/sticky.py +++ b/reflex/components/core/sticky.py @@ -107,9 +107,7 @@ class StickyBadge(A): default=True, global_ref=False, ) - localhost_hostnames = Var.create( - ["localhost", "127.0.0.1", "[::1]"] - ).guess_type() + localhost_hostnames = Var.create(["localhost", "127.0.0.1", "[::1]"]) is_localhost_expr = localhost_hostnames.contains( Var("window.location.hostname", _var_type=str).guess_type(), ) diff --git a/reflex/components/lucide/icon.py b/reflex/components/lucide/icon.py index 6c7cbede7..269ef7f79 100644 --- a/reflex/components/lucide/icon.py +++ b/reflex/components/lucide/icon.py @@ -4,7 +4,7 @@ from reflex.components.component import Component from reflex.utils import format from reflex.utils.imports import ImportVar from reflex.vars.base import LiteralVar, Var -from reflex.vars.sequence import LiteralStringVar +from reflex.vars.sequence import LiteralStringVar, StringVar class LucideIconComponent(Component): @@ -40,7 +40,12 @@ class Icon(LucideIconComponent): The created component. """ if children: - if len(children) == 1 and isinstance(children[0], str): + if len(children) == 1: + child = Var.create(children[0]).guess_type() + if not isinstance(child, StringVar): + raise AttributeError( + f"Icon name must be a string, got {children[0]._var_type if isinstance(children[0], Var) else children[0]}" + ) props["tag"] = children[0] else: raise AttributeError( @@ -56,7 +61,10 @@ class Icon(LucideIconComponent): else: raise TypeError(f"Icon name must be a string, got {type(tag)}") elif isinstance(tag, Var): - return DynamicIcon.create(name=tag, **props) + tag_stringified = tag.guess_type() + if not isinstance(tag_stringified, StringVar): + raise TypeError(f"Icon name must be a string, got {tag._var_type}") + return DynamicIcon.create(name=tag_stringified.replace("_", "-"), **props) if ( not isinstance(tag, str) diff --git a/reflex/vars/base.py b/reflex/vars/base.py index c9dd81986..0d8af8f3c 100644 --- a/reflex/vars/base.py +++ b/reflex/vars/base.py @@ -75,9 +75,9 @@ from reflex.utils.types import ( if TYPE_CHECKING: from reflex.state import BaseState - from .number import BooleanVar, NumberVar - from .object import ObjectVar - from .sequence import ArrayVar, StringVar + from .number import BooleanVar, LiteralBooleanVar, LiteralNumberVar, NumberVar + from .object import LiteralObjectVar, ObjectVar + from .sequence import ArrayVar, LiteralArrayVar, LiteralStringVar, StringVar VAR_TYPE = TypeVar("VAR_TYPE", covariant=True) @@ -573,13 +573,21 @@ class Var(Generic[VAR_TYPE]): return value_with_replaced + @overload + @classmethod + def create( # pyright: ignore[reportOverlappingOverload] + cls, + value: NoReturn, + _var_data: VarData | None = None, + ) -> Var[Any]: ... + @overload @classmethod def create( # pyright: ignore[reportOverlappingOverload] cls, value: bool, _var_data: VarData | None = None, - ) -> BooleanVar: ... + ) -> LiteralBooleanVar: ... @overload @classmethod @@ -587,7 +595,7 @@ class Var(Generic[VAR_TYPE]): cls, value: int, _var_data: VarData | None = None, - ) -> NumberVar[int]: ... + ) -> LiteralNumberVar[int]: ... @overload @classmethod @@ -595,7 +603,15 @@ class Var(Generic[VAR_TYPE]): cls, value: float, _var_data: VarData | None = None, - ) -> NumberVar[float]: ... + ) -> LiteralNumberVar[float]: ... + + @overload + @classmethod + def create( # pyright: ignore [reportOverlappingOverload] + cls, + value: str, + _var_data: VarData | None = None, + ) -> LiteralStringVar: ... @overload @classmethod @@ -611,7 +627,7 @@ class Var(Generic[VAR_TYPE]): cls, value: None, _var_data: VarData | None = None, - ) -> NoneVar: ... + ) -> LiteralNoneVar: ... @overload @classmethod @@ -619,7 +635,7 @@ class Var(Generic[VAR_TYPE]): cls, value: MAPPING_TYPE, _var_data: VarData | None = None, - ) -> ObjectVar[MAPPING_TYPE]: ... + ) -> LiteralObjectVar[MAPPING_TYPE]: ... @overload @classmethod @@ -627,7 +643,7 @@ class Var(Generic[VAR_TYPE]): cls, value: SEQUENCE_TYPE, _var_data: VarData | None = None, - ) -> ArrayVar[SEQUENCE_TYPE]: ... + ) -> LiteralArrayVar[SEQUENCE_TYPE]: ... @overload @classmethod diff --git a/reflex/vars/number.py b/reflex/vars/number.py index 35a55490a..87f1760a6 100644 --- a/reflex/vars/number.py +++ b/reflex/vars/number.py @@ -974,7 +974,7 @@ def boolean_not_operation(value: BooleanVar): frozen=True, slots=True, ) -class LiteralNumberVar(LiteralVar, NumberVar): +class LiteralNumberVar(LiteralVar, NumberVar[NUMBER_T]): """Base class for immutable literal number vars.""" _var_value: float | int = dataclasses.field(default=0) diff --git a/reflex/vars/sequence.py b/reflex/vars/sequence.py index fb797b4ec..0e7b082f9 100644 --- a/reflex/vars/sequence.py +++ b/reflex/vars/sequence.py @@ -372,6 +372,33 @@ class StringVar(Var[STRING_TYPE], python_types=str): return string_ge_operation(self, other) + @overload + def replace( # pyright: ignore [reportOverlappingOverload] + self, search_value: StringVar | str, new_value: StringVar | str + ) -> StringVar: ... + + @overload + def replace( + self, search_value: Any, new_value: Any + ) -> CustomVarOperationReturn[StringVar]: ... + + def replace(self, search_value: Any, new_value: Any) -> StringVar: # pyright: ignore [reportInconsistentOverload] + """Replace a string with a value. + + Args: + search_value: The string to search. + new_value: The value to be replaced with. + + Returns: + The string replace operation. + """ + if not isinstance(search_value, (StringVar, str)): + raise_unsupported_operand_types("replace", (type(self), type(search_value))) + if not isinstance(new_value, (StringVar, str)): + raise_unsupported_operand_types("replace", (type(self), type(new_value))) + + return string_replace_operation(self, search_value, new_value) + @var_operation def string_lt_operation(lhs: StringVar[Any] | str, rhs: StringVar[Any] | str): @@ -570,7 +597,7 @@ def array_join_operation(array: ArrayVar, sep: StringVar[Any] | str = ""): @var_operation def string_replace_operation( - string: StringVar, search_value: StringVar | str, new_value: StringVar | str + string: StringVar[Any], search_value: StringVar | str, new_value: StringVar | str ): """Replace a string with a value. @@ -583,7 +610,7 @@ def string_replace_operation( The string replace operation. """ return var_operation_return( - js_expression=f"{string}.replace({search_value}, {new_value})", + js_expression=f"{string}.replaceAll({search_value}, {new_value})", var_type=str, ) diff --git a/tests/units/components/datadisplay/test_shiki_code.py b/tests/units/components/datadisplay/test_shiki_code.py index cc05c35b0..e1c7984f1 100644 --- a/tests/units/components/datadisplay/test_shiki_code.py +++ b/tests/units/components/datadisplay/test_shiki_code.py @@ -11,6 +11,7 @@ from reflex.components.lucide.icon import Icon from reflex.components.radix.themes.layout.box import Box from reflex.style import Style from reflex.vars import Var +from reflex.vars.base import LiteralVar @pytest.mark.parametrize( @@ -99,7 +100,9 @@ def test_create_shiki_code_block( applied_styles = component.style for key, value in expected_styles.items(): - assert Var.create(applied_styles[key])._var_value == value + var = Var.create(applied_styles[key]) + assert isinstance(var, LiteralVar) + assert var._var_value == value @pytest.mark.parametrize( diff --git a/tests/units/vars/test_object.py b/tests/units/vars/test_object.py index 90e34be96..89ace55bb 100644 --- a/tests/units/vars/test_object.py +++ b/tests/units/vars/test_object.py @@ -74,11 +74,11 @@ class ObjectState(rx.State): @pytest.mark.parametrize("type_", [Base, Bare, SqlaModel, Dataclass]) -def test_var_create(type_: GenericType) -> None: +def test_var_create(type_: type[Base | Bare | SqlaModel | Dataclass]) -> None: my_object = type_() var = Var.create(my_object) assert var._var_type is type_ - + assert isinstance(var, ObjectVar) quantity = var.quantity assert quantity._var_type is int @@ -94,12 +94,12 @@ def test_literal_create(type_: GenericType) -> None: @pytest.mark.parametrize("type_", [Base, Bare, SqlaModel, Dataclass]) -def test_guess(type_: GenericType) -> None: +def test_guess(type_: type[Base | Bare | SqlaModel | Dataclass]) -> None: my_object = type_() var = Var.create(my_object) var = var.guess_type() assert var._var_type is type_ - + assert isinstance(var, ObjectVar) quantity = var.quantity assert quantity._var_type is int From c17cda3e951a54e47222a6eb75c6584a8b56491b Mon Sep 17 00:00:00 2001 From: Masen Furer Date: Fri, 7 Feb 2025 14:57:12 -0800 Subject: [PATCH 06/10] Ensure EventCallback exposes EventActionsMixin properties (#4772) --- reflex/event.py | 31 +++++++------------------------ 1 file changed, 7 insertions(+), 24 deletions(-) diff --git a/reflex/event.py b/reflex/event.py index f247047cf..c2eb8db3a 100644 --- a/reflex/event.py +++ b/reflex/event.py @@ -26,6 +26,7 @@ from typing import ( from typing_extensions import ( Protocol, + Self, TypeAliasType, TypedDict, TypeVar, @@ -110,7 +111,7 @@ class EventActionsMixin: event_actions: Dict[str, Union[bool, int]] = dataclasses.field(default_factory=dict) @property - def stop_propagation(self): + def stop_propagation(self) -> Self: """Stop the event from bubbling up the DOM tree. Returns: @@ -122,7 +123,7 @@ class EventActionsMixin: ) @property - def prevent_default(self): + def prevent_default(self) -> Self: """Prevent the default behavior of the event. Returns: @@ -133,7 +134,7 @@ class EventActionsMixin: event_actions={"preventDefault": True, **self.event_actions}, ) - def throttle(self, limit_ms: int): + def throttle(self, limit_ms: int) -> Self: """Throttle the event handler. Args: @@ -147,7 +148,7 @@ class EventActionsMixin: event_actions={"throttle": limit_ms, **self.event_actions}, ) - def debounce(self, delay_ms: int): + def debounce(self, delay_ms: int) -> Self: """Debounce the event handler. Args: @@ -162,7 +163,7 @@ class EventActionsMixin: ) @property - def temporal(self): + def temporal(self) -> Self: """Do not queue the event if the backend is down. Returns: @@ -1773,7 +1774,7 @@ V4 = TypeVar("V4") V5 = TypeVar("V5") -class EventCallback(Generic[Unpack[P]]): +class EventCallback(Generic[Unpack[P]], EventActionsMixin): """A descriptor that wraps a function to be used as an event.""" def __init__(self, func: Callable[[Any, Unpack[P]], Any]): @@ -1784,24 +1785,6 @@ class EventCallback(Generic[Unpack[P]]): """ self.func = func - @property - def prevent_default(self): - """Prevent default behavior. - - Returns: - The event callback with prevent default behavior. - """ - return self - - @property - def stop_propagation(self): - """Stop event propagation. - - Returns: - The event callback with stop propagation behavior. - """ - return self - @overload def __call__( self: EventCallback[Unpack[Q]], From 70920a64be4fb1b55827010f33a6117006c132e1 Mon Sep 17 00:00:00 2001 From: Masen Furer Date: Fri, 7 Feb 2025 14:59:22 -0800 Subject: [PATCH 07/10] Copy/update assets on compile (#4765) * Add path_ops.update_directory_tree: Copy missing and newer files from src to dest * add console.timing context Log debug messages with timing for different processes. * Update assets tree as app._compile step. If the assets change between hot reload, then update them before reloading (in case a CSS file was added or something). * Add timing for other app._compile events * Only copy assets if assets exist * Fix docstring for update_directory_tree --- reflex/app.py | 66 +++++++++++++++++++++++++++------------- reflex/utils/console.py | 19 ++++++++++++ reflex/utils/path_ops.py | 30 ++++++++++++++++++ 3 files changed, 94 insertions(+), 21 deletions(-) diff --git a/reflex/app.py b/reflex/app.py index a3d0d8e10..18cce69d2 100644 --- a/reflex/app.py +++ b/reflex/app.py @@ -99,7 +99,15 @@ from reflex.state import ( _substate_key, code_uses_state_contexts, ) -from reflex.utils import codespaces, console, exceptions, format, prerequisites, types +from reflex.utils import ( + codespaces, + console, + exceptions, + format, + path_ops, + prerequisites, + types, +) from reflex.utils.exec import is_prod_mode, is_testing_env from reflex.utils.imports import ImportVar @@ -991,9 +999,10 @@ class App(MiddlewareMixin, LifespanMixin): should_compile = self._should_compile() if not should_compile: - for route in self._unevaluated_pages: - console.debug(f"Evaluating page: {route}") - self._compile_page(route, save_page=should_compile) + with console.timing("Evaluate Pages (Backend)"): + for route in self._unevaluated_pages: + console.debug(f"Evaluating page: {route}") + self._compile_page(route, save_page=should_compile) # Add the optional endpoints (_upload) self._add_optional_endpoints() @@ -1019,10 +1028,11 @@ class App(MiddlewareMixin, LifespanMixin): + adhoc_steps_without_executor, ) - for route in self._unevaluated_pages: - console.debug(f"Evaluating page: {route}") - self._compile_page(route, save_page=should_compile) - progress.advance(task) + with console.timing("Evaluate Pages (Frontend)"): + for route in self._unevaluated_pages: + console.debug(f"Evaluating page: {route}") + self._compile_page(route, save_page=should_compile) + progress.advance(task) # Add the optional endpoints (_upload) self._add_optional_endpoints() @@ -1057,13 +1067,13 @@ class App(MiddlewareMixin, LifespanMixin): custom_components |= component._get_all_custom_components() # Perform auto-memoization of stateful components. - ( - stateful_components_path, - stateful_components_code, - page_components, - ) = compiler.compile_stateful_components(self._pages.values()) - - progress.advance(task) + with console.timing("Auto-memoize StatefulComponents"): + ( + stateful_components_path, + stateful_components_code, + page_components, + ) = compiler.compile_stateful_components(self._pages.values()) + progress.advance(task) # Catch "static" apps (that do not define a rx.State subclass) which are trying to access rx.State. if code_uses_state_contexts(stateful_components_code) and self._state is None: @@ -1086,6 +1096,17 @@ class App(MiddlewareMixin, LifespanMixin): progress.advance(task) + # Copy the assets. + assets_src = Path.cwd() / constants.Dirs.APP_ASSETS + if assets_src.is_dir(): + with console.timing("Copy assets"): + path_ops.update_directory_tree( + src=assets_src, + dest=( + Path.cwd() / prerequisites.get_web_dir() / constants.Dirs.PUBLIC + ), + ) + # Use a forking process pool, if possible. Much faster, especially for large sites. # Fallback to ThreadPoolExecutor as something that will always work. executor = None @@ -1138,9 +1159,10 @@ class App(MiddlewareMixin, LifespanMixin): _submit_work(compiler.remove_tailwind_from_postcss) # Wait for all compilation tasks to complete. - for future in concurrent.futures.as_completed(result_futures): - compile_results.append(future.result()) - progress.advance(task) + with console.timing("Compile to Javascript"): + for future in concurrent.futures.as_completed(result_futures): + compile_results.append(future.result()) + progress.advance(task) app_root = self._app_root(app_wrappers=app_wrappers) @@ -1175,7 +1197,8 @@ class App(MiddlewareMixin, LifespanMixin): progress.stop() # Install frontend packages. - self._get_frontend_packages(all_imports) + with console.timing("Install Frontend Packages"): + self._get_frontend_packages(all_imports) # Setup the next.config.js transpile_packages = [ @@ -1201,8 +1224,9 @@ class App(MiddlewareMixin, LifespanMixin): # Remove pages that are no longer in the app. p.unlink() - for output_path, code in compile_results: - compiler_utils.write_page(output_path, code) + with console.timing("Write to Disk"): + for output_path, code in compile_results: + compiler_utils.write_page(output_path, code) @contextlib.asynccontextmanager async def modify_state(self, token: str) -> AsyncIterator[BaseState]: diff --git a/reflex/utils/console.py b/reflex/utils/console.py index d5b7a0d6e..5c47eee6f 100644 --- a/reflex/utils/console.py +++ b/reflex/utils/console.py @@ -2,8 +2,10 @@ from __future__ import annotations +import contextlib import inspect import shutil +import time from pathlib import Path from types import FrameType @@ -317,3 +319,20 @@ def status(*args, **kwargs): A new status. """ return _console.status(*args, **kwargs) + + +@contextlib.contextmanager +def timing(msg: str): + """Create a context manager to time a block of code. + + Args: + msg: The message to display. + + Yields: + None. + """ + start = time.time() + try: + yield + finally: + debug(f"[white]\\[timing] {msg}: {time.time() - start:.2f}s[/white]") diff --git a/reflex/utils/path_ops.py b/reflex/utils/path_ops.py index 07a541201..92557d801 100644 --- a/reflex/utils/path_ops.py +++ b/reflex/utils/path_ops.py @@ -260,3 +260,33 @@ def find_replace(directory: str | Path, find: str, replace: str): text = filepath.read_text(encoding="utf-8") text = re.sub(find, replace, text) filepath.write_text(text, encoding="utf-8") + + +def update_directory_tree(src: Path, dest: Path): + """Recursively copies a directory tree from src to dest. + Only copies files if the destination file is missing or modified earlier than the source file. + + Args: + src: Source directory + dest: Destination directory + + Raises: + ValueError: If the source is not a directory + """ + if not src.is_dir(): + raise ValueError(f"Source {src} is not a directory") + + # Ensure the destination directory exists + dest.mkdir(parents=True, exist_ok=True) + + for item in src.iterdir(): + dest_item = dest / item.name + + if item.is_dir(): + # Recursively copy subdirectories + update_directory_tree(item, dest_item) + elif item.is_file() and ( + not dest_item.exists() or item.stat().st_mtime > dest_item.stat().st_mtime + ): + # Copy file if it doesn't exist in the destination or is older than the source + shutil.copy2(item, dest_item) From ee731a908d0da39aae651d60f034d6a4a77350bb Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Fri, 7 Feb 2025 17:19:28 -0800 Subject: [PATCH 08/10] provide plotly subpackages (#4776) --- reflex/compiler/utils.py | 34 +- reflex/components/plotly/__init__.py | 31 +- reflex/components/plotly/plotly.py | 235 ++++++++ reflex/components/plotly/plotly.pyi | 765 +++++++++++++++++++++++++++ reflex/utils/imports.py | 3 + 5 files changed, 1054 insertions(+), 14 deletions(-) diff --git a/reflex/compiler/utils.py b/reflex/compiler/utils.py index c797a095f..91ee18b86 100644 --- a/reflex/compiler/utils.py +++ b/reflex/compiler/utils.py @@ -119,24 +119,34 @@ def compile_imports(import_dict: ParsedImportDict) -> list[dict]: validate_imports(collapsed_import_dict) import_dicts = [] for lib, fields in collapsed_import_dict.items(): - default, rest = compile_import_statement(fields) - # prevent lib from being rendered on the page if all imports are non rendered kind if not any(f.render for f in fields): continue - if not lib: - if default: - raise ValueError("No default field allowed for empty library.") - if rest is None or len(rest) == 0: - raise ValueError("No fields to import.") - import_dicts.extend(get_import_dict(module) for module in sorted(rest)) - continue + lib_paths: dict[str, list[ImportVar]] = {} - # remove the version before rendering the package imports - lib = format.format_library_name(lib) + for field in fields: + lib_paths.setdefault(field.package_path, []).append(field) - import_dicts.append(get_import_dict(lib, default, rest)) + compiled = { + path: compile_import_statement(fields) for path, fields in lib_paths.items() + } + + for path, (default, rest) in compiled.items(): + if not lib: + if default: + raise ValueError("No default field allowed for empty library.") + if rest is None or len(rest) == 0: + raise ValueError("No fields to import.") + import_dicts.extend(get_import_dict(module) for module in sorted(rest)) + continue + + # remove the version before rendering the package imports + formatted_lib = format.format_library_name(lib) + ( + path if path != "/" else "" + ) + + import_dicts.append(get_import_dict(formatted_lib, default, rest)) return import_dicts diff --git a/reflex/components/plotly/__init__.py b/reflex/components/plotly/__init__.py index 5620d5fc4..8743b31b2 100644 --- a/reflex/components/plotly/__init__.py +++ b/reflex/components/plotly/__init__.py @@ -1,5 +1,32 @@ """Plotly components.""" -from .plotly import Plotly +from reflex.components.component import ComponentNamespace -plotly = Plotly.create +from .plotly import ( + Plotly, + PlotlyBasic, + PlotlyCartesian, + PlotlyFinance, + PlotlyGeo, + PlotlyGl2d, + PlotlyGl3d, + PlotlyMapbox, + PlotlyStrict, +) + + +class PlotlyNamespace(ComponentNamespace): + """Plotly namespace.""" + + __call__ = Plotly.create + basic = PlotlyBasic.create + cartesian = PlotlyCartesian.create + geo = PlotlyGeo.create + gl2d = PlotlyGl2d.create + gl3d = PlotlyGl3d.create + finance = PlotlyFinance.create + mapbox = PlotlyMapbox.create + strict = PlotlyStrict.create + + +plotly = PlotlyNamespace() diff --git a/reflex/components/plotly/plotly.py b/reflex/components/plotly/plotly.py index c85423d35..2ddaad8d7 100644 --- a/reflex/components/plotly/plotly.py +++ b/reflex/components/plotly/plotly.py @@ -10,6 +10,7 @@ from reflex.components.component import Component, NoSSRComponent from reflex.components.core.cond import color_mode_cond from reflex.event import EventHandler, no_args_event_spec from reflex.utils import console +from reflex.utils.imports import ImportDict, ImportVar from reflex.vars.base import LiteralVar, Var try: @@ -278,3 +279,237 @@ const extractPoints = (points) => { # Spread the figure dict over props, nothing to merge. tag.special_props.append(Var(_js_expr=f"{{...{figure!s}}}")) return tag + + +CREATE_PLOTLY_COMPONENT: ImportDict = { + "react-plotly.js": [ + ImportVar( + tag="createPlotlyComponent", + is_default=True, + package_path="/factory", + ), + ] +} + + +def dynamic_plotly_import(name: str, package: str) -> str: + """Create a dynamic import for a plotly component. + + Args: + name: The name of the component. + package: The package path of the component. + + Returns: + The dynamic import for the plotly component. + """ + return f""" +const {name} = dynamic(() => import('{package}').then(mod => createPlotlyComponent(mod)), {{ssr: false}}) +""" + + +class PlotlyBasic(Plotly): + """Display a basic plotly graph.""" + + tag: str = "BasicPlotlyPlot" + + library = "react-plotly.js@2.6.0" + + lib_dependencies: list[str] = ["plotly.js-basic-dist-min@3.0.0"] + + def add_imports(self) -> ImportDict | list[ImportDict]: + """Add imports for the plotly basic component. + + Returns: + The imports for the plotly basic component. + """ + return CREATE_PLOTLY_COMPONENT + + def _get_dynamic_imports(self) -> str: + """Get the dynamic imports for the plotly basic component. + + Returns: + The dynamic imports for the plotly basic component. + """ + return dynamic_plotly_import(self.tag, "plotly.js-basic-dist-min") + + +class PlotlyCartesian(Plotly): + """Display a plotly cartesian graph.""" + + tag: str = "CartesianPlotlyPlot" + + library = "react-plotly.js@2.6.0" + + lib_dependencies: list[str] = ["plotly.js-cartesian-dist-min@3.0.0"] + + def add_imports(self) -> ImportDict | list[ImportDict]: + """Add imports for the plotly cartesian component. + + Returns: + The imports for the plotly cartesian component. + """ + return CREATE_PLOTLY_COMPONENT + + def _get_dynamic_imports(self) -> str: + """Get the dynamic imports for the plotly cartesian component. + + Returns: + The dynamic imports for the plotly cartesian component. + """ + return dynamic_plotly_import(self.tag, "plotly.js-cartesian-dist-min") + + +class PlotlyGeo(Plotly): + """Display a plotly geo graph.""" + + tag: str = "GeoPlotlyPlot" + + library = "react-plotly.js@2.6.0" + + lib_dependencies: list[str] = ["plotly.js-geo-dist-min@3.0.0"] + + def add_imports(self) -> ImportDict | list[ImportDict]: + """Add imports for the plotly geo component. + + Returns: + The imports for the plotly geo component. + """ + return CREATE_PLOTLY_COMPONENT + + def _get_dynamic_imports(self) -> str: + """Get the dynamic imports for the plotly geo component. + + Returns: + The dynamic imports for the plotly geo component. + """ + return dynamic_plotly_import(self.tag, "plotly.js-geo-dist-min") + + +class PlotlyGl3d(Plotly): + """Display a plotly 3d graph.""" + + tag: str = "Gl3dPlotlyPlot" + + library = "react-plotly.js@2.6.0" + + lib_dependencies: list[str] = ["plotly.js-gl3d-dist-min@3.0.0"] + + def add_imports(self) -> ImportDict | list[ImportDict]: + """Add imports for the plotly 3d component. + + Returns: + The imports for the plotly 3d component. + """ + return CREATE_PLOTLY_COMPONENT + + def _get_dynamic_imports(self) -> str: + """Get the dynamic imports for the plotly 3d component. + + Returns: + The dynamic imports for the plotly 3d component. + """ + return dynamic_plotly_import(self.tag, "plotly.js-gl3d-dist-min") + + +class PlotlyGl2d(Plotly): + """Display a plotly 2d graph.""" + + tag: str = "Gl2dPlotlyPlot" + + library = "react-plotly.js@2.6.0" + + lib_dependencies: list[str] = ["plotly.js-gl2d-dist-min@3.0.0"] + + def add_imports(self) -> ImportDict | list[ImportDict]: + """Add imports for the plotly 2d component. + + Returns: + The imports for the plotly 2d component. + """ + return CREATE_PLOTLY_COMPONENT + + def _get_dynamic_imports(self) -> str: + """Get the dynamic imports for the plotly 2d component. + + Returns: + The dynamic imports for the plotly 2d component. + """ + return dynamic_plotly_import(self.tag, "plotly.js-gl2d-dist-min") + + +class PlotlyMapbox(Plotly): + """Display a plotly mapbox graph.""" + + tag: str = "MapboxPlotlyPlot" + + library = "react-plotly.js@2.6.0" + + lib_dependencies: list[str] = ["plotly.js-mapbox-dist-min@3.0.0"] + + def add_imports(self) -> ImportDict | list[ImportDict]: + """Add imports for the plotly mapbox component. + + Returns: + The imports for the plotly mapbox component. + """ + return CREATE_PLOTLY_COMPONENT + + def _get_dynamic_imports(self) -> str: + """Get the dynamic imports for the plotly mapbox component. + + Returns: + The dynamic imports for the plotly mapbox component. + """ + return dynamic_plotly_import(self.tag, "plotly.js-mapbox-dist-min") + + +class PlotlyFinance(Plotly): + """Display a plotly finance graph.""" + + tag: str = "FinancePlotlyPlot" + + library = "react-plotly.js@2.6.0" + + lib_dependencies: list[str] = ["plotly.js-finance-dist-min@3.0.0"] + + def add_imports(self) -> ImportDict | list[ImportDict]: + """Add imports for the plotly finance component. + + Returns: + The imports for the plotly finance component. + """ + return CREATE_PLOTLY_COMPONENT + + def _get_dynamic_imports(self) -> str: + """Get the dynamic imports for the plotly finance component. + + Returns: + The dynamic imports for the plotly finance component. + """ + return dynamic_plotly_import(self.tag, "plotly.js-finance-dist-min") + + +class PlotlyStrict(Plotly): + """Display a plotly strict graph.""" + + tag: str = "StrictPlotlyPlot" + + library = "react-plotly.js@2.6.0" + + lib_dependencies: list[str] = ["plotly.js-strict-dist-min@3.0.0"] + + def add_imports(self) -> ImportDict | list[ImportDict]: + """Add imports for the plotly strict component. + + Returns: + The imports for the plotly strict component. + """ + return CREATE_PLOTLY_COMPONENT + + def _get_dynamic_imports(self) -> str: + """Get the dynamic imports for the plotly strict component. + + Returns: + The dynamic imports for the plotly strict component. + """ + return dynamic_plotly_import(self.tag, "plotly.js-strict-dist-min") diff --git a/reflex/components/plotly/plotly.pyi b/reflex/components/plotly/plotly.pyi index f60e5a6a4..c4d8bf64a 100644 --- a/reflex/components/plotly/plotly.pyi +++ b/reflex/components/plotly/plotly.pyi @@ -11,6 +11,7 @@ from reflex.components.component import NoSSRComponent from reflex.event import EventType from reflex.style import Style from reflex.utils import console +from reflex.utils.imports import ImportDict from reflex.vars.base import Var try: @@ -141,3 +142,767 @@ class Plotly(NoSSRComponent): The Plotly component. """ ... + +CREATE_PLOTLY_COMPONENT: ImportDict + +def dynamic_plotly_import(name: str, package: str) -> str: ... + +class PlotlyBasic(Plotly): + def add_imports(self) -> ImportDict | list[ImportDict]: ... + @overload + @classmethod + def create( # type: ignore + cls, + *children, + data: Optional[Union[Figure, Var[Figure]]] = None, # type: ignore + layout: Optional[Union[Dict, Var[Dict]]] = None, + template: Optional[Union[Template, Var[Template]]] = None, # type: ignore + config: Optional[Union[Dict, Var[Dict]]] = None, + use_resize_handler: 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_after_plot: Optional[EventType[()]] = None, + on_animated: Optional[EventType[()]] = None, + on_animating_frame: Optional[EventType[()]] = None, + on_animation_interrupted: Optional[EventType[()]] = None, + on_autosize: Optional[EventType[()]] = None, + on_before_hover: Optional[EventType[()]] = None, + on_blur: Optional[EventType[()]] = None, + on_button_clicked: Optional[EventType[()]] = None, + on_click: Optional[Union[EventType[()], EventType[List[Point]]]] = None, + on_context_menu: Optional[EventType[()]] = None, + on_deselect: Optional[EventType[()]] = None, + on_double_click: Optional[EventType[()]] = None, + on_focus: Optional[EventType[()]] = None, + on_hover: Optional[Union[EventType[()], EventType[List[Point]]]] = 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_redraw: Optional[EventType[()]] = None, + on_relayout: Optional[EventType[()]] = None, + on_relayouting: Optional[EventType[()]] = None, + on_restyle: Optional[EventType[()]] = None, + on_scroll: Optional[EventType[()]] = None, + on_selected: Optional[Union[EventType[()], EventType[List[Point]]]] = None, + on_selecting: Optional[Union[EventType[()], EventType[List[Point]]]] = None, + on_transition_interrupted: Optional[EventType[()]] = None, + on_transitioning: Optional[EventType[()]] = None, + on_unhover: Optional[Union[EventType[()], EventType[List[Point]]]] = None, + on_unmount: Optional[EventType[()]] = None, + **props, + ) -> "PlotlyBasic": + """Create the Plotly component. + + Args: + *children: The children of the component. + data: The figure to display. This can be a plotly figure or a plotly data json. + layout: The layout of the graph. + template: The template for visual appearance of the graph. + config: The config of the graph. + use_resize_handler: If true, the graph will resize when the window is resized. + on_after_plot: Fired after the plot is redrawn. + on_animated: Fired after the plot was animated. + on_animating_frame: Fired while animating a single frame (does not currently pass data through). + on_animation_interrupted: Fired when an animation is interrupted (to start a new animation for example). + on_autosize: Fired when the plot is responsively sized. + on_before_hover: Fired whenever mouse moves over a plot. + on_button_clicked: Fired when a plotly UI button is clicked. + on_click: Fired when the plot is clicked. + on_deselect: Fired when a selection is cleared (via double click). + on_double_click: Fired when the plot is double clicked. + on_hover: Fired when a plot element is hovered over. + on_relayout: Fired after the plot is laid out (zoom, pan, etc). + on_relayouting: Fired while the plot is being laid out. + on_restyle: Fired after the plot style is changed. + on_redraw: Fired after the plot is redrawn. + on_selected: Fired after selecting plot elements. + on_selecting: Fired while dragging a selection. + on_transitioning: Fired while an animation is occurring. + on_transition_interrupted: Fired when a transition is stopped early. + on_unhover: Fired when a hovered element is no longer hovered. + 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: The properties of the component. + + Returns: + The Plotly component. + """ + ... + +class PlotlyCartesian(Plotly): + def add_imports(self) -> ImportDict | list[ImportDict]: ... + @overload + @classmethod + def create( # type: ignore + cls, + *children, + data: Optional[Union[Figure, Var[Figure]]] = None, # type: ignore + layout: Optional[Union[Dict, Var[Dict]]] = None, + template: Optional[Union[Template, Var[Template]]] = None, # type: ignore + config: Optional[Union[Dict, Var[Dict]]] = None, + use_resize_handler: 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_after_plot: Optional[EventType[()]] = None, + on_animated: Optional[EventType[()]] = None, + on_animating_frame: Optional[EventType[()]] = None, + on_animation_interrupted: Optional[EventType[()]] = None, + on_autosize: Optional[EventType[()]] = None, + on_before_hover: Optional[EventType[()]] = None, + on_blur: Optional[EventType[()]] = None, + on_button_clicked: Optional[EventType[()]] = None, + on_click: Optional[Union[EventType[()], EventType[List[Point]]]] = None, + on_context_menu: Optional[EventType[()]] = None, + on_deselect: Optional[EventType[()]] = None, + on_double_click: Optional[EventType[()]] = None, + on_focus: Optional[EventType[()]] = None, + on_hover: Optional[Union[EventType[()], EventType[List[Point]]]] = 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_redraw: Optional[EventType[()]] = None, + on_relayout: Optional[EventType[()]] = None, + on_relayouting: Optional[EventType[()]] = None, + on_restyle: Optional[EventType[()]] = None, + on_scroll: Optional[EventType[()]] = None, + on_selected: Optional[Union[EventType[()], EventType[List[Point]]]] = None, + on_selecting: Optional[Union[EventType[()], EventType[List[Point]]]] = None, + on_transition_interrupted: Optional[EventType[()]] = None, + on_transitioning: Optional[EventType[()]] = None, + on_unhover: Optional[Union[EventType[()], EventType[List[Point]]]] = None, + on_unmount: Optional[EventType[()]] = None, + **props, + ) -> "PlotlyCartesian": + """Create the Plotly component. + + Args: + *children: The children of the component. + data: The figure to display. This can be a plotly figure or a plotly data json. + layout: The layout of the graph. + template: The template for visual appearance of the graph. + config: The config of the graph. + use_resize_handler: If true, the graph will resize when the window is resized. + on_after_plot: Fired after the plot is redrawn. + on_animated: Fired after the plot was animated. + on_animating_frame: Fired while animating a single frame (does not currently pass data through). + on_animation_interrupted: Fired when an animation is interrupted (to start a new animation for example). + on_autosize: Fired when the plot is responsively sized. + on_before_hover: Fired whenever mouse moves over a plot. + on_button_clicked: Fired when a plotly UI button is clicked. + on_click: Fired when the plot is clicked. + on_deselect: Fired when a selection is cleared (via double click). + on_double_click: Fired when the plot is double clicked. + on_hover: Fired when a plot element is hovered over. + on_relayout: Fired after the plot is laid out (zoom, pan, etc). + on_relayouting: Fired while the plot is being laid out. + on_restyle: Fired after the plot style is changed. + on_redraw: Fired after the plot is redrawn. + on_selected: Fired after selecting plot elements. + on_selecting: Fired while dragging a selection. + on_transitioning: Fired while an animation is occurring. + on_transition_interrupted: Fired when a transition is stopped early. + on_unhover: Fired when a hovered element is no longer hovered. + 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: The properties of the component. + + Returns: + The Plotly component. + """ + ... + +class PlotlyGeo(Plotly): + def add_imports(self) -> ImportDict | list[ImportDict]: ... + @overload + @classmethod + def create( # type: ignore + cls, + *children, + data: Optional[Union[Figure, Var[Figure]]] = None, # type: ignore + layout: Optional[Union[Dict, Var[Dict]]] = None, + template: Optional[Union[Template, Var[Template]]] = None, # type: ignore + config: Optional[Union[Dict, Var[Dict]]] = None, + use_resize_handler: 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_after_plot: Optional[EventType[()]] = None, + on_animated: Optional[EventType[()]] = None, + on_animating_frame: Optional[EventType[()]] = None, + on_animation_interrupted: Optional[EventType[()]] = None, + on_autosize: Optional[EventType[()]] = None, + on_before_hover: Optional[EventType[()]] = None, + on_blur: Optional[EventType[()]] = None, + on_button_clicked: Optional[EventType[()]] = None, + on_click: Optional[Union[EventType[()], EventType[List[Point]]]] = None, + on_context_menu: Optional[EventType[()]] = None, + on_deselect: Optional[EventType[()]] = None, + on_double_click: Optional[EventType[()]] = None, + on_focus: Optional[EventType[()]] = None, + on_hover: Optional[Union[EventType[()], EventType[List[Point]]]] = 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_redraw: Optional[EventType[()]] = None, + on_relayout: Optional[EventType[()]] = None, + on_relayouting: Optional[EventType[()]] = None, + on_restyle: Optional[EventType[()]] = None, + on_scroll: Optional[EventType[()]] = None, + on_selected: Optional[Union[EventType[()], EventType[List[Point]]]] = None, + on_selecting: Optional[Union[EventType[()], EventType[List[Point]]]] = None, + on_transition_interrupted: Optional[EventType[()]] = None, + on_transitioning: Optional[EventType[()]] = None, + on_unhover: Optional[Union[EventType[()], EventType[List[Point]]]] = None, + on_unmount: Optional[EventType[()]] = None, + **props, + ) -> "PlotlyGeo": + """Create the Plotly component. + + Args: + *children: The children of the component. + data: The figure to display. This can be a plotly figure or a plotly data json. + layout: The layout of the graph. + template: The template for visual appearance of the graph. + config: The config of the graph. + use_resize_handler: If true, the graph will resize when the window is resized. + on_after_plot: Fired after the plot is redrawn. + on_animated: Fired after the plot was animated. + on_animating_frame: Fired while animating a single frame (does not currently pass data through). + on_animation_interrupted: Fired when an animation is interrupted (to start a new animation for example). + on_autosize: Fired when the plot is responsively sized. + on_before_hover: Fired whenever mouse moves over a plot. + on_button_clicked: Fired when a plotly UI button is clicked. + on_click: Fired when the plot is clicked. + on_deselect: Fired when a selection is cleared (via double click). + on_double_click: Fired when the plot is double clicked. + on_hover: Fired when a plot element is hovered over. + on_relayout: Fired after the plot is laid out (zoom, pan, etc). + on_relayouting: Fired while the plot is being laid out. + on_restyle: Fired after the plot style is changed. + on_redraw: Fired after the plot is redrawn. + on_selected: Fired after selecting plot elements. + on_selecting: Fired while dragging a selection. + on_transitioning: Fired while an animation is occurring. + on_transition_interrupted: Fired when a transition is stopped early. + on_unhover: Fired when a hovered element is no longer hovered. + 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: The properties of the component. + + Returns: + The Plotly component. + """ + ... + +class PlotlyGl3d(Plotly): + def add_imports(self) -> ImportDict | list[ImportDict]: ... + @overload + @classmethod + def create( # type: ignore + cls, + *children, + data: Optional[Union[Figure, Var[Figure]]] = None, # type: ignore + layout: Optional[Union[Dict, Var[Dict]]] = None, + template: Optional[Union[Template, Var[Template]]] = None, # type: ignore + config: Optional[Union[Dict, Var[Dict]]] = None, + use_resize_handler: 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_after_plot: Optional[EventType[()]] = None, + on_animated: Optional[EventType[()]] = None, + on_animating_frame: Optional[EventType[()]] = None, + on_animation_interrupted: Optional[EventType[()]] = None, + on_autosize: Optional[EventType[()]] = None, + on_before_hover: Optional[EventType[()]] = None, + on_blur: Optional[EventType[()]] = None, + on_button_clicked: Optional[EventType[()]] = None, + on_click: Optional[Union[EventType[()], EventType[List[Point]]]] = None, + on_context_menu: Optional[EventType[()]] = None, + on_deselect: Optional[EventType[()]] = None, + on_double_click: Optional[EventType[()]] = None, + on_focus: Optional[EventType[()]] = None, + on_hover: Optional[Union[EventType[()], EventType[List[Point]]]] = 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_redraw: Optional[EventType[()]] = None, + on_relayout: Optional[EventType[()]] = None, + on_relayouting: Optional[EventType[()]] = None, + on_restyle: Optional[EventType[()]] = None, + on_scroll: Optional[EventType[()]] = None, + on_selected: Optional[Union[EventType[()], EventType[List[Point]]]] = None, + on_selecting: Optional[Union[EventType[()], EventType[List[Point]]]] = None, + on_transition_interrupted: Optional[EventType[()]] = None, + on_transitioning: Optional[EventType[()]] = None, + on_unhover: Optional[Union[EventType[()], EventType[List[Point]]]] = None, + on_unmount: Optional[EventType[()]] = None, + **props, + ) -> "PlotlyGl3d": + """Create the Plotly component. + + Args: + *children: The children of the component. + data: The figure to display. This can be a plotly figure or a plotly data json. + layout: The layout of the graph. + template: The template for visual appearance of the graph. + config: The config of the graph. + use_resize_handler: If true, the graph will resize when the window is resized. + on_after_plot: Fired after the plot is redrawn. + on_animated: Fired after the plot was animated. + on_animating_frame: Fired while animating a single frame (does not currently pass data through). + on_animation_interrupted: Fired when an animation is interrupted (to start a new animation for example). + on_autosize: Fired when the plot is responsively sized. + on_before_hover: Fired whenever mouse moves over a plot. + on_button_clicked: Fired when a plotly UI button is clicked. + on_click: Fired when the plot is clicked. + on_deselect: Fired when a selection is cleared (via double click). + on_double_click: Fired when the plot is double clicked. + on_hover: Fired when a plot element is hovered over. + on_relayout: Fired after the plot is laid out (zoom, pan, etc). + on_relayouting: Fired while the plot is being laid out. + on_restyle: Fired after the plot style is changed. + on_redraw: Fired after the plot is redrawn. + on_selected: Fired after selecting plot elements. + on_selecting: Fired while dragging a selection. + on_transitioning: Fired while an animation is occurring. + on_transition_interrupted: Fired when a transition is stopped early. + on_unhover: Fired when a hovered element is no longer hovered. + 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: The properties of the component. + + Returns: + The Plotly component. + """ + ... + +class PlotlyGl2d(Plotly): + def add_imports(self) -> ImportDict | list[ImportDict]: ... + @overload + @classmethod + def create( # type: ignore + cls, + *children, + data: Optional[Union[Figure, Var[Figure]]] = None, # type: ignore + layout: Optional[Union[Dict, Var[Dict]]] = None, + template: Optional[Union[Template, Var[Template]]] = None, # type: ignore + config: Optional[Union[Dict, Var[Dict]]] = None, + use_resize_handler: 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_after_plot: Optional[EventType[()]] = None, + on_animated: Optional[EventType[()]] = None, + on_animating_frame: Optional[EventType[()]] = None, + on_animation_interrupted: Optional[EventType[()]] = None, + on_autosize: Optional[EventType[()]] = None, + on_before_hover: Optional[EventType[()]] = None, + on_blur: Optional[EventType[()]] = None, + on_button_clicked: Optional[EventType[()]] = None, + on_click: Optional[Union[EventType[()], EventType[List[Point]]]] = None, + on_context_menu: Optional[EventType[()]] = None, + on_deselect: Optional[EventType[()]] = None, + on_double_click: Optional[EventType[()]] = None, + on_focus: Optional[EventType[()]] = None, + on_hover: Optional[Union[EventType[()], EventType[List[Point]]]] = 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_redraw: Optional[EventType[()]] = None, + on_relayout: Optional[EventType[()]] = None, + on_relayouting: Optional[EventType[()]] = None, + on_restyle: Optional[EventType[()]] = None, + on_scroll: Optional[EventType[()]] = None, + on_selected: Optional[Union[EventType[()], EventType[List[Point]]]] = None, + on_selecting: Optional[Union[EventType[()], EventType[List[Point]]]] = None, + on_transition_interrupted: Optional[EventType[()]] = None, + on_transitioning: Optional[EventType[()]] = None, + on_unhover: Optional[Union[EventType[()], EventType[List[Point]]]] = None, + on_unmount: Optional[EventType[()]] = None, + **props, + ) -> "PlotlyGl2d": + """Create the Plotly component. + + Args: + *children: The children of the component. + data: The figure to display. This can be a plotly figure or a plotly data json. + layout: The layout of the graph. + template: The template for visual appearance of the graph. + config: The config of the graph. + use_resize_handler: If true, the graph will resize when the window is resized. + on_after_plot: Fired after the plot is redrawn. + on_animated: Fired after the plot was animated. + on_animating_frame: Fired while animating a single frame (does not currently pass data through). + on_animation_interrupted: Fired when an animation is interrupted (to start a new animation for example). + on_autosize: Fired when the plot is responsively sized. + on_before_hover: Fired whenever mouse moves over a plot. + on_button_clicked: Fired when a plotly UI button is clicked. + on_click: Fired when the plot is clicked. + on_deselect: Fired when a selection is cleared (via double click). + on_double_click: Fired when the plot is double clicked. + on_hover: Fired when a plot element is hovered over. + on_relayout: Fired after the plot is laid out (zoom, pan, etc). + on_relayouting: Fired while the plot is being laid out. + on_restyle: Fired after the plot style is changed. + on_redraw: Fired after the plot is redrawn. + on_selected: Fired after selecting plot elements. + on_selecting: Fired while dragging a selection. + on_transitioning: Fired while an animation is occurring. + on_transition_interrupted: Fired when a transition is stopped early. + on_unhover: Fired when a hovered element is no longer hovered. + 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: The properties of the component. + + Returns: + The Plotly component. + """ + ... + +class PlotlyMapbox(Plotly): + def add_imports(self) -> ImportDict | list[ImportDict]: ... + @overload + @classmethod + def create( # type: ignore + cls, + *children, + data: Optional[Union[Figure, Var[Figure]]] = None, # type: ignore + layout: Optional[Union[Dict, Var[Dict]]] = None, + template: Optional[Union[Template, Var[Template]]] = None, # type: ignore + config: Optional[Union[Dict, Var[Dict]]] = None, + use_resize_handler: 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_after_plot: Optional[EventType[()]] = None, + on_animated: Optional[EventType[()]] = None, + on_animating_frame: Optional[EventType[()]] = None, + on_animation_interrupted: Optional[EventType[()]] = None, + on_autosize: Optional[EventType[()]] = None, + on_before_hover: Optional[EventType[()]] = None, + on_blur: Optional[EventType[()]] = None, + on_button_clicked: Optional[EventType[()]] = None, + on_click: Optional[Union[EventType[()], EventType[List[Point]]]] = None, + on_context_menu: Optional[EventType[()]] = None, + on_deselect: Optional[EventType[()]] = None, + on_double_click: Optional[EventType[()]] = None, + on_focus: Optional[EventType[()]] = None, + on_hover: Optional[Union[EventType[()], EventType[List[Point]]]] = 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_redraw: Optional[EventType[()]] = None, + on_relayout: Optional[EventType[()]] = None, + on_relayouting: Optional[EventType[()]] = None, + on_restyle: Optional[EventType[()]] = None, + on_scroll: Optional[EventType[()]] = None, + on_selected: Optional[Union[EventType[()], EventType[List[Point]]]] = None, + on_selecting: Optional[Union[EventType[()], EventType[List[Point]]]] = None, + on_transition_interrupted: Optional[EventType[()]] = None, + on_transitioning: Optional[EventType[()]] = None, + on_unhover: Optional[Union[EventType[()], EventType[List[Point]]]] = None, + on_unmount: Optional[EventType[()]] = None, + **props, + ) -> "PlotlyMapbox": + """Create the Plotly component. + + Args: + *children: The children of the component. + data: The figure to display. This can be a plotly figure or a plotly data json. + layout: The layout of the graph. + template: The template for visual appearance of the graph. + config: The config of the graph. + use_resize_handler: If true, the graph will resize when the window is resized. + on_after_plot: Fired after the plot is redrawn. + on_animated: Fired after the plot was animated. + on_animating_frame: Fired while animating a single frame (does not currently pass data through). + on_animation_interrupted: Fired when an animation is interrupted (to start a new animation for example). + on_autosize: Fired when the plot is responsively sized. + on_before_hover: Fired whenever mouse moves over a plot. + on_button_clicked: Fired when a plotly UI button is clicked. + on_click: Fired when the plot is clicked. + on_deselect: Fired when a selection is cleared (via double click). + on_double_click: Fired when the plot is double clicked. + on_hover: Fired when a plot element is hovered over. + on_relayout: Fired after the plot is laid out (zoom, pan, etc). + on_relayouting: Fired while the plot is being laid out. + on_restyle: Fired after the plot style is changed. + on_redraw: Fired after the plot is redrawn. + on_selected: Fired after selecting plot elements. + on_selecting: Fired while dragging a selection. + on_transitioning: Fired while an animation is occurring. + on_transition_interrupted: Fired when a transition is stopped early. + on_unhover: Fired when a hovered element is no longer hovered. + 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: The properties of the component. + + Returns: + The Plotly component. + """ + ... + +class PlotlyFinance(Plotly): + def add_imports(self) -> ImportDict | list[ImportDict]: ... + @overload + @classmethod + def create( # type: ignore + cls, + *children, + data: Optional[Union[Figure, Var[Figure]]] = None, # type: ignore + layout: Optional[Union[Dict, Var[Dict]]] = None, + template: Optional[Union[Template, Var[Template]]] = None, # type: ignore + config: Optional[Union[Dict, Var[Dict]]] = None, + use_resize_handler: 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_after_plot: Optional[EventType[()]] = None, + on_animated: Optional[EventType[()]] = None, + on_animating_frame: Optional[EventType[()]] = None, + on_animation_interrupted: Optional[EventType[()]] = None, + on_autosize: Optional[EventType[()]] = None, + on_before_hover: Optional[EventType[()]] = None, + on_blur: Optional[EventType[()]] = None, + on_button_clicked: Optional[EventType[()]] = None, + on_click: Optional[Union[EventType[()], EventType[List[Point]]]] = None, + on_context_menu: Optional[EventType[()]] = None, + on_deselect: Optional[EventType[()]] = None, + on_double_click: Optional[EventType[()]] = None, + on_focus: Optional[EventType[()]] = None, + on_hover: Optional[Union[EventType[()], EventType[List[Point]]]] = 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_redraw: Optional[EventType[()]] = None, + on_relayout: Optional[EventType[()]] = None, + on_relayouting: Optional[EventType[()]] = None, + on_restyle: Optional[EventType[()]] = None, + on_scroll: Optional[EventType[()]] = None, + on_selected: Optional[Union[EventType[()], EventType[List[Point]]]] = None, + on_selecting: Optional[Union[EventType[()], EventType[List[Point]]]] = None, + on_transition_interrupted: Optional[EventType[()]] = None, + on_transitioning: Optional[EventType[()]] = None, + on_unhover: Optional[Union[EventType[()], EventType[List[Point]]]] = None, + on_unmount: Optional[EventType[()]] = None, + **props, + ) -> "PlotlyFinance": + """Create the Plotly component. + + Args: + *children: The children of the component. + data: The figure to display. This can be a plotly figure or a plotly data json. + layout: The layout of the graph. + template: The template for visual appearance of the graph. + config: The config of the graph. + use_resize_handler: If true, the graph will resize when the window is resized. + on_after_plot: Fired after the plot is redrawn. + on_animated: Fired after the plot was animated. + on_animating_frame: Fired while animating a single frame (does not currently pass data through). + on_animation_interrupted: Fired when an animation is interrupted (to start a new animation for example). + on_autosize: Fired when the plot is responsively sized. + on_before_hover: Fired whenever mouse moves over a plot. + on_button_clicked: Fired when a plotly UI button is clicked. + on_click: Fired when the plot is clicked. + on_deselect: Fired when a selection is cleared (via double click). + on_double_click: Fired when the plot is double clicked. + on_hover: Fired when a plot element is hovered over. + on_relayout: Fired after the plot is laid out (zoom, pan, etc). + on_relayouting: Fired while the plot is being laid out. + on_restyle: Fired after the plot style is changed. + on_redraw: Fired after the plot is redrawn. + on_selected: Fired after selecting plot elements. + on_selecting: Fired while dragging a selection. + on_transitioning: Fired while an animation is occurring. + on_transition_interrupted: Fired when a transition is stopped early. + on_unhover: Fired when a hovered element is no longer hovered. + 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: The properties of the component. + + Returns: + The Plotly component. + """ + ... + +class PlotlyStrict(Plotly): + def add_imports(self) -> ImportDict | list[ImportDict]: ... + @overload + @classmethod + def create( # type: ignore + cls, + *children, + data: Optional[Union[Figure, Var[Figure]]] = None, # type: ignore + layout: Optional[Union[Dict, Var[Dict]]] = None, + template: Optional[Union[Template, Var[Template]]] = None, # type: ignore + config: Optional[Union[Dict, Var[Dict]]] = None, + use_resize_handler: 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_after_plot: Optional[EventType[()]] = None, + on_animated: Optional[EventType[()]] = None, + on_animating_frame: Optional[EventType[()]] = None, + on_animation_interrupted: Optional[EventType[()]] = None, + on_autosize: Optional[EventType[()]] = None, + on_before_hover: Optional[EventType[()]] = None, + on_blur: Optional[EventType[()]] = None, + on_button_clicked: Optional[EventType[()]] = None, + on_click: Optional[Union[EventType[()], EventType[List[Point]]]] = None, + on_context_menu: Optional[EventType[()]] = None, + on_deselect: Optional[EventType[()]] = None, + on_double_click: Optional[EventType[()]] = None, + on_focus: Optional[EventType[()]] = None, + on_hover: Optional[Union[EventType[()], EventType[List[Point]]]] = 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_redraw: Optional[EventType[()]] = None, + on_relayout: Optional[EventType[()]] = None, + on_relayouting: Optional[EventType[()]] = None, + on_restyle: Optional[EventType[()]] = None, + on_scroll: Optional[EventType[()]] = None, + on_selected: Optional[Union[EventType[()], EventType[List[Point]]]] = None, + on_selecting: Optional[Union[EventType[()], EventType[List[Point]]]] = None, + on_transition_interrupted: Optional[EventType[()]] = None, + on_transitioning: Optional[EventType[()]] = None, + on_unhover: Optional[Union[EventType[()], EventType[List[Point]]]] = None, + on_unmount: Optional[EventType[()]] = None, + **props, + ) -> "PlotlyStrict": + """Create the Plotly component. + + Args: + *children: The children of the component. + data: The figure to display. This can be a plotly figure or a plotly data json. + layout: The layout of the graph. + template: The template for visual appearance of the graph. + config: The config of the graph. + use_resize_handler: If true, the graph will resize when the window is resized. + on_after_plot: Fired after the plot is redrawn. + on_animated: Fired after the plot was animated. + on_animating_frame: Fired while animating a single frame (does not currently pass data through). + on_animation_interrupted: Fired when an animation is interrupted (to start a new animation for example). + on_autosize: Fired when the plot is responsively sized. + on_before_hover: Fired whenever mouse moves over a plot. + on_button_clicked: Fired when a plotly UI button is clicked. + on_click: Fired when the plot is clicked. + on_deselect: Fired when a selection is cleared (via double click). + on_double_click: Fired when the plot is double clicked. + on_hover: Fired when a plot element is hovered over. + on_relayout: Fired after the plot is laid out (zoom, pan, etc). + on_relayouting: Fired while the plot is being laid out. + on_restyle: Fired after the plot style is changed. + on_redraw: Fired after the plot is redrawn. + on_selected: Fired after selecting plot elements. + on_selecting: Fired while dragging a selection. + on_transitioning: Fired while an animation is occurring. + on_transition_interrupted: Fired when a transition is stopped early. + on_unhover: Fired when a hovered element is no longer hovered. + 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: The properties of the component. + + Returns: + The Plotly component. + """ + ... diff --git a/reflex/utils/imports.py b/reflex/utils/imports.py index 46e8e7362..66ae4b023 100644 --- a/reflex/utils/imports.py +++ b/reflex/utils/imports.py @@ -109,6 +109,9 @@ class ImportVar: # whether this import should be rendered or not render: Optional[bool] = True + # The path of the package to import from. + package_path: str = "/" + # whether this import package should be added to transpilePackages in next.config.js # https://nextjs.org/docs/app/api-reference/next-config-js/transpilePackages transpile: Optional[bool] = False From 3de04156e9da1ccbbbb80a7be170e9d7ffe319f4 Mon Sep 17 00:00:00 2001 From: Simon Young <40179067+Kastier1@users.noreply.github.com> Date: Fri, 7 Feb 2025 17:20:35 -0800 Subject: [PATCH 09/10] allow gunicorn worker to be disabled (#4774) * allow gunicorn worker to be disabled * allow gunicorn worker to be disabled * rewrite the command --------- Co-authored-by: Khaleel Al-Adhami --- reflex/config.py | 2 +- reflex/utils/exec.py | 47 +++++++++++++++++++++++++++++--------------- 2 files changed, 32 insertions(+), 17 deletions(-) diff --git a/reflex/config.py b/reflex/config.py index f3d40dc37..dbc88619b 100644 --- a/reflex/config.py +++ b/reflex/config.py @@ -679,7 +679,7 @@ class Config(Base): # Number of gunicorn workers from user gunicorn_workers: Optional[int] = None - # Number of requests before a worker is restarted + # Number of requests before a worker is restarted; set to 0 to disable gunicorn_max_requests: int = 100 # Variance limit for max requests; gunicorn only diff --git a/reflex/utils/exec.py b/reflex/utils/exec.py index 67df7ea91..de326dacc 100644 --- a/reflex/utils/exec.py +++ b/reflex/utils/exec.py @@ -368,34 +368,49 @@ def run_uvicorn_backend_prod(host: str, port: int, loglevel: LogLevel): app_module = get_app_module() - run_backend_prod = f"gunicorn --worker-class {config.gunicorn_worker_class} --max-requests {config.gunicorn_max_requests} --max-requests-jitter {config.gunicorn_max_requests_jitter} --preload --timeout {config.timeout} --log-level critical".split() - run_backend_prod_windows = f"uvicorn --limit-max-requests {config.gunicorn_max_requests} --timeout-keep-alive {config.timeout}".split() command = ( [ - *run_backend_prod_windows, - "--host", - host, - "--port", - str(port), + "uvicorn", + *( + [ + "--limit-max-requests", + str(config.gunicorn_max_requests), + ] + if config.gunicorn_max_requests > 0 + else [] + ), + *("--timeout-keep-alive", str(config.timeout)), + *("--host", host), + *("--port", str(port)), + *("--workers", str(_get_backend_workers())), app_module, ] if constants.IS_WINDOWS else [ - *run_backend_prod, - "--bind", - f"{host}:{port}", - "--threads", - str(_get_backend_workers()), + "gunicorn", + *("--worker-class", config.gunicorn_worker_class), + *( + [ + "--max-requests", + str(config.gunicorn_max_requests), + "--max-requests-jitter", + str(config.gunicorn_max_requests_jitter), + ] + if config.gunicorn_max_requests > 0 + else [] + ), + "--preload", + *("--timeout", str(config.timeout)), + *("--bind", f"{host}:{port}"), + *("--threads", str(_get_backend_workers())), f"{app_module}()", ] ) command += [ - "--log-level", - loglevel.value, - "--workers", - str(_get_backend_workers()), + *("--log-level", loglevel.value), ] + processes.new_process( command, run=True, From 8b2c7291d3ed605fc1cd82192555ac76ac8b76d3 Mon Sep 17 00:00:00 2001 From: Masen Furer Date: Fri, 7 Feb 2025 17:38:42 -0800 Subject: [PATCH 10/10] Add ComputedVar overloads for BASE_TYPE, SQLA_TYPE, and DATACLASS_TYPE (#4777) Allow typing to find __getattr__ for rx.var that returns an object-like model. --- reflex/vars/base.py | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/reflex/vars/base.py b/reflex/vars/base.py index 0d8af8f3c..a24db4010 100644 --- a/reflex/vars/base.py +++ b/reflex/vars/base.py @@ -2254,6 +2254,27 @@ class ComputedVar(Var[RETURN_TYPE]): owner: Type, ) -> ArrayVar[tuple[LIST_INSIDE, ...]]: ... + @overload + def __get__( + self: ComputedVar[BASE_TYPE], + instance: None, + owner: Type, + ) -> ObjectVar[BASE_TYPE]: ... + + @overload + def __get__( + self: ComputedVar[SQLA_TYPE], + instance: None, + owner: Type, + ) -> ObjectVar[SQLA_TYPE]: ... + + if TYPE_CHECKING: + + @overload + def __get__( + self: ComputedVar[DATACLASS_TYPE], instance: None, owner: Any + ) -> ObjectVar[DATACLASS_TYPE]: ... + @overload def __get__(self, instance: None, owner: Type) -> ComputedVar[RETURN_TYPE]: ... @@ -2500,6 +2521,27 @@ class AsyncComputedVar(ComputedVar[RETURN_TYPE]): owner: Type, ) -> ArrayVar[tuple[LIST_INSIDE, ...]]: ... + @overload + def __get__( + self: AsyncComputedVar[BASE_TYPE], + instance: None, + owner: Type, + ) -> ObjectVar[BASE_TYPE]: ... + + @overload + def __get__( + self: AsyncComputedVar[SQLA_TYPE], + instance: None, + owner: Type, + ) -> ObjectVar[SQLA_TYPE]: ... + + if TYPE_CHECKING: + + @overload + def __get__( + self: AsyncComputedVar[DATACLASS_TYPE], instance: None, owner: Any + ) -> ObjectVar[DATACLASS_TYPE]: ... + @overload def __get__(self, instance: None, owner: Type) -> AsyncComputedVar[RETURN_TYPE]: ...