From a38d4750ebbfc00a9e74ee3a70e0a16273e299b2 Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Mon, 23 Mar 2026 18:29:54 -0700 Subject: [PATCH 001/113] add docs --- docs/DEBUGGING.md => DEBUGGING.md | 0 docs/__init__.py | 0 docs/advanced_onboarding/code_structure.md | 365 ++++ docs/advanced_onboarding/configuration.md | 60 + docs/advanced_onboarding/how-reflex-works.md | 243 +++ docs/api-reference/browser_javascript.md | 224 ++ docs/api-reference/browser_storage.md | 333 +++ docs/api-reference/cli.md | 128 ++ docs/api-reference/event_triggers.md | 390 ++++ docs/api-reference/plugins.md | 242 +++ docs/api-reference/special_events.md | 123 ++ docs/api-reference/utils.md | 170 ++ docs/api-reference/var_system.md | 82 + docs/api-routes/overview.md | 152 ++ docs/assets/overview.md | 95 + docs/assets/upload_and_download_files.md | 154 ++ .../authentication/authentication_overview.md | 28 + docs/client_storage/overview.md | 45 + docs/components/conditional_rendering.md | 131 ++ docs/components/html_to_reflex.md | 16 + docs/components/props.md | 97 + docs/components/rendering_iterables.md | 299 +++ docs/custom-components/command-reference.md | 157 ++ docs/custom-components/overview.md | 75 + .../prerequisites-for-publishing.md | 40 + docs/database/overview.md | 87 + docs/database/queries.md | 344 +++ docs/database/relationships.md | 164 ++ docs/database/tables.md | 70 + docs/de/README.md | 250 --- docs/enterprise/ag_chart.md | 30 + docs/enterprise/ag_grid/aligned-grids.md | 65 + docs/enterprise/ag_grid/cell-selection.md | 288 +++ docs/enterprise/ag_grid/column-defs.md | 35 + docs/enterprise/ag_grid/index.md | 681 ++++++ docs/enterprise/ag_grid/model-wrapper.md | 64 + docs/enterprise/ag_grid/pivot-mode.md | 130 ++ docs/enterprise/ag_grid/theme.md | 63 + docs/enterprise/ag_grid/value-transformers.md | 76 + docs/enterprise/built-with-reflex.md | 17 + docs/enterprise/components.md | 119 ++ docs/enterprise/drag-and-drop.md | 582 ++++++ docs/enterprise/mantine/autocomplete.md | 22 + docs/enterprise/mantine/collapse.md | 36 + docs/enterprise/mantine/combobox.md | 36 + docs/enterprise/mantine/index.md | 26 + docs/enterprise/mantine/json-input.md | 35 + docs/enterprise/mantine/loading-overlay.md | 32 + docs/enterprise/mantine/multi-select.md | 31 + docs/enterprise/mantine/number-formatter.md | 27 + docs/enterprise/mantine/pill.md | 94 + docs/enterprise/mantine/ring-progress.md | 31 + .../mantine/semi-circle-progress.md | 28 + docs/enterprise/mantine/spoiler.md | 22 + docs/enterprise/mantine/tags-input.md | 55 + docs/enterprise/mantine/timeline.md | 34 + docs/enterprise/mantine/tree.md | 31 + docs/enterprise/map/index.md | 565 +++++ docs/enterprise/overview.md | 295 +++ docs/enterprise/react_flow/basic_flow.md | 96 + docs/enterprise/react_flow/components.md | 92 + docs/enterprise/react_flow/edges.md | 217 ++ docs/enterprise/react_flow/examples.md | 235 +++ docs/enterprise/react_flow/hooks.md | 33 + docs/enterprise/react_flow/interactivity.md | 71 + docs/enterprise/react_flow/nodes.md | 277 +++ docs/enterprise/react_flow/overview.md | 23 + docs/enterprise/react_flow/theming.md | 74 + docs/enterprise/react_flow/utils.md | 29 + docs/enterprise/single-port-proxy.md | 15 + docs/es/README.md | 247 --- docs/events/background_events.md | 159 ++ docs/events/chaining_events.md | 94 + docs/events/decentralized_event_handlers.md | 149 ++ docs/events/event_actions.md | 252 +++ docs/events/event_arguments.md | 123 ++ docs/events/events_overview.md | 52 + docs/events/page_load_events.md | 40 + docs/events/setters.md | 57 + docs/events/special_events.md | 28 + docs/events/yield_events.md | 107 + docs/getting_started/basics.md | 406 ++++ docs/getting_started/chat_tutorial_style.py | 27 + docs/getting_started/chat_tutorial_utils.py | 86 + docs/getting_started/chatapp_tutorial.md | 807 ++++++++ docs/getting_started/dashboard_tutorial.md | 1840 +++++++++++++++++ docs/getting_started/installation.md | 172 ++ docs/getting_started/introduction.md | 353 ++++ docs/getting_started/open_source_templates.md | 67 + docs/getting_started/project-structure.md | 71 + docs/hosting/adding-members.md | 39 + docs/hosting/app-management.md | 58 + docs/hosting/billing.md | 32 + docs/hosting/compute.md | 230 +++ docs/hosting/config_file.md | 181 ++ docs/hosting/custom-domains.md | 75 + docs/hosting/databricks.md | 209 ++ docs/hosting/deploy-quick-start.md | 90 + docs/hosting/deploy-with-github-actions.md | 118 ++ docs/hosting/logs.md | 46 + docs/hosting/machine-types.md | 35 + docs/hosting/regions.md | 151 ++ docs/hosting/secrets-environment-vars.md | 55 + docs/hosting/self-hosting.md | 121 ++ docs/hosting/tokens.md | 22 + docs/images/dalle.gif | Bin 918253 -> 0 bytes docs/images/dalle_colored_code_example.png | Bin 378844 -> 0 bytes docs/images/reflex.png | Bin 768668 -> 0 bytes docs/images/reflex.svg | 1 - docs/images/reflex_dark.svg | 8 - docs/images/reflex_light.svg | 8 - docs/in/README.md | 249 --- docs/it/README.md | 250 --- docs/ja/README.md | 250 --- docs/kr/README.md | 251 --- docs/library/data-display/avatar.md | 146 ++ docs/library/data-display/badge.md | 128 ++ docs/library/data-display/callout-ll.md | 140 ++ docs/library/data-display/callout.md | 97 + docs/library/data-display/code_block.md | 25 + docs/library/data-display/data_list.md | 91 + docs/library/data-display/icon.md | 197 ++ docs/library/data-display/list.md | 70 + docs/library/data-display/moment.md | 170 ++ docs/library/data-display/progress.md | 54 + docs/library/data-display/scroll_area.md | 239 +++ docs/library/data-display/spinner.md | 56 + docs/library/disclosure/accordion.md | 359 ++++ docs/library/disclosure/segmented_control.md | 56 + docs/library/disclosure/tabs.md | 330 +++ docs/library/dynamic-rendering/auto_scroll.md | 70 + docs/library/dynamic-rendering/cond.md | 161 ++ docs/library/dynamic-rendering/foreach.md | 199 ++ docs/library/dynamic-rendering/match.md | 295 +++ docs/library/forms/button.md | 63 + docs/library/forms/checkbox.md | 73 + docs/library/forms/form-ll.md | 464 +++++ docs/library/forms/form.md | 239 +++ docs/library/forms/input-ll.md | 128 ++ docs/library/forms/input.md | 164 ++ docs/library/forms/radio_group.md | 101 + docs/library/forms/select-ll.md | 305 +++ docs/library/forms/select.md | 203 ++ docs/library/forms/slider.md | 133 ++ docs/library/forms/switch.md | 108 + docs/library/forms/text_area.md | 88 + docs/library/forms/upload.md | 523 +++++ docs/library/graphing/charts/areachart.md | 444 ++++ docs/library/graphing/charts/barchart.md | 388 ++++ docs/library/graphing/charts/composedchart.md | 90 + docs/library/graphing/charts/errorbar.md | 101 + docs/library/graphing/charts/funnelchart.md | 210 ++ docs/library/graphing/charts/linechart.md | 309 +++ docs/library/graphing/charts/piechart.md | 216 ++ docs/library/graphing/charts/radarchart.md | 285 +++ .../library/graphing/charts/radialbarchart.md | 109 + docs/library/graphing/charts/scatterchart.md | 296 +++ docs/library/graphing/general/axis.md | 296 +++ docs/library/graphing/general/brush.md | 153 ++ .../library/graphing/general/cartesiangrid.md | 203 ++ docs/library/graphing/general/label.md | 182 ++ docs/library/graphing/general/legend.md | 171 ++ docs/library/graphing/general/reference.md | 258 +++ docs/library/graphing/general/tooltip.md | 172 ++ docs/library/graphing/other-charts/plotly.md | 144 ++ docs/library/graphing/other-charts/pyplot.md | 155 ++ docs/library/layout/aspect_ratio.md | 85 + docs/library/layout/box.md | 47 + docs/library/layout/card.md | 59 + docs/library/layout/center.md | 21 + docs/library/layout/container.md | 40 + docs/library/layout/flex.md | 208 ++ docs/library/layout/fragment.md | 27 + docs/library/layout/grid.md | 40 + docs/library/layout/inset.md | 91 + docs/library/layout/section.md | 36 + docs/library/layout/separator.md | 59 + docs/library/layout/spacer.md | 25 + docs/library/layout/stack.md | 172 ++ docs/library/media/audio.md | 28 + docs/library/media/image.md | 63 + docs/library/media/video.md | 29 + docs/library/other/clipboard.md | 79 + docs/library/other/html.md | 119 ++ docs/library/other/html_embed.md | 38 + docs/library/other/memo.md | 165 ++ docs/library/other/script.md | 38 + docs/library/other/skeleton.md | 27 + docs/library/other/theme.md | 31 + docs/library/overlay/alert_dialog.md | 367 ++++ docs/library/overlay/context_menu.md | 343 +++ docs/library/overlay/dialog.md | 283 +++ docs/library/overlay/drawer.md | 119 ++ docs/library/overlay/dropdown_menu.md | 315 +++ docs/library/overlay/hover_card.md | 126 ++ docs/library/overlay/popover.md | 224 ++ docs/library/overlay/toast.md | 105 + docs/library/overlay/tooltip.md | 60 + .../tables-and-data-grids/data_editor.md | 356 ++++ .../tables-and-data-grids/data_table.md | 73 + docs/library/tables-and-data-grids/table.md | 1206 +++++++++++ docs/library/typography/blockquote.md | 77 + docs/library/typography/code.md | 110 + docs/library/typography/em.md | 16 + docs/library/typography/heading.md | 152 ++ docs/library/typography/kbd.md | 36 + docs/library/typography/link.md | 147 ++ docs/library/typography/markdown.md | 195 ++ docs/library/typography/quote.md | 19 + docs/library/typography/strong.md | 16 + docs/library/typography/text.md | 204 ++ docs/pages/dynamic_routing.md | 110 + docs/pages/overview.md | 241 +++ docs/pe/README.md | 251 --- docs/pt/pt_br/README.md | 251 --- docs/recipes/auth/login_form.md | 243 +++ docs/recipes/auth/signup_form.md | 260 +++ docs/recipes/content/forms.md | 173 ++ docs/recipes/content/grid.md | 52 + docs/recipes/content/multi_column_row.md | 64 + docs/recipes/content/stats.md | 107 + docs/recipes/content/top_banner.md | 271 +++ docs/recipes/layout/footer.md | 281 +++ docs/recipes/layout/navbar.md | 367 ++++ docs/recipes/layout/sidebar.md | 421 ++++ docs/recipes/others/checkboxes.md | 68 + docs/recipes/others/chips.md | 247 +++ docs/recipes/others/dark_mode_toggle.md | 33 + docs/recipes/others/pricing_cards.md | 199 ++ docs/recipes/others/speed_dial.md | 454 ++++ docs/state/overview.md | 189 ++ docs/state_structure/component_state.md | 233 +++ docs/state_structure/mixins.md | 329 +++ docs/state_structure/overview.md | 234 +++ docs/state_structure/shared_state.md | 216 ++ docs/styling/common-props.md | 227 ++ docs/styling/custom-stylesheets.md | 191 ++ docs/styling/layout.md | 155 ++ docs/styling/overview.md | 185 ++ docs/styling/responsive.md | 172 ++ docs/styling/tailwind.md | 256 +++ docs/styling/theming.md | 206 ++ docs/tr/README.md | 248 --- docs/ui/overview.md | 79 + docs/utility_methods/exception_handlers.md | 43 + docs/utility_methods/lifespan_tasks.md | 84 + docs/utility_methods/other_methods.md | 14 + docs/utility_methods/router_attributes.md | 116 ++ docs/vars/base_vars.md | 228 ++ docs/vars/computed_vars.md | 190 ++ docs/vars/custom_vars.md | 82 + docs/vars/var-operations.md | 555 +++++ docs/vi/README.md | 255 --- docs/wrapping-react/custom-code-and-hooks.md | 108 + docs/wrapping-react/example.md | 408 ++++ docs/wrapping-react/imports-and-styles.md | 51 + docs/wrapping-react/library-and-tags.md | 166 ++ docs/wrapping-react/local-packages.md | 171 ++ docs/wrapping-react/more-wrapping-examples.md | 449 ++++ docs/wrapping-react/overview.md | 154 ++ docs/wrapping-react/props.md | 202 ++ docs/wrapping-react/serializers.md | 44 + docs/wrapping-react/step-by-step.md | 0 docs/zh/zh_cn/README.md | 251 --- docs/zh/zh_tw/README.md | 251 --- 265 files changed, 40439 insertions(+), 3021 deletions(-) rename docs/DEBUGGING.md => DEBUGGING.md (100%) create mode 100644 docs/__init__.py create mode 100644 docs/advanced_onboarding/code_structure.md create mode 100644 docs/advanced_onboarding/configuration.md create mode 100644 docs/advanced_onboarding/how-reflex-works.md create mode 100644 docs/api-reference/browser_javascript.md create mode 100644 docs/api-reference/browser_storage.md create mode 100644 docs/api-reference/cli.md create mode 100644 docs/api-reference/event_triggers.md create mode 100644 docs/api-reference/plugins.md create mode 100644 docs/api-reference/special_events.md create mode 100644 docs/api-reference/utils.md create mode 100644 docs/api-reference/var_system.md create mode 100644 docs/api-routes/overview.md create mode 100644 docs/assets/overview.md create mode 100644 docs/assets/upload_and_download_files.md create mode 100644 docs/authentication/authentication_overview.md create mode 100644 docs/client_storage/overview.md create mode 100644 docs/components/conditional_rendering.md create mode 100644 docs/components/html_to_reflex.md create mode 100644 docs/components/props.md create mode 100644 docs/components/rendering_iterables.md create mode 100644 docs/custom-components/command-reference.md create mode 100644 docs/custom-components/overview.md create mode 100644 docs/custom-components/prerequisites-for-publishing.md create mode 100644 docs/database/overview.md create mode 100644 docs/database/queries.md create mode 100644 docs/database/relationships.md create mode 100644 docs/database/tables.md delete mode 100644 docs/de/README.md create mode 100644 docs/enterprise/ag_chart.md create mode 100644 docs/enterprise/ag_grid/aligned-grids.md create mode 100644 docs/enterprise/ag_grid/cell-selection.md create mode 100644 docs/enterprise/ag_grid/column-defs.md create mode 100644 docs/enterprise/ag_grid/index.md create mode 100644 docs/enterprise/ag_grid/model-wrapper.md create mode 100644 docs/enterprise/ag_grid/pivot-mode.md create mode 100644 docs/enterprise/ag_grid/theme.md create mode 100644 docs/enterprise/ag_grid/value-transformers.md create mode 100644 docs/enterprise/built-with-reflex.md create mode 100644 docs/enterprise/components.md create mode 100644 docs/enterprise/drag-and-drop.md create mode 100644 docs/enterprise/mantine/autocomplete.md create mode 100644 docs/enterprise/mantine/collapse.md create mode 100644 docs/enterprise/mantine/combobox.md create mode 100644 docs/enterprise/mantine/index.md create mode 100644 docs/enterprise/mantine/json-input.md create mode 100644 docs/enterprise/mantine/loading-overlay.md create mode 100644 docs/enterprise/mantine/multi-select.md create mode 100644 docs/enterprise/mantine/number-formatter.md create mode 100644 docs/enterprise/mantine/pill.md create mode 100644 docs/enterprise/mantine/ring-progress.md create mode 100644 docs/enterprise/mantine/semi-circle-progress.md create mode 100644 docs/enterprise/mantine/spoiler.md create mode 100644 docs/enterprise/mantine/tags-input.md create mode 100644 docs/enterprise/mantine/timeline.md create mode 100644 docs/enterprise/mantine/tree.md create mode 100644 docs/enterprise/map/index.md create mode 100644 docs/enterprise/overview.md create mode 100644 docs/enterprise/react_flow/basic_flow.md create mode 100644 docs/enterprise/react_flow/components.md create mode 100644 docs/enterprise/react_flow/edges.md create mode 100644 docs/enterprise/react_flow/examples.md create mode 100644 docs/enterprise/react_flow/hooks.md create mode 100644 docs/enterprise/react_flow/interactivity.md create mode 100644 docs/enterprise/react_flow/nodes.md create mode 100644 docs/enterprise/react_flow/overview.md create mode 100644 docs/enterprise/react_flow/theming.md create mode 100644 docs/enterprise/react_flow/utils.md create mode 100644 docs/enterprise/single-port-proxy.md delete mode 100644 docs/es/README.md create mode 100644 docs/events/background_events.md create mode 100644 docs/events/chaining_events.md create mode 100644 docs/events/decentralized_event_handlers.md create mode 100644 docs/events/event_actions.md create mode 100644 docs/events/event_arguments.md create mode 100644 docs/events/events_overview.md create mode 100644 docs/events/page_load_events.md create mode 100644 docs/events/setters.md create mode 100644 docs/events/special_events.md create mode 100644 docs/events/yield_events.md create mode 100644 docs/getting_started/basics.md create mode 100644 docs/getting_started/chat_tutorial_style.py create mode 100644 docs/getting_started/chat_tutorial_utils.py create mode 100644 docs/getting_started/chatapp_tutorial.md create mode 100644 docs/getting_started/dashboard_tutorial.md create mode 100644 docs/getting_started/installation.md create mode 100644 docs/getting_started/introduction.md create mode 100644 docs/getting_started/open_source_templates.md create mode 100644 docs/getting_started/project-structure.md create mode 100644 docs/hosting/adding-members.md create mode 100644 docs/hosting/app-management.md create mode 100644 docs/hosting/billing.md create mode 100644 docs/hosting/compute.md create mode 100644 docs/hosting/config_file.md create mode 100644 docs/hosting/custom-domains.md create mode 100644 docs/hosting/databricks.md create mode 100644 docs/hosting/deploy-quick-start.md create mode 100644 docs/hosting/deploy-with-github-actions.md create mode 100644 docs/hosting/logs.md create mode 100644 docs/hosting/machine-types.md create mode 100644 docs/hosting/regions.md create mode 100644 docs/hosting/secrets-environment-vars.md create mode 100644 docs/hosting/self-hosting.md create mode 100644 docs/hosting/tokens.md delete mode 100644 docs/images/dalle.gif delete mode 100644 docs/images/dalle_colored_code_example.png delete mode 100644 docs/images/reflex.png delete mode 100644 docs/images/reflex.svg delete mode 100644 docs/images/reflex_dark.svg delete mode 100644 docs/images/reflex_light.svg delete mode 100644 docs/in/README.md delete mode 100644 docs/it/README.md delete mode 100644 docs/ja/README.md delete mode 100644 docs/kr/README.md create mode 100644 docs/library/data-display/avatar.md create mode 100644 docs/library/data-display/badge.md create mode 100644 docs/library/data-display/callout-ll.md create mode 100644 docs/library/data-display/callout.md create mode 100644 docs/library/data-display/code_block.md create mode 100644 docs/library/data-display/data_list.md create mode 100644 docs/library/data-display/icon.md create mode 100644 docs/library/data-display/list.md create mode 100644 docs/library/data-display/moment.md create mode 100644 docs/library/data-display/progress.md create mode 100644 docs/library/data-display/scroll_area.md create mode 100644 docs/library/data-display/spinner.md create mode 100644 docs/library/disclosure/accordion.md create mode 100644 docs/library/disclosure/segmented_control.md create mode 100644 docs/library/disclosure/tabs.md create mode 100644 docs/library/dynamic-rendering/auto_scroll.md create mode 100644 docs/library/dynamic-rendering/cond.md create mode 100644 docs/library/dynamic-rendering/foreach.md create mode 100644 docs/library/dynamic-rendering/match.md create mode 100644 docs/library/forms/button.md create mode 100644 docs/library/forms/checkbox.md create mode 100644 docs/library/forms/form-ll.md create mode 100644 docs/library/forms/form.md create mode 100644 docs/library/forms/input-ll.md create mode 100644 docs/library/forms/input.md create mode 100644 docs/library/forms/radio_group.md create mode 100644 docs/library/forms/select-ll.md create mode 100644 docs/library/forms/select.md create mode 100644 docs/library/forms/slider.md create mode 100644 docs/library/forms/switch.md create mode 100644 docs/library/forms/text_area.md create mode 100644 docs/library/forms/upload.md create mode 100644 docs/library/graphing/charts/areachart.md create mode 100644 docs/library/graphing/charts/barchart.md create mode 100644 docs/library/graphing/charts/composedchart.md create mode 100644 docs/library/graphing/charts/errorbar.md create mode 100644 docs/library/graphing/charts/funnelchart.md create mode 100644 docs/library/graphing/charts/linechart.md create mode 100644 docs/library/graphing/charts/piechart.md create mode 100644 docs/library/graphing/charts/radarchart.md create mode 100644 docs/library/graphing/charts/radialbarchart.md create mode 100644 docs/library/graphing/charts/scatterchart.md create mode 100644 docs/library/graphing/general/axis.md create mode 100644 docs/library/graphing/general/brush.md create mode 100644 docs/library/graphing/general/cartesiangrid.md create mode 100644 docs/library/graphing/general/label.md create mode 100644 docs/library/graphing/general/legend.md create mode 100644 docs/library/graphing/general/reference.md create mode 100644 docs/library/graphing/general/tooltip.md create mode 100644 docs/library/graphing/other-charts/plotly.md create mode 100644 docs/library/graphing/other-charts/pyplot.md create mode 100644 docs/library/layout/aspect_ratio.md create mode 100644 docs/library/layout/box.md create mode 100644 docs/library/layout/card.md create mode 100644 docs/library/layout/center.md create mode 100644 docs/library/layout/container.md create mode 100644 docs/library/layout/flex.md create mode 100644 docs/library/layout/fragment.md create mode 100644 docs/library/layout/grid.md create mode 100644 docs/library/layout/inset.md create mode 100644 docs/library/layout/section.md create mode 100644 docs/library/layout/separator.md create mode 100644 docs/library/layout/spacer.md create mode 100644 docs/library/layout/stack.md create mode 100644 docs/library/media/audio.md create mode 100644 docs/library/media/image.md create mode 100644 docs/library/media/video.md create mode 100644 docs/library/other/clipboard.md create mode 100644 docs/library/other/html.md create mode 100644 docs/library/other/html_embed.md create mode 100644 docs/library/other/memo.md create mode 100644 docs/library/other/script.md create mode 100644 docs/library/other/skeleton.md create mode 100644 docs/library/other/theme.md create mode 100644 docs/library/overlay/alert_dialog.md create mode 100644 docs/library/overlay/context_menu.md create mode 100644 docs/library/overlay/dialog.md create mode 100644 docs/library/overlay/drawer.md create mode 100644 docs/library/overlay/dropdown_menu.md create mode 100644 docs/library/overlay/hover_card.md create mode 100644 docs/library/overlay/popover.md create mode 100644 docs/library/overlay/toast.md create mode 100644 docs/library/overlay/tooltip.md create mode 100644 docs/library/tables-and-data-grids/data_editor.md create mode 100644 docs/library/tables-and-data-grids/data_table.md create mode 100644 docs/library/tables-and-data-grids/table.md create mode 100644 docs/library/typography/blockquote.md create mode 100644 docs/library/typography/code.md create mode 100644 docs/library/typography/em.md create mode 100644 docs/library/typography/heading.md create mode 100644 docs/library/typography/kbd.md create mode 100644 docs/library/typography/link.md create mode 100644 docs/library/typography/markdown.md create mode 100644 docs/library/typography/quote.md create mode 100644 docs/library/typography/strong.md create mode 100644 docs/library/typography/text.md create mode 100644 docs/pages/dynamic_routing.md create mode 100644 docs/pages/overview.md delete mode 100644 docs/pe/README.md delete mode 100644 docs/pt/pt_br/README.md create mode 100644 docs/recipes/auth/login_form.md create mode 100644 docs/recipes/auth/signup_form.md create mode 100644 docs/recipes/content/forms.md create mode 100644 docs/recipes/content/grid.md create mode 100644 docs/recipes/content/multi_column_row.md create mode 100644 docs/recipes/content/stats.md create mode 100644 docs/recipes/content/top_banner.md create mode 100644 docs/recipes/layout/footer.md create mode 100644 docs/recipes/layout/navbar.md create mode 100644 docs/recipes/layout/sidebar.md create mode 100644 docs/recipes/others/checkboxes.md create mode 100644 docs/recipes/others/chips.md create mode 100644 docs/recipes/others/dark_mode_toggle.md create mode 100644 docs/recipes/others/pricing_cards.md create mode 100644 docs/recipes/others/speed_dial.md create mode 100644 docs/state/overview.md create mode 100644 docs/state_structure/component_state.md create mode 100644 docs/state_structure/mixins.md create mode 100644 docs/state_structure/overview.md create mode 100644 docs/state_structure/shared_state.md create mode 100644 docs/styling/common-props.md create mode 100644 docs/styling/custom-stylesheets.md create mode 100644 docs/styling/layout.md create mode 100644 docs/styling/overview.md create mode 100644 docs/styling/responsive.md create mode 100644 docs/styling/tailwind.md create mode 100644 docs/styling/theming.md delete mode 100644 docs/tr/README.md create mode 100644 docs/ui/overview.md create mode 100644 docs/utility_methods/exception_handlers.md create mode 100644 docs/utility_methods/lifespan_tasks.md create mode 100644 docs/utility_methods/other_methods.md create mode 100644 docs/utility_methods/router_attributes.md create mode 100644 docs/vars/base_vars.md create mode 100644 docs/vars/computed_vars.md create mode 100644 docs/vars/custom_vars.md create mode 100644 docs/vars/var-operations.md delete mode 100644 docs/vi/README.md create mode 100644 docs/wrapping-react/custom-code-and-hooks.md create mode 100644 docs/wrapping-react/example.md create mode 100644 docs/wrapping-react/imports-and-styles.md create mode 100644 docs/wrapping-react/library-and-tags.md create mode 100644 docs/wrapping-react/local-packages.md create mode 100644 docs/wrapping-react/more-wrapping-examples.md create mode 100644 docs/wrapping-react/overview.md create mode 100644 docs/wrapping-react/props.md create mode 100644 docs/wrapping-react/serializers.md create mode 100644 docs/wrapping-react/step-by-step.md delete mode 100644 docs/zh/zh_cn/README.md delete mode 100644 docs/zh/zh_tw/README.md diff --git a/docs/DEBUGGING.md b/DEBUGGING.md similarity index 100% rename from docs/DEBUGGING.md rename to DEBUGGING.md diff --git a/docs/__init__.py b/docs/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/docs/advanced_onboarding/code_structure.md b/docs/advanced_onboarding/code_structure.md new file mode 100644 index 00000000000..70c53648aa2 --- /dev/null +++ b/docs/advanced_onboarding/code_structure.md @@ -0,0 +1,365 @@ +```python exec +from pcweb.pages.docs import custom_components +``` + +# Project Structure (Advanced) + +## App Module + +Reflex imports the main app module based on the `app_name` from the config, which **must define a module-level global named `app` as an instance of `rx.App`**. + +The main app module is responsible for importing all other modules that make up the app and defining `app = rx.App()`. + +**All other modules containing pages, state, and models MUST be imported by the main app module or package** for Reflex to include them in the compiled output. + +# Breaking the App into Smaller Pieces + +As applications scale, effective organization is crucial. This is achieved by breaking the application down into smaller, manageable modules and organizing them into logical packages that avoid circular dependencies. + +In the following documentation there will be an app with an `app_name` of `example_big_app`. The main module would be `example_big_app/example_big_app.py`. + +In the [Putting it all together](#putting-it-all-together) section there is a visual of the project folder structure to help follow along with the examples below. + +### Pages Package: `example_big_app/pages` + +All complex apps will have multiple pages, so it is recommended to create `example_big_app/pages` as a package. + +1. This package should contain one module per page in the app. +2. If a particular page depends on the state, the substate should be defined in the same module as the page. +3. The page-returning function should be decorated with `rx.page()` to have it added as a route in the app. + +```python +import reflex as rx + +from ..state import AuthState + + +class LoginState(AuthState): + @rx.event + def handle_submit(self, form_data): + self.logged_in = authenticate(form_data["username"], form_data["password"]) + + +def login_field(name: str, **input_props): + return rx.hstack( + rx.text(name.capitalize()), + rx.input(name=name, **input_props), + width="100%", + justify="between", + ) + + +@rx.page(route="/login") +def login(): + return rx.card( + rx.form( + rx.vstack( + login_field("username"), + login_field("password", type="password"), + rx.button("Login"), + width="100%", + justify="center", + ), + on_submit=LoginState.handle_submit, + ), + ) +``` + +### Templating: `example_big_app/template.py` + +Most applications maintain a consistent layout and structure across pages. Defining this common structure in a separate module facilitates easy sharing and reuse when constructing individual pages. + +**Best Practices** + +1. Factor out common frontend UI elements into a function that returns a component. +2. If a function accepts a function that returns a component, it can be used as a decorator as seen below. + +```python +from typing import Callable + +import reflex as rx + +from .components.menu import menu +from .components.navbar import navbar + + +def template(page: Callable[[], rx.Component]) -> rx.Component: + return rx.vstack( + navbar(), + rx.hstack( + menu(), + rx.container(page()), + ), + width="100%", + ) +``` + +The `@template` decorator should appear below the `@rx.page` decorator and above the page-returning function. See the [Posts Page](#a-post-page-example_big_apppagespostspy) code for an example. + +## State Management + +Most pages will use State in some capacity. You should avoid adding vars to a +shared state that will only be used in a single page. Instead, define a new +subclass of `rx.State` and keep it in the same module as the page. + +### Accessing other States + +As of Reflex 0.4.3, any event handler can get access to an instance of any other +substate via the `get_state` API. From a practical perspective, this means that +state can be split up into smaller pieces without requiring a complex +inheritance hierarchy to share access to other states. + +In previous releases, if an app wanted to store settings in `SettingsState` with +a page or component for modifying them, any other state with an event handler +that needed to access those settings would have to inherit from `SettingsState`, +even if the other state was mostly orthogonal. The other state would also now +always have to load the settings, even for event handlers that didn't need to +access them. + +A better strategy is to load the desired state on demand from only the event +handler which needs access to the substate. + +### A Settings Component: `example_big_app/components/settings.py` + +```python +import reflex as rx + + +class SettingsState(rx.State): + refresh_interval: int = 15 + auto_update: bool = True + prefer_plain_text: bool = True + posts_per_page: int = 20 + + +def settings_dialog(): + return rx.dialog(...) +``` + +### A Post Page: `example_big_app/pages/posts.py` + +This page loads the `SettingsState` to determine how many posts to display per page +and how often to refresh. + +```python +import reflex as rx + +from ..models import Post +from ..template import template +from ..components.settings import SettingsState + + +class PostsState(rx.State): + refresh_tick: int + page: int + posts: list[Post] + + @rx.event + async def on_load(self): + settings = await self.get_state(SettingsState) + if settings.auto_update: + self.refresh_tick = settings.refresh_interval * 1000 + else: + self.refresh_tick = 0 + + @rx.event + async def tick(self, _): + settings = await self.get_state(SettingsState) + with rx.session() as session: + q = Post.select().offset(self.page * settings.posts_per_page).limit(settings.posts_per_page) + self.posts = q.all() + + @rx.event + def go_to_previous(self): + if self.page > 0: + self.page = self.page - 1 + + @rx.event + def go_to_next(self): + if self.posts: + self.page = self.page + 1 + + +@rx.page(route="/posts", on_load=PostsState.on_load) +@template +def posts(): + return rx.vstack( + rx.foreach(PostsState.posts, post_view), + rx.hstack( + rx.button("< Prev", on_click=PostsState.go_to_previous), + rx.button("Next >", on_click=PostsState.go_to_next), + justify="between", + ), + rx.moment(interval=PostsState.refresh_tick, on_change=PostsState.tick, display="none"), + width="100%", + ) +``` + +### Common State: `example_big_app/state.py` + +_Common_ states and substates that are shared by multiple pages or components +should be implemented in a separate module to avoid circular imports. This +module should not import other modules in the app. + +## Component Reusability + +The primary mechanism for reusing components in Reflex is to define a function that returns +the component, then simply call it where that functionality is needed. + +Component functions typically should not take any State classes as arguments, but prefer +to import the needed state and access the vars on the class directly. + +### Memoize Functions for Improved Performance + +In a large app, if a component has many subcomponents or is used in a large number of places, it can improve compile and runtime performance to memoize the function with the `@lru_cache` decorator. + +To memoize the `foo` component to avoid re-creating it many times simply add `@lru_cache` to the function definition, and the component will only be created once per unique set of arguments. + +```python +from functools import lru_cache + +import reflex as rx + +class State(rx.State): + v: str = "foo" + + +@lru_cache +def foo(): + return rx.text(State.v) + + +def index(): + return rx.flex( + rx.button("Change", on_click=State.set_v(rx.cond(State.v != "bar", "bar", "foo"))), + *[ + foo() + for _ in range(100) + ], + direction="row", + wrap="wrap", + ) +``` + +### example_big_app/components + +This package contains reusable parts of the app, for example headers, footers, +and menus. If a particular component requires state, the substate may be defined +in the same module for locality. Any substate defined in a component module +should only contain fields and event handlers pertaining to that individual +component. + +### External Components + +Reflex 0.4.3 introduced support for the [`reflex component` CLI commands]({custom_components.overview.path}), which makes it easy +to bundle up common functionality to publish on PyPI as a standalone Python package +that can be installed and used in any Reflex app. + +When wrapping npm components or other self-contained bits of functionality, it can be helpful +to move this complexity outside the app itself for easier maintenance and reuse in other apps. + +## Database Models: `example_big_app/models.py` + +It is recommended to implement all database models in a single file to make it easier to define relationships and understand the entire schema. + +However, if the schema is very large, it might make sense to have a `models` package with individual models defined in their own modules. + +At any rate, defining the models separately allows any page or component to import and use them without circular imports. + +## Top-level Package: `example_big_app/__init__.py` + +This is a great place to import all state, models, and pages that should be part of the app. +Typically, components and helpers do not need to imported, because they will be imported by +pages that use them (or they would be unused). + +```python +from . import state, models +from .pages import index, login, post, product, profile, schedule + +__all__ = [ + "state", + "models", + "index", + "login", + "post", + "product", + "profile", + "schedule", +] +``` + +If any pages are not imported here, they will not be compiled as part of the app. + +## example_big_app/example_big_app.py + +This is the main app module. Since everything else is defined in other modules, this file becomes very simple. + +```python +import reflex as rx + +app = rx.App() +``` + +## File Management + +There are two categories of non-code assets (media, fonts, stylesheets, +documents) typically used in a Reflex app. + +### assets + +The `assets` directory is used for **static** files that should be accessible +relative to the root of the frontend (default port 3000). When an app is deployed in +production mode, changes to the assets directory will NOT be available at runtime! + +When referencing an asset, always use a leading forward slash, so the +asset can be resolved regardless of the page route where it may appear. + +### uploaded_files + +If an app needs to make files available dynamically at runtime, it is +recommended to set the target directory via `REFLEX_UPLOADED_FILES_DIR` +environment variable (default `./uploaded_files`), write files relative to the +path returned by `rx.get_upload_dir()`, and create working links via +`rx.get_upload_url(relative_path)`. + +Uploaded files are served from the backend (default port 8000) via +`/_upload/` + +## Putting it all together + +Based on the previous discussion, the recommended project layout look like this. + +```text +example-big-app/ +├─ assets/ +├─ example_big_app/ +│ ├─ components/ +│ │ ├─ __init__.py +│ │ ├─ auth.py +│ │ ├─ footer.py +│ │ ├─ menu.py +│ │ ├─ navbar.py +│ ├─ pages/ +│ │ ├─ __init__.py +│ │ ├─ index.py +│ │ ├─ login.py +│ │ ├─ posts.py +│ │ ├─ product.py +│ │ ├─ profile.py +│ │ ├─ schedule.py +│ ├─ __init__.py +│ ├─ example_big_app.py +│ ├─ models.py +│ ├─ state.py +│ ├─ template.py +├─ uploaded_files/ +├─ requirements.txt +├─ rxconfig.py +``` + +## Key Takeaways + +- Like any other Python project, **split up the app into modules and packages** to keep the codebase organized and manageable. +- Using smaller modules and packages makes it easier to **reuse components and state** across the app + without introducing circular dependencies. +- Create **individual functions** to encapsulate units of functionality and **reuse them** where needed. diff --git a/docs/advanced_onboarding/configuration.md b/docs/advanced_onboarding/configuration.md new file mode 100644 index 00000000000..bce5179c727 --- /dev/null +++ b/docs/advanced_onboarding/configuration.md @@ -0,0 +1,60 @@ +```python exec +from pcweb.pages.docs import api_reference +``` + +# Configuration + +Reflex apps can be configured using a configuration file, environment variables, and command line arguments. + +## Configuration File + +Running `reflex init` will create an `rxconfig.py` file in your root directory. +You can pass keyword arguments to the `Config` class to configure your app. + +For example: + +```python +# rxconfig.py +import reflex as rx + +config = rx.Config( + app_name="my_app_name", + # Connect to your own database. + db_url="postgresql://user:password@localhost:5432/my_db", + # Change the frontend port. + frontend_port=3001, +) +``` + +See the [config reference]({api_reference.config.path}) for all the parameters available. + +## Environment Variables + +You can override the configuration file by setting environment variables. +For example, to override the `frontend_port` setting, you can set the `FRONTEND_PORT` environment variable. + +```bash +FRONTEND_PORT=3001 reflex run +``` + +## Command Line Arguments + +Finally, you can override the configuration file and environment variables by passing command line arguments to `reflex run`. + +```bash +reflex run --frontend-port 3001 +``` + +See the [CLI reference]({api_reference.cli.path}) for all the arguments available. + +## Customizable App Data Directory + +The `REFLEX_DIR` environment variable can be set, which allows users to set the location where Reflex writes helper tools like Bun and NodeJS. + +By default we use Platform specific directories: + +On windows, `C:/Users//AppData/Local/reflex` is used. + +On macOS, `~/Library/Application Support/reflex` is used. + +On linux, `~/.local/share/reflex` is used. diff --git a/docs/advanced_onboarding/how-reflex-works.md b/docs/advanced_onboarding/how-reflex-works.md new file mode 100644 index 00000000000..47b42afab50 --- /dev/null +++ b/docs/advanced_onboarding/how-reflex-works.md @@ -0,0 +1,243 @@ +```python exec +from pcweb import constants +from pcweb.constants import REFLEX_ASSETS_CDN +from pcweb.pages.docs import wrapping_react, custom_components, styling, events +from pcweb.pages.docs.custom_components import custom_components as cc +``` + +# How Reflex Works + +We'll use the following basic app that displays Github profile images as an example to explain the different parts of the architecture. + +```python demo exec +import requests +import reflex as rx + +class GithubState(rx.State): + url: str = "https://github.com/reflex-dev" + profile_image: str = "https://avatars.githubusercontent.com/u/104714959" + + @rx.event + def set_profile(self, username: str): + if username == "": + return + try: + github_data = requests.get(f"https://api.github.com/users/{username}").json() + except: + return + self.url = github_data["url"] + self.profile_image = github_data["avatar_url"] + +def index(): + return rx.hstack( + rx.link( + rx.avatar(src=GithubState.profile_image), + href=GithubState.url, + ), + rx.input( + placeholder="Your Github username", + on_blur=GithubState.set_profile, + ), + ) +``` + +## The Reflex Architecture + +Full-stack web apps are made up of a frontend and a backend. The frontend is the user interface, and is served as a web page that runs on the user's browser. The backend handles the logic and state management (such as databases and APIs), and is run on a server. + +In traditional web development, these are usually two separate apps, and are often written in different frameworks or languages. For example, you may combine a Flask backend with a React frontend. With this approach, you have to maintain two separate apps and end up writing a lot of boilerplate code to connect the frontend and backend. + +We wanted to simplify this process in Reflex by defining both the frontend and backend in a single codebase, while using Python for everything. Developers should only worry about their app's logic and not about the low-level implementation details. + +### TLDR + +Under the hood, Reflex apps compile down to a [React](https://react.dev) frontend app and a [FastAPI](https://github.com/tiangolo/fastapi) backend app. Only the UI is compiled to Javascript; all the app logic and state management stays in Python and is run on the server. Reflex uses [WebSockets](https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API) to send events from the frontend to the backend, and to send state updates from the backend to the frontend. + +The diagram below provides a detailed overview of how a Reflex app works. We'll go through each part in more detail in the following sections. + +```python exec +from reflex_image_zoom import image_zoom +``` + +```python eval +image_zoom(rx.image(src=f"{REFLEX_ASSETS_CDN}other/architecture.webp")) +``` + +```python eval +rx.box(height="1em") +``` + +## Frontend + +We wanted Reflex apps to look and feel like a traditional web app to the end user, while still being easy to build and maintain for the developer. To do this, we built on top of mature and popular web technologies. + +When you `reflex run` your app, Reflex compiles the frontend down to a single-page [Next.js](https://nextjs.org) app and serves it on a port (by default `3000`) that you can access in your browser. + +The frontend's job is to reflect the app's state, and send events to the backend when the user interacts with the UI. No actual logic is run on the frontend. + +### Components + +Reflex frontends are built using components that can be composed together to create complex UIs. Instead of using a templating language that mixes HTML and Python, we just use Python functions to define the UI. + +```python +def index(): + return rx.hstack( + rx.link( + rx.avatar(src=GithubState.profile_image), + href=GithubState.url, + ), + rx.input( + placeholder="Your Github username", + on_blur=GithubState.set_profile, + ), + ) +``` + +In our example app, we have components such as `rx.hstack`, `rx.avatar`, and `rx.input`. These components can have different **props** that affect their appearance and functionality - for example the `rx.input` component has a `placeholder` prop to display the default text. + +We can make our components respond to user interactions with events such as `on_blur`, which we will discuss more below. + +Under the hood, these components compile down to React components. For example, the above code compiles down to the following React code: + +```jsx + + + + + + +``` + +Many of our core components are based on [Radix](https://radix-ui.com/), a popular React component library. We also have many other components for graphing, datatables, and more. + +We chose React because it is a popular library with a huge ecosystem. Our goal isn't to recreate the web ecosystem, but to make it accessible to Python developers. + +This also lets our users bring their own components if we don't have a component they need. Users can [wrap their own React components]({wrapping_react.overview.path}) and then [publish them]({custom_components.overview.path}) for others to use. Over time we will build out our [third party component ecosystem]({cc.path}) so that users can easily find and use components that others have built. + +### Styling + +We wanted to make sure Reflex apps look good out of the box, while still giving developers full control over the appearance of their app. + +We have a core [theming system]({styling.theming.path}) that lets you set high level styling options such as dark mode and accent color throughout your app to give it a unified look and feel. + +Beyond this, Reflex components can be styled using the full power of CSS. We leverage the [Emotion](https://emotion.sh/docs/introduction) library to allow "CSS-in-Python" styling, so you can pass any CSS prop as a keyword argument to a component. This includes [responsive props]({styling.responsive.path}) by passing a list of values. + +## Backend + +Now let's look at how we added interactivity to our apps. + +In Reflex only the frontend compiles to Javascript and runs on the user's browser, while all the state and logic stays in Python and is run on the server. When you `reflex run`, we start a FastAPI server (by default on port `8000`) that the frontend connects to through a websocket. + +All the state and logic are defined within a `State` class. + +```python +class GithubState(rx.State): + url: str = "https://github.com/reflex-dev" + profile_image: str = "https://avatars.githubusercontent.com/u/104714959" + + def set_profile(self, username: str): + if username == "": + return + github_data = requests.get(f"https://api.github.com/users/\{username}").json() + self.url = github_data["url"] + self.profile_image = github_data["avatar_url"] +``` + +The state is made up of **vars** and **event handlers**. + +Vars are any values in your app that can change over time. They are defined as class attributes on your `State` class, and may be any Python type that can be serialized to JSON. In our example, `url` and `profile_image` are vars. + +Event handlers are methods in your `State` class that are called when the user interacts with the UI. They are the only way that we can modify the vars in Reflex, and can be called in response to user actions, such as clicking a button or typing in a text box. In our example, `set_profile` is an event handler that updates the `url` and `profile_image` vars. + +Since event handlers are run on the backend, you can use any Python library within them. In our example, we use the `requests` library to make an API call to Github to get the user's profile image. + +## Event Processing + +Now we get into the interesting part - how we handle events and state updates. + +Normally when writing web apps, you have to write a lot of boilerplate code to connect the frontend and backend. With Reflex, you don't have to worry about that - we handle the communication between the frontend and backend for you. Developers just have to write their event handler logic, and when the vars are updated the UI is automatically updated. + +You can refer to the diagram above for a visual representation of the process. Let's walk through it with our Github profile image example. + +### Event Triggers + +The user can interact with the UI in many ways, such as clicking a button, typing in a text box, or hovering over an element. In Reflex, we call these **event triggers**. + +```python +rx.input( + placeholder="Your Github username", + on_blur=GithubState.set_profile, +) +``` + +In our example we bind the `on_blur` event trigger to the `set_profile` event handler. This means that when the user types in the input field and then clicks away, the `set_profile` event handler is called. + +### Event Queue + +On the frontend, we maintain an event queue of all pending events. An event consists of three major pieces of data: + +- **client token**: Each client (browser tab) has a unique token to identify it. This let's the backend know which state to update. +- **event handler**: The event handler to run on the state. +- **arguments**: The arguments to pass to the event handler. + +Let's assume I type my username "picklelo" into the input. In this example, our event would look something like this: + +```json +{ + "client_token": "abc123", + "event_handler": "GithubState.set_profile", + "arguments": ["picklelo"] +} +``` + +On the frontend, we maintain an event queue of all pending events. + +When an event is triggered, it is added to the queue. We have a `processing` flag to make sure only one event is processed at a time. This ensures that the state is always consistent and there aren't any race conditions with two event handlers modifying the state at the same time. + +```md alert info +# There are exceptions to this, such as [background events]({events.background_events.path}) which allow you to run events in the background without blocking the UI. +``` + +Once the event is ready to be processed, it is sent to the backend through a WebSocket connection. + +### State Manager + +Once the event is received, it is processed on the backend. + +Reflex uses a **state manager** which maintains a mapping between client tokens and their state. By default, the state manager is just an in-memory dictionary, but it can be extended to use a database or cache. In production we use Redis as our state manager. + +### Event Handling + +Once we have the user's state, the next step is to run the event handler with the arguments. + +```python + def set_profile(self, username: str): + if username == "": + return + github_data = requests.get(f"https://api.github.com/users/\{username}").json() + self.url = github_data["url"] + self.profile_image = github_data["avatar_url"] +``` + +In our example, the `set_profile` event handler is run on the user's state. This makes an API call to Github to get the user's profile image, and then updates the state's `url` and `profile_image` vars. + +### State Updates + +Every time an event handler returns (or [yields]({events.yield_events.path})), we save the state in the state manager and send the **state updates** to the frontend to update the UI. + +To maintain performance as your state grows, internally Reflex keeps track of vars that were updated during the event handler (**dirty vars**). When the event handler is done processing, we find all the dirty vars and create a state update to send to the frontend. + +In our case, the state update may look something like this: + +```json +{ + "url": "https://github.com/picklelo", + "profile_image": "https://avatars.githubusercontent.com/u/104714959" +} +``` + +We store the new state in our state manager, and then send the state update to the frontend. The frontend then updates the UI to reflect the new state. In our example, the new Github profile image is displayed. diff --git a/docs/api-reference/browser_javascript.md b/docs/api-reference/browser_javascript.md new file mode 100644 index 00000000000..7321653d108 --- /dev/null +++ b/docs/api-reference/browser_javascript.md @@ -0,0 +1,224 @@ +```python exec +import asyncio +from typing import Any +import reflex as rx +from pcweb.pages.docs import wrapping_react +from pcweb.pages.docs import library +``` + +# Browser Javascript + +Reflex compiles your frontend code, defined as python functions, into a Javascript web application +that runs in the user's browser. There are instances where you may need to supply custom javascript +code to interop with Web APIs, use certain third-party libraries, or wrap low-level functionality +that is not exposed via Reflex's Python API. + +```md alert +# Avoid Custom Javascript + +Custom Javascript code in your Reflex app presents a maintenance challenge, as it will be harder to debug and may be unstable across Reflex versions. + +Prefer to use the Python API whenever possible and file an issue if you need additional functionality that is not currently provided. +``` + +## Executing Script + +There are four ways to execute custom Javascript code into your Reflex app: + +- `rx.script` - Injects the script via `next/script` for efficient loading of inline and external Javascript code. Described further in the [component library]({library.other.script.path}). + - These components can be directly included in the body of a page, or they may + be passed to `rx.App(head_components=[rx.script(...)])` to be included in + the `` tag of all pages. +- `rx.call_script` - An event handler that evaluates arbitrary Javascript code, + and optionally returns the result to another event handler. + +These previous two methods can work in tandem to load external scripts and then +call functions defined within them in response to user events. + +The following two methods are geared towards wrapping components and are +described with examples in the [Wrapping React]({wrapping_react.overview.path}) +section. + +- `_get_hooks` and `_get_custom_code` in an `rx.Component` subclass +- `Var.create` with `_var_is_local=False` + +## Inline Scripts + +The `rx.script` component is the recommended way to load inline Javascript for greater control over +frontend behavior. + +The functions and variables in the script can be accessed from backend event +handlers or frontend event triggers via the `rx.call_script` interface. + +```python demo exec +class SoundEffectState(rx.State): + @rx.event(background=True) + async def delayed_play(self): + await asyncio.sleep(1) + return rx.call_script("playFromStart(button_sfx)") + + +def sound_effect_demo(): + return rx.hstack( + rx.script(""" + var button_sfx = new Audio("/vintage-button-sound-effect.mp3") + function playFromStart (sfx) {sfx.load(); sfx.play()}"""), + rx.button("Play Immediately", on_click=rx.call_script("playFromStart(button_sfx)")), + rx.button("Play Later", on_click=SoundEffectState.delayed_play), + ) +``` + +## External Scripts + +External scripts can be loaded either from the `assets` directory, or from CDN URL, and then controlled +via `rx.call_script`. + +```python demo +rx.vstack( + rx.script( + src="https://cdn.jsdelivr.net/gh/scottschiller/snowstorm@snowstorm_20131208/snowstorm-min.js", + ), + rx.script(""" + window.addEventListener('load', function() { + if (typeof snowStorm !== 'undefined') { + snowStorm.autoStart = false; + snowStorm.snowColor = '#111'; + } + }); + """), + rx.button("Start Duststorm", on_click=rx.call_script("snowStorm.start()")), + rx.button("Toggle Duststorm", on_click=rx.call_script("snowStorm.toggleSnow()")), +) +``` + +## Accessing Client Side Values + +The `rx.call_script` function accepts a `callback` parameter that expects an +Event Handler with one argument which will receive the result of evaluating the +Javascript code. This can be used to access client-side values such as the +`window.location` or current scroll location, or any previously defined value. + +```python demo exec +class WindowState(rx.State): + location: dict[str, str] = {} + scroll_position: dict[str, int] = {} + + def update_location(self, location): + self.location = location + + def update_scroll_position(self, scroll_position): + self.scroll_position = { + "x": scroll_position[0], + "y": scroll_position[1], + } + + @rx.event + def get_client_values(self): + return [ + rx.call_script( + "window.location", + callback=WindowState.update_location + ), + rx.call_script( + "[window.scrollX, window.scrollY]", + callback=WindowState.update_scroll_position, + ), + ] + + +def window_state_demo(): + return rx.vstack( + rx.button("Update Values", on_click=WindowState.get_client_values), + rx.text(f"Scroll Position: {WindowState.scroll_position.to_string()}"), + rx.text("window.location:"), + rx.text_area(value=WindowState.location.to_string(), is_read_only=True), + on_mount=WindowState.get_client_values, + ) +``` + +```md alert +# Allowed Callback Values + +The `callback` parameter may be an `EventHandler` with one argument, or a lambda with one argument that returns an `EventHandler`. +If the callback is None, then no event is triggered. +``` + +## Using React Hooks + +To use React Hooks directly in a Reflex app, you must subclass `rx.Component`, +typically `rx.Fragment` is used when the hook functionality has no visual +element. The hook code is returned by the `add_hooks` method, which is expected +to return a `list[str]` containing Javascript code which will be inserted into the +page component (i.e the render function itself). + +For supporting code that must be defined outside of the component render +function, use `_get_custom_code`. + +The following example uses `useEffect` to register global hotkeys on the +`document` object, and then triggers an event when a specific key is pressed. + +```python demo exec +import dataclasses + +from reflex.utils import imports + +@dataclasses.dataclass +class KeyEvent: + """Interface of Javascript KeyboardEvent""" + key: str = "" + +def key_event_spec(ev: rx.Var[KeyEvent]) -> tuple[rx.Var[str]]: + # Takes the event object and returns the key pressed to send to the state + return (ev.key,) + +class GlobalHotkeyState(rx.State): + key: str = "" + + @rx.event + def update_key(self, key): + self.key = key + + +class GlobalHotkeyWatcher(rx.Fragment): + """A component that listens for key events globally.""" + + # The event handler that will be called + on_key_down: rx.EventHandler[key_event_spec] + + def add_imports(self) -> imports.ImportDict: + """Add the imports for the component.""" + return { + "react": [imports.ImportVar(tag="useEffect")], + } + + def add_hooks(self) -> list[str | rx.Var]: + """Add the hooks for the component.""" + return [ + """ + useEffect(() => { + const handle_key = %s; + document.addEventListener("keydown", handle_key, false); + return () => { + document.removeEventListener("keydown", handle_key, false); + } + }) + """ + % str(rx.Var.create(self.event_triggers["on_key_down"])) + ] + +def global_key_demo(): + return rx.vstack( + GlobalHotkeyWatcher.create( + keys=["a", "s", "d", "w"], + on_key_down=lambda key: rx.cond( + rx.Var.create(["a", "s", "d", "w"]).contains(key), + GlobalHotkeyState.update_key(key), + rx.console_log(key) + ) + ), + rx.text("Press a, s, d or w to trigger an event"), + rx.heading(f"Last watched key pressed: {GlobalHotkeyState.key}"), + ) +``` + +This snippet can also be imported through pip: [reflex-global-hotkey](https://pypi.org/project/reflex-global-hotkey/). diff --git a/docs/api-reference/browser_storage.md b/docs/api-reference/browser_storage.md new file mode 100644 index 00000000000..525a502ef9c --- /dev/null +++ b/docs/api-reference/browser_storage.md @@ -0,0 +1,333 @@ +# Browser Storage + +## rx.Cookie + +Represents a state Var that is stored as a cookie in the browser. Currently only supports string values. + +Parameters + +- `name` : The name of the cookie on the client side. +- `path`: The cookie path. Use `/` to make the cookie accessible on all pages. +- `max_age` : Relative max age of the cookie in seconds from when the client receives it. +- `domain`: Domain for the cookie (e.g., `sub.domain.com` or `.allsubdomains.com`). +- `secure`: If the cookie is only accessible through HTTPS. +- `same_site`: Whether the cookie is sent with third-party requests. Can be one of (`True`, `False`, `None`, `lax`, `strict`). + +```python +class CookieState(rx.State): + c1: str = rx.Cookie() + c2: str = rx.Cookie('c2 default') + + # cookies with custom settings + c3: str = rx.Cookie(max_age=2) # expires after 2 second + c4: str = rx.Cookie(same_site='strict') + c5: str = rx.Cookie(path='/foo/') # only accessible on `/foo/` + c6: str = rx.Cookie(name='c6-custom-name') +``` + +```md alert warning +# **The default value of a Cookie is never set in the browser!** + +The Cookie value is only set when the Var is assigned. If you need to set a +default value, you can assign a value to the cookie in an `on_load` event +handler. +``` + +## Accessing Cookies + +Cookies are accessed like any other Var in the state. If another state needs access +to the value of a cookie, the state should be a substate of the state that defines +the cookie. Alternatively the `get_state` API can be used to access the other state. + +For rendering cookies in the frontend, import the state that defines the cookie and +reference it directly. + +```md alert warning +# **Two separate states should _avoid_ defining `rx.Cookie` with the same name.** + +Although it is technically possible, the cookie options may differ, leading to +unexpected results. + +Additionally, updating the cookie value in one state will not automatically +update the value in the other state without a page refresh or navigation event. +``` + +## rx.remove_cookies + +Remove a cookie from the client's browser. + +Parameters: + +- `key`: The name of cookie to remove. + +```python +rx.button( + 'Remove cookie', on_click=rx.remove_cookie('key') +) +``` + +This event can also be returned from an event handler: + +```python +class CookieState(rx.State): + ... + def logout(self): + return rx.remove_cookie('auth_token') +``` + +## rx.LocalStorage + +Represents a state Var that is stored in localStorage in the browser. Currently only supports string values. + +Parameters + +- `name`: The name of the storage key on the client side. +- `sync`: Boolean indicates if the state should be kept in sync across tabs of the same browser. + +```python +class LocalStorageState(rx.State): + # local storage with default settings + l1: str = rx.LocalStorage() + + # local storage with custom settings + l2: str = rx.LocalStorage("l2 default") + l3: str = rx.LocalStorage(name="l3") + + # local storage that automatically updates in other states across tabs + l4: str = rx.LocalStorage(sync=True) +``` + +### Syncing Vars + +Because LocalStorage applies to the entire browser, all LocalStorage Vars are +automatically shared across tabs. + +The `sync` parameter controls whether an update in one tab should be actively +propagated to other tabs without requiring a navigation or page refresh event. + +## rx.remove_local_storage + +Remove a local storage item from the client's browser. + +Parameters + +- `key`: The key to remove from local storage. + +```python +rx.button( + 'Remove Local Storage', + on_click=rx.remove_local_storage('key'), +) +``` + +This event can also be returned from an event handler: + +```python +class LocalStorageState(rx.State): + ... + def logout(self): + return rx.remove_local_storage('local_storage_state.l1') +``` + +## rx.clear_local_storage() + +Clear all local storage items from the client's browser. This may affect other +apps running in the same domain or libraries within your app that use local +storage. + +```python +rx.button( + 'Clear all Local Storage', + on_click=rx.clear_local_storage(), +) +``` + +## rx.SessionStorage + +Represents a state Var that is stored in sessionStorage in the browser. Similar to localStorage, but the data is cleared when the page session ends (when the browser/tab is closed). Currently only supports string values. + +Parameters + +- `name`: The name of the storage key on the client side. + +```python +class SessionStorageState(rx.State): + # session storage with default settings + s1: str = rx.SessionStorage() + + # session storage with custom settings + s2: str = rx.SessionStorage("s2 default") + s3: str = rx.SessionStorage(name="s3") +``` + +### Session Persistence + +SessionStorage data is cleared when the page session ends. A page session lasts as long as the browser is open and survives page refreshes and restores, but is cleared when the tab or browser is closed. + +Unlike LocalStorage, SessionStorage is isolated to the tab/window in which it was created, so it's not shared with other tabs/windows of the same origin. + +## rx.remove_session_storage + +Remove a session storage item from the client's browser. + +Parameters + +- `key`: The key to remove from session storage. + +```python +rx.button( + 'Remove Session Storage', + on_click=rx.remove_session_storage('key'), +) +``` + +This event can also be returned from an event handler: + +```python +class SessionStorageState(rx.State): + ... + def logout(self): + return rx.remove_session_storage('session_storage_state.s1') +``` + +## rx.clear_session_storage() + +Clear all session storage items from the client's browser. This may affect other +apps running in the same domain or libraries within your app that use session +storage. + +```python +rx.button( + 'Clear all Session Storage', + on_click=rx.clear_session_storage(), +) +``` + +# Serialization Strategies + +If a non-trivial data structure should be stored in a `Cookie`, `LocalStorage`, or `SessionStorage` var it needs to be serialized before and after storing it. It is recommended to use a pydantic class for the data which provides simple serialization helpers and works recursively in complex object structures. + +```python demo exec +import reflex as rx +import pydantic + + +class AppSettings(pydantic.BaseModel): + theme: str = 'light' + sidebar_visible: bool = True + update_frequency: int = 60 + error_messages: list[str] = pydantic.Field(default_factory=list) + + +class ComplexLocalStorageState(rx.State): + data_raw: str = rx.LocalStorage("{}") + data: AppSettings = AppSettings() + settings_open: bool = False + + @rx.event + def save_settings(self): + self.data_raw = self.data.model_dump_json() + self.settings_open = False + + @rx.event + def open_settings(self): + self.data = AppSettings.model_validate_json(self.data_raw) + self.settings_open = True + + @rx.event + def set_field(self, field, value): + setattr(self.data, field, value) + + +def app_settings(): + return rx.form.root( + rx.foreach( + ComplexLocalStorageState.data.error_messages, + rx.text, + ), + rx.form.field( + rx.flex( + rx.form.label( + "Theme", + rx.input( + value=ComplexLocalStorageState.data.theme, + on_change=lambda v: ComplexLocalStorageState.set_field( + "theme", v + ), + ), + ), + rx.form.label( + "Sidebar Visible", + rx.switch( + checked=ComplexLocalStorageState.data.sidebar_visible, + on_change=lambda v: ComplexLocalStorageState.set_field( + "sidebar_visible", + v, + ), + ), + ), + rx.form.label( + "Update Frequency (seconds)", + rx.input( + value=ComplexLocalStorageState.data.update_frequency, + on_change=lambda v: ComplexLocalStorageState.set_field( + "update_frequency", + v, + ), + ), + ), + rx.dialog.close(rx.button("Save", type="submit")), + gap=2, + direction="column", + ) + ), + on_submit=lambda _: ComplexLocalStorageState.save_settings(), + ) + +def app_settings_example(): + return rx.dialog.root( + rx.dialog.trigger( + rx.button("App Settings", on_click=ComplexLocalStorageState.open_settings), + ), + rx.dialog.content( + rx.dialog.title("App Settings"), + app_settings(), + ), + ) +``` + +# Comparison of Storage Types + +Here's a comparison of the different client-side storage options in Reflex: + +| Feature | rx.Cookie | rx.LocalStorage | rx.SessionStorage | +|---------|-----------|----------------|------------------| +| Persistence | Until cookie expires | Until explicitly deleted | Until browser/tab is closed | +| Storage Limit | ~4KB | ~5MB | ~5MB | +| Sent with Requests | Yes | No | No | +| Accessibility | Server & Client | Client Only | Client Only | +| Expiration | Configurable | Never | End of session | +| Scope | Configurable (domain, path) | Origin (domain) | Tab/Window | +| Syncing Across Tabs | No | Yes (with sync=True) | No | +| Use Case | Authentication, Server-side state | User preferences, App state | Temporary session data | + +# When to Use Each Storage Type + +## Use rx.Cookie When: +- You need the data to be accessible on the server side (cookies are sent with HTTP requests) +- You're handling user authentication +- You need fine-grained control over expiration and scope +- You need to limit the data to specific paths in your app + +## Use rx.LocalStorage When: +- You need to store larger amounts of data (up to ~5MB) +- You want the data to persist indefinitely (until explicitly deleted) +- You need to share data between different tabs/windows of your app +- You want to store user preferences that should be remembered across browser sessions + +## Use rx.SessionStorage When: +- You need temporary data that should be cleared when the browser/tab is closed +- You want to isolate data to a specific tab/window +- You're storing sensitive information that shouldn't persist after the session ends +- You're implementing per-session features like form data, shopping carts, or multi-step processes +- You want to persist data for a state after Redis expiration (for server-side state that needs to survive longer than Redis TTL) diff --git a/docs/api-reference/cli.md b/docs/api-reference/cli.md new file mode 100644 index 00000000000..76da441c036 --- /dev/null +++ b/docs/api-reference/cli.md @@ -0,0 +1,128 @@ +# CLI + +The `reflex` command line interface (CLI) is a tool for creating and managing Reflex apps. + +To see a list of all available commands, run `reflex --help`. + +```bash +$ reflex --help + +Usage: reflex [OPTIONS] COMMAND [ARGS]... + + Reflex CLI to create, run, and deploy apps. + +Options: + --version Show the version and exit. + --help Show this message and exit. + +Commands: + cloud The Hosting CLI. + component CLI for creating custom components. + db Subcommands for managing the database schema. + deploy Deploy the app to the Reflex hosting service. + export Export the app to a zip file. + init Initialize a new Reflex app in the current directory. + login Authenticate with experimental Reflex hosting service. + logout Log out of access to Reflex hosting service. + rename Rename the app in the current directory. + run Run the app in the current directory. + script Subcommands for running helper scripts. +``` + +## Init + +The `reflex init` command creates a new Reflex app in the current directory. +If an `rxconfig.py` file already exists already, it will re-initialize the app with the latest template. + +```bash +$ reflex init --help +Usage: reflex init [OPTIONS] + + Initialize a new Reflex app in the current directory. + +Options: + --name APP_NAME The name of the app to initialize. + --template [demo|sidebar|blank] + The template to initialize the app with. + --loglevel [debug|info|warning|error|critical] + The log level to use. [default: + LogLevel.INFO] + --help Show this message and exit. +``` + +## Run + +The `reflex run` command runs the app in the current directory. + +By default it runs your app in development mode. +This means that the app will automatically reload when you make changes to the code. +You can also run in production mode which will create an optimized build of your app. + +You can configure the mode, as well as other options through flags. + +```bash +$ reflex run --help +Usage: reflex run [OPTIONS] + + Run the app in the current directory. + +Options: + --env [dev|prod] The environment to run the app in. + [default: Env.DEV] + --frontend-only Execute only frontend. + --backend-only Execute only backend. + --frontend-port TEXT Specify a different frontend port. + [default: 3000] + --backend-port TEXT Specify a different backend port. [default: + 8000] + --backend-host TEXT Specify the backend host. [default: + 0.0.0.0] + --loglevel [debug|info|warning|error|critical] + The log level to use. [default: + LogLevel.INFO] + --help Show this message and exit. +``` + +## Export + +You can export your app's frontend and backend to zip files using the `reflex export` command. + +The frontend is a compiled NextJS app, which can be deployed to a static hosting service like Github Pages or Vercel. +However this is just a static build, so you will need to deploy the backend separately. +See the self-hosting guide for more information. + +## Rename + +The `reflex rename` command allows you to rename your Reflex app. This updates the app name in the configuration files. + +```bash +$ reflex rename --help +Usage: reflex rename [OPTIONS] NEW_NAME + + Rename the app in the current directory. + +Options: + --loglevel [debug|default|info|warning|error|critical] + The log level to use. + --help Show this message and exit. +``` + +## Cloud + +The `reflex cloud` command provides access to the Reflex Cloud hosting service. It includes subcommands for managing apps, projects, secrets, and more. + +For detailed documentation on Reflex Cloud and deployment, see the [Cloud Quick Start Guide](https://reflex.dev/docs/hosting/deploy-quick-start/). + +## Script + +The `reflex script` command provides access to helper scripts for Reflex development. + +```bash +$ reflex script --help +Usage: reflex script [OPTIONS] COMMAND [ARGS]... + + Subcommands for running helper scripts. + +Options: + --help Show this message and exit. +``` diff --git a/docs/api-reference/event_triggers.md b/docs/api-reference/event_triggers.md new file mode 100644 index 00000000000..4a9620d99f5 --- /dev/null +++ b/docs/api-reference/event_triggers.md @@ -0,0 +1,390 @@ +```python exec +from datetime import datetime + +import reflex as rx + +from pcweb.templates.docpage import docdemo, h1_comp, text_comp, docpage +from pcweb.pages.docs import events + +SYNTHETIC_EVENTS = [ + { + "name": "on_focus", + "description": "The on_focus event handler is called when the element (or some element inside of it) receives focus. For example, it’s called when the user clicks on a text input.", + "state": """class FocusState(rx.State): + text = "Change Me!" + + @rx.event + def change_text(self, text): + if self.text == "Change Me!": + self.text = "Changed!" + else: + self.text = "Change Me!" +""", + "example": """rx.input(value=FocusState.text, on_focus=FocusState.change_text)""", + }, + { + "name": "on_blur", + "description": "The on_blur event handler is called when focus has left the element (or left some element inside of it). For example, it’s called when the user clicks outside of a focused text input.", + "state": """class BlurState(rx.State): + text = "Change Me!" + + @rx.event + def change_text(self, text): + if self.text == "Change Me!": + self.text = "Changed!" + else: + self.text = "Change Me!" +""", + "example": """rx.input(value=BlurState.text, on_blur=BlurState.change_text)""", + }, + { + "name": "on_change", + "description": "The on_change event handler is called when the value of an element has changed. For example, it’s called when the user types into a text input each keystroke triggers the on change.", + "state": """class ChangeState(rx.State): + checked: bool = False + + @rx.event + def set_checked(self): + self.checked = not self.checked + +""", + "example": """rx.switch(on_change=ChangeState.set_checked)""", + }, + { + "name": "on_click", + "description": "The on_click event handler is called when the user clicks on an element. For example, it’s called when the user clicks on a button.", + "state": """class ClickState(rx.State): + text = "Change Me!" + + @rx.event + def change_text(self): + if self.text == "Change Me!": + self.text = "Changed!" + else: + self.text = "Change Me!" +""", + "example": """rx.button(ClickState.text, on_click=ClickState.change_text)""", + }, + { + "name": "on_context_menu", + "description": "The on_context_menu event handler is called when the user right-clicks on an element. For example, it’s called when the user right-clicks on a button.", + "state": """class ContextState(rx.State): + text = "Change Me!" + + @rx.event + def change_text(self): + if self.text == "Change Me!": + self.text = "Changed!" + else: + self.text = "Change Me!" +""", + "example": """rx.button(ContextState.text, on_context_menu=ContextState.change_text)""", + }, + { + "name": "on_double_click", + "description": "The on_double_click event handler is called when the user double-clicks on an element. For example, it’s called when the user double-clicks on a button.", + "state": """class DoubleClickState(rx.State): + text = "Change Me!" + + @rx.event + def change_text(self): + if self.text == "Change Me!": + self.text = "Changed!" + else: + self.text = "Change Me!" +""", + "example": """rx.button(DoubleClickState.text, on_double_click=DoubleClickState.change_text)""", + }, + { + "name": "on_mount", + "description": "The on_mount event handler is called after the component is rendered on the page. It is similar to a page on_load event, although it does not necessarily fire when navigating between pages. This event is particularly useful for initializing data, making API calls, or setting up component-specific state when a component first appears.", + "state": """class MountState(rx.State): + events: list[str] = [] + data: list[dict] = [] + loading: bool = False + + @rx.event + def on_mount(self): + self.events = self.events[-4:] + ["on_mount @ " + str(datetime.now())] + + @rx.event + async def load_data(self): + # Common pattern: Set loading state, yield to update UI, then fetch data + self.loading = True + yield + # Simulate API call + import asyncio + await asyncio.sleep(1) + self.data = [dict(id=1, name="Item 1"), dict(id=2, name="Item 2")] + self.loading = False +""", + "example": """rx.vstack( + rx.heading("Component Lifecycle Demo"), + rx.foreach(MountState.events, rx.text), + rx.cond( + MountState.loading, + rx.spinner(), + rx.foreach( + MountState.data, + lambda item: rx.text(f"ID: {item['id']} - {item['name']}") + ) + ), + on_mount=MountState.on_mount, +)""", + }, + { + "name": "on_unmount", + "description": "The on_unmount event handler is called after removing the component from the page. However, on_unmount will only be called for internal navigation, not when following external links or refreshing the page. This event is useful for cleaning up resources, saving state, or performing cleanup operations before a component is removed from the DOM.", + "state": """class UnmountState(rx.State): + events: list[str] = [] + resource_id: str = "resource-12345" + status: str = "Resource active" + + @rx.event + def on_unmount(self): + self.events = self.events[-4:] + ["on_unmount @ " + str(datetime.now())] + # Common pattern: Clean up resources when component is removed + self.status = f"Resource {self.resource_id} cleaned up" + + @rx.event + def initialize_resource(self): + self.status = f"Resource {self.resource_id} initialized" +""", + "example": """rx.vstack( + rx.heading("Unmount Demo"), + rx.foreach(UnmountState.events, rx.text), + rx.text(UnmountState.status), + rx.link( + rx.button("Navigate Away (Triggers Unmount)"), + href="/docs", + ), + on_mount=UnmountState.initialize_resource, + on_unmount=UnmountState.on_unmount, +)""", + }, + { + "name": "on_mouse_up", + "description": "The on_mouse_up event handler is called when the user releases a mouse button on an element. For example, it’s called when the user releases the left mouse button on a button.", + "state": """class MouseUpState(rx.State): + text = "Change Me!" + + @rx.event + def change_text(self): + if self.text == "Change Me!": + self.text = "Changed!" + else: + self.text = "Change Me!" +""", + "example": """rx.button(MouseUpState.text, on_mouse_up=MouseUpState.change_text)""", + }, + { + "name": "on_mouse_down", + "description": "The on_mouse_down event handler is called when the user presses a mouse button on an element. For example, it’s called when the user presses the left mouse button on a button.", + "state": """class MouseDown(rx.State): + text = "Change Me!" + + @rx.event + def change_text(self): + if self.text == "Change Me!": + self.text = "Changed!" + else: + self.text = "Change Me!" +""", + "example": """rx.button(MouseDown.text, on_mouse_down=MouseDown.change_text)""", + }, + { + "name": "on_mouse_enter", + "description": "The on_mouse_enter event handler is called when the user’s mouse enters an element. For example, it’s called when the user’s mouse enters a button.", + "state": """class MouseEnter(rx.State): + text = "Change Me!" + + @rx.event + def change_text(self): + if self.text == "Change Me!": + self.text = "Changed!" + else: + self.text = "Change Me!" +""", + "example": """rx.button(MouseEnter.text, on_mouse_enter=MouseEnter.change_text)""", + }, + { + "name": "on_mouse_leave", + "description": "The on_mouse_leave event handler is called when the user’s mouse leaves an element. For example, it’s called when the user’s mouse leaves a button.", + "state": """class MouseLeave(rx.State): + text = "Change Me!" + + @rx.event + def change_text(self): + if self.text == "Change Me!": + self.text = "Changed!" + else: + self.text = "Change Me!" +""", + "example": """rx.button(MouseLeave.text, on_mouse_leave=MouseLeave.change_text)""", + }, + { + "name": "on_mouse_move", + "description": "The on_mouse_move event handler is called when the user moves the mouse over an element. For example, it’s called when the user moves the mouse over a button.", + "state": """class MouseMove(rx.State): + text = "Change Me!" + + @rx.event + def change_text(self): + if self.text == "Change Me!": + self.text = "Changed!" + else: + self.text = "Change Me!" +""", + "example": """rx.button(MouseMove.text, on_mouse_move=MouseMove.change_text)""", + }, + { + "name": "on_mouse_out", + "description": "The on_mouse_out event handler is called when the user’s mouse leaves an element. For example, it’s called when the user’s mouse leaves a button.", + "state": """class MouseOut(rx.State): + text = "Change Me!" + + @rx.event + def change_text(self): + if self.text == "Change Me!": + self.text = "Changed!" + else: + self.text = "Change Me!" +""", + "example": """rx.button(MouseOut.text, on_mouse_out=MouseOut.change_text)""", + }, + { + "name": "on_mouse_over", + "description": "The on_mouse_over event handler is called when the user’s mouse enters an element. For example, it’s called when the user’s mouse enters a button.", + "state": """class MouseOver(rx.State): + text = "Change Me!" + + @rx.event + def change_text(self): + if self.text == "Change Me!": + self.text = "Changed!" + else: + self.text = "Change Me!" +""", + "example": """rx.button(MouseOver.text, on_mouse_over=MouseOver.change_text)""", + }, + { + "name": "on_scroll", + "description": "The on_scroll event handler is called when the user scrolls the page. For example, it’s called when the user scrolls the page down.", + "state": """class ScrollState(rx.State): + text = "Change Me!" + + @rx.event + def change_text(self): + if self.text == "Change Me!": + self.text = "Changed!" + else: + self.text = "Change Me!" +""", + "example": """rx.vstack( + rx.text("Scroll to make the text below change."), + rx.text(ScrollState.text), + rx.text("Scroll to make the text above change."), + on_scroll=ScrollState.change_text, + overflow = "auto", + height = "3em", + width = "100%", + )""", + }, +] +for i in SYNTHETIC_EVENTS: + exec(i["state"]) + +def component_grid(): + events = [] + for event in SYNTHETIC_EVENTS: + events.append( + rx.vstack( + h1_comp(text=event["name"]), + text_comp(text=event["description"]), + docdemo( + event["example"], state=event["state"], comp=eval(event["example"]) + ), + align_items="left", + ) + ) + + return rx.box(*events) +``` + +# Event Triggers + +Components can modify the state based on user events such as clicking a button or entering text in a field. +These events are triggered by event triggers. + +Event triggers are component specific and are listed in the documentation for each component. + +## Component Lifecycle Events + +Reflex components have lifecycle events like `on_mount` and `on_unmount` that allow you to execute code at specific points in a component's existence. These events are crucial for initializing data, cleaning up resources, and creating dynamic user interfaces. + +### When Lifecycle Events Are Activated + +- **on_mount**: This event is triggered immediately after a component is rendered and attached to the DOM. It fires: + - When a page containing the component is first loaded + - When a component is conditionally rendered (appears after being hidden) + - When navigating to a page containing the component using internal navigation + - It does NOT fire when the page is refreshed or when following external links + +- **on_unmount**: This event is triggered just before a component is removed from the DOM. It fires: + - When navigating away from a page containing the component using internal navigation + - When a component is conditionally removed from the DOM (e.g., via a condition that hides it) + - It does NOT fire when refreshing the page, closing the browser tab, or following external links + +## Page Load Events + +In addition to component lifecycle events, Reflex also provides page-level events like `on_load` that are triggered when a page loads. The `on_load` event is useful for: + +- Fetching data when a page first loads +- Checking authentication status +- Initializing page-specific state +- Setting default values for cookies or browser storage + +You can specify an event handler to run when the page loads using the `on_load` parameter in the `@rx.page` decorator or `app.add_page()` method: + +```python +class State(rx.State): + data: dict = dict() + + @rx.event + def get_data(self): + # Fetch data when the page loads + self.data = fetch_data() + +@rx.page(on_load=State.get_data) +def index(): + return rx.text('Data loaded on page load') +``` + +This is particularly useful for authentication checks: + +```python +class State(rx.State): + authenticated: bool = False + + @rx.event + def check_auth(self): + # Check if user is authenticated + self.authenticated = check_auth() + if not self.authenticated: + return rx.redirect('/login') + +@rx.page(on_load=State.check_auth) +def protected_page(): + return rx.text('Protected content') +``` + +For more details on page load events, see the [page load events documentation]({events.page_load_events.path}). + +# Event Reference + +```python eval +rx.box( + rx.divider(), + component_grid(), +) +``` diff --git a/docs/api-reference/plugins.md b/docs/api-reference/plugins.md new file mode 100644 index 00000000000..7170cd114b9 --- /dev/null +++ b/docs/api-reference/plugins.md @@ -0,0 +1,242 @@ +```python exec +import reflex as rx +from pcweb.pages.docs import advanced_onboarding +``` + +# Plugins + +Reflex supports a plugin system that allows you to extend the framework's functionality during the compilation process. Plugins can add frontend dependencies, modify build configurations, generate static assets, and perform custom tasks before compilation. + +## Configuring Plugins + +Plugins are configured in your `rxconfig.py` file using the `plugins` parameter: + +```python +import reflex as rx + +config = rx.Config( + app_name="my_app", + plugins=[ + rx.plugins.SitemapPlugin(), + rx.plugins.TailwindV4Plugin(), + ], +) +``` + +## Built-in Plugins + +Reflex comes with several built-in plugins that provide common functionality. + +### SitemapPlugin + +The `SitemapPlugin` automatically generates a sitemap.xml file for your application, which helps search engines discover and index your pages. + +```python +import reflex as rx + +config = rx.Config( + app_name="my_app", + plugins=[ + rx.plugins.SitemapPlugin(), + ], +) +``` + +The sitemap plugin automatically includes all your app's routes. For dynamic routes or custom configuration, you can add sitemap metadata to individual pages: + +```python +@rx.page(route="/blog/[slug]", context={"sitemap": {"changefreq": "weekly", "priority": 0.8}}) +def blog_post(): + return rx.text("Blog post content") + +@rx.page(route="/about", context={"sitemap": {"changefreq": "monthly", "priority": 0.5}}) +def about(): + return rx.text("About page") +``` + +The sitemap configuration supports the following options: +- `loc`: Custom URL for the page (required for dynamic routes) +- `lastmod`: Last modification date (datetime object) +- `changefreq`: How frequently the page changes (`"always"`, `"hourly"`, `"daily"`, `"weekly"`, `"monthly"`, `"yearly"`, `"never"`) +- `priority`: Priority of this URL relative to other URLs (0.0 to 1.0) + +### TailwindV4Plugin + +The `TailwindV4Plugin` provides support for Tailwind CSS v4, which is the recommended version for new projects and includes performance improvements and new features. + +```python +import reflex as rx + +# Basic configuration +config = rx.Config( + app_name="my_app", + plugins=[ + rx.plugins.TailwindV4Plugin(), + ], +) +``` + +You can customize the Tailwind configuration by passing a config dictionary: + +```python +import reflex as rx + +tailwind_config = { + "theme": { + "extend": { + "colors": { + "brand": { + "50": "#eff6ff", + "500": "#3b82f6", + "900": "#1e3a8a", + } + } + } + }, + "plugins": ["@tailwindcss/typography"], +} + +config = rx.Config( + app_name="my_app", + plugins=[ + rx.plugins.TailwindV4Plugin(tailwind_config), + ], +) +``` + +### TailwindV3Plugin + +The `TailwindV3Plugin` integrates Tailwind CSS v3 into your Reflex application. While still supported, TailwindV4Plugin is recommended for new projects. + +```python +import reflex as rx + +# Basic configuration +config = rx.Config( + app_name="my_app", + plugins=[ + rx.plugins.TailwindV3Plugin(), + ], +) +``` + +You can customize the Tailwind configuration by passing a config dictionary: + +```python +import reflex as rx + +tailwind_config = { + "theme": { + "extend": { + "colors": { + "primary": "#3b82f6", + "secondary": "#64748b", + } + } + }, + "plugins": ["@tailwindcss/typography", "@tailwindcss/forms"], +} + +config = rx.Config( + app_name="my_app", + plugins=[ + rx.plugins.TailwindV3Plugin(tailwind_config), + ], +) +``` + +## Plugin Management + +### Default Plugins + +Some plugins are enabled by default. Currently, the `SitemapPlugin` is enabled automatically. If you want to disable a default plugin, use the `disable_plugins` parameter: + +```python +import reflex as rx + +config = rx.Config( + app_name="my_app", + disable_plugins=["reflex.plugins.sitemap.SitemapPlugin"], +) +``` + +### Plugin Order + +Plugins are executed in the order they appear in the `plugins` list. This can be important if plugins have dependencies on each other or modify the same files. + +```python +import reflex as rx + +config = rx.Config( + app_name="my_app", + plugins=[ + rx.plugins.TailwindV4Plugin(), # Runs first + rx.plugins.SitemapPlugin(), # Runs second + ], +) +``` + + +## Plugin Architecture + +All plugins inherit from the base `Plugin` class and can implement several lifecycle methods: + +```python +class Plugin: + def get_frontend_development_dependencies(self, **context) -> list[str]: + """Get NPM packages required by the plugin for development.""" + return [] + + def get_frontend_dependencies(self, **context) -> list[str]: + """Get NPM packages required by the plugin.""" + return [] + + def get_static_assets(self, **context) -> Sequence[tuple[Path, str | bytes]]: + """Get static assets required by the plugin.""" + return [] + + def get_stylesheet_paths(self, **context) -> Sequence[str]: + """Get paths to stylesheets required by the plugin.""" + return [] + + def pre_compile(self, **context) -> None: + """Called before compilation to perform custom tasks.""" + pass +``` + +### Creating Custom Plugins + +You can create custom plugins by inheriting from the base `Plugin` class: + +```python +from reflex.plugins.base import Plugin +from pathlib import Path + +class CustomPlugin(Plugin): + def get_frontend_dependencies(self, **context): + return ["my-custom-package@1.0.0"] + + def pre_compile(self, **context): + # Custom logic before compilation + print("Running custom plugin logic...") + + # Add a custom task + context["add_save_task"](self.create_custom_file) + + def create_custom_file(self): + return "public/custom.txt", "Custom content" +``` + +Then use it in your configuration: + +```python +import reflex as rx +from my_plugins import CustomPlugin + +config = rx.Config( + app_name="my_app", + plugins=[ + CustomPlugin(), + ], +) +``` diff --git a/docs/api-reference/special_events.md b/docs/api-reference/special_events.md new file mode 100644 index 00000000000..675f465ca70 --- /dev/null +++ b/docs/api-reference/special_events.md @@ -0,0 +1,123 @@ +```python exec +import reflex as rx +``` + +# Special Events + +Reflex includes a set of built-in special events that can be utilized as event triggers +or returned from event handlers in your applications. These events enhance interactivity and user experience. +Below are the special events available in Reflex, along with explanations of their functionality: + +## rx.console_log + +Perform a console.log in the browser's console. + +```python demo +rx.button('Log', on_click=rx.console_log('Hello World!')) +``` + +When triggered, this event logs a specified message to the browser's developer console. +It's useful for debugging and monitoring the behavior of your application. + +## rx.scroll_to + +scroll to an element in the page + +```python demo +rx.button( + "Scroll to download button", + on_click=rx.scroll_to("download button") + +) +``` + +When this is triggered, it scrolls to an element passed by id as parameter. Click on button to scroll to download button (rx.download section) at the bottom of the page + +## rx.redirect + +Redirect the user to a new path within the application. + +### Parameters + +- `path`: The destination path or URL to which the user should be redirected. +- `external`: If set to True, the redirection will open in a new tab. Defaults to `False`. + +```python demo +rx.vstack( + rx.button("open in tab", on_click=rx.redirect("/docs/api-reference/special-events")), + rx.button("open in new tab", on_click=rx.redirect('https://github.com/reflex-dev/reflex/', is_external=True)) +) +``` + +When this event is triggered, it navigates the user to a different page or location within your Reflex application. +By default, the redirection occurs in the same tab. However, if you set the external parameter to True, the redirection +will open in a new tab or window, providing a seamless user experience. + +This event can also be run from an event handler in State. It is necessary to `return` the `rx.redirect()`. + +```python demo exec +class RedirectExampleState(rx.State): + """The app state.""" + + @rx.event + def change_page(self): + return rx.redirect('https://github.com/reflex-dev/reflex/', is_external=True) + +def redirect_example(): + return rx.vstack( + rx.button("Change page in State", on_click=RedirectExampleState.change_page), + ) +``` + +## rx.set_clipboard + +Set the specified text content to the clipboard. + +```python demo +rx.button('Copy "Hello World" to clipboard',on_click=rx.set_clipboard('Hello World'),) +``` + +This event allows you to copy a given text or content to the user's clipboard. +It's handy when you want to provide a "Copy to Clipboard" feature in your application, +allowing users to easily copy information to paste elsewhere. + +## rx.set_value + +Set the value of a specified reference element. + +```python demo +rx.hstack( + rx.input(id='input1'), + rx.button( + 'Erase', on_click=rx.set_value('input1', '') + ), +) +``` + +With this event, you can modify the value of a particular HTML element, typically an input field or another form element. + +## rx.window_alert + +Create a window alert in the browser. + +```python demo +rx.button('Alert', on_click=rx.window_alert('Hello World!')) +``` + +## rx.download + +Download a file at a given path. + +Parameters: + +- `url`: The URL of the file to be downloaded. +- `data`: The data to be downloaded. Should be `str` or `bytes`, `data:` URI, `PIL.Image`, or any state Var (to be converted to JSON). +- `filename`: The desired filename of the downloaded file. + +```md alert +# `url` and `data` args are mutually exclusive, and at least one of them must be provided. +``` + +```python demo +rx.button("Download", on_click=rx.download(url="/reflex_banner.webp", filename="different_name_logo.webp"), id="download button") +``` diff --git a/docs/api-reference/utils.md b/docs/api-reference/utils.md new file mode 100644 index 00000000000..48beb020dc2 --- /dev/null +++ b/docs/api-reference/utils.md @@ -0,0 +1,170 @@ +```python exec +import reflex as rx +from pcweb import constants, styles +``` + +# Utility Functions + +Reflex provides utility functions to help with common tasks in your applications. + +## run_in_thread + +The `run_in_thread` function allows you to run a **non-async** function in a separate thread, which is useful for preventing long-running operations from blocking the UI event queue. + +```python +async def run_in_thread(func: Callable) -> Any +``` + +### Parameters + +- `func`: The non-async function to run in a separate thread. + +### Returns + +- The return value of the function. + +### Raises + +- `ValueError`: If the function is an async function. + +### Usage + +```python demo exec id=run_in_thread_demo +import asyncio +import dataclasses +import time +import reflex as rx + + +def quick_blocking_function(): + time.sleep(0.5) + return "Quick task completed successfully!" + + +def slow_blocking_function(): + time.sleep(3.0) + return "This should never be returned due to timeout!" + + +@dataclasses.dataclass +class TaskInfo: + result: str = "No result yet" + status: str = "Idle" + + +class RunInThreadState(rx.State): + tasks: list[TaskInfo] = [] + + @rx.event(background=True) + async def run_quick_task(self): + """Run a quick task that completes within the timeout.""" + async with self: + task_ix = len(self.tasks) + self.tasks.append(TaskInfo(status="Running quick task...")) + task_info = self.tasks[task_ix] + + try: + result = await rx.run_in_thread(quick_blocking_function) + async with self: + task_info.result = result + task_info.status = "Complete" + except Exception as e: + async with self: + task_info.result = f"Error: {str(e)}" + task_info.status = "Failed" + + @rx.event(background=True) + async def run_slow_task(self): + """Run a slow task that exceeds the timeout.""" + async with self: + task_ix = len(self.tasks) + self.tasks.append(TaskInfo(status="Running slow task...")) + task_info = self.tasks[task_ix] + + try: + # Run with a timeout of 1 second (not enough time) + result = await asyncio.wait_for( + rx.run_in_thread(slow_blocking_function), + timeout=1.0, + ) + async with self: + task_info.result = result + task_info.status = "Complete" + except asyncio.TimeoutError: + async with self: + # Warning: even though we stopped waiting for the task, + # it may still be running in thread + task_info.result = "Task timed out after 1 second!" + task_info.status = "Timeout" + except Exception as e: + async with self: + task_info.result = f"Error: {str(e)}" + task_info.status = "Failed" + + +def run_in_thread_example(): + return rx.vstack( + rx.heading("run_in_thread Example", size="3"), + rx.hstack( + rx.button( + "Run Quick Task", + on_click=RunInThreadState.run_quick_task, + color_scheme="green", + ), + rx.button( + "Run Slow Task (exceeds timeout)", + on_click=RunInThreadState.run_slow_task, + color_scheme="red", + ), + ), + rx.vstack( + rx.foreach( + RunInThreadState.tasks.reverse()[:10], + lambda task: rx.hstack( + rx.text(task.status), + rx.spacer(), + rx.text(task.result), + ), + ), + align="start", + width="100%", + ), + width="100%", + align_items="start", + spacing="4", + ) +``` + +### When to Use run_in_thread + +Use `run_in_thread` when you need to: + +1. Execute CPU-bound operations that would otherwise block the event loop +2. Call synchronous libraries that don't have async equivalents +3. Prevent long-running operations from blocking UI responsiveness + +### Example: Processing a Large File + +```python +import reflex as rx +import time + +class FileProcessingState(rx.State): + progress: str = "Ready" + + @rx.event(background=True) + async def process_large_file(self): + async with self: + self.progress = "Processing file..." + + def process_file(): + # Simulate processing a large file + time.sleep(5) + return "File processed successfully!" + + # Save the result to a local variable to avoid blocking the event loop. + result = await rx.run_in_thread(process_file) + async with self: + # Then assign the local result to the state while holding the lock. + self.progress = result +``` diff --git a/docs/api-reference/var_system.md b/docs/api-reference/var_system.md new file mode 100644 index 00000000000..39482420e03 --- /dev/null +++ b/docs/api-reference/var_system.md @@ -0,0 +1,82 @@ +# Reflex's Var System + +## Motivation + +Reflex supports some basic operations in state variables on the frontend. +Reflex automatically converts variable operations from Python into a JavaScript equivalent. + +Here's an example of a Reflex conditional in Python that returns "Pass" if the threshold is equal to or greater than 50 and "Fail" otherwise: + +```py +rx.cond( + State.threshold >= 50, + "Pass", + "Fail", +) +``` + + The conditional to roughly the following in Javascript: + +```js +state.threshold >= 50 ? "Pass" : "Fail"; +``` + +## Overview + +Simply put, a `Var` in Reflex represents a Javascript expression. +If the type is known, it can be any of the following: + +- `NumberVar` represents an expression that evaluates to a Javascript `number`. `NumberVar` can support both integers and floating point values +- `BooleanVar` represents a boolean expression. For example: `false`, `3 > 2`. +- `StringVar` represents an expression that evaluates to a string. For example: `'hello'`, `(2).toString()`. +- `ArrayVar` represents an expression that evaluates to an array object. For example: `[1, 2, 3]`, `'words'.split()`. +- `ObjectVar` represents an expression that evaluates to an object. For example: `\{a: 2, b: 3}`, `\{deeply: \{nested: \{value: false}}}`. +- `NoneVar` represent null values. These can be either `undefined` or `null`. + +## Creating Vars + +State fields are converted to `Var` by default. Additionally, you can create a `Var` from Python values using `rx.Var.create()`: + +```py +rx.Var.create(4) # NumberVar +rx.Var.create("hello") # StringVar +rx.Var.create([1, 2, 3]) # ArrayVar +``` + +If you want to explicitly create a `Var` from a raw Javascript string, you can instantiate `rx.Var` directly: + +```py +rx.Var("2", _var_type=int).guess_type() # NumberVar +``` + +In the example above, `.guess_type()` will attempt to downcast from a generic `Var` type into `NumberVar`. +For this example, calling the function `.to(int)` can also be used in place of `.guess_type()`. + +## Operations + +The `Var` system also supports some other basic operations. +For example, `NumberVar` supports basic arithmetic operations like `+` and `-`, as in Python. +It also supports comparisons that return a `BooleanVar`. + +Custom `Var` operations can also be defined: + +```py +from reflex.vars import var_operation, var_operation_return, ArrayVar, NumberVar + +@var_operation +def multiply_array_values(a: ArrayVar): + return var_operation_return( + js_expression=f"\{a}.reduce((p, c) => p * c, 1)", + var_type=int, + ) + +def factorial(value: NumberVar): + return rx.cond( + value <= 1, + 1, + multiply_array_values(rx.Var.range(1, value+1)) + ) +``` + +Use `js_expression` to pass explicit JavaScript expressions; in the `multiply_array_values` example, we pass in a JavaScript expression that calculates the product of all elements in an array called `a` by using the reduce method to multiply each element with the accumulated result, starting from an initial value of 1. +Later, we leverage `rx.cond` in the' factorial' function, we instantiate an array using the `range` function, and pass this array to `multiply_array_values`. diff --git a/docs/api-routes/overview.md b/docs/api-routes/overview.md new file mode 100644 index 00000000000..30e5ef31f7a --- /dev/null +++ b/docs/api-routes/overview.md @@ -0,0 +1,152 @@ +```python exec +import reflex as rx +``` + +# API Transformer + +In addition to your frontend app, Reflex uses a FastAPI backend to serve your app. The API transformer feature allows you to transform or extend the ASGI app that serves your Reflex application. + +## Overview + +The API transformer provides a way to: + +1. Integrate existing FastAPI or Starlette applications with your Reflex app +2. Apply middleware or transformations to the ASGI app +3. Extend your Reflex app with additional API endpoints + +This is useful for creating a backend API that can be used for purposes beyond your Reflex app, or for integrating Reflex with existing backend services. + +## Using API Transformer + +You can set the `api_transformer` parameter when initializing your Reflex app: + +```python +import reflex as rx +from fastapi import FastAPI, Depends +from fastapi.security import OAuth2PasswordBearer + +# Create a FastAPI app +fastapi_app = FastAPI(title="My API") + +# Add routes to the FastAPI app +@fastapi_app.get("/api/items") +async def get_items(): + return dict(items=["Item1", "Item2", "Item3"]) + +# Create a Reflex app with the FastAPI app as the API transformer +app = rx.App(api_transformer=fastapi_app) +``` + +## Types of API Transformers + +The `api_transformer` parameter can accept: + +1. A Starlette or FastAPI instance +2. A callable that takes an ASGIApp and returns an ASGIApp +3. A sequence of the above + +### Using a FastAPI or Starlette Instance + +When you provide a FastAPI or Starlette instance as the API transformer, Reflex will mount its internal API to your app, allowing you to define additional routes: + +```python +import reflex as rx +from fastapi import FastAPI, Depends +from fastapi.security import OAuth2PasswordBearer + +# Create a FastAPI app with authentication +fastapi_app = FastAPI(title="Secure API") +oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token") + +# Add a protected route +@fastapi_app.get("/api/protected") +async def protected_route(token: str = Depends(oauth2_scheme)): + return dict(message="This is a protected endpoint") + +# Create a token endpoint +@fastapi_app.post("/token") +async def login(username: str, password: str): + # In a real app, you would validate credentials + if username == "user" and password == "password": + return dict(access_token="example_token", token_type="bearer") + return dict(error="Invalid credentials") + +# Create a Reflex app with the FastAPI app as the API transformer +app = rx.App(api_transformer=fastapi_app) +``` + +### Using a Callable Transformer + +You can also provide a callable that transforms the ASGI app: + +```python +import reflex as rx +from starlette.middleware.cors import CORSMiddleware + +# Create a transformer function that returns a transformed ASGI app +def add_cors_middleware(app): + # Wrap the app with CORS middleware and return the wrapped app + return CORSMiddleware( + app=app, + allow_origins=["https://example.com"], + allow_methods=["*"], + allow_headers=["*"], + ) + +# Create a Reflex app with the transformer +app = rx.App(api_transformer=add_cors_middleware) +``` + +### Using Multiple Transformers + +You can apply multiple transformers by providing a sequence: + +```python +import reflex as rx +from fastapi import FastAPI +from starlette.middleware import Middleware +from starlette.middleware.cors import CORSMiddleware + +# Create a FastAPI app +fastapi_app = FastAPI(title="My API") + +# Add routes to the FastAPI app +@fastapi_app.get("/api/items") +async def get_items(): + return dict(items=["Item1", "Item2", "Item3"]) + +# Create a transformer function +def add_logging_middleware(app): + # This is a simple example middleware that logs requests + async def middleware(scope, receive, send): + # Log the request path + path = scope["path"] + print("Request:", path) + await app(scope, receive, send) + return middleware + +# Create a Reflex app with multiple transformers +app = rx.App(api_transformer=[fastapi_app, add_logging_middleware]) +``` + +## Reserved Routes + +Some routes on the backend are reserved for the runtime of Reflex, and should not be overridden unless you know what you are doing. + +### Ping + +`localhost:8000/ping/`: You can use this route to check the health of the backend. + +The expected return is `"pong"`. + +### Event + +`localhost:8000/_event`: the frontend will use this route to notify the backend that an event occurred. + +```md alert error +# Overriding this route will break the event communication +``` + +### Upload + +`localhost:8000/_upload`: This route is used for the upload of file when using `rx.upload()`. diff --git a/docs/assets/overview.md b/docs/assets/overview.md new file mode 100644 index 00000000000..51ba7e1599d --- /dev/null +++ b/docs/assets/overview.md @@ -0,0 +1,95 @@ +```python exec +import reflex as rx +from pcweb.constants import REFLEX_ASSETS_CDN +``` + +# Assets + +Static files such as images and stylesheets can be placed in `assets/` folder of the project. These files can be referenced within your app. + +```md alert +# Assets are copied during the build process. + +Any files placed within the `assets/` folder at runtime will not be available to the app +when running in production mode. The `assets/` folder should only be used for static files. +``` + +## Referencing Assets + +There are two ways to reference assets in your Reflex app: + +### 1. Direct Path Reference + +To reference an image in the `assets/` folder, pass the relative path as a prop. + +For example, you can store your logo in your assets folder: + +```bash +assets +└── Reflex.svg +``` + +Then you can display it using a `rx.image` component: + +```python demo +rx.image(src=f"{REFLEX_ASSETS_CDN}other/Reflex.svg", width="5em") +``` + +```md alert +# Always prefix the asset path with a forward slash `/` to reference the asset from the root of the project, or it may not display correctly on non-root pages. +``` + +### 2. Using rx.asset Function + +The `rx.asset` function provides a more flexible way to reference assets in your app. It supports both local assets (in the app's `assets/` directory) and shared assets (placed next to your Python files). + +#### Local Assets + +Local assets are stored in the app's `assets/` directory and are referenced using `rx.asset`: + +```python demo +rx.image(src=rx.asset("Reflex.svg"), width="5em") +``` + +#### Shared Assets + +Shared assets are placed next to your Python file and are linked to the app's external assets directory. This is useful for creating reusable components with their own assets: + +```python box +# my_component.py +import reflex as rx +from pcweb.constants import REFLEX_ASSETS_CDN + +# my_script.js is located in the same directory as this Python file +def my_component(): + return rx.box( + rx.script(src=rx.asset("my_script.js", shared=True)), + "Component with custom script" + ) +``` + +You can also specify a subfolder for shared assets: + +```python box +# my_component.py +import reflex as rx +from pcweb.constants import REFLEX_ASSETS_CDN + +# image.png is located in a subfolder next to this Python file +def my_component_with_image(): + return rx.image( + src=rx.asset("image.png", shared=True, subfolder="images") + ) +``` + +```md alert +# Shared assets are linked to your app via symlinks. + +When using `shared=True`, the asset is symlinked from its original location to your app's external assets directory. This allows you to keep assets alongside their related code. +``` + +## Favicon + +The favicon is the small icon that appears in the browser tab. + +You can add a `favicon.ico` file to the `assets/` folder to change the favicon. diff --git a/docs/assets/upload_and_download_files.md b/docs/assets/upload_and_download_files.md new file mode 100644 index 00000000000..75911965434 --- /dev/null +++ b/docs/assets/upload_and_download_files.md @@ -0,0 +1,154 @@ +```python exec +import reflex as rx +from pcweb.constants import REFLEX_ASSETS_CDN +from pcweb.pages.docs import library +from pcweb.pages.docs import api_reference +from pcweb.styles.styles import get_code_style +from pcweb.styles.colors import c_color +``` + +# Files + +In addition to any assets you ship with your app, many web app will often need to receive or send files, whether you want to share media, allow user to import their data, or export some backend data. + +In this section, we will cover all you need to know for manipulating files in Reflex. + +## Assets vs Upload Directory + +Before diving into file uploads and downloads, it's important to understand the difference between assets and the upload directory in Reflex: + +```python eval +# Simple table comparing assets vs upload directory +rx.table.root( + rx.table.header( + rx.table.row( + rx.table.column_header_cell("Feature"), + rx.table.column_header_cell("Assets"), + rx.table.column_header_cell("Upload Directory"), + ), + ), + rx.table.body( + rx.table.row( + rx.table.cell(rx.text("Purpose", font_weight="bold")), + rx.table.cell(rx.text("Static files included with your app (images, stylesheets, scripts)")), + rx.table.cell(rx.text("Dynamic files uploaded by users during runtime")), + ), + rx.table.row( + rx.table.cell(rx.text("Location", font_weight="bold")), + rx.table.cell(rx.hstack( + rx.code("assets/", style=get_code_style("violet")), + rx.text(" folder or next to Python files (shared assets)"), + spacing="2", + )), + rx.table.cell(rx.hstack( + rx.code("uploaded_files/", style=get_code_style("violet")), + rx.text(" directory (configurable)"), + spacing="2", + )), + ), + rx.table.row( + rx.table.cell(rx.text("Access Method", font_weight="bold")), + rx.table.cell(rx.hstack( + rx.code("rx.asset()", style=get_code_style("violet")), + rx.text(" or direct path reference"), + spacing="2", + )), + rx.table.cell(rx.code("rx.get_upload_url()", style=get_code_style("violet"))), + ), + rx.table.row( + rx.table.cell(rx.text("When to Use", font_weight="bold")), + rx.table.cell(rx.text("For files that are part of your application's codebase")), + rx.table.cell(rx.text("For files that users upload or generate through your application")), + ), + rx.table.row( + rx.table.cell(rx.text("Availability", font_weight="bold")), + rx.table.cell(rx.text("Available at compile time")), + rx.table.cell(rx.text("Available at runtime")), + ), + ), + width="100%", +) +``` + + + +For more information about assets, see the [Assets Overview](/docs/assets/overview/). + +## Download + +If you want to let the users of your app download files from your server to their computer, Reflex offer you two way. + +### With a regular link + +For some basic usage, simply providing the path to your resource in a `rx.link` will work, and clicking the link will download or display the resource. + +```python demo +rx.link("Download", href="/reflex_banner.webp") +``` + +### With `rx.download` event + +Using the `rx.download` event will always prompt the browser to download the file, even if it could be displayed in the browser. + +The `rx.download` event also allows the download to be triggered from another backend event handler. + +```python demo +rx.button( + "Download", + on_click=rx.download(url="/reflex_banner.webp"), +) +``` + +`rx.download` lets you specify a name for the file that will be downloaded, if you want it to be different from the name on the server side. + +```python demo +rx.button( + "Download and Rename", + on_click=rx.download( + url="/reflex_banner.webp", + filename="different_name_logo.png" + ), +) +``` + +If the data to download is not already available at a known URL, pass the `data` directly to the `rx.download` event from the backend. + +```python demo exec +import random + +class DownloadState(rx.State): + @rx.event + def download_random_data(self): + return rx.download( + data=",".join([str(random.randint(0, 100)) for _ in range(10)]), + filename="random_numbers.csv" + ) + +def download_random_data_button(): + return rx.button( + "Download random numbers", + on_click=DownloadState.download_random_data + ) +``` + +The `data` arg accepts `str` or `bytes` data, a `data:` URI, `PIL.Image`, or any state Var. If the Var is not already a string, it will be converted to a string using `JSON.stringify`. This allows complex state structures to be offered as JSON downloads. + +Reference page for `rx.download` [here]({api_reference.special_events.path}#rx.download). + +## Upload + +Uploading files to your server let your users interact with your app in a different way than just filling forms to provide data. + +The component `rx.upload` let your users upload files on the server. + +Here is a basic example of how it is used: + +```python +def index(): + return rx.fragment( + rx.upload(rx.text("Upload files"), rx.icon(tag="upload")), + rx.button(on_submit=State.) + ) +``` + +For detailed information, see the reference page of the component [here]({library.forms.upload.path}). diff --git a/docs/authentication/authentication_overview.md b/docs/authentication/authentication_overview.md new file mode 100644 index 00000000000..72dc4de4ecd --- /dev/null +++ b/docs/authentication/authentication_overview.md @@ -0,0 +1,28 @@ +```python exec +from pcweb.pages.docs import vars +``` + +# Authentication Overview + +Many apps require authentication to manage users. There are a few different ways to accomplish this in Reflex: + +We have solutions that currently exist outside of the core framework: + +1. Local Auth: Uses your own database: https://github.com/masenf/reflex-local-auth +2. Google Auth: Uses sign in with Google: https://github.com/masenf/reflex-google-auth +3. Captcha: Generates tests that humans can pass but automated systems cannot: https://github.com/masenf/reflex-google-recaptcha-v2 +4. Magic Link Auth: A passwordless login method that sends a unique, one-time-use URL to a user's email: https://github.com/masenf/reflex-magic-link-auth +5. Clerk Auth: A community member wrapped this component and hooked it up in this app: https://github.com/TimChild/reflex-clerk-api +6. Descope Auth: Enables authentication with Descope, supporting passwordless, social login, SSO, and MFA: https://github.com/descope-sample-apps/reflex-descope-auth + +If you're using the AI Builder, you can also use the built-in [Authentication Integrations](/docs/ai-builder/integrations/overview) which include Azure Auth, Google Auth, Okta Auth, and Descope. + +## Guidance for Implementing Authentication + +- Store sensitive user tokens and information in [backend-only vars]({vars.base_vars.path}#backend-only-vars). +- Validate user session and permissions for each event handler that performs an authenticated action and all computed vars or loader events that access private data. +- All content that is statically rendered in the frontend (for example, data hardcoded or loaded at compile time in the UI) will be publicly available, even if the page redirects to a login or uses `rx.cond` to hide content. +- Only data that originates from state can be truly private and protected. +- When using cookies or local storage, a signed JWT can detect and invalidate any local tampering. + +More auth documentation on the way. Check back soon! diff --git a/docs/client_storage/overview.md b/docs/client_storage/overview.md new file mode 100644 index 00000000000..7016080a1d9 --- /dev/null +++ b/docs/client_storage/overview.md @@ -0,0 +1,45 @@ +```python exec +import reflex as rx +``` + +# Client-storage + +You can use the browser's local storage to persist state between sessions. +This allows user preferences, authentication cookies, other bits of information +to be stored on the client and accessed from different browser tabs. + +A client-side storage var looks and acts like a normal `str` var, except the +default value is either `rx.Cookie` or `rx.LocalStorage` depending on where the +value should be stored. The key name will be based on the var name, but this +can be overridden by passing `name="my_custom_name"` as a keyword argument. + +For more information see [Browser Storage](/docs/api-reference/browser-storage/). + +Try entering some values in the text boxes below and then load the page in a separate +tab or check the storage section of browser devtools to see the values saved in the browser. + +```python demo exec +class ClientStorageState(rx.State): + my_cookie: str = rx.Cookie("") + my_local_storage: str = rx.LocalStorage("") + custom_cookie: str = rx.Cookie(name="CustomNamedCookie", max_age=3600) + + @rx.event + def set_my_cookie(self, value: str): + self.my_cookie = value + + @rx.event + def set_my_local_storage(self, value: str): + self.my_local_storage = value + + @rx.event + def set_custom_cookie(self, value: str): + self.custom_cookie = value + +def client_storage_example(): + return rx.vstack( + rx.hstack(rx.text("my_cookie"), rx.input(value=ClientStorageState.my_cookie, on_change=ClientStorageState.set_my_cookie)), + rx.hstack(rx.text("my_local_storage"), rx.input(value=ClientStorageState.my_local_storage, on_change=ClientStorageState.set_my_local_storage)), + rx.hstack(rx.text("custom_cookie"), rx.input(value=ClientStorageState.custom_cookie, on_change=ClientStorageState.set_custom_cookie)), + ) +``` diff --git a/docs/components/conditional_rendering.md b/docs/components/conditional_rendering.md new file mode 100644 index 00000000000..5df4111a40c --- /dev/null +++ b/docs/components/conditional_rendering.md @@ -0,0 +1,131 @@ +```python exec +import reflex as rx + +from pcweb.pages.docs import library +from pcweb.pages import docs +``` + +# Conditional Rendering + +Recall from the [basics]({docs.getting_started.basics.path}) that we cannot use Python `if/else` statements when referencing state vars in Reflex. Instead, use the `rx.cond` component to conditionally render components or set props based on the value of a state var. + +```md video https://youtube.com/embed/ITOZkzjtjUA?start=6040&end=6463 +# Video: Conditional Rendering +``` + +```md alert +# Check out the API reference for [cond docs]({library.dynamic_rendering.cond.path}). +``` + +```python eval +rx.box(height="2em") +``` + +Below is a simple example showing how to toggle between two text components by checking the value of the state var `show`. + +```python demo exec +class CondSimpleState(rx.State): + show: bool = True + + @rx.event + def change(self): + self.show = not (self.show) + + +def cond_simple_example(): + return rx.vstack( + rx.button("Toggle", on_click=CondSimpleState.change), + rx.cond( + CondSimpleState.show, + rx.text("Text 1", color="blue"), + rx.text("Text 2", color="red"), + ), + ) +``` + +If `show` is `True` then the first component is rendered (in this case the blue text). Otherwise the second component is rendered (in this case the red text). + +## Conditional Props + +You can also set props conditionally using `rx.cond`. In this example, we set the `color` prop of a text component based on the value of the state var `show`. + +```python demo exec +class PropCondState(rx.State): + + value: int + + @rx.event + def set_end(self, value: list[int | float]): + self.value = value[0] + + +def cond_prop(): + return rx.slider( + default_value=[50], + on_value_commit=PropCondState.set_end, + color_scheme=rx.cond(PropCondState.value > 50, "green", "pink"), + width="100%", + ) +``` + + +## Var Operations + +You can use [var operations]({docs.vars.var_operations.path}) with the `cond` component for more complex conditions. See the full [cond reference]({library.dynamic_rendering.cond.path}) for more details. + + +## Multiple Conditional Statements + +The [`rx.match`]({library.dynamic_rendering.match.path}) component in Reflex provides a powerful alternative to`rx.cond` for handling multiple conditional statements and structural pattern matching. This component allows you to handle multiple conditions and their associated components in a cleaner and more readable way compared to nested `rx.cond` structures. + +```python demo exec +from typing import List + +import reflex as rx + + +class MatchState(rx.State): + cat_breed: str = "" + animal_options: List[str] = [ + "persian", + "siamese", + "maine coon", + "ragdoll", + "pug", + "corgi", + ] + + @rx.event + def set_cat_breed(self, breed: str): + self.cat_breed = breed + + +def match_demo(): + return rx.flex( + rx.match( + MatchState.cat_breed, + ("persian", rx.text("Persian cat selected.")), + ("siamese", rx.text("Siamese cat selected.")), + ( + "maine coon", + rx.text("Maine Coon cat selected."), + ), + ("ragdoll", rx.text("Ragdoll cat selected.")), + rx.text("Unknown cat breed selected."), + ), + rx.select( + [ + "persian", + "siamese", + "maine coon", + "ragdoll", + "pug", + "corgi", + ], + value=MatchState.cat_breed, + on_change=MatchState.set_cat_breed, + ), + direction="column", + gap="2", + ) +``` diff --git a/docs/components/html_to_reflex.md b/docs/components/html_to_reflex.md new file mode 100644 index 00000000000..42dcd0f5f60 --- /dev/null +++ b/docs/components/html_to_reflex.md @@ -0,0 +1,16 @@ +# Convert HTML to Reflex + +To convert HTML, CSS, or any design into Reflex code, use our AI-powered build tool at [Reflex Build](https://build.reflex.dev). + +Simply paste your HTML, CSS, or describe what you want to build, and our AI will generate the corresponding Reflex code for you. + +## How to use Reflex Build + +1. Go to [Reflex Build](https://build.reflex.dev) +2. Paste your HTML/CSS code or describe your design +3. The AI will automatically generate Reflex code +4. Copy the generated code into your Reflex application + +## Convert Figma file to Reflex + +Check out this [Notion doc](https://www.notion.so/reflex-dev/Convert-HTML-to-Reflex-fe22d0641dcd4d5c91c8404ca41c7e77) for a walk through on how to convert a Figma file into Reflex code. diff --git a/docs/components/props.md b/docs/components/props.md new file mode 100644 index 00000000000..1c3d4217546 --- /dev/null +++ b/docs/components/props.md @@ -0,0 +1,97 @@ +```python exec +import reflex as rx +from pcweb.pages.docs.library import library +from pcweb.pages import docs +``` + +# Props + +Props modify the behavior and appearance of a component. They are passed in as keyword arguments to a component. + +## Component Props + +There are props that are shared between all components, but each component can also define its own props. + +For example, the `rx.image` component has a `src` prop that specifies the URL of the image to display and an `alt` prop that specifies the alternate text for the image. + +```python demo +rx.image( + src="https://web.reflex-assets.dev/other/logo.jpg", + alt="Reflex Logo", +) +``` + +Check the docs for the component you are using to see what props are available and how they affect the component (see the `rx.image` [reference]({docs.library.media.image.path}#api-reference) page for example). + + +## Common Props + +Components support many standard HTML properties as props. For example: the HTML [id]({"https://www.w3schools.com/html/html_id.asp"}) property is exposed directly as the prop `id`. The HTML [className]({"https://www.w3schools.com/jsref/prop_html_classname.asp"}) property is exposed as the prop `class_name` (note the Pythonic snake_casing!). + +```python demo +rx.box( + "Hello World", + id="box-id", + class_name=["class-name-1", "class-name-2",], +) +``` + +In the example above, the `class_name` prop of the `rx.box` component is assigned a list of class names. This means the `rx.box` component will be styled with the CSS classes `class-name-1` and `class-name-2`. + +## Style Props + +In addition to component-specific props, most built-in components support a full range of style props. You can use any [CSS property](https://www.w3schools.com/cssref/index.php) to style a component. + +```python demo +rx.button( + "Fancy Button", + border_radius="1em", + box_shadow="rgba(151, 65, 252, 0.8) 0 15px 30px -10px", + background_image="linear-gradient(144deg,#AF40FF,#5B42F3 50%,#00DDEB)", + box_sizing="border-box", + color="white", + opacity= 1, +) +``` + +See the [styling docs]({docs.styling.overview.path}) to learn more about customizing the appearance of your app. + + +## Binding Props to State + +```md alert warning +# Optional: Learn all about [State]({docs.state.overview.path}) first. +``` + +Reflex apps define [State]({docs.state.overview.path}) classes that hold variables that can change over time. + +State may be modified in response to things like user input like clicking a button, or in response to events like loading a page. + +State vars can be bound to component props, so that the UI always reflects the current state of the app. + +Try clicking the badge below to change its color. + +```python demo exec +class PropExampleState(rx.State): + text: str = "Hello World" + color: str = "red" + + @rx.event + def flip_color(self): + if self.color == "red": + self.color = "blue" + else: + self.color = "red" + + +def index(): + return rx.button( + PropExampleState.text, + color_scheme=PropExampleState.color, + on_click=PropExampleState.flip_color, + ) +``` + +In this example, the `color_scheme` prop is bound to the `color` state var. + +When the `flip_color` event handler is called, the `color` var is updated, and the `color_scheme` prop is updated to match. diff --git a/docs/components/rendering_iterables.md b/docs/components/rendering_iterables.md new file mode 100644 index 00000000000..89564d6b341 --- /dev/null +++ b/docs/components/rendering_iterables.md @@ -0,0 +1,299 @@ +```python exec +import reflex as rx + +from pcweb.pages import docs +``` + +# Rendering Iterables + +Recall again from the [basics]({docs.getting_started.basics.path}) that we cannot use Python `for` loops when referencing state vars in Reflex. Instead, use the `rx.foreach` component to render components from a collection of data. + +For dynamic content that should automatically scroll to show the newest items, consider using the [auto scroll]({docs.library.dynamic_rendering.auto_scroll.path}) component together with `rx.foreach`. + +```python demo exec +class IterState(rx.State): + color: list[str] = [ + "red", + "green", + "blue", + ] + + +def colored_box(color: str): + return rx.button(color, background_color=color) + + +def dynamic_buttons(): + return rx.vstack( + rx.foreach(IterState.color, colored_box), + ) + +``` + +Here's the same example using a lambda function. + +```python +def dynamic_buttons(): + return rx.vstack( + rx.foreach(IterState.color, lambda color: colored_box(color)), + ) +``` + +You can also use lambda functions directly with components without defining a separate function. + +```python +def dynamic_buttons(): + return rx.vstack( + rx.foreach(IterState.color, lambda color: rx.button(color, background_color=color)), + ) +``` + +In this first simple example we iterate through a `list` of colors and render a dynamic number of buttons. + +The first argument of the `rx.foreach` function is the state var that you want to iterate through. The second argument is a function that takes in an item from the data and returns a component. In this case, the `colored_box` function takes in a color and returns a button with that color. + +## For vs Foreach + +```md definition +# Regular For Loop +* Use when iterating over constants. + +# Foreach +* Use when iterating over state vars. +``` + +The above example could have been written using a regular Python `for` loop, since the data is constant. + +```python demo exec +colors = ["red", "green", "blue"] +def dynamic_buttons_for(): + return rx.vstack( + [colored_box(color) for color in colors], + ) +``` + +However, as soon as you need the data to be dynamic, you must use `rx.foreach`. + +```python demo exec +class DynamicIterState(rx.State): + color: list[str] = [ + "red", + "green", + "blue", + ] + + def add_color(self, form_data): + self.color.append(form_data["color"]) + +def dynamic_buttons_foreach(): + return rx.vstack( + rx.foreach(DynamicIterState.color, colored_box), + rx.form( + rx.input(name="color", placeholder="Add a color"), + rx.button("Add"), + on_submit=DynamicIterState.add_color, + ) + ) +``` + +## Render Function + +The function to render each item can be defined either as a separate function or as a lambda function. In the example below, we define the function `colored_box` separately and pass it to the `rx.foreach` function. + +```python demo exec +class IterState2(rx.State): + color: list[str] = [ + "red", + "green", + "blue", + ] + +def colored_box(color: rx.Var[str]): + return rx.button(color, background_color=color) + +def dynamic_buttons2(): + return rx.vstack( + rx.foreach(IterState2.color, colored_box), + ) + +``` + +Notice that the type annotation for the `color` parameter in the `colored_box` function is `rx.Var[str]` (rather than just `str`). This is because the `rx.foreach` function passes the item as a `Var` object, which is a wrapper around the actual value. This is what allows us to compile the frontend without knowing the actual value of the state var (which is only known at runtime). + +## Enumerating Iterables + +The function can also take an index as a second argument, meaning that we can enumerate through data as shown in the example below. + +```python demo exec +class IterIndexState(rx.State): + color: list[str] = [ + "red", + "green", + "blue", + ] + + +def create_button(color: rx.Var[str], index: int): + return rx.box( + rx.button(f"{index + 1}. {color}"), + padding_y="0.5em", + ) + +def enumerate_foreach(): + return rx.vstack( + rx.foreach( + IterIndexState.color, + create_button + ), + ) +``` + +Here's the same example using a lambda function. + +```python +def enumerate_foreach(): + return rx.vstack( + rx.foreach( + IterIndexState.color, + lambda color, index: create_button(color, index) + ), + ) +``` + +## Iterating Dictionaries + +We can iterate through a `dict` using a `foreach`. When the dict is passed through to the function that renders each item, it is presented as a list of key-value pairs `[("sky", "blue"), ("balloon", "red"), ("grass", "green")]`. + +```python demo exec +class SimpleDictIterState(rx.State): + color_chart: dict[str, str] = { + "sky": "blue", + "balloon": "red", + "grass": "green", + } + + +def display_color(color: list): + # color is presented as a list key-value pairs [("sky", "blue"), ("balloon", "red"), ("grass", "green")] + return rx.box(rx.text(color[0]), bg=color[1], padding_x="1.5em") + + +def dict_foreach(): + return rx.grid( + rx.foreach( + SimpleDictIterState.color_chart, + display_color, + ), + columns="3", + ) + +``` + +```md alert warning +# Dict Type Annotation. +It is essential to provide the correct full type annotation for the dictionary in the state definition (e.g., `dict[str, str]` instead of `dict`) to ensure `rx.foreach` works as expected. Proper typing allows Reflex to infer and validate the structure of the data during rendering. +``` + +## Nested examples + +`rx.foreach` can be used with nested state vars. Here we use nested `foreach` components to render the nested state vars. The `rx.foreach(project["technologies"], get_badge)` inside of the `project_item` function, renders the `dict` values which are of type `list`. The `rx.box(rx.foreach(NestedStateFE.projects, project_item))` inside of the `projects_example` function renders each `dict` inside of the overall state var `projects`. + +```python demo exec +class NestedStateFE(rx.State): + projects: list[dict[str, list]] = [ + { + "technologies": ["Next.js", "Prisma", "Tailwind", "Google Cloud", "Docker", "MySQL"] + }, + { + "technologies": ["Python", "Flask", "Google Cloud", "Docker"] + } + ] + +def get_badge(technology: rx.Var[str]) -> rx.Component: + return rx.badge(technology, variant="soft", color_scheme="green") + +def project_item(project: rx.Var[dict[str, list]]) -> rx.Component: + return rx.box( + rx.hstack( + rx.foreach(project["technologies"], get_badge) + ), + ) + +def projects_example() -> rx.Component: + return rx.box(rx.foreach(NestedStateFE.projects, project_item)) +``` + +If you want an example where not all of the values in the dict are the same type then check out the example on [var operations using foreach]({docs.vars.var_operations.path}). + +Here is a further example of how to use `foreach` with a nested data structure. + +```python demo exec +class NestedDictIterState(rx.State): + color_chart: dict[str, list[str]] = { + "purple": ["red", "blue"], + "orange": ["yellow", "red"], + "green": ["blue", "yellow"], + } + + +def display_colors(color: rx.Var[tuple[str, list[str]]]): + return rx.vstack( + rx.text(color[0], color=color[0]), + rx.hstack( + rx.foreach( + color[1], + lambda x: rx.box( + rx.text(x, color="black"), bg=x + ), + ) + ), + ) + + +def nested_dict_foreach(): + return rx.grid( + rx.foreach( + NestedDictIterState.color_chart, + display_colors, + ), + columns="3", + ) + +``` + +## Foreach with Cond + +We can also use `foreach` with the `cond` component. + +In this example we define the function `render_item`. This function takes in an `item`, uses the `cond` to check if the item `is_packed`. If it is packed it returns the `item_name` with a `✔` next to it, and if not then it just returns the `item_name`. We use the `foreach` to iterate over all of the items in the `to_do_list` using the `render_item` function. + +```python demo exec +import dataclasses + +@dataclasses.dataclass +class ToDoListItem: + item_name: str + is_packed: bool + +class ForeachCondState(rx.State): + to_do_list: list[ToDoListItem] = [ + ToDoListItem(item_name="Space suit", is_packed=True), + ToDoListItem(item_name="Helmet", is_packed=True), + ToDoListItem(item_name="Back Pack", is_packed=False), + ] + + +def render_item(item: rx.Var[ToDoListItem]): + return rx.cond( + item.is_packed, + rx.list.item(item.item_name + ' ✔'), + rx.list.item(item.item_name), + ) + +def packing_list(): + return rx.vstack( + rx.text("Sammy's Packing List"), + rx.list(rx.foreach(ForeachCondState.to_do_list, render_item)), + ) + +``` diff --git a/docs/custom-components/command-reference.md b/docs/custom-components/command-reference.md new file mode 100644 index 00000000000..56b95b74676 --- /dev/null +++ b/docs/custom-components/command-reference.md @@ -0,0 +1,157 @@ +```python exec +from pcweb.pages import docs +``` + +# Command Reference + +The custom component commands are under `reflex component` subcommand. To see the list of available commands, run `reflex component --help`. To see the manual on a specific command, run `reflex component --help`, for example, `reflex component init --help`. + +```bash +reflex component --help +``` + +```text +Usage: reflex component [OPTIONS] COMMAND [ARGS]... + + Subcommands for creating and publishing Custom Components. + +Options: + --help Show this message and exit. + +Commands: + init Initialize a custom component. + build Build a custom component. + share Collect more details on the published package for gallery. +``` + +## reflex component init + +Below is an example of running the `init` command. + +```bash +reflex component init +``` + +```text +reflex component init +─────────────────────────────────────── Initializing reflex-google-auth project ─────────────────────────────────────── +Info: Populating pyproject.toml with package name: reflex-google-auth +Info: Initializing the component directory: custom_components/reflex_google_auth +Info: Creating app for testing: google_auth_demo +──────────────────────────────────────────── Initializing google_auth_demo ──────────────────────────────────────────── +[07:58:16] Initializing the app directory. console.py:85 + Initializing the web directory. console.py:85 +Success: Initialized google_auth_demo +─────────────────────────────────── Installing reflex-google-auth in editable mode. ─────────────────────────────────── +Info: Package reflex-google-auth installed! +Custom component initialized successfully! +─────────────────────────────────────────────────── Project Summary ─────────────────────────────────────────────────── +[ README.md ]: Package description. Please add usage examples. +[ pyproject.toml ]: Project configuration file. Please fill in details such as your name, email, homepage URL. +[ custom_components/ ]: Custom component code template. Start by editing it with your component implementation. +[ google_auth_demo/ ]: Demo App. Add more code to this app and test. +``` + +The `init` command uses the current enclosing folder name to construct a python package name, typically in the kebab case. For example, if running init in folder `google_auth`, the package name will be `reflex-google-auth`. The added prefix reduces the chance of name collision on PyPI (the Python Package Index), and it indicates that the package is a Reflex custom component. The user can override the package name by providing the `--package-name` option. + +The `init` command creates a set of files and folders prefilled with the package name and other details. During the init, the `custom_component` folder is installed locally in editable mode, so a developer can incrementally develop and test with ease. The changes in component implementation is automatically reflected where it is used. Below is the folder structure after the `init` command. + +```text +google_auth/ +├── pyproject.toml +├── README.md +├── custom_components/ +│ └── reflex_google_auth/ +│ ├── google_auth.py +│ └── __init__.py +└── google_auth_demo/ + └── assets/ + google_auth_demo/ + requirements.txt + rxconfig.py +``` + +### pyproject.toml + +The `pyproject.toml` is required for the package to build and be published. It is prefilled with information such as the package name, version (`0.0.1`), author name and email, homepage URL. By default the **Apache-2.0** license is used, the same as Reflex. If any of this information requires update, the user can edit the file by hand. + +### README + +The `README.md` file is created with installation instructions, e.g. `pip install reflex-google-auth`, and a brief description of the package. Typically the `README.md` contains usage examples. On PyPI, the `README.md` is rendered as part of the package page. + +### Custom Components Folder + +The `custom_components` folder is where the actual implementation is. Do not worry about this folder name: there is no need to change it. It is where `pyproject.toml` specifies the source of the python package is. The published package contains the contents inside it, excluding this folder. + +`reflex_google_auth` is the top folder for importable code. The `reflex_google_auth/__init__.py` imports everything from the `reflex_google_auth/google_auth.py`. For the user of the package, the import looks like `from reflex_google_auth import ABC, XYZ`. + +`reflex_google_auth/google_auth.py` is prefilled with code example and instructions from the [wrapping react guide]({docs.wrapping_react.overview.path}). + +### Demo App Folder + +A demo app is generated inside `google_auth_demo` folder with import statements and example usage of the component. This is a regular Reflex app. Go into this directory and start using any reflex commands for testing. + +### Help Manual + +The help manual is shown when adding the `--help` option to the command. + +```bash +reflex component init --help +``` + +```text +Usage: reflex component init [OPTIONS] + + Initialize a custom component. + + Args: library_name: The name of the library. install: Whether to + install package from this local custom component in editable mode. + loglevel: The log level to use. + + Raises: Exit: If the pyproject.toml already exists. + +Options: + --library-name TEXT The name of your library. On PyPI, package + will be published as `reflex-{library- + name}`. + --install / --no-install Whether to install package from this local + custom component in editable mode. + [default: install] + --loglevel [debug|info|warning|error|critical] + The log level to use. [default: + LogLevel.INFO] + --help Show this message and exit. +``` + +## reflex component publish + +To publish to a package index, a user is required to already have an account with them. As of **0.7.5**, Reflex does not handle the publishing process for you. You can do so manually by first running `reflex component build` followed by `twine upload` or `uv publish` or your choice of a publishing utility. + +You can then share your build on our website with `reflex component share`. + +## reflex component build + +It is not required to run the `build` command separately before publishing. The `publish` command will build the package if it is not already built. The `build` command is provided for the user's convenience. + +The `build` command generates the `.tar.gz` and `.whl` distribution files to be uploaded to the desired package index, for example, PyPI. This command must be run at the top level of the project where the `pyproject.toml` file is. As a result of a successful build, there is a new `dist` folder with the distribution files. + +```bash +reflex component build --help +``` + +```text +Usage: reflex component build [OPTIONS] + + Build a custom component. Must be run from the project root directory where + the pyproject.toml is. + + Args: loglevel: The log level to use. + + Raises: Exit: If the build fails. + +Options: + --loglevel [debug|info|warning|error|critical] + The log level to use. [default: + LogLevel.INFO] + --help Show this message and exit. +``` diff --git a/docs/custom-components/overview.md b/docs/custom-components/overview.md new file mode 100644 index 00000000000..3970c73d97f --- /dev/null +++ b/docs/custom-components/overview.md @@ -0,0 +1,75 @@ +# Custom Components Overview + +```python exec +import reflex as rx +from pcweb.constants import REFLEX_ASSETS_CDN +from pcweb.pages.docs import custom_components +from pcweb.pages.docs.custom_components import custom_components as custom_components_gallery +``` + +Reflex users create many components of their own: ready to use high level components, or nicely wrapped React components. With **Custom Components**, the community can easily share these components now. + +Release **0.4.3** introduces a series of `reflex component` commands that help developers wrap react components, test, and publish them as python packages. As shown in the image below, there are already a few custom components published on PyPI, such as `reflex-spline`, `reflex-webcam`. + +Check out the custom components gallery [here]({custom_components_gallery.path}). + +```python eval +rx.center( + rx.image(src=f"{REFLEX_ASSETS_CDN}custom_components/pypi_reflex_custom_components.webp", width="400px", border_radius="15px", border="1px solid"), +) +``` + +## Prerequisites for Publishing + +In order to publish a Python package, an account is required with a python package index, for example, PyPI. The documentation to create accounts and generate API tokens can be found on their websites. For a quick reference, check out our [Prerequisites for Publishing]({custom_components.prerequisites_for_publishing.path}) page. + +## Steps to Publishing + +Follow these steps to publish the custom component as a python package: + +1. `reflex component init`: creates a new custom component project from templates. +2. dev and test: developer implements and tests the custom component. +3. `reflex component build`: builds the package. +4. `twine upload` or `uv publish`: uploads the package to a python package index. + +### Initialization + +```bash +reflex component init +``` + +First create a new folder for your custom component project, for example `color_picker`. The package name will be `reflex-color-picker`. The prefix `reflex-` is intentionally added for all custom components for easy search on PyPI. If you prefer a particular name for the package, you can either change it manually in the `pyproject.toml` file or add the `--library-name` option in the `reflex component init` command initially. + +Run `reflex component init`, and a set of files and folders will be created in the `color_picker` folder. The `pyproject.toml` file is the configuration file for the project. The `custom_components` folder is where the custom component implementation is. The `color_picker_demo` folder is a demo Reflex app that uses the custom component. If this is the first time of creating python packages, it is encouraged to browse through all the files (there are not that many) to understand the structure of the project. + +```bash +color_picker/ +├── pyproject.toml <- Configuration file +├── README.md +├── .gitignore <- Exclude dist/ and metadata folders +├── custom_components/ +│ └── reflex_color_picker/ <- Custom component source directory +│ ├── color_picker.py +│ └── __init__.py +└── color_picker_demo/ <- Demo Reflex app directory + └── assets/ + color_picker_demo/ + requirements.txt + rxconfig.py +``` + +### Develop and Test + +After finishing the custom component implementation, the user is encouraged to fully test it before publishing. The generated Reflex demo app `color_picker_demo` is a good place to start. It is a regular Reflex app prefilled with imports and usage of this component. During the init, the `custom_component` folder is installed locally in editable mode, so a developer can incrementally develop and test with ease. The changes in component implementation are automatically reflected in the demo app. + +### Publish + +```bash +reflex component build +``` + +Once you're ready to publish your package, run `reflex component build` to build the package. The command builds the distribution files if they are not already built. The end result is a `dist` folder containing the distribution files. The user does not need to do anything manually with these distribution files. + +In order to publish these files as a Python package, you need to use a publishing utility. Any would work, but we recommend either [Twine](https://twine.readthedocs.io/en/stable/) or (uv)[https://docs.astral.sh/uv/guides/package/#publishing-your-package]. Make sure to keep your package version in pyproject.toml updated. + +You can also share your components with the rest of the community at our website using the command `reflex component share`. See you there! diff --git a/docs/custom-components/prerequisites-for-publishing.md b/docs/custom-components/prerequisites-for-publishing.md new file mode 100644 index 00000000000..f836e4dad12 --- /dev/null +++ b/docs/custom-components/prerequisites-for-publishing.md @@ -0,0 +1,40 @@ +# Python Package Index + +```python exec +import reflex as rx +from pcweb.constants import REFLEX_ASSETS_CDN +from pcweb.styles.colors import c_color +image_style = { + "width": "400px", + "border_radius": "12px", + "border": f"1px solid {c_color('slate', 5)}", +} +``` + +In order to publish a Python package, you need to use a publishing utility. Any would work, but we recommend either [Twine](https://twine.readthedocs.io/en/stable/) or [uv](https://docs.astral.sh/uv/guides/package/#publishing-your-package). + +## PyPI + +It is straightforward to create accounts and API tokens with PyPI. There is official help on the [PyPI website](https://pypi.org/help/). For a quick reference here, go to the top right corner of the PyPI website and look for the button to register and fill out personal information. + +```python eval +rx.center( + rx.image(src=f"{REFLEX_ASSETS_CDN}custom_components/pypi_register.webp", style=image_style, margin_bottom="16px", loading="lazy"), +) +``` + +A user can use username and password to authenticate with PyPI when publishing. + +```python eval +rx.center( + rx.image(src=f"{REFLEX_ASSETS_CDN}custom_components/pypi_account_settings.webp", style=image_style, margin_bottom="16px", loading="lazy"), +) +``` + +Scroll down to the API tokens section and click on the "Add API token" button. Fill out the form and click "Generate API token". + +```python eval +rx.center( + rx.image(src=f"{REFLEX_ASSETS_CDN}custom_components/pypi_api_tokens.webp", style=image_style, width="700px", loading="lazy"), +) +``` diff --git a/docs/database/overview.md b/docs/database/overview.md new file mode 100644 index 00000000000..49a4ef4b328 --- /dev/null +++ b/docs/database/overview.md @@ -0,0 +1,87 @@ +# Database + +Reflex uses [sqlmodel](https://sqlmodel.tiangolo.com) to provide a built-in ORM wrapping SQLAlchemy. + +The examples on this page refer specifically to how Reflex uses various tools to +expose an integrated database interface. Only basic use cases will be covered +below, but you can refer to the +[sqlmodel tutorial](https://sqlmodel.tiangolo.com/tutorial/select/) +for more examples and information, just replace `SQLModel` with `rx.Model` and +`Session(engine)` with `rx.session()` + +For advanced use cases, please see the +[SQLAlchemy docs](https://docs.sqlalchemy.org/en/14/orm/quickstart.html) (v1.4). + +```md alert info +# Using NoSQL Databases + +If you are using a NoSQL database (e.g. MongoDB), you can work with it in Reflex by installing the appropriate Python client library. In this case, Reflex will not provide any ORM features. +``` + +## Connecting + +Reflex provides a built-in SQLite database for storing and retrieving data. + +You can connect to your own SQL compatible database by modifying the +`rxconfig.py` file with your database url. + +```python +config = rx.Config( + app_name="my_app", + db_url="sqlite:///reflex.db", +) +``` + +For more examples of database URLs that can be used, see the [SQLAlchemy +docs](https://docs.sqlalchemy.org/en/14/core/engines.html#backend-specific-urls). +Be sure to install the appropriate DBAPI driver for the database you intend to +use. + +## Tables + +To create a table make a class that inherits from `rx.Model` with and specify +that it is a table. + +```python +class User(rx.Model, table=True): + username: str + email: str + password: str +``` + +## Migrations + +Reflex leverages [alembic](https://alembic.sqlalchemy.org/en/latest/) +to manage database schema changes. + +Before the database feature can be used in a new app you must call `reflex db init` +to initialize alembic and create a migration script with the current schema. + +After making changes to the schema, use +`reflex db makemigrations --message 'something changed'` +to generate a script in the `alembic/versions` directory that will update the +database schema. It is recommended that generated scripts be inspected before applying them. + +Bear in mind that your newest models will not be detected by the `reflex db makemigrations` +command unless imported and used somewhere within the application. + +The `reflex db migrate` command is used to apply migration scripts to bring the +database up to date. During app startup, if Reflex detects that the current +database schema is not up to date, a warning will be displayed on the console. + +## Queries + +To query the database you can create a `rx.session()` +which handles opening and closing the database connection. + +You can use normal SQLAlchemy queries to query the database. + +```python +with rx.session() as session: + session.add(User(username="test", email="admin@reflex.dev", password="admin")) + session.commit() +``` + +```md video https://youtube.com/embed/ITOZkzjtjUA?start=6835&end=8225 +# Video: Tutorial of Database Model with Forms, Model Field Changes and Migrations, and adding a DateTime Field +``` diff --git a/docs/database/queries.md b/docs/database/queries.md new file mode 100644 index 00000000000..0a69c99f200 --- /dev/null +++ b/docs/database/queries.md @@ -0,0 +1,344 @@ +# Queries + +Queries are used to retrieve data from a database. + +A query is a request for information from a database table or combination of +tables. A query can be used to retrieve data from a single table or multiple +tables. A query can also be used to insert, update, or delete data from a table. + +## Session + +To execute a query you must first create a `rx.session`. You can use the session +to query the database using SQLModel or SQLAlchemy syntax. + +The `rx.session` statement will automatically close the session when the code +block is finished. **If `session.commit()` is not called, the changes will be +rolled back and not persisted to the database.** The code can also explicitly +rollback without closing the session via `session.rollback()`. + +The following example shows how to create a session and query the database. +First we create a table called `User`. + +```python +class User(rx.Model, table=True): + username: str + email: str +``` + +### Select + +Then we create a session and query the User table. + +```python +class QueryUser(rx.State): + name: str + users: list[User] + + @rx.event + def get_users(self): + with rx.session() as session: + self.users = session.exec( + User.select().where( + User.username.contains(self.name))).all() +``` + +The `get_users` method will query the database for all users that contain the +value of the state var `name`. + +### Insert + +Similarly, the `session.add()` method to add a new record to the +database or persist an existing object. + +```python +class AddUser(rx.State): + username: str + email: str + + @rx.event + def add_user(self): + with rx.session() as session: + session.add(User(username=self.username, email=self.email)) + session.commit() +``` + +### Update + +To update the user, first query the database for the object, make the desired +modifications, `.add` the object to the session and finally call `.commit()`. + +```python +class ChangeEmail(rx.State): + username: str + email: str + + @rx.event + def modify_user(self): + with rx.session() as session: + user = session.exec(User.select().where( + (User.username == self.username))).first() + user.email = self.email + session.add(user) + session.commit() +``` + +### Delete + +To delete a user, first query the database for the object, then call +`.delete()` on the session and finally call `.commit()`. + +```python +class RemoveUser(rx.State): + username: str + + @rx.event + def delete_user(self): + with rx.session() as session: + user = session.exec(User.select().where( + User.username == self.username)).first() + session.delete(user) + session.commit() +``` + +## ORM Object Lifecycle + +The objects returned by queries are bound to the session that created them, and cannot generally +be used outside that session. After adding or updating an object, not all fields are automatically +updated, so accessing certain attributes may trigger additional queries to refresh the object. + +To avoid this, the `session.refresh()` method can be used to update the object explicitly and +ensure all fields are up to date before exiting the session. + +```python +class AddUserForm(rx.State): + user: User | None = None + + @rx.event + def add_user(self, form_data: dict[str, Any]): + with rx.session() as session: + self.user = User(**form_data) + session.add(self.user) + session.commit() + session.refresh(self.user) +``` + +Now the `self.user` object will have a correct reference to the autogenerated +primary key, `id`, even though this was not provided when the object was created +from the form data. + +If `self.user` needs to be modified or used in another query in a new session, +it must be added to the session. Adding an object to a session does not +necessarily create the object, but rather associates it with a session where it +may either be created or updated accordingly. + +```python +class AddUserForm(rx.State): + ... + + @rx.event + def update_user(self, form_data: dict[str, Any]): + if self.user is None: + return + with rx.session() as session: + self.user.set(**form_data) + session.add(self.user) + session.commit() + session.refresh(self.user) +``` + +If an ORM object will be referenced and accessed outside of a session, you +should call `.refresh()` on it to avoid stale object exceptions. + +## Using SQL Directly + +Avoiding SQL is one of the main benefits of using an ORM, but sometimes it is +necessary for particularly complex queries, or when using database-specific +features. + +SQLModel exposes the `session.execute()` method that can be used to execute raw +SQL strings. If parameter binding is needed, the query may be wrapped in +[`sqlalchemy.text`](https://docs.sqlalchemy.org/en/14/core/sqlelement.html#sqlalchemy.sql.expression.text), +which allows colon-prefix names to be used as placeholders. + +```md alert info +# Never use string formatting to construct SQL queries, as this may lead to SQL injection vulnerabilities in the app. +``` + +```python +import sqlalchemy + +import reflex as rx + + +class State(rx.State): + + @rx.event + def insert_user_raw(self, username, email): + with rx.session() as session: + session.execute( + sqlalchemy.text( + "INSERT INTO user (username, email) " + "VALUES (:username, :email)" + ), + \{"username": username, "email": email}, + ) + session.commit() + + @rx.var + def raw_user_tuples(self) -> list[list]: + with rx.session() as session: + return [list(row) for row in session.execute("SELECT * FROM user").all()] +``` + +## Async Database Operations + +Reflex provides an async version of the session function called `rx.asession` for asynchronous database operations. This is useful when you need to perform database operations in an async context, such as within async event handlers. + +The `rx.asession` function returns an async SQLAlchemy session that must be used with an async context manager. Most operations against the `asession` must be awaited. + +```python +import sqlalchemy.ext.asyncio +import sqlalchemy + +import reflex as rx + + +class AsyncUserState(rx.State): + users: list[User] = [] + + @rx.event(background=True) + async def get_users_async(self): + async with rx.asession() as asession: + result = await asession.execute(User.select()) + async with self: + self.users = result.all() +``` + +### Async Select + +The following example shows how to query the database asynchronously: + +```python +class AsyncQueryUser(rx.State): + name: str + users: list[User] = [] + + @rx.event(background=True) + async def get_users(self): + async with rx.asession() as asession: + stmt = User.select().where(User.username.contains(self.name)) + result = await asession.execute(stmt) + async with self: + self.users = result.all() +``` + +### Async Insert + +To add a new record to the database asynchronously: + +```python +class AsyncAddUser(rx.State): + username: str + email: str + + @rx.event(background=True) + async def add_user(self): + async with rx.asession() as asession: + asession.add(User(username=self.username, email=self.email)) + await asession.commit() +``` + +### Async Update + +To update a user asynchronously: + +```python +class AsyncChangeEmail(rx.State): + username: str + email: str + + @rx.event(background=True) + async def modify_user(self): + async with rx.asession() as asession: + stmt = User.select().where(User.username == self.username) + result = await asession.execute(stmt) + user = result.first() + if user: + user.email = self.email + asession.add(user) + await asession.commit() +``` + +### Async Delete + +To delete a user asynchronously: + +```python +class AsyncRemoveUser(rx.State): + username: str + + @rx.event(background=True) + async def delete_user(self): + async with rx.asession() as asession: + stmt = User.select().where(User.username == self.username) + result = await asession.execute(stmt) + user = result.first() + if user: + await asession.delete(user) + await asession.commit() +``` + +### Async Refresh + +Similar to the regular session, you can refresh an object to ensure all fields are up to date: + +```python +class AsyncAddUserForm(rx.State): + user: User | None = None + + @rx.event(background=True) + async def add_user(self, form_data: dict[str, str]): + async with rx.asession() as asession: + async with self: + self.user = User(**form_data) + asession.add(self.user) + await asession.commit() + await asession.refresh(self.user) +``` + +### Async SQL Execution + +You can also execute raw SQL asynchronously: + +```python +class AsyncRawSQL(rx.State): + users: list[list] = [] + + @rx.event(background=True) + async def insert_user_raw(self, username, email): + async with rx.asession() as asession: + await asession.execute( + sqlalchemy.text( + "INSERT INTO user (username, email) " + "VALUES (:username, :email)" + ), + dict(username=username, email=email), + ) + await asession.commit() + + @rx.event(background=True) + async def get_raw_users(self): + async with rx.asession() as asession: + result = await asession.execute("SELECT * FROM user") + async with self: + self.users = [list(row) for row in result.all()] +``` + +```md alert info +# Important Notes for Async Database Operations +- Always use the `@rx.event(background=True)` decorator for async event handlers +- Most operations against the `asession` must be awaited, including `commit()`, `execute()`, `refresh()`, and `delete()` +- The `add()` method does not need to be awaited +- Result objects from queries have methods like `all()` and `first()` that are synchronous and return data directly +- Use `async with self:` when updating state variables in background tasks +``` diff --git a/docs/database/relationships.md b/docs/database/relationships.md new file mode 100644 index 00000000000..9b06daeeb44 --- /dev/null +++ b/docs/database/relationships.md @@ -0,0 +1,164 @@ +# Relationships + +Foreign key relationships are used to link two tables together. For example, +the `Post` model may have a field, `user_id`, with a foreign key of `user.id`, +referencing a `User` model. This would allow us to automatically query the `Post` objects +associated with a user, or find the `User` object associated with a `Post`. + +To establish bidirectional relationships a model must correctly set the +`back_populates` keyword argument on the `Relationship` to the relationship +attribute in the _other_ model. + +## Foreign Key Relationships + +To create a relationship, first add a field to the model that references the +primary key of the related table, then add a `sqlmodel.Relationship` attribute +which can be used to access the related objects. + +Defining relationships like this requires the use of `sqlmodel` objects as +seen in the example. + +```python +from typing import List, Optional + +import sqlmodel + +import reflex as rx + + +class Post(rx.Model, table=True): + title: str + body: str + user_id: int = sqlmodel.Field(foreign_key="user.id") + + user: Optional["User"] = sqlmodel.Relationship(back_populates="posts") + flags: Optional[List["Flag"]] = sqlmodel.Relationship(back_populates="post") + + +class User(rx.Model, table=True): + username: str + email: str + + posts: List[Post] = sqlmodel.Relationship(back_populates="user") + flags: List["Flag"] = sqlmodel.Relationship(back_populates="user") + + +class Flag(rx.Model, table=True): + post_id: int = sqlmodel.Field(foreign_key="post.id") + user_id: int = sqlmodel.Field(foreign_key="user.id") + message: str + + post: Optional[Post] = sqlmodel.Relationship(back_populates="flags") + user: Optional[User] = sqlmodel.Relationship(back_populates="flags") +``` + +See the [SQLModel Relationship Docs](https://sqlmodel.tiangolo.com/tutorial/relationship-attributes/define-relationships-attributes/) for more details. + +## Querying Relationships + +### Inserting Linked Objects + +The following example assumes that the flagging user is stored in the state as a +`User` instance and that the post `id` is provided in the data submitted in the +form. + +```python +class FlagPostForm(rx.State): + user: User + + @rx.event + def flag_post(self, form_data: dict[str, Any]): + with rx.session() as session: + post = session.get(Post, int(form_data.pop("post_id"))) + flag = Flag(message=form_data.pop("message"), post=post, user=self.user) + session.add(flag) + session.commit() +``` + +### How are Relationships Dereferenced? + +By default, the relationship attributes are in **lazy loading** or `"select"` +mode, which generates a query _on access_ to the relationship attribute. Lazy +loading is generally fine for single object lookups and manipulation, but can be +inefficient when accessing many linked objects for serialization purposes. + +There are several alternative loading mechanisms available that can be set on +the relationship object or when performing the query. + +* "joined" or `joinload` - generates a single query to load all related objects + at once. +* "subquery" or `subqueryload` - generates a single query to load all related + objects at once, but uses a subquery to do the join, instead of a join in the + main query. +* "selectin" or `selectinload` - emits a second (or more) SELECT statement which + assembles the primary key identifiers of the parent objects into an IN clause, + so that all members of related collections / scalar references are loaded at + once by primary key + +There are also non-loading mechanisms, "raise" and "noload" which are used to +specifically avoid loading a relationship. + +Each loading method comes with tradeoffs and some are better suited for different +data access patterns. +See [SQLAlchemy: Relationship Loading Techniques](https://docs.sqlalchemy.org/en/14/orm/loading_relationships.html) +for more detail. + +### Querying Linked Objects + +To query the `Post` table and include all `User` and `Flag` objects up front, +the `.options` interface will be used to specify `selectinload` for the required +relationships. Using this method, the linked objects will be available for +rendering in frontend code without additional steps. + +```python +import sqlalchemy + + +class PostState(rx.State): + posts: List[Post] + + @rx.event + def load_posts(self): + with rx.session() as session: + self.posts = session.exec( + Post.select + .options( + sqlalchemy.orm.selectinload(Post.user), + sqlalchemy.orm.selectinload(Post.flags).options( + sqlalchemy.orm.selectinload(Flag.user), + ), + ) + .limit(15) + ).all() +``` + +The loading methods create new query objects and thus may be linked if the +relationship itself has other relationships that need to be loaded. In this +example, since `Flag` references `User`, the `Flag.user` relationship must be +chain loaded from the `Post.flags` relationship. + +### Specifying the Loading Mechanism on the Relationship + +Alternatively, the loading mechanism can be specified on the relationship by +passing `sa_relationship_kwargs=\{"lazy": method}` to `sqlmodel.Relationship`, +which will use the given loading mechanism in all queries by default. + +```python +from typing import List, Optional + +import sqlmodel + +import reflex as rx + + +class Post(rx.Model, table=True): + ... + user: Optional["User"] = sqlmodel.Relationship( + back_populates="posts", + sa_relationship_kwargs=\{"lazy": "selectin"}, + ) + flags: Optional[List["Flag"]] = sqlmodel.Relationship( + back_populates="post", + sa_relationship_kwargs=\{"lazy": "selectin"}, + ) +``` diff --git a/docs/database/tables.md b/docs/database/tables.md new file mode 100644 index 00000000000..4be4b164540 --- /dev/null +++ b/docs/database/tables.md @@ -0,0 +1,70 @@ +# Tables + +Tables are database objects that contain all the data in a database. + +In tables, data is logically organized in a row-and-column format similar to a +spreadsheet. Each row represents a unique record, and each column represents a +field in the record. + +## Creating a Table + +To create a table, make a class that inherits from `rx.Model`. + +The following example shows how to create a table called `User`. + +```python +class User(rx.Model, table=True): + username: str + email: str +``` + +The `table=True` argument tells Reflex to create a table in the database for +this class. + +### Primary Key + +By default, Reflex will create a primary key column called `id` for each table. + +However, if an `rx.Model` defines a different field with `primary_key=True`, then the +default `id` field will not be created. A table may also redefine `id` as needed. + +It is not currently possible to create a table without a primary key. + +## Advanced Column Types + +SQLModel automatically maps basic python types to SQLAlchemy column types, but +for more advanced use cases, it is possible to define the column type using +`sqlalchemy` directly. For example, we can add a last updated timestamp to the +post example as a proper `DateTime` field with timezone. + +```python +import datetime + +import sqlmodel +import sqlalchemy + +class Post(rx.Model, table=True): + ... + update_ts: datetime.datetime = sqlmodel.Field( + default=None, + sa_column=sqlalchemy.Column( + "update_ts", + sqlalchemy.DateTime(timezone=True), + server_default=sqlalchemy.func.now(), + ), + ) +``` + +To make the `Post` model more usable on the frontend, a `dict` method may be provided +that converts any fields to a JSON serializable value. In this case, the dict method is +overriding the default `datetime` serializer to strip off the microsecond part. + +```python +class Post(rx.Model, table=True): + ... + + def dict(self, *args, **kwargs) -> dict: + d = super().dict(*args, **kwargs) + d["update_ts"] = self.update_ts.replace(microsecond=0).isoformat() + return d +``` diff --git a/docs/de/README.md b/docs/de/README.md deleted file mode 100644 index 3ce5c6c02f8..00000000000 --- a/docs/de/README.md +++ /dev/null @@ -1,250 +0,0 @@ -
-Reflex Logo -
- -### **✨ Performante, anpassbare Web-Apps in purem Python. Bereitstellung in Sekunden. ✨** - -[![PyPI version](https://badge.fury.io/py/reflex.svg)](https://badge.fury.io/py/reflex) -![versions](https://img.shields.io/pypi/pyversions/reflex.svg) -[![Documentation](https://img.shields.io/badge/Documentation%20-Introduction%20-%20%23007ec6)](https://reflex.dev/docs/getting-started/introduction) -[![PyPI Downloads](https://static.pepy.tech/badge/reflex)](https://pepy.tech/projects/reflex) -[![Discord](https://img.shields.io/discord/1029853095527727165?color=%237289da&label=Discord)](https://discord.gg/T5WSbC2YtQ) - -
- ---- - -[English](https://github.com/reflex-dev/reflex/blob/main/README.md) | [简体中文](https://github.com/reflex-dev/reflex/blob/main/docs/zh/zh_cn/README.md) | [繁體中文](https://github.com/reflex-dev/reflex/blob/main/docs/zh/zh_tw/README.md) | [Türkçe](https://github.com/reflex-dev/reflex/blob/main/docs/tr/README.md) | [हिंदी](https://github.com/reflex-dev/reflex/blob/main/docs/in/README.md) | [Português (Brasil)](https://github.com/reflex-dev/reflex/blob/main/docs/pt/pt_br/README.md) | [Italiano](https://github.com/reflex-dev/reflex/blob/main/docs/it/README.md) | [Español](https://github.com/reflex-dev/reflex/blob/main/docs/es/README.md) | [한국어](https://github.com/reflex-dev/reflex/blob/main/docs/kr/README.md) | [日本語](https://github.com/reflex-dev/reflex/blob/main/docs/ja/README.md) | [Deutsch](https://github.com/reflex-dev/reflex/blob/main/docs/de/README.md) | [Persian (پارسی)](https://github.com/reflex-dev/reflex/blob/main/docs/pe/README.md) | [Tiếng Việt](https://github.com/reflex-dev/reflex/blob/main/docs/vi/README.md) - ---- - -# Reflex - -Reflex ist eine Bibliothek, mit der man Full-Stack-Web-Applikationen in purem Python erstellen kann. - -Wesentliche Merkmale: - -- **Pures Python** - Schreibe dein Front- und Backend in Python, es gibt also keinen Grund, JavaScript zu lernen. -- **Volle Flexibilität** - Reflex ist einfach zu handhaben, kann aber auch für komplexe Anwendungen skaliert werden. -- **Sofortige Bereitstellung** - Nach dem Erstellen kannst du deine App mit einem [einzigen Befehl](https://reflex.dev/docs/hosting/deploy-quick-start/) bereitstellen oder auf deinem eigenen Server hosten. - -Auf unserer [Architektur-Seite](https://reflex.dev/blog/2024-03-21-reflex-architecture/#the-reflex-architecture) erfahren Sie, wie Reflex unter der Haube funktioniert. - -## ⚙️ Installation - -Öffne ein Terminal und führe den folgenden Befehl aus (benötigt Python 3.10+): - -```bash -pip install reflex -``` - -## 🥳 Erstelle deine erste App - -Die Installation von `reflex` installiert auch das `reflex`-Kommandozeilen-Tool. - -Teste, ob die Installation erfolgreich war, indem du ein neues Projekt erstellst. (Ersetze `my_app_name` durch deinen Projektnamen): - -```bash -mkdir my_app_name -cd my_app_name -reflex init -``` - -Dieser Befehl initialisiert eine Vorlage in deinem neuen Verzeichnis. - -Du kannst diese App im Entwicklungsmodus ausführen: - -```bash -reflex run -``` - -Du solltest deine App unter http://localhost:3000 laufen sehen. - -Nun kannst du den Quellcode in `my_app_name/my_app_name.py` ändern. Reflex hat schnelle Aktualisierungen, sodass du deine Änderungen sofort siehst, wenn du deinen Code speicherst. - -## 🫧 Beispiel-App - -Lass uns ein Beispiel durchgehen: die Erstellung einer Benutzeroberfläche für die Bildgenerierung mit [DALL·E](https://platform.openai.com/docs/guides/images/image-generation?context=node). Zur Vereinfachung rufen wir einfach die [OpenAI-API](https://platform.openai.com/docs/api-reference/authentication) auf, aber du könntest dies auch durch ein lokal ausgeführtes ML-Modell ersetzen. - -  - -
-Eine Benutzeroberfläche für DALL·E, die im Prozess der Bildgenerierung gezeigt wird. -
- -  - -Hier ist der komplette Code, um dies zu erstellen. Das alles wird in einer Python-Datei gemacht! - -```python -import reflex as rx -import openai - -openai_client = openai.OpenAI() - - -class State(rx.State): - """Der Zustand der App.""" - - prompt = "" - image_url = "" - processing = False - complete = False - - def get_image(self): - """Hole das Bild aus dem Prompt.""" - if self.prompt == "": - return rx.window_alert("Prompt Empty") - - self.processing, self.complete = True, False - yield - response = openai_client.images.generate( - prompt=self.prompt, n=1, size="1024x1024" - ) - self.image_url = response.data[0].url - self.processing, self.complete = False, True - - -def index(): - return rx.center( - rx.vstack( - rx.heading("DALL-E", font_size="1.5em"), - rx.input( - placeholder="Enter a prompt..", - on_blur=State.set_prompt, - width="25em", - ), - rx.button( - "Generate Image", - on_click=State.get_image, - width="25em", - loading=State.processing - ), - rx.cond( - State.complete, - rx.image(src=State.image_url, width="20em"), - ), - align="center", - ), - width="100%", - height="100vh", - ) - -# Füge Zustand und Seite zur App hinzu. -app = rx.App() -app.add_page(index, title="Reflex:DALL-E") -``` - -## Schauen wir uns das mal genauer an. - -
-Erläuterung der Unterschiede zwischen Backend- und Frontend-Teilen der DALL-E-App. -
- -### **Reflex-UI** - -Fangen wir mit der Benutzeroberfläche an. - -```python -def index(): - return rx.center( - ... - ) -``` - -Diese `index`-Funktion definiert das Frontend der App. - -Wir verwenden verschiedene Komponenten wie `center`, `vstack`, `input` und `button`, um das Frontend zu erstellen. Komponenten können ineinander verschachtelt werden, um komplexe Layouts zu erstellen. Und du kannst Schlüsselwortargumente verwenden, um sie mit der vollen Kraft von CSS zu stylen. - -Reflex wird mit [über 60 eingebauten Komponenten](https://reflex.dev/docs/library) geliefert, die dir den Einstieg erleichtern. Wir fügen aktiv weitere Komponenten hinzu, und es ist einfach, [eigene Komponenten zu erstellen](https://reflex.dev/docs/wrapping-react/overview/). - -### **State** - -Reflex stellt deine Benutzeroberfläche als Funktion deines Zustands dar. - -```python -class State(rx.State): - """Der Zustand der App.""" - prompt = "" - image_url = "" - processing = False - complete = False - -``` - -Der Zustand definiert alle Variablen (genannt Vars) in einer App, die sich ändern können, und die Funktionen, die sie ändern. - -Hier besteht der Zustand aus einem `prompt` und einer `image_url`. Es gibt auch die Booleans `processing` und `complete`, um anzuzeigen, wann der Button deaktiviert werden soll (während der Bildgenerierung) und wann das resultierende Bild angezeigt werden soll. - -### **Event-Handler** - -```python -def get_image(self): - """Hole das Bild aus dem Prompt.""" - if self.prompt == "": - return rx.window_alert("Prompt Empty") - - self.processing, self.complete = True, False - yield - response = openai_client.images.generate( - prompt=self.prompt, n=1, size="1024x1024" - ) - self.image_url = response.data[0].url - self.processing, self.complete = False, True -``` - -Innerhalb des Zustands definieren wir Funktionen, die als Event-Handler bezeichnet werden und die Zustand-Variablen ändern. Event-Handler sind die Art und Weise, wie wir den Zustand in Reflex ändern können. Sie können als Reaktion auf Benutzeraktionen aufgerufen werden, z.B. beim Klicken auf eine Schaltfläche oder bei der Eingabe in ein Textfeld. Diese Aktionen werden als Ereignisse bezeichnet. - -Unsere DALL-E.-App hat einen Event-Handler, `get_image`, der dieses Bild von der OpenAI-API abruft. Die Verwendung von `yield` in der Mitte eines Event-Handlers führt zu einer Aktualisierung der Benutzeroberfläche. Andernfalls wird die Benutzeroberfläche am Ende des Ereignishandlers aktualisiert. - -### **Routing** - -Schließlich definieren wir unsere App. - -```python -app = rx.App() -``` - -Wir fügen der Indexkomponente eine Seite aus dem Stammverzeichnis der Anwendung hinzu. Wir fügen auch einen Titel hinzu, der in der Seitenvorschau/Browser-Registerkarte angezeigt wird. - -```python -app.add_page(index, title="DALL-E") -``` - -Du kannst eine mehrseitige App erstellen, indem du weitere Seiten hinzufügst. - -## 📑 Ressourcen - -
- -📑 [Docs](https://reflex.dev/docs/getting-started/introduction)   |   🗞️ [Blog](https://reflex.dev/blog)   |   📱 [Komponentenbibliothek](https://reflex.dev/docs/library)   |   🖼️ [Templates](https://reflex.dev/templates/)   |   🛸 [Bereitstellung](https://reflex.dev/docs/hosting/deploy-quick-start)   - -
- -## ✅ Status - -Reflex wurde im Dezember 2022 unter dem Namen Pynecone gestartet. - -Ab 2025 wurde [Reflex Cloud](https://cloud.reflex.dev) gestartet, um die beste Hosting-Erfahrung für Reflex-Apps zu bieten. Wir werden es weiterhin entwickeln und mehr Funktionen implementieren. - -Reflex hat wöchentliche Veröffentlichungen und neue Features! Stelle sicher, dass du dieses Repository mit einem :star: Stern markierst und :eyes: beobachtest, um auf dem Laufenden zu bleiben. - -## Beitragende - -Wir begrüßen Beiträge jeder Größe! Hier sind einige gute Möglichkeiten, um in der Reflex-Community zu starten. - -- **Tritt unserem Discord bei**: Unser [Discord](https://discord.gg/T5WSbC2YtQ) ist der beste Ort, um Hilfe für dein Reflex-Projekt zu bekommen und zu besprechen, wie du beitragen kannst. -- **GitHub-Diskussionen**: Eine großartige Möglichkeit, über Funktionen zu sprechen, die du hinzugefügt haben möchtest oder Dinge, die verwirrend sind/geklärt werden müssen. -- **GitHub-Issues**: [Issues](https://github.com/reflex-dev/reflex/issues) sind eine ausgezeichnete Möglichkeit, Bugs zu melden. Außerdem kannst du versuchen, ein bestehendes Problem zu lösen und eine PR einzureichen. - -Wir suchen aktiv nach Mitwirkenden, unabhängig von deinem Erfahrungslevel oder deiner Erfahrung. Um beizutragen, sieh dir [CONTRIBUTING.md](https://github.com/reflex-dev/reflex/blob/main/CONTRIBUTING.md) an. - -## Vielen Dank an unsere Mitwirkenden: - - - - - -## Lizenz - -Reflex ist Open-Source und lizenziert unter der [Apache License 2.0](/LICENSE). diff --git a/docs/enterprise/ag_chart.md b/docs/enterprise/ag_chart.md new file mode 100644 index 00000000000..30fdc4b53ee --- /dev/null +++ b/docs/enterprise/ag_chart.md @@ -0,0 +1,30 @@ +# AG Chart + +AG Chart is a powerful charting library that provides interactive charts and data visualization components for enterprise applications. + +```python demo exec +import reflex as rx +import reflex_enterprise as rxe + +def basic_chart(): + return rxe.ag_chart( + options={ + "data": [ + {"month": "Jan", "value": 10}, + {"month": "Feb", "value": 20}, + {"month": "Mar", "value": 15}, + ], + "series": [ + { + "type": "line", + "xKey": "month", + "yKey": "value", + } + ], + }, + width="100%", + height="400px", + ) +``` + +For more detailed documentation, see the [AG Chart Documentation](https://charts.ag-grid.com/). diff --git a/docs/enterprise/ag_grid/aligned-grids.md b/docs/enterprise/ag_grid/aligned-grids.md new file mode 100644 index 00000000000..da63bef7f63 --- /dev/null +++ b/docs/enterprise/ag_grid/aligned-grids.md @@ -0,0 +1,65 @@ +--- +title: Aligned Grids +--- + +AgGrid provides a way to align multiple grids together. This is useful when you want to display related data in a synchronized manner. + +You can do so through the `aligned_grids` prop. This prop takes a list of grid IDs that you want to align. + +```python demo exec +import pandas as pd +import reflex as rx +import reflex_enterprise as rxe + +# Olympic winners data (originally from https://www.ag-grid.com/example-assets/olympic-winners.json) +df = pd.read_json("data/olympic-winners.json") + +row_data = df.to_dict("records") + +column_defs = [ + {"field": "athlete"}, + {"field": "age"}, + {"field": "country"}, + {"field": "year"}, + {"field": "sport"}, + { + "header_name": "Medals", + "children": [ + { + "field": "total", + "column_group_show": "closed", + "col_id": "total", + "value_getter": "params.data.gold + params.data.silver + params.data.bronze", + "width": 100, + }, + {"field": "gold", "column_group_show": "open", "width": 100}, + {"field": "silver", "column_group_show": "open", "width": 100}, + {"field": "bronze", "column_group_show": "open", "width": 100}, + ], + }, +] + +def aligned_grids_page(): + """Aligned grids demo.""" + return rx.el.div( + rxe.ag_grid( + id="grid1", + column_defs=column_defs, + row_data=row_data, + aligned_grids=["grid2"], + width="100%", + ), rxe.ag_grid( + id="grid2", + column_defs=column_defs, + row_data=row_data, + aligned_grids=["grid1"], + width="100%", + ), + class_name="flex flex-col gap-y-6 w-full" + ) + +``` + +```md alert warning +# The pivot functionality does not work with aligned grids. This is because pivoting data changes the columns, which would make the aligned grids incompatible, as they are no longer sharing the same set of columns. +``` diff --git a/docs/enterprise/ag_grid/cell-selection.md b/docs/enterprise/ag_grid/cell-selection.md new file mode 100644 index 00000000000..c428d46fd03 --- /dev/null +++ b/docs/enterprise/ag_grid/cell-selection.md @@ -0,0 +1,288 @@ +--- +title: "Cell Selection" +order: 8 +--- + +```python exec +from pcweb.pages.docs import enterprise +``` + +# Cell Selection + +AG Grid provides powerful cell selection capabilities that allow users to select individual cells or ranges of cells. This feature is essential for data manipulation, copying, and advanced interactions like fill handle operations. + +## Range Selection + +To enable cell selection in your AG Grid, set the `cell_selection` prop to `True`. This automatically enables both single cell selection and range selection capabilities. + +### Basic Selection Example + +```python demo exec +import reflex as rx +import reflex_enterprise as rxe +import pandas as pd + +class CellSelectionState(rx.State): + data: list[dict] = [] + + @rx.event + def load_data(self): + df = pd.read_json("https://www.ag-grid.com/example-assets/olympic-winners.json") + self.data = df.head(10).to_dict("records") + + @rx.event + def echo_selection(self, ranges: list[dict], started: bool, finished: bool): + if finished: + yield rx.toast(f"Selected ranges: {ranges}") + +column_defs = [ + {"field": "athlete", "width": 150}, + {"field": "age", "width": 90}, + {"field": "country", "width": 120}, + {"field": "year", "width": 90}, + {"field": "sport", "width": 120}, + {"field": "gold", "width": 100}, + {"field": "silver", "width": 100}, + {"field": "bronze", "width": 100}, +] + +def basic_cell_selection(): + return rx.vstack( + rx.text("Click and drag to select cells. Selection info will appear in a toast.", size="2"), + rxe.ag_grid( + id="basic_cell_selection_grid", + column_defs=column_defs, + row_data=CellSelectionState.data, + cell_selection=True, + on_cell_selection_changed=CellSelectionState.echo_selection, + width="100%", + height="400px", + ), + on_mount=CellSelectionState.load_data, + width="100%", + ) +``` + +### Advanced Selection Event Handling + +For more sophisticated selection handling, you can process the selection ranges to calculate detailed information: + +```python demo exec +import reflex as rx +import reflex_enterprise as rxe +import pandas as pd + +class AdvancedSelectionState(rx.State): + data: list[dict] = [] + + @rx.event + def load_data(self): + df = pd.DataFrame({ + "name": ["Alice", "Bob", "Charlie", "Diana", "Eve"], + "score": [85, 92, 78, 96, 88], + "grade": ["B", "A", "C", "A", "B"], + "attempts": [3, 2, 4, 1, 3] + }) + self.data = df.to_dict("records") + + @rx.event + def handle_selection(self, ranges: list[dict], started: bool, finished: bool): + if finished and ranges: + total_cells = sum( + (r.get("endRow", 0) - r.get("startRow", 0) + 1) * + len(r.get("columns", [])) + for r in ranges + ) + yield rx.toast(f"Selected {total_cells} cells across {len(ranges)} ranges") + +editable_column_defs = [ + {"field": "name", "width": 120}, + {"field": "score", "width": 100, "editable": True}, + {"field": "grade", "width": 100, "editable": True}, + {"field": "attempts", "width": 120, "editable": True}, +] + +def advanced_selection_example(): + return rx.vstack( + rx.text("Select ranges of cells. Try selecting multiple ranges by holding Ctrl/Cmd.", size="2"), + rxe.ag_grid( + id="advanced_selection_grid", + column_defs=editable_column_defs, + row_data=AdvancedSelectionState.data, + cell_selection=True, + on_cell_selection_changed=AdvancedSelectionState.handle_selection, + width="100%", + height="300px", + ), + on_mount=AdvancedSelectionState.load_data, + width="100%", + ) +``` + +## Fill Handle + +The fill handle is a powerful feature that allows users to quickly fill cells by dragging from a selected cell or range. When enabled, a small square appears at the bottom-right corner of the selection that users can drag to fill adjacent cells. + +### Enabling Fill Handle + +To enable the fill handle, configure the `cell_selection` prop with a dictionary containing the handle configuration: + +```python +cell_selection={ + "handle": { + "mode": "fill", # Enable fill handle + } +} +``` + +### Fill Handle Events + +When using the fill handle, it will trigger `on_cell_value_changed` for each cell receiving a fill value. This allows your backend to handle the data changes appropriately. + +```python demo exec +import reflex as rx +import reflex_enterprise as rxe +import pandas as pd + +class FillHandleState(rx.State): + data: list[dict] = [] + change_log: list[str] = [] + + @rx.event + def load_data(self): + df = pd.DataFrame({ + "item": ["Apple", "Banana", "Cherry", "Date", "Elderberry"], + "quantity": [10, 15, 8, 12, 20], + "price": [1.50, 0.75, 2.00, 3.00, 4.50], + "total": [15.00, 11.25, 16.00, 36.00, 90.00] + }) + self.data = df.to_dict("records") + + @rx.event + def handle_cell_change(self, data: dict): + row_index = data.get("rowIndex", 0) + field = data.get("colId", "") + new_value = data.get("newValue", "") + old_value = data.get("oldValue", "") + + change_msg = f"Row {row_index + 1}, {field}: '{old_value}' → '{new_value}'" + self.change_log = [change_msg] + self.change_log[:9] # Keep last 10 changes + + # Update the data + if 0 <= row_index < len(self.data): + self.data[row_index][field] = new_value + +fill_column_defs = [ + {"field": "item", "width": 120}, + {"field": "quantity", "width": 100, "editable": True, "type": "numericColumn"}, + {"field": "price", "width": 100, "editable": True, "type": "numericColumn"}, + {"field": "total", "width": 100, "editable": True, "type": "numericColumn"}, +] + +def fill_handle_example(): + return rx.vstack( + rx.text("Select a cell and drag the fill handle (small square at bottom-right) to fill adjacent cells.", size="2"), + rxe.ag_grid( + id="fill_handle_grid", + column_defs=fill_column_defs, + row_data=FillHandleState.data, + cell_selection={ + "handle": { + "mode": "fill", # Enable fill handle + } + }, + on_cell_value_changed=FillHandleState.handle_cell_change, + width="100%", + height="300px", + ), + rx.divider(), + rx.text("Recent Changes:", weight="bold", size="3"), + rx.cond( + FillHandleState.change_log, + rx.vstack( + rx.foreach( + FillHandleState.change_log, + lambda change: rx.text(change, size="1", color="gray") + ), + spacing="1", + ), + rx.text("No changes yet", size="2", color="gray") + ), + on_mount=FillHandleState.load_data, + width="100%", + spacing="4", + ) +``` + +## Advanced Configuration Options + +You can further customize cell selection behavior using additional configuration options: + +```python demo exec +import reflex as rx +import reflex_enterprise as rxe +import pandas as pd + +class ConfigurationState(rx.State): + data: list[dict] = [] + + @rx.event + def load_data(self): + df = pd.DataFrame({ + "id": range(1, 8), + "name": ["Product A", "Product B", "Product C", "Product D", "Product E", "Product F", "Product G"], + "category": ["Electronics", "Clothing", "Electronics", "Books", "Clothing", "Electronics", "Books"], + "price": [299.99, 49.99, 199.99, 24.99, 79.99, 399.99, 19.99], + "stock": [15, 32, 8, 45, 23, 12, 67] + }) + self.data = df.to_dict("records") + +configuration_column_defs = [ + {"field": "id", "width": 80}, + {"field": "name", "width": 150, "editable": True}, + {"field": "category", "width": 120}, + {"field": "price", "width": 100, "editable": True, "type": "numericColumn"}, + {"field": "stock", "width": 100, "editable": True, "type": "numericColumn"}, +] + +def configuration_example(): + return rx.vstack( + rx.text("Cell selection with additional configuration options", size="2"), + rxe.ag_grid( + id="configuration_grid", + column_defs=configuration_column_defs, + row_data=ConfigurationState.data, + cell_selection={ + "handle": { + "mode": "fill", + } + }, + enable_cell_text_selection=True, # Allow text selection within cells + suppress_cell_focus=False, # Allow cell focus + width="100%", + height="350px", + ), + on_mount=ConfigurationState.load_data, + width="100%", + ) +``` + +## Key Features + +- **Cell Selection**: Enable with `cell_selection=True` for both single cell and range selection capabilities +- **Fill Handle**: Configure with `cell_selection={"handle": {"mode": "fill"}}` for drag-to-fill functionality +- **Event Handling**: Use `on_cell_selection_changed` to respond to selection changes +- **Value Changes**: Use `on_cell_value_changed` to handle individual cell edits and fill operations +- **Text Selection**: Enable `enable_cell_text_selection=True` to allow text selection within cells + +## Best Practices + +1. **Use cell_selection configuration**: Both single cell and range selection are automatically enabled with `cell_selection=True`, providing all necessary selection capabilities for fill operations. + +2. **Handle cell value changes**: When using fill handle, implement `on_cell_value_changed` to process the data updates in your backend. + +3. **Provide user feedback**: Use toasts or other UI elements to confirm selection actions and data changes. + +4. **Consider performance**: For large datasets, be mindful of the performance impact of frequent cell value change events. + +5. **Validate fill operations**: Implement validation logic in your `on_cell_value_changed` handler to ensure data integrity. diff --git a/docs/enterprise/ag_grid/column-defs.md b/docs/enterprise/ag_grid/column-defs.md new file mode 100644 index 00000000000..a1dea018114 --- /dev/null +++ b/docs/enterprise/ag_grid/column-defs.md @@ -0,0 +1,35 @@ +--- +order: 1 +--- + +```python exec +from pcweb.pages.docs import enterprise +``` + +# Column Definitions + +## Basic Columns + +AgGrid allows you to define the columns of your grid, passed to the prop `column_defs`. Each dictionary represents a column. + +```md alert warning +# If you are converting from other AG Grid implementation, we also support camelCase for the name of the properties. +``` + +Here we define a grid with 3 columns: +```python +column_defs = [ + {"field": "direction"}, + {"field": "strength"}, + {"field": "frequency"}, +] +``` + +To set default properties for all your columns, you can define `default_col_def` in your grid: +```python +default_col_def = { + "sortable": True, + "filter": True, + "resizable": True, +} +``` \ No newline at end of file diff --git a/docs/enterprise/ag_grid/index.md b/docs/enterprise/ag_grid/index.md new file mode 100644 index 00000000000..a6ed8c25b48 --- /dev/null +++ b/docs/enterprise/ag_grid/index.md @@ -0,0 +1,681 @@ +--- +title: "AgGrid Overview" +order: 3 +--- + +```python exec +from pcweb.pages.docs import enterprise +``` + +# AG Grid + +AG Grid is a powerful, feature-rich data grid component that brings enterprise-grade table functionality to your Reflex applications. With support for sorting, filtering, pagination, row selection, and much more, AG Grid transforms how you display and interact with tabular data. + +[Explore the full AG Grid showcase and examples](https://aggrid.reflex.run/) + +## Your First Reflex AG Grid + +A basic Reflex AG Grid contains column definitions `column_defs`, which define the columns to be displayed in the grid, and `row_data`, which contains the data to be displayed in the grid. + +Each grid also requires a unique `id`, which is needed to uniquely identify the Ag-Grid instance on the page. If you have multiple grids on the same page, each grid must have a unique `id` so that it can be correctly rendered and managed. + +```python demo exec +import reflex as rx +import reflex_enterprise as rxe +import pandas as pd + + +df = pd.read_csv("data/wind_dataset.csv") + +column_defs = [ + {"field": "direction"}, + {"field": "strength"}, + {"field": "frequency"}, +] + +def ag_grid_simple(): + return rxe.ag_grid( + id="ag_grid_basic_1", + row_data=df.to_dict("records"), + column_defs=column_defs, + width="100%", + ) +``` + +📊 **Dataset source:** [wind_dataset.csv](https://raw.githubusercontent.com/plotly/datasets/master/wind_dataset.csv) + +The format of the data passed to the `row_data` prop is a list of dictionaries. Each dictionary represents a row in the grid as seen below. + +```python +[ + \{"direction": "N", "strength": "0-1", "frequency": 0.5\}, + \{"direction": "NNE", "strength": "0-1", "frequency": 0.6\}, + \{"direction": "NE", "strength": "0-1", "frequency": 0.5\}, +] +``` + +The previous example showed the `column_defs` written out in full. You can also extract the required information from the dataframe's column names: + +```python demo exec +import reflex as rx +import reflex_enterprise as rxe +import pandas as pd + +df = pd.read_csv("data/wind_dataset.csv") + + +def ag_grid_simple_2(): + return rxe.ag_grid( + id="ag_grid_basic_2", + row_data=df.to_dict("records"), + column_defs=[{"field": i} for i in df.columns], + width="100%", + height="40vh", + ) +``` + +📊 **Dataset source:** [wind_dataset.csv](https://raw.githubusercontent.com/plotly/datasets/master/wind_dataset.csv) + +## Headers + +In the above example, the first letter of the field names provided are capitalized when displaying the header name. You can customize the header names by providing a `header_name` key in the column definition. In this example, the `header_name` is customized for the second and third columns. + +```python demo exec +import reflex as rx +import reflex_enterprise as rxe +import pandas as pd + + +df = pd.read_csv("data/gapminder2007.csv") + +column_defs = [ + {"field": "country"}, + {"field": "pop", "headerName": "Population"}, + {"field": "lifeExp", "headerName": "Life Expectancy"}, +] + +def ag_grid_simple_headers(): + return rxe.ag_grid( + id="ag_grid_basic_headers", + row_data=df.to_dict("records"), + column_defs=column_defs, + width="100%", + height="40vh", + ) +``` + +📊 **Dataset source:** [gapminder2007.csv](https://raw.githubusercontent.com/plotly/datasets/master/gapminder2007.csv) + +## Column Filtering + +Allow a user to filter a column by setting the `filter` key to `True` in the column definition. In this example we enable filtering for the first and last columns. + +```python demo exec +import reflex as rx +import reflex_enterprise as rxe +import pandas as pd + + +df = pd.read_csv("data/gapminder2007.csv") + +column_defs = [ + {"field": "country", "headerName": "Country", "filter": True}, + {"field": "pop", "headerName": "Population"}, + {"field": "lifeExp", "headerName": "Life Expectancy", "filter": True}, +] + +def ag_grid_simple_column_filtering(): + return rxe.ag_grid( + id="ag_grid_basic_column_filtering", + row_data=df.to_dict("records"), + column_defs=column_defs, + width="100%", + height="40vh", + ) +``` + +### Filter Types + +You can set `filter=True` to enable the default filter for a column. + +You can also set the filter type using the `filter` key. The following filter types are available: `ag_grid.filters.date`, `ag_grid.filters.number` and `ag_grid.filters.text`. These ensure that the input you enter to the filter is of the correct type. + +(`ag_grid.filters.set` and `ag_grid.filters.multi` are available with AG Grid Enterprise) + +```python demo exec +import reflex as rx +import reflex_enterprise as rxe +import pandas as pd + + +df = pd.read_csv("data/GanttChart-updated.csv") + +column_defs = [ + {"field": "Task", "filter": True}, + {"field": "Start", "filter": rxe.ag_grid.filters.date}, + {"field": "Duration", "filter": rxe.ag_grid.filters.number}, + {"field": "Resource", "filter": rxe.ag_grid.filters.text}, +] + +def ag_grid_simple_column_filtering(): + return rxe.ag_grid( + id="ag_grid_basic_column_filtering", + row_data=df.to_dict("records"), + column_defs=column_defs, + width="100%", + height="40vh", + ) +``` + +📊 **Dataset source:** [GanttChart-updated.csv](https://raw.githubusercontent.com/plotly/datasets/master/GanttChart-updated.csv) + +## Row Sorting + +By default, the rows can be sorted by any column by clicking on the column header. You can disable sorting of the rows for a column by setting the `sortable` key to `False` in the column definition. + +In this example, we disable sorting for the first column. + +```python demo exec +import reflex as rx +import reflex_enterprise as rxe +import pandas as pd + + +df = pd.read_csv("data/gapminder2007.csv") + +column_defs = [ + {"field": "country", "sortable": False}, + {"field": "pop", "headerName": "Population"}, + {"field": "lifeExp", "headerName": "Life Expectancy"}, +] + +def ag_grid_simple_row_sorting(): + return rxe.ag_grid( + id="ag_grid_basic_row_sorting", + row_data=df.to_dict("records"), + column_defs=column_defs, + width="100%", + height="40vh", + ) +``` + +📊 **Dataset source:** [gapminder2007.csv](https://raw.githubusercontent.com/plotly/datasets/master/gapminder2007.csv) + +## Row Selection + +Row Selection is enabled using the `row_selection` attribute. Setting it to `multiple` allows users to select multiple rows at a time. You can use the `checkbox_selection` column definition attribute to render checkboxes for selection. + +```python demo exec +import reflex as rx +import reflex_enterprise as rxe +import pandas as pd + + +df = pd.read_csv("data/gapminder2007.csv") + +column_defs = [ + {"field": "country", "checkboxSelection": True}, + {"field": "pop", "headerName": "Population"}, + {"field": "continent"}, +] + +def ag_grid_simple_row_selection(): + return rxe.ag_grid( + id="ag_grid_basic_row_selection", + row_data=df.to_dict("records"), + column_defs=column_defs, + row_selection={"mode":"multiple"}, + width="100%", + height="40vh", + ) +``` + +📊 **Dataset source:** [gapminder2007.csv](https://raw.githubusercontent.com/plotly/datasets/master/gapminder2007.csv) + +## Editing + +Enable Editing by setting the `editable` attribute to `True`. The cell editor is inferred from the cell data type. Set the cell editor type using the `cell_editor` attribute. + +There are 7 provided cell editors in AG Grid: + +1. `ag_grid.editors.text` +2. `ag_grid.editors.large_text` +3. `ag_grid.editors.select` +4. `ag_grid.editors.rich_select` +5. `ag_grid.editors.number` +6. `ag_grid.editors.date` +7. `ag_grid.editors.checkbox` + +In this example, we enable editing for the second and third columns. The second column uses the `number` cell editor, and the third column uses the `select` cell editor. + +The `on_cell_value_changed` event trigger is linked to the `cell_value_changed` event handler in the state. This event handler is called whenever a cell value is changed and changes the value of the backend var `_data_df` and the state var `data`. + +```python +import reflex as rx +import reflex_enterprise as rxe +import pandas as pd + +class AGGridEditingState(rx.State): + data: list[dict] = [] + _data_df: pd.DataFrame + + @rx.event + def load_data(self): + self._data_df = pd.read_csv("data/gapminder2007.csv") + self.data = self._data_df.to_dict("records") + + @rx.event + def cell_value_changed(self, row, col_field, new_value): + self._data_df.at[row, col_field] = new_value + self.data = self._data_df.to_dict("records") + yield rx.toast(f"Cell value changed, Row: {row}, Column: {col_field}, New Value: {new_value}") + + +column_defs = [ + \{"field": "country"\}, + \{"field": "pop", "headerName": "Population", "editable": True, "cellEditor": rxe.ag_grid.editors.number\}, + \{"field": "continent", "editable": True, "cellEditor": rxe.ag_grid.editors.select, "cellEditorParams": \{"values": ['Asia', 'Europe', 'Africa', 'Americas', 'Oceania']\}\}, +] + +def ag_grid_simple_editing(): + return rxe.ag_grid( + id="ag_grid_basic_editing", + row_data=AGGridEditingState.data, + column_defs=column_defs, + on_cell_value_changed=AGGridEditingState.cell_value_changed, + on_mount=AGGridEditingState.load_data, + width="100%", + height="40vh", + ) +``` + +## Pagination + +By default, the grid uses a vertical scroll. You can reduce the amount of scrolling required by adding pagination. To add pagination, set `pagination=True`. You can set the `pagination_page_size` to the number of rows per page and `pagination_page_size_selector` to a list of options for the user to select from. + +```python demo exec +import reflex as rx +import reflex_enterprise as rxe +import pandas as pd + +df = pd.read_csv("data/gapminder2007.csv") + +column_defs = [ + {"field": "country"}, + {"field": "pop", "headerName": "Population"}, + {"field": "lifeExp", "headerName": "Life Expectancy"}, +] + +def ag_grid_simple_pagination(): + return rxe.ag_grid( + id="ag_grid_basic_pagination", + row_data=df.to_dict("records"), + column_defs=column_defs, + pagination=True, + pagination_page_size=10, + pagination_page_size_selector=[10, 40, 100], + width="100%", + height="40vh", + ) +``` + +📊 **Dataset source:** [gapminder2007.csv](https://raw.githubusercontent.com/plotly/datasets/master/gapminder2007.csv) + +## AG Grid with State + +### Putting Data in State + +Assuming you want to make any edit to your data, you can put the data in State. This allows you to update the grid based on user input. Whenever the `data` var is updated, the grid will be re-rendered with the new data. + +```python +from typing import Any +import reflex as rx +import reflex_enterprise as rxe +import pandas as pd + +class AGGridState2(rx.State): + data: list[dict] = [] + + @rx.event + def load_data(self): + _df = pd.read_csv("data/gapminder2007.csv") + self.data = _df.to_dict("records") + +column_defs = [ + \{"field": "country"\}, + \{"field": "pop", "headerName": "Population"\}, + \{"field": "continent"\}, +] + +def ag_grid_state_2(): + return rxe.ag_grid( + id="ag_grid_state_2", + row_data=AGGridState2.data, + column_defs=column_defs, + on_mount=AGGridState2.load_data, + width="100%", + height="40vh", + ) +``` + +### Updating the Grid with State + +You can use State to update the grid based on a users input. In this example, we update the `column_defs` of the grid when a user clicks a button. + +```python +import reflex as rx +import reflex_enterprise as rxe +import pandas as pd + +class AgGridState(rx.State): + """The app state.""" + all_columns: list = [] + + two_columns: list = [] + column_defs: list = all_columns + n_clicks = 0 + + @rx.event + def init_columns(self): + self.all_columns = [ + \{"field": "country"\}, + \{"field": "pop"\}, + \{"field": "continent"\}, + \{"field": "lifeExp"\}, + \{"field": "gdpPercap"\}, + ] + self.two_columns = [ + \{"field": "country"\}, + \{"field": "pop"\}, + ] + self.column_defs = self.all_columns + + @rx.event + def update_columns(self): + self.n_clicks += 1 + if self.n_clicks % 2 != 0: + self.column_defs = self.two_columns + else: + self.column_defs = self.all_columns + + +df = pd.read_csv("data/gapminder2007.csv") + + +def ag_grid_simple_with_state(): + return rx.box( + rx.button("Toggle Columns", on_click=AgGridState.update_columns), + rxe.ag_grid( + id="ag_grid_basic_with_state", + row_data=df.to_dict("records"), + column_defs=AgGridState.column_defs, + on_mount=AgGridState.init_columns, + width="100%", + height="40vh", + ), + width="100%", + ) +``` + +📊 **Dataset source:** [gapminder2007.csv](https://raw.githubusercontent.com/plotly/datasets/master/gapminder2007.csv) + +## AG Grid with Data from a Database + +In this example, we will use a database to store the data. The data is loaded from a csv file and inserted into the database when the page is loaded using the `insert_dataframe_to_db` event handler. + +The data is then fetched from the database and displayed in the grid using the `data` computed var. + +When a cell value is changed, the data is updated in the database using the `cell_value_changed` event handler. + +```python +import reflex as rx +import reflex_enterprise as rxe +import pandas as pd +from sqlmodel import select + +class Country(rx.Model, table=True): + country: str + population: int + continent: str + + +class AGGridDatabaseState(rx.State): + + countries: list[Country] + + # Insert data from a csv loaded dataframe to the database (Do this on the page load) + @rx.event + def insert_dataframe_to_db(self): + data = pd.read_csv("data/gapminder2007.csv") + with rx.session() as session: + for _, row in data.iterrows(): + db_record = Country( + country=row['country'], + population=row['pop'], + continent=row['continent'], + ) + session.add(db_record) + session.commit() + + # Fetch data from the database using a computed variable + @rx.var + def data(self) -> list[dict]: + with rx.session() as session: + results = session.exec(select(Country)).all() + self.countries = [result.dict() for result in results] + return self.countries + + # Update the database when a cell value is changed + @rx.event + def cell_value_changed(self, row, col_field, new_value): + self.countries[row][col_field] = new_value + with rx.session() as session: + country = Country(**self.countries[row]) + session.merge(country) + session.commit() + yield rx.toast(f"Cell value changed, Row: \{row}, Column: \{col_field}, New Value: \{new_value}") + + +column_defs = [ + \{"field": "country"\}, + \{"field": "population", "headerName": "Population", "editable": True, "cellEditor": rxe.ag_grid.editors.number\}, + \{"field": "continent", "editable": True, "cellEditor": rxe.ag_grid.editors.select, "cellEditorParams": \{"values": ['Asia', 'Europe', 'Africa', 'Americas', 'Oceania']\}\}, +] + +def index(): + return rxe.ag_grid( + id="ag_grid_basic_editing", + row_data=AGGridDatabaseState.data, + column_defs=column_defs, + on_cell_value_changed=AGGridDatabaseState.cell_value_changed, + width="100%", + height="40vh", + ) + +# Add state and page to the app. +app = rx.App() +app.add_page(index, on_load=AGGridDatabaseState.insert_dataframe_to_db) +``` + +## Using AG Grid Enterprise + +AG Grid offers both community and enterprise versions. See the [AG Grid docs](https://www.ag-grid.com/archive/31.2.0/react-data-grid/licensing/) for details on purchasing a license key. + +To use an AG Grid Enterprise license key with Reflex AG Grid set the environment variable `AG_GRID_LICENSE_KEY`: + +```bash +export AG_GRID_LICENSE_KEY="your_license_key" +``` + +## column_def props + +The following props are available for `column_defs` as well as many others that can be found here: [AG Grid Column Def Docs](https://www.ag-grid.com/react-data-grid/column-properties/). (it is necessary to use snake_case for the keys in Reflex, unlike in the AG Grid docs where camelCase is used) + +- `field`: `str`: The field of the row object to get the cell's data from. +- `col_id`: `str | None`: The unique ID to give the column. This is optional. If missing, the ID will default to the field. +- `type`: `str | None`: The type of the column. +- `cell_data_type`: `bool | str | None`: The data type of the cell values for this column. Can either infer the data type from the row data (true - the default behaviour), define a specific data type (string), or have no data type (false). +- `hide`: `bool`: Set to true for this column to be hidden. +- `editable`: `bool | None`: Set to true if this column is editable, otherwise false. +- `filter`: `AGFilters | str | None`: Filter component to use for this column. Set to true to use the default filter. Set to the name of a provided filter to use that filter. (Check out the Filter Types section of this page for more information) +- `floating_filter`: `bool`: Whether to display a floating filter for this column. +- `header_name`: `str | None`: The name to render in the column header. If not specified and field is specified, the field name will be used as the header name. +- `header_tooltip`: `str | None`: Tooltip for the column header. +- `checkbox_selection`: `bool | None`: Set to true to render a checkbox for row selection. +- `cell_editor`: `AGEditors | str | None`: Provide your own cell editor component for this column's cells. (Check out the Editing section of this page for more information) +- `cell_editor_params`: `dict[str, list[Any]] | None`: Params to be passed to the cellEditor component. + + + +## Functionality you need is not available/working in Reflex + +All AGGrid options found in this [documentation](https://www.ag-grid.com/react-data-grid/reference/) are mapped in rxe.ag_grid, but some features might not have been fully tested, due to the sheer number of existing features in the underlying AG Grid library. + +If one of the ag_grid props does not import the expected module, you can pass it manually via the props `community_modules` or `enterprise_modules`, which expect a `set[str]` of the module names. You will get a warning in the browser console if a module is missing, so you can check there if a feature is not working as expected. + +You can also report the missing module on our discord or GitHub issues page of the main Reflex repository. + +Best practice is to create a single instance of `ag_grid.api()` with the same `id` as the `id` of the `ag_grid` component that is to be referenced, `"ag_grid_basic_row_selection"` in this first example. + +The example below uses the `select_all()` and `deselect_all()` methods of the AG Grid API to select and deselect all rows in the grid. This method is not available in Reflex directly. Check out this [documentation](https://www.ag-grid.com/react-data-grid/grid-api/#reference-selection-selectAll) to see what the methods look like in the AG Grid docs. + +```md alert info +# Ensure that the docs are set to React tab in AG Grid +``` + +```python demo exec +import reflex as rx +import reflex_enterprise as rxe +import pandas as pd + +df = pd.read_csv( + "data/gapminder2007.csv" +) + +column_defs = [ + {"field": "country", "checkboxSelection": True}, + {"field": "pop"}, + {"field": "continent"}, +] + +def ag_grid_api_simple(): + my_api = rxe.ag_grid.api(id="ag_grid_basic_row_selection") + return rx.vstack( + rxe.ag_grid( + id="ag_grid_basic_row_selection", + row_data=df.to_dict("records"), + column_defs=column_defs, + row_selection="single", + width="100%", + height="40vh", + ), + rx.button("Select All", on_click=my_api.select_all()), + rx.button("Deselect All", on_click=my_api.deselect_all()), + spacing="4", + width="100%", + ) +``` + +📊 **Dataset source:** [gapminder2007.csv](https://raw.githubusercontent.com/plotly/datasets/master/gapminder2007.csv) + +The react code for the `select_all()` event handler is `selectAll = (source?: SelectionEventSourceType) => void;`. + +To use this in Reflex as you can see, it should be called in snake case rather than camel case. The `void` means it doesn't return anything. The `source?` indicates that it takes an optional `source` argument. + + +```md alert info +# Another way to use the AG Grid API +It is also possible to use the AG Grid API directly with the event trigger (`on_click`) of the component. This removes the need to create a variable `my_api`. This is shown in the example below. It is necessary to use the `id` of the `ag_grid` component that is to be referenced. + +```python +rx.button("Select all", on_click=rxe.ag_grid.api(id="ag_grid_basic_row_selection").select_all()), +``` + +### More examples + +The following example lets a user [export the data as a csv](https://www.ag-grid.com/javascript-data-grid/grid-api/#reference-export-exportDataAsCsv) and [adjust the size of columns to fit the available horizontal space](https://www.ag-grid.com/javascript-data-grid/grid-api/#reference-columnSizing-sizeColumnsToFit). (Try resizing the screen and then clicking the resize columns button) + + +```python demo exec +import reflex as rx +import reflex_enterprise as rxe +import pandas as pd + +df = pd.read_csv( + "data/gapminder2007.csv" +) + +column_defs = [ + {"field": "country", "checkboxSelection": True}, + {"field": "pop"}, + {"field": "continent"}, +] + +def ag_grid_api_simple2(): + my_api = rxe.ag_grid.api(id="ag_grid_export_and_resize") + return rx.vstack( + rxe.ag_grid( + id="ag_grid_export_and_resize", + row_data=df.to_dict("records"), + column_defs=column_defs, + width="100%", + height="40vh", + ), + rx.button("Export", on_click=my_api.export_data_as_csv()), + rx.button("Resize Columns", on_click=my_api.size_columns_to_fit()), + spacing="4", + width="100%", + ) +``` + +📊 **Dataset source:** [gapminder2007.csv](https://raw.githubusercontent.com/plotly/datasets/master/gapminder2007.csv) + +The react code for both of these is shown below. The key point to see is that both of these functions return `void` and therefore does not return anything. + +`exportDataAsCsv = (params?: CsvExportParams) => void;` + +`sizeColumnsToFit = (paramsOrGridWidth?: ISizeColumnsToFitParams | number) => void;` + + +### Example with a Return Value + +This example shows how to get the data from `ag_grid` as a [csv on the backend](https://www.ag-grid.com/javascript-data-grid/grid-api/#reference-export-getDataAsCsv). The data that was passed to the backend is then displayed as a toast with the data. + +```python +import reflex as rx +import reflex_enterprise as rxe +import pandas as pd + +class AGGridStateAPI(rx.State): + def handle_get_data(self, data: str): + yield rx.toast(f"Got CSV data: {data}") + +df = pd.read_csv( + "data/gapminder2007.csv" +) + +column_defs = [ + \{"field": "country", "checkboxSelection": True\}, + \{"field": "pop"\}, + \{"field": "continent"\}, +] + +def ag_grid_api_argument(): + my_api = rxe.ag_grid.api(id="ag_grid_get_data_as_csv") + return rx.vstack( + rxe.ag_grid( + id="ag_grid_get_data_as_csv", + row_data=df.to_dict("records"), + column_defs=column_defs, + width="100%", + height="40vh", + ), + rx.button("Get CSV data on backend", on_click=my_api.get_data_as_csv(callback=AGGridStateAPI.handle_get_data)), + spacing="4", + width="100%", + ) +``` + +The react code for the `get_data_as_csv` method of the AG Grid API is `getDataAsCsv = (params?: CsvExportParams) => string | undefined;`. Here the function returns a `string` (or undefined). + +In Reflex to handle this returned value it is necessary to pass a `callback` as an argument to the `get_data_as_csv` method that will get the returned value. In this example the `handle_get_data` event handler is passed as the callback. This event handler will be called with the returned value from the `get_data_as_csv` method. diff --git a/docs/enterprise/ag_grid/model-wrapper.md b/docs/enterprise/ag_grid/model-wrapper.md new file mode 100644 index 00000000000..4615ee87296 --- /dev/null +++ b/docs/enterprise/ag_grid/model-wrapper.md @@ -0,0 +1,64 @@ +--- +order: 6 +--- + +# Model Wrapper + +A model wrapper is an utility used to wrap a database model and provide a consistent interface over it. It allows automatically adding new rows to the database, updating existing rows, and deleting rows. + +## Default Model Wrapper + +You can use the basic functionality of the model wrapper by using the `rxe.model_wrapper` function. This function takes a database model and returns a wrapper object that can be used to interact with the model. + +```python +import reflex_enterprise as rxe + +def index_page(): + return rxe.model_wrapper(class_model=MyModel) +``` + +By default the model_wrapper use the infinite rows model from AgGrid. + +## Custom Model Wrapper + +If the default model wrapper does not fit your needs, you can create a custom model wrapper by subclassing the `rxe.ModelWrapper` class. This allows you to customize the behavior of the model wrapper to fit your specific use case. + +```python +import reflex_enterprise as rxe + +class MyCustomWrapper(rxe.ModelWrapper[MyModel]): + pass +``` + +In the custom model wrapper, you can override the following methods: +- `_get_columns_defs` +- `_get_data` +- `_row_count` +- `on_value_setter` + +to modify how the model wrapper will behave. + + +## SSRM Model Wrapper + +The SSRM model wrapper, used with `rxe.model_wrapper_ssrm`, is a version of the model wrapper that allows you to use the ServerSideRowModel of AgGrid. + +```python +import reflex_enterprise as rxe + +def index_page(): + return rxe.model_wrapper_ssrm(class_model=MyModel) +``` + +## SSRM Custom Model Wrapper + +In the same way you can extend the default model wrapper, you can extend the SSRM custom model wrapper by subclassing the `rxe.ModelWrapperSSRM` class. This allows you to customize the behavior of the model wrapper to fit your specific use case. + +```python +import reflex_enterprise as rxe + +class MyCustomSSRMWrapper(rxe.ModelWrapperSSRM[MyModel]): + pass +``` + +The overridable methods are the same as the standard model wrapper. \ No newline at end of file diff --git a/docs/enterprise/ag_grid/pivot-mode.md b/docs/enterprise/ag_grid/pivot-mode.md new file mode 100644 index 00000000000..f2f9b863526 --- /dev/null +++ b/docs/enterprise/ag_grid/pivot-mode.md @@ -0,0 +1,130 @@ +```python exec +import reflex as rx +import reflex_enterprise as rxe +from pcweb.pages.docs import enterprise +``` + +# Pivot Mode + +Pivot mode allows you to visualize your data in a different way than how they are originally structured in the data source. When pivoting on a column, the values in that column will be used as column headers. This allows you to see the data in a more compact way, and can be useful when you have a lot of data to display. + +To enable pivot mode, set the `pivot_mode` property to `True` in the grid props. Once pivot mode is enabled, you can define which column to pivot on by setting the `pivot` property in a column definition. In addition to the pivot column, at least one column definition must have `row_group` property set to `True` to define the row grouping. + +You can also define how rows are aggregated by passing the `agg_func` property in the column definition. The `agg_func` property should be set to a string that represents the aggregation function to use. The built-in aggregation functions are `sum`, `min`, `max`, `count`, `avg`, `first`, and `last`. + +You can find a live example here: [Pivot Mode Example](https://aggrid.reflex.run/pivot). + +```python demo exec +import pandas as pd +import reflex as rx + +import reflex_enterprise as rxe + +# Olympic winners data (originally from https://www.ag-grid.com/example-assets/olympic-winners.json) +df = pd.read_json("data/olympic-winners.json") + +def pivot_page(): + return rxe.ag_grid( + id="sandbox_grid", + column_defs=[ + {"field": "country", "row_group": True}, + {"field": "sport", "pivot": True}, + {"field": "year", "pivot": True}, + {"field": "gold", "aggFunc": "sum"}, + ], + loading=False, + row_data=df.to_dict("records"), + default_col_def={ + "flex": 1, + "min_width": 130, + "enable_value": True, + "enable_row_group": True, + "enable_pivot": True, + }, + auto_group_column_def={ + "minWidth": 200, + "pinned": "left", + }, + pivot_mode=True, + side_bar="columns", + pivot_panel_show="always", + width="100%", + height="500px", + ), +``` + +# Pivot using State + +```python demo exec +import pandas as pd +import reflex as rx + +import reflex_enterprise as rxe + +df = pd.read_csv("data/wind_dataset.csv") + + +class PivotState(rx.State): + """State for the sandbox page.""" + + pivot = False + row_grouping = False + + @rx.event + def toggle_pivot(self): + """Toggle the pivot.""" + self.pivot = not self.pivot + + @rx.event + def toggle_row_grouping(self): + """Toggle the row grouping.""" + self.row_grouping = not self.row_grouping + + +def sandbox_page(): + """Sandbox page.""" + return rx.vstack( + rx.hstack( + rx.text("Toggle Pivot"), + rx.switch( + on_click=PivotState.toggle_pivot, + name="pivot", + checked=PivotState.pivot, + ), + rx.text("Toggle Row Grouping"), + rx.switch( + on_click=PivotState.toggle_row_grouping, + name="row_grouping", + checked=PivotState.row_grouping, + ) + ), + rxe.ag_grid( + id="sandbox_grid", + column_defs=[ + rxe.ag_grid.column_def( + field="direction", + pivot=True, + ), + rxe.ag_grid.column_def( + field="strength", + ), + rxe.ag_grid.column_def( + field="frequency", + agg_func="count", + row_group=PivotState.row_grouping, + ), + ], + row_data=df.to_dict("records"), + pivot_mode=PivotState.pivot, + pivot_panel_show="onlyWhenPivoting", + width="100%", + height="500px", + ), + width="100%", + ) + +``` + +📊 **Dataset source:** [wind_dataset.csv](https://raw.githubusercontent.com/plotly/datasets/master/wind_dataset.csv) + + diff --git a/docs/enterprise/ag_grid/theme.md b/docs/enterprise/ag_grid/theme.md new file mode 100644 index 00000000000..f88fa392453 --- /dev/null +++ b/docs/enterprise/ag_grid/theme.md @@ -0,0 +1,63 @@ +--- +order: 3 +--- + +```python exec +import reflex as rx +import reflex_enterprise as rxe +from pcweb.pages.docs import enterprise +``` + +# Themes + +```md alert warning +# Only the old theme API of AG Grid is currently supported. The new theme API is not supported yet. +``` + +You can style your grid with a theme. AG Grid includes the following themes: + +1. `quartz` +2. `alpine` +3. `balham` +4. `material` + +The grid uses `quartz` by default. To use any other theme, set it using the `theme` prop, i.e. `theme="alpine"`. + +```python +import reflex as rx +import reflex_enterprise as rxe +import pandas as pd + +class AGGridThemeState(rx.State): + """The app state.""" + + theme: str = "quartz" + themes: list[str] = ["quartz", "balham", "alpine", "material"] + +df = pd.read_csv("data/gapminder2007.csv") + +column_defs = [ + {"field": "country"}, + {"field": "pop", "headerName": "Population"}, + {"field": "lifeExp", "headerName": "Life Expectancy"}, +] + +def ag_grid_simple_themes(): + return rx.vstack( + rx.hstack( + rx.text("Theme:"), + rx.select(AGGridThemeState.themes, value=AGGridThemeState.theme, on_change=AGGridThemeState.set_theme), + ), + rxe.ag_grid( + id="ag_grid_basic_themes", + row_data=df.to_dict("records"), + column_defs=column_defs, + theme=AGGridThemeState.theme, + width="100%", + height="40vh", + ), + width="100%", + ) +``` + +📊 **Dataset source:** [gapminder2007.csv](https://raw.githubusercontent.com/plotly/datasets/master/gapminder2007.csv) \ No newline at end of file diff --git a/docs/enterprise/ag_grid/value-transformers.md b/docs/enterprise/ag_grid/value-transformers.md new file mode 100644 index 00000000000..c9de70ebbc2 --- /dev/null +++ b/docs/enterprise/ag_grid/value-transformers.md @@ -0,0 +1,76 @@ +--- +order: 2 +--- + +```python exec +import reflex as rx +import reflex_enterprise as rxe +from pcweb.pages.docs import enterprise +``` + +# Value Transformers + +AgGrid allow you to apply transformers based on the column of your grid. This allow you to perform operations on the data before displaying it on the grid, without having to pre-process the data on the backend, reducing the load on your application. + +TOC: +- [Value Getter](#value-getter) +- [Value Formatter](#value-formatter) + +## Value Getter + +`value_getter` is a property of the column definition that allows you to define a function that will be called to get the value of the cell. This function will receive the row data as a parameter and should return the value to be displayed on the cell. + +If you have two columns `col_a` and `col_b` and you want to display the sum of these two columns in a third column `sum`, you can define the `value_getter` of `sum` as follows: + +```python demo exec +import reflex as rx +import reflex_enterprise as rxe +import pandas as pd + + +df = pd.DataFrame({"col_a": [1, 2, 3, 4, 5], "col_b": [10, 20, 30, 40, 50]}) + +column_defs = [ + {"field": "col_a", "header_name": "Column A"}, + {"field": "col_b", "header_name": "Column B"}, + {"field": "sum", "header_name": "Sum", "value_getter": "params.data.col_a + params.data.col_b"}, + rxe.ag_grid.column_def(field="diff", header_name="Difference", value_getter="params.data.col_b - params.data.col_a"), +] + +def ag_grid_value_getter(): + return rxe.ag_grid( + id="ag_grid_value_getter", + row_data=df.to_dict("records"), + column_defs=column_defs, + width="100%", + ) +``` + +## Value Formatter + +`value_formatter` is a property of the column definition that allows you to define a function that will be called to format the value of the cell. This function will receive the value of the cell as a parameter and should return the formatted value to be displayed on the cell. + +If you have a column `price` and you want to display the price with a currency symbol, you can define the `value_formatter` of `price` as follows: + +```python demo exec +import reflex as rx +import reflex_enterprise as rxe +import pandas as pd + +df = pd.DataFrame({"product_name":["Product A", "Product B", "Product C", "Product D", "Product E"], "price": [100, 200, 300, 400, 500]}) +column_defs = [ + {"field": "product_name", "header_name": "Product Name"}, + {"field": "price", "header_name": "Price ($)", "value_formatter": "'$' + params.value"}, + rxe.ag_grid.column_def(col_id="price_eur", header_name="Price (€)", value_formatter="params.data.price + ' €'"), +] + +def ag_grid_value_formatter(): + return rxe.ag_grid( + id="ag_grid_value_formatter", + row_data=df.to_dict("records"), + column_defs=column_defs, + width="100%", + ) +``` + + diff --git a/docs/enterprise/built-with-reflex.md b/docs/enterprise/built-with-reflex.md new file mode 100644 index 00000000000..8fb0f60b950 --- /dev/null +++ b/docs/enterprise/built-with-reflex.md @@ -0,0 +1,17 @@ +# Built with Reflex Badge + +The "Built with Reflex" badge appears in the bottom right corner of apps using reflex-enterprise components. + +## Removing the Badge + +To remove the badge, you need to be on the Enterprise tier. + +## Configuration + +```python +import reflex_enterprise as rxe + +config = rxe.Config( + show_built_with_reflex=False, # Requires paid tier +) +``` \ No newline at end of file diff --git a/docs/enterprise/components.md b/docs/enterprise/components.md new file mode 100644 index 00000000000..3e82efae420 --- /dev/null +++ b/docs/enterprise/components.md @@ -0,0 +1,119 @@ +--- +title: Enterprise Components +--- + +```python exec +import reflex as rx +from pcweb.pages.docs import enterprise +from pcweb.templates.docpage import h1_comp, text_comp_2 +from pcweb.components.icons import get_icon + +def enterprise_component_grid(): + sections = [ + { + "title": "AG Grid", + "description": "Advanced data grid with sorting, filtering, editing, and pagination", + "link": enterprise.ag_grid.index.path, + "components": [ + ("Overview", enterprise.ag_grid.index.path), + ("Column Definitions", enterprise.ag_grid.column_defs.path), + ("Aligned Grids", enterprise.ag_grid.aligned_grids.path), + ("Model Wrapper", enterprise.ag_grid.model_wrapper.path), + ("Pivot Mode", enterprise.ag_grid.pivot_mode.path), + ("Theme", enterprise.ag_grid.theme.path), + ("Value Transformers", enterprise.ag_grid.value_transformers.path), + ] + }, + { + "title": "AG Chart", + "description": "Interactive charts and data visualization", + "link": enterprise.ag_chart.path, + "components": [ + ("Overview", enterprise.ag_chart.path), + ] + }, + { + "title": "Interactive Components", + "description": "Drag-and-drop and mapping functionality", + "link": enterprise.drag_and_drop.path, + "components": [ + ("Drag and Drop", enterprise.drag_and_drop.path), + ("Mapping", enterprise.map.index.path), + ] + }, + { + "title": "Mantine", + "description": "Rich UI components from Mantine library", + "link": enterprise.mantine.index.path, + "components": [ + ("Overview", enterprise.mantine.index.path), + ("Autocomplete", enterprise.mantine.autocomplete.path), + ("Collapse", enterprise.mantine.collapse.path), + ("Combobox", enterprise.mantine.combobox.path), + ("JSON Input", enterprise.mantine.json_input.path), + ("Loading Overlay", enterprise.mantine.loading_overlay.path), + ("Multi Select", enterprise.mantine.multi_select.path), + ("Number Formatter", enterprise.mantine.number_formatter.path), + ("Pill", enterprise.mantine.pill.path), + ("Ring Progress", enterprise.mantine.ring_progress.path), + ("Semi Circle Progress", enterprise.mantine.semi_circle_progress.path), + ("Spoiler", enterprise.mantine.spoiler.path), + ("Tags Input", enterprise.mantine.tags_input.path), + ("Timeline", enterprise.mantine.timeline.path), + ("Tree", enterprise.mantine.tree.path), + ] + } + ] + + cards = [] + for section in sections: + cards.append( + rx.box( + rx.link( + rx.el.h1( + section["title"], + class_name="font-large text-slate-12", + ), + get_icon("new_tab", class_name="text-slate-11 [&>svg]:size-4"), + href=section["link"], + underline="none", + class_name="px-4 py-2 bg-slate-1 hover:bg-slate-3 transition-bg flex flex-row justify-between items-center !text-slate-12", + ), + rx.text( + section["description"], + class_name="px-4 py-2 font-small text-slate-9 border-t border-slate-5", + ), + rx.box( + *[ + rx.link( + comp[0], + href=comp[1], + class_name="font-small text-slate-11 hover:!text-violet-9 transition-color w-fit", + ) + for comp in section["components"] + ], + class_name="flex flex-col gap-2.5 px-4 py-2 border-t border-slate-5", + ), + class_name="flex flex-col border border-slate-5 rounded-xl bg-slate-2 shadow-large overflow-hidden", + ) + ) + + return rx.box( + *cards, + class_name="grid grid-cols-1 lg:grid-cols-2 gap-6", + ) + +component_grid = enterprise_component_grid() +``` + +```python eval +h1_comp(text="Enterprise Components") +``` + +```python eval +text_comp_2(text="Advanced UI components and features to enhance your Reflex applications. Available for free with the 'Built with Reflex' badge, or without the badge with an enterprise license.") +``` + +```python eval +component_grid +``` \ No newline at end of file diff --git a/docs/enterprise/drag-and-drop.md b/docs/enterprise/drag-and-drop.md new file mode 100644 index 00000000000..2bf8913d38b --- /dev/null +++ b/docs/enterprise/drag-and-drop.md @@ -0,0 +1,582 @@ +--- +title: Drag and Drop +--- + +```python exec +import reflex as rx +import reflex_enterprise as rxe +from pcweb.pages.docs import enterprise +``` + +# Drag and Drop + +Reflex Enterprise provides comprehensive drag and drop functionality for creating interactive UI elements using the `rxe.dnd` module. Built on top of react-dnd, it offers both high-level components for common use cases and low-level hooks for advanced scenarios. + +```md alert warning +# Important: Always decorate functions defining `rxe.dnd.draggable` components with `@rx.memo` to avoid compilation errors. +``` + +## Basic Usage + +### Simple Drag and Drop + +Here's a basic example showing how to create a draggable item and drop target: + +```python demo exec +import reflex as rx +import reflex_enterprise as rxe + +class BasicDndState(rx.State): + drop_count: int = 0 + + def increment_drop_count(self): + self.drop_count += 1 + + +@rx.memo +def draggable_card(): + return rxe.dnd.draggable( + rx.card( + rx.text("Drag me!", weight="bold"), + rx.text("I can be moved around"), + bg="blue.500", + color="white", + p=4, + cursor="grab", + width="200px", + height="100px" + ), + type="BasicCard", + item={"message": "Hello from draggable!"}, + ) + +def basic_drag_drop(): + return rx.vstack( + rx.text(f"Items dropped: {BasicDndState.drop_count}"), + rx.hstack( + draggable_card(), + rxe.dnd.drop_target( + rx.box( + "Drop Zone", + bg="gray.100", + border="2px dashed gray", + min_height="150px", + min_width="200px", + display="flex", + align_items="center", + justify_content="center", + font_weight="bold" + ), + accept=["BasicCard"], + on_drop=BasicDndState.increment_drop_count, + ), + spacing="4", + align="start" + ), + spacing="4" + ) +``` + +### Multi-Position Drag and Drop + +Create a draggable item that can be moved between multiple drop targets: + +```python demo exec +import reflex as rx +import reflex_enterprise as rxe + +class MultiPositionState(rx.State): + card_position: int = 0 + + def set_card_position(self, position: int): + self.card_position = position + + +@rx.memo +def movable_card(): + return rxe.dnd.draggable( + rx.card( + rx.text("Movable Card", weight="bold"), + rx.text("Position: " + MultiPositionState.card_position.to_string()), + bg="purple.500", + color="white", + p=4, + width="180px", + height="120px" + ), + type="MovableCard", + border="2px solid purple", + ) + +def drop_zone(position: int): + params = rxe.dnd.DropTarget.collected_params + return rxe.dnd.drop_target( + rx.cond( + MultiPositionState.card_position == position, + movable_card(), + rx.box( + f"Drop Zone {position}", + color="gray.600", + font_weight="bold" + ) + ), + width="200px", + height="200px", + border="2px solid red", + border_color=rx.cond(params.is_over, "green.500", "red.500"), + bg=rx.cond(params.is_over, "green.100", "blue.100"), + accept=["MovableCard"], + on_drop=lambda _: MultiPositionState.set_card_position(position), + display="flex", + align_items="center", + justify_content="center" + ) + +def multi_position_example(): + return rx.vstack( + rx.text("Drag the card between positions", weight="bold"), + rx.grid( + drop_zone(0), + drop_zone(1), + drop_zone(2), + drop_zone(3), + columns="2", + spacing="4" + ), + spacing="4" + ) +``` + +## Advanced Features + +### State Tracking with Collected Parameters + +Access drag and drop state information using collected parameters: + +```python demo exec +import reflex as rx +import reflex_enterprise as rxe + +class StateTrackingState(rx.State): + drag_info: str = "No drag activity" + + def set_drag_info(self, value: str): + self.drag_info = value + +@rx.memo +def tracked_draggable(): + drag_params = rxe.dnd.Draggable.collected_params + return rxe.dnd.draggable( + rx.card( + rx.text("Tracked Draggable"), + rx.text(rx.cond(drag_params.is_dragging, "Dragging...", "Ready to drag")), + bg=rx.cond(drag_params.is_dragging, "orange.500", "blue.500"), + color="white", + p=4, + opacity=rx.cond(drag_params.is_dragging, 0.5, 1.0) + ), + type="TrackedItem", + on_end=StateTrackingState.set_drag_info("Drag ended") + ) + +def tracked_drop_target(): + drop_params = rxe.dnd.DropTarget.collected_params + return rxe.dnd.drop_target( + rx.box( + rx.text("Smart Drop Zone"), + rx.text(rx.cond(drop_params.is_over, "Ready to receive!", "Waiting...")), + bg=rx.cond(drop_params.is_over, "green.200", "gray.100"), + border=rx.cond(drop_params.is_over, "2px solid green", "2px dashed gray"), + p=4, + min_height="150px", + display="flex", + flex_direction="column", + align_items="center", + justify_content="center" + ), + accept=["TrackedItem"], + on_drop=StateTrackingState.set_drag_info("Item successfully dropped!"), + on_hover=StateTrackingState.set_drag_info("Item hovering over drop zone") + ) + +def state_tracking_example(): + return rx.vstack( + rx.text(f"Status: {StateTrackingState.drag_info}"), + rx.hstack( + tracked_draggable(), + tracked_drop_target(), + spacing="4" + ), + spacing="4" + ) +``` + +### Dynamic Lists with Drag and Drop + +Create dynamic draggable lists using `rx.foreach`: + +```python demo exec +import reflex as rx +import reflex_enterprise as rxe + +class ListItem(rx.Base): + id: str + text: str + list_id: str + +class DynamicListState(rx.State): + list_a: list[ListItem] = [ + ListItem(id="1", text="Item 1", list_id="A"), + ListItem(id="2", text="Item 2", list_id="A"), + ListItem(id="3", text="Item 3", list_id="A"), + ] + list_b: list[ListItem] = [ + ListItem(id="4", text="Item 4", list_id="B"), + ListItem(id="5", text="Item 5", list_id="B"), + ] + + def move_item(self, item_data: dict, target_list: str): + item_id = item_data.get("id") + source_list = item_data.get("list_id") + + if not item_id or not source_list: + return + + # Find the item in the source list + source_items = getattr(self, f"list_{source_list.lower()}") + item_to_move = None + for item in source_items: + if item.id == item_id: + item_to_move = item + break + + if not item_to_move: + return + + # Remove from source list only + if source_list == "A": + self.list_a = [item for item in self.list_a if item.id != item_id] + else: + self.list_b = [item for item in self.list_b if item.id != item_id] + + # Create new item for target list + new_item = ListItem( + id=item_id, + text=item_to_move.text, + list_id=target_list + ) + + # Add to target list + if target_list == "A": + self.list_a.append(new_item) + else: + self.list_b.append(new_item) + +@rx.memo +def draggable_list_item(item: ListItem): + return rxe.dnd.draggable( + rx.card( + rx.text(item.text, weight="bold"), + rx.text(f"From List {item.list_id}", size="2", color="gray.600"), + p=3, + cursor="grab", + _hover={"bg": "gray.50"} + ), + type="ListItem", + item={"id": item.id, "text": item.text, "list_id": item.list_id}, + ) + +def droppable_list(title: str, items: list[ListItem], list_id: str): + return rxe.dnd.drop_target( + rx.vstack( + rx.text(title, weight="bold", size="5"), + rx.vstack( + rx.foreach(items, lambda item, index: draggable_list_item(item=item)), + spacing="2", + min_height="200px", + width="100%" + ), + bg="gray.50", + p=4, + border_radius="md", + border="2px dashed gray", + width="250px" + ), + accept=["ListItem"], + on_drop=lambda item: DynamicListState.move_item(item, list_id) + ) + +def dynamic_list_example(): + return rx.hstack( + droppable_list("List A", DynamicListState.list_a, "A"), + droppable_list("List B", DynamicListState.list_b, "B"), + spacing="6", + align="start" + ) +``` + +## Core Components + +### Draggable + +The `rxe.dnd.draggable` component makes any element draggable: + +**Key Properties:** +- `type`: String identifier for drag type matching +- `item`: Data object passed to drop handlers +- `on_end`: Called when drag operation ends + +### Drop Target + +The `rxe.dnd.drop_target` component creates areas that accept draggable items: + +**Key Properties:** +- `accept`: List of drag types this target accepts +- `on_drop`: Called when item is dropped +- `on_hover`: Called when item hovers over target + +### Collected Parameters + +Access real-time drag/drop state: + +**Draggable Parameters (`rxe.dnd.Draggable.collected_params`):** +- `is_dragging`: Boolean indicating if item is being dragged + +**Drop Target Parameters (`rxe.dnd.DropTarget.collected_params`):** +- `is_over`: Boolean indicating if draggable is hovering +- `can_drop`: Boolean indicating if drop is allowed + +# API Reference + +### rxe.dnd.draggable + +Creates a draggable component that can be moved around the interface. + +**Parameters:** + +- **`type`** (str, required): String identifier that must match the `accept` list of drop targets +- **`item`** (dict | Callable): Data object passed to drop handlers. Can be a static dictionary or a function that receives a `DragSourceMonitor` and returns data +- **`preview_options`** (dict): Configuration for the drag preview appearance +- **`options`** (dict): Additional drag source options like `dropEffect` +- **`on_end`** (EventHandler): Event handler called when drag operation completes +- **`can_drag`** (Callable): Function that determines if the item can be dragged +- **`is_dragging`** (Callable): Function to override the default dragging state detection +- **`collect`** (Callable): Function to collect custom properties from the drag monitor + +### rxe.dnd.drop_target + +Creates a drop target that can receive draggable items. + +**Parameters:** + +- **`accept`** (str | list[str], required): Drag type(s) this target accepts +- **`options`** (dict): Additional drop target configuration options +- **`on_drop`** (EventHandler): Event handler called when an item is dropped, receives the `item` data +- **`on_hover`** (EventHandler): Event handler called when an item hovers over the target +- **`can_drop`** (Callable): Function that determines if a specific item can be dropped +- **`collect`** (Callable): Function to collect custom properties from the drop monitor + +## Monitor Classes + +### DragSourceMonitor + +Provides information about the drag operation state: + +- **`is_dragging()`**: Returns `True` if this item is currently being dragged +- **`can_drag()`**: Returns `True` if the item can be dragged +- **`get_item()`**: Returns the item data being dragged +- **`get_item_type()`**: Returns the drag type string +- **`get_drop_result()`**: Returns the drop result (available in `on_end`) +- **`did_drop()`**: Returns `True` if the item was successfully dropped + +### DropTargetMonitor + +Provides information about the drop target state: + +- **`is_over()`**: Returns `True` if a draggable item is hovering over this target +- **`can_drop()`**: Returns `True` if the hovering item can be dropped +- **`get_item()`**: Returns the item data of the hovering draggable +- **`get_item_type()`**: Returns the drag type of the hovering item + +## Default Collected Parameters + +### Draggable.collected_params + +```python +{ + "is_dragging": bool, # True when this item is being dragged + "can_drag": bool # True when this item can be dragged +} +``` + +### DropTarget.collected_params + +```python +{ + "is_over": bool, # True when a draggable is hovering + "can_drop": bool, # True when the hovering item can be dropped + "item": dict | None # Data from the hovering draggable item +} +``` + +## Advanced Usage Examples + +### Data Passing with Item Parameter + +The `item` parameter allows you to pass data from draggable components to drop handlers: + +```python demo exec toggle +import reflex as rx +import reflex_enterprise as rxe + +class SimpleState(rx.State): + message: str = "No items dropped yet" + + def set_message_from_item(self, item: dict): + self.message = f"Dropped: {item['name']}" + +def simple_draggable(): + return rxe.dnd.draggable( + rx.box( + "Drag me!", + p=4, + bg="blue.100", + border="1px solid blue", + cursor="grab" + ), + type="simple", + item={"name": "test_item", "value": 42} + ) + +def simple_drop_target(): + return rxe.dnd.drop_target( + rx.box( + rx.text(SimpleState.message), + p=4, + bg="gray.100", + border="2px dashed gray", + min_height="100px" + ), + accept=["simple"], + on_drop=SimpleState.set_message_from_item + ) + +def item_data_example(): + return rx.vstack( + simple_draggable(), + simple_drop_target(), + spacing="4" + ) +``` + +### Custom Collect Functions + +The `collect` parameter allows you to access drag and drop state information in real-time: + +```python demo exec toggle +import reflex as rx +import reflex_enterprise as rxe + +class CollectState(rx.State): + drag_info: str = "No drag activity" + drop_info: str = "No drop activity" + + def handle_drop(self, item: dict): + self.drop_info = f"Dropped: {item.get('name', 'Unknown')}" + return rx.toast(f"Successfully dropped {item.get('name', 'item')}") + + + +def collect_draggable(): + params = rxe.dnd.Draggable.collected_params + return rxe.dnd.draggable( + rx.box( + rx.vstack( + rx.text("Drag me!", weight="bold"), + rx.text(f"Dragging: {params.is_dragging}", size="2"), + rx.text(f"Can drag: {params.can_drag}", size="2"), + spacing="1" + ), + p=4, + bg=rx.cond(params.is_dragging, "blue.200", "blue.100"), + border="1px solid blue", + cursor=rx.cond(params.is_dragging, "grabbing", "grab"), + opacity=rx.cond(params.is_dragging, 0.7, 1.0) + ), + type="collect_item", + item={"id": "collect_test", "name": "Test Item"} + ) + +def collect_drop_target(): + params = rxe.dnd.DropTarget.collected_params + return rxe.dnd.drop_target( + rx.box( + rx.vstack( + rx.text("Drop Zone", weight="bold"), + rx.text(f"Is over: {params.is_over}", size="2"), + rx.text(f"Can drop: {params.can_drop}", size="2"), + rx.cond( + params.item, + rx.text(f"Hovering item: {params.item.get('name', 'Unknown')}", size="2"), + rx.text("No item hovering", size="2") + ), + spacing="1" + ), + p=4, + bg=rx.cond( + params.is_over & params.can_drop, + "green.200", + rx.cond(params.is_over, "yellow.200", "gray.100") + ), + border=rx.cond( + params.is_over & params.can_drop, + "2px solid green", + rx.cond(params.is_over, "2px solid yellow", "2px dashed gray") + ), + min_height="120px" + ), + accept=["collect_item"], + on_drop=CollectState.handle_drop, + ) + +def custom_collect_example(): + return rx.vstack( + rx.text("Real-time Monitor State", weight="bold", size="4"), + rx.hstack( + collect_draggable(), + collect_drop_target(), + spacing="6", + align="start" + ), + rx.text(CollectState.drop_info, size="2", color="gray.600"), + spacing="4" + ) +``` + +## Provider + +Drag and drop functionality requires the `rxe.dnd.provider` component to wrap your app. The provider is automatically added when using `draggable` or `drop_target` components. + +For manual control: + +```python +def app(): + return rxe.dnd.provider( + # Your app content + your_app_content(), + backend="HTML5" # or "Touch" for mobile + ) +``` + +## Best Practices + +1. **Always use `@rx.memo`** on functions containing draggable components +2. **Use descriptive type names** for better debugging +3. **Handle edge cases** in drop handlers (invalid items, etc.) +4. **Provide visual feedback** using collected parameters +5. **Test on mobile devices** with touch backend +6. **Keep item data lightweight** for better performance + +--- + +[← Back to main documentation]({enterprise.overview.path}) diff --git a/docs/enterprise/mantine/autocomplete.md b/docs/enterprise/mantine/autocomplete.md new file mode 100644 index 00000000000..6a9f826c3d7 --- /dev/null +++ b/docs/enterprise/mantine/autocomplete.md @@ -0,0 +1,22 @@ +--- +title: Autocomplete +--- + +# Autocomplete component + +`rxe.mantine.autocomplete` is a component for providing suggestions as the user types. It is useful for enhancing user experience by offering relevant options based on input. + +```python demo exec +import reflex as rx +import reflex_enterprise as rxe + +def autocomplete_example(): + return rx.vstack( + rxe.mantine.autocomplete( + data=["Apple", "Banana", "Cherry", "Date", "Elderberry"], + placeholder="Type a fruit", + label="Fruit Autocomplete", + description="Select a fruit from the list", + ), + ) +``` diff --git a/docs/enterprise/mantine/collapse.md b/docs/enterprise/mantine/collapse.md new file mode 100644 index 00000000000..7ed25026163 --- /dev/null +++ b/docs/enterprise/mantine/collapse.md @@ -0,0 +1,36 @@ +--- +title: Collapse +--- + +# Collapse component + +`rxe.mantine.collapse` is a component that allows you to create collapsible sections in your application. It is useful for hiding or showing content based on user interaction, such as clicking a button or a link. + +```python demo exec +import reflex as rx +import reflex_enterprise as rxe + +class CollapseState(rx.State): + is_open: bool = False + + @rx.event + def toggle_collapse(self): + self.is_open = not self.is_open + +def collapse_example(): + return rx.vstack( + rxe.mantine.collapse( + rx.text( + "This is a collapsible section. Click the button to toggle the collapse.", + font_size="lg", + ), + in_=CollapseState.is_open, + label="Collapsible Section", + description="Click the button to toggle the collapse.", + ), + rx.button( + "Toggle Collapse", + on_click=lambda: CollapseState.toggle_collapse, + ), + ) +``` diff --git a/docs/enterprise/mantine/combobox.md b/docs/enterprise/mantine/combobox.md new file mode 100644 index 00000000000..d212a9f1c1e --- /dev/null +++ b/docs/enterprise/mantine/combobox.md @@ -0,0 +1,36 @@ +--- +title: Combobox +--- + +```python exec +import reflex as rx +import reflex_enterprise as rxe +from pcweb.pages.docs import enterprise +``` + +# Combobox + +`rxe.mantine.combobox` is a wrapping of the mantine component [Combobox](https://mantine.dev/core/combobox/). It is a simple component that can be used to display a list of options, and allows the user to select one or more options from the list. It can be used in various contexts, such as in a form or as a standalone component. + +```python +import reflex as rx +import reflex_enterprise as rxe + +def combobox_page(): + """Combobox demo.""" + return rxe.mantine.combobox( + rxe.mantine.combobox.target( + rx.input(type="button"), + ), + rxe.mantine.combobox.dropdown( + rxe.mantine.combobox.options( + rxe.mantine.combobox.option("Option 1"), + rxe.mantine.combobox.option("Option 2"), + rxe.mantine.combobox.option("Option 3"), + ), + ), + label="Combobox", + placeholder="Select a value", + ) +``` + diff --git a/docs/enterprise/mantine/index.md b/docs/enterprise/mantine/index.md new file mode 100644 index 00000000000..58d48822d84 --- /dev/null +++ b/docs/enterprise/mantine/index.md @@ -0,0 +1,26 @@ +--- +title: Mantine +order: 4 +--- + +# Mantine + +Mantine is a React component library that provides a set of high-quality components and hooks for building modern web applications. It is designed to be flexible, customizable, and easy to use, making it a popular choice among developers. + +Some of those components have been integrated into Reflex Enterprise, allowing you to use them in your Reflex applications. The following components are available: +- JsonInput +- Autocomplete +- ComboBox +- Multiselect +- Pill +- PillsInput +- TagsInput +- Tree +- RingProgress +- SemiCircleProgress +- LoadingOverlay +- NumberFormatter +- Spoiler +- Timeline +- Collapse + diff --git a/docs/enterprise/mantine/json-input.md b/docs/enterprise/mantine/json-input.md new file mode 100644 index 00000000000..5737732370a --- /dev/null +++ b/docs/enterprise/mantine/json-input.md @@ -0,0 +1,35 @@ +--- +title: JSON Input +--- + +# JSON Input + +`rxe.mantine.json_input` is a component that allows you to input JSON data in a user-friendly way. It provides validation and formatting features to ensure that the JSON data is correctly structured. + +## Example + +```python demo exec +import reflex as rx +import reflex_enterprise as rxe + +class JsonInputState(rx.State): + json_data: str = "" + + def set_json_data(self, value: str): + self.json_data = value + + +def json_input_example(): + return rxe.mantine.json_input( + id="json-input", + value=JsonInputState.json_data, + placeholder="Enter JSON data", + label="JSON Input", + description="Please enter valid JSON data.", + required=True, + size="md", + format_on_blur=True, + on_change=JsonInputState.set_json_data, + width="300px", + ) +``` diff --git a/docs/enterprise/mantine/loading-overlay.md b/docs/enterprise/mantine/loading-overlay.md new file mode 100644 index 00000000000..8aebe7cdd3f --- /dev/null +++ b/docs/enterprise/mantine/loading-overlay.md @@ -0,0 +1,32 @@ +--- +title: Loading Overlay +--- + +# Loading Overlay component +`rxe.mantine.loading_overlay` is a component that displays a loading overlay on top of its children. It is useful for indicating that a process is ongoing and prevents user interaction with the underlying content. + +```python demo exec +import reflex as rx +import reflex_enterprise as rxe + +class LoadingOverlayState(rx.State): + loading: bool = False + + @rx.event + def toggle_loading(self): + self.loading = not self.loading + +def loading_overlay_example(): + return rx.container( + rxe.mantine.loading_overlay( + rx.text( + "Loading Overlay Example", + height="200px", + width="100px", + ), + overlay_props={"radius": "sm", "blur": 2}, + visible=LoadingOverlayState.loading, + z_index=1000, + ), + ),rx.button("Toggle Loading", on_click=LoadingOverlayState.toggle_loading), +``` diff --git a/docs/enterprise/mantine/multi-select.md b/docs/enterprise/mantine/multi-select.md new file mode 100644 index 00000000000..5137409be71 --- /dev/null +++ b/docs/enterprise/mantine/multi-select.md @@ -0,0 +1,31 @@ +--- +title: MultiSelect +--- + +# MultiSelect component + +`rxe.mantine.multi_select` is a component for selecting multiple options from a list. It allows users to choose one or more items, making it suitable for scenarios where multiple selections are required. + +```python demo exec +import reflex as rx +import reflex_enterprise as rxe + +class MultiSelectState(rx.State): + selected_fruits: list = [] + + def set_selected_fruits(self, value: list): + self.selected_fruits = value + + +def multi_select_example(): + return rx.vstack( + rxe.mantine.multi_select( + label="Select fruits", + placeholder="Pick all that you like", + data=["Apple", "Banana", "Cherry", "Date", "Elderberry"], + value=MultiSelectState.selected_fruits, + on_change=MultiSelectState.set_selected_fruits, + ) + ) + +``` diff --git a/docs/enterprise/mantine/number-formatter.md b/docs/enterprise/mantine/number-formatter.md new file mode 100644 index 00000000000..d2833125d75 --- /dev/null +++ b/docs/enterprise/mantine/number-formatter.md @@ -0,0 +1,27 @@ +--- +title: Number Formatter +--- + +# Number Formatter component +`rxe.mantine.number_formatter` is a component for formatting numbers in a user-friendly way. It allows you to specify the format, precision, and other options for displaying numbers. + +```python demo exec +import reflex as rx +import reflex_enterprise as rxe + +def number_formatter_example(): + return rx.vstack( + rxe.mantine.number_formatter( + value=100, + prefix="$", + ), + rxe.mantine.number_formatter( + value=100, + suffix="€", + ), + rxe.mantine.number_formatter( + value=1234567.89, + thousand_separator=True, + ), + ) +``` diff --git a/docs/enterprise/mantine/pill.md b/docs/enterprise/mantine/pill.md new file mode 100644 index 00000000000..e401685eb02 --- /dev/null +++ b/docs/enterprise/mantine/pill.md @@ -0,0 +1,94 @@ +--- +title: Pill +--- + +```python exec +import reflex as rx +import reflex_enterprise as rxe +from pcweb.pages.docs import enterprise +``` + +# Pill + +`rxe.mantine.pill` is a wrapping of the mantine component [Pill](https://mantine.dev/core/pill/). It is a simple component that can be used to display a small piece of information, such as a tag or a label. It can be used in various contexts, such as in a list of tags or labels, or as a standalone component. + +```python demo exec +import reflex as rx +import reflex_enterprise as rxe + +def pill_page(): + """Pill demo.""" + return rxe.mantine.pill( + "Pill", + color="blue", + size="md", + variant="outline", + radius="xl", + with_remove_button=True, + on_remove=lambda: rx.toast("Pill on_remove triggered"), + ) +``` + +## Pill Group +`rxe.mantine.pill.group` allows grouping multiple `rxe.mantine.pill` components together, with a predefined layout. + +```python demo exec +import reflex as rx +import reflex_enterprise as rxe + +def pill_group_page(): + """Pill demo.""" + return rxe.mantine.pill.group( + rxe.mantine.pill("Pill 1"), + rxe.mantine.pill("Pill 2"), + ) +``` + + +# PillsInput + +`rxe.mantine.pills_input` is a wrapping of the mantine component [PillsInput](https://mantine.dev/core/pills-input/). It is an utility component that can be used to display a list of tags or labels. It can be used in various contexts, such as in a form or as a standalone component. +By itself it does not include any logic, it only renders given children. + +```md alert info +# For a fully functional out-of-the-box component, consider using [`rxe.mantine.tags_input`](/docs/enterprise/mantine/tags-input/) instead. +``` + +## Example + +```python demo exec +import reflex as rx +import reflex_enterprise as rxe + +class PillInputState(rx.State): + """State for the PillsInput demo.""" + + tags: set[str] = {"Foo", "Bar"} + + @rx.event + def add_tag(self, tag: str): + """Add a tag to the list of tags.""" + self.tags.add(tag) + + @rx.event + def remove_tag(self, tag: str): + """Remove a tag from the list of tags.""" + self.tags.remove(tag) + +def pills_input_page(): + """PillsInput demo.""" + return rxe.mantine.pills_input( + rxe.mantine.pill.group( + rx.foreach( + PillInputState.tags, lambda tag: rxe.mantine.pill(tag, with_remove_button=True, on_remove=PillInputState.remove_tag(tag)) + ), + rxe.mantine.pills_input.field( + placeholder="Enter tags", + # on_blur=PillInputState.add_tag, + ), + ), + label="PillsInput", + id="pills-input", + value=["tag1", "tag2"], + ) +``` diff --git a/docs/enterprise/mantine/ring-progress.md b/docs/enterprise/mantine/ring-progress.md new file mode 100644 index 00000000000..9612531d25f --- /dev/null +++ b/docs/enterprise/mantine/ring-progress.md @@ -0,0 +1,31 @@ +--- +title: Ring Progress +--- + +# Ring Progress component + +`rxe.mantine.ring_progress` is a component for displaying progress in a circular format. It is useful for visualizing completion percentages or other metrics in a compact and visually appealing way. + +```python demo exec +import reflex as rx +import reflex_enterprise as rxe +import random + +class RingProgressState(rx.State): + value: int = 50 + + @rx.event + def random(self): + self.value = random.randint(0, 100) + +def ring_progress_example(): + return rx.vstack( + rxe.mantine.ring_progress( + size=100, + sections=[ + {"value": RingProgressState.value, "color": "blue"}, + ], + ), + rx.button("Randomize", on_click=RingProgressState.random), + ) +``` diff --git a/docs/enterprise/mantine/semi-circle-progress.md b/docs/enterprise/mantine/semi-circle-progress.md new file mode 100644 index 00000000000..3f7f1f5df03 --- /dev/null +++ b/docs/enterprise/mantine/semi-circle-progress.md @@ -0,0 +1,28 @@ +--- +title: Semi Circle Progress +--- + +# Semi Circle Progress component +`rxe.mantine.semi_circle_progress` is a component for displaying progress in a semi-circular format. It is useful for visualizing completion percentages or other metrics in a compact and visually appealing way. + +```python demo exec +import reflex as rx +import reflex_enterprise as rxe +import random + +class SemiCircleProgressState(rx.State): + value: int = 50 + + @rx.event + def random(self): + self.value = random.randint(0, 100) + +def semi_circle_progress_example(): + return rx.vstack( + rxe.mantine.semi_circle_progress( + size=100, + value=SemiCircleProgressState.value, + ), + rx.button("Randomize", on_click=SemiCircleProgressState.random), + ) +``` diff --git a/docs/enterprise/mantine/spoiler.md b/docs/enterprise/mantine/spoiler.md new file mode 100644 index 00000000000..09a1b0dc1a8 --- /dev/null +++ b/docs/enterprise/mantine/spoiler.md @@ -0,0 +1,22 @@ +--- +title: Spoiler +--- + +# Spoiler component + +`rxe.mantine.spoiler` is a component that allows you to hide or reveal content. It is useful for displaying additional information or details that may not be immediately relevant to the user. + +```python demo exec +import reflex as rx +import reflex_enterprise as rxe + +def spoiler_example(): + return rx.vstack( + rxe.mantine.spoiler( + "This is a spoiler zone where lorem ipsum text dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit...", + max_height=50, + show_label="Show more", + hide_label="Show less", + ), + ) +``` diff --git a/docs/enterprise/mantine/tags-input.md b/docs/enterprise/mantine/tags-input.md new file mode 100644 index 00000000000..5b24a1a63db --- /dev/null +++ b/docs/enterprise/mantine/tags-input.md @@ -0,0 +1,55 @@ +--- +title: TagsInput +--- + +# TagsInput + +`rxe.mantine.tags_input` is a wrapping of the mantine component [TagsInput](https://mantine.dev/core/tags-input/). It is an utility component that can be used to display a list of tags or labels. It can be used in various contexts, such as in a form or as a standalone component. + +```md alert info +# You can use the props mentioned in the Mantine documentation, but they need to be passed in snake_case. +``` + +## Basic Example + +```python demo exec +import reflex as rx +import reflex_enterprise as rxe + +def tags_input_simple_page(): + """TagsInput demo.""" + return rxe.mantine.tags_input( + placeholder="Enter tags", + label="Press Enter to ad a tag", + ) +``` + +## State Example + +```python demo exec +import reflex as rx +import reflex_enterprise as rxe + +class TagsInputState(rx.State): + """State for the TagsInput component.""" + + tags: list[str] = ["Tag1", "Tag2"] + + @rx.event + def update_tags(self, tags: list[str]): + """Add a tag to the list of tags.""" + self.tags = tags + +def tags_input_page(): + """TagsInput demo.""" + return rxe.mantine.tags_input( + value=TagsInputState.tags, + on_change=TagsInputState.update_tags, + placeholder="Enter tags", + label="TagsInput", + description="This is a TagsInput component", + error="", + size="md", + radius="xl", + ) +``` diff --git a/docs/enterprise/mantine/timeline.md b/docs/enterprise/mantine/timeline.md new file mode 100644 index 00000000000..32843a35b6e --- /dev/null +++ b/docs/enterprise/mantine/timeline.md @@ -0,0 +1,34 @@ +--- +title: Timeline +--- + +# Timeline component +`rxe.mantine.timeline` is a component for displaying a sequence of events or milestones in a linear format. It is useful for visualizing progress, history, or any sequential information. + +```python demo exec +import reflex as rx +import reflex_enterprise as rxe + +def timeline_example(): + return rx.vstack( + rxe.mantine.timeline( + rxe.mantine.timeline.item( + title="Step 1", + bullet="•", + ), + rxe.mantine.timeline.item( + title="Step 2", + bullet="•", + ), + rxe.mantine.timeline.item( + title="Step 3", + bullet="•", + ), + active=1, + bullet_size=24, + line_width=2, + color="blue", + + ) + ) +``` diff --git a/docs/enterprise/mantine/tree.md b/docs/enterprise/mantine/tree.md new file mode 100644 index 00000000000..a3ce63eddb2 --- /dev/null +++ b/docs/enterprise/mantine/tree.md @@ -0,0 +1,31 @@ +--- +title: Tree +--- + +# Tree component + +`rxe.mantine.tree` is a component for displaying hierarchical data in a tree structure. It allows users to expand and collapse nodes, making it easy to navigate through large datasets. + +```md alert warning +# Due to some technical limitations(pydantic), the tree component only supports 5 levels of depths for the `data` props. +``` + +```python demo exec +import reflex as rx +import reflex_enterprise as rxe + +def tree_example(): + # return rx.text("Placeholder for tree example") + return rxe.mantine.tree( + data=[ + { + "value": "0", + "label": "Root", + "children": [ + {"value": "0-1", "label": "Child 1"}, + {"value": "0-2", "label": "Child 2"}, + ], + } + ], + ) +``` diff --git a/docs/enterprise/map/index.md b/docs/enterprise/map/index.md new file mode 100644 index 00000000000..20af518807e --- /dev/null +++ b/docs/enterprise/map/index.md @@ -0,0 +1,565 @@ +--- +title: Interactive Maps +--- + +# Interactive Maps + +```python exec +import reflex as rx +import reflex_enterprise as rxe +from pcweb.pages.docs import enterprise +``` + +The map components in Reflex Enterprise provide interactive mapping capabilities built on top of **Leaflet**, one of the most popular open-source JavaScript mapping libraries. These components enable you to create rich, interactive maps with markers, layers, controls, and event handling. + +```md alert info +# All map components are built using Leaflet and react-leaflet, providing a familiar and powerful mapping experience. +For advanced Leaflet features, refer to the [Leaflet documentation](https://leafletjs.com/reference.html). +``` + +🌍 **[View Live Demo](https://map.reflex.run)** - See the map components in action with interactive examples. + +## Installation & Setup + +Map components are included with `reflex-enterprise`. No additional installation is required. + +## Basic Usage + +Here's a simple example of creating a map with a marker: + +```python demo exec +import reflex as rx +import reflex_enterprise as rxe + +class MapState(rx.State): + center: rxe.map.LatLng = rxe.map.latlng(lat=51.505, lng=-0.09) + zoom: float = 13.0 + +def basic_map(): + return rxe.map( + rxe.map.tile_layer( + url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", + attribution='© OpenStreetMap contributors' + ), + rxe.map.marker( + rxe.map.popup("Hello from London!"), + position=MapState.center, + ), + id="basic-map", + center=MapState.center, + zoom=MapState.zoom, + height="400px", + width="100%", + ) +``` + +## Core Components + +### Map Container + +The `rxe.map()` component is the primary container that holds all other map elements: + +```python +rxe.map( + # Child components (markers, layers, controls) + id="my-map", + center=rxe.map.latlng(lat=51.505, lng=-0.09), + zoom=13, + height="400px", + width="100%" +) +``` + +**Key Properties:** +- `center`: Initial map center coordinates +- `zoom`: Initial zoom level (0-18+ depending on tile provider) +- `bounds`: Alternative to center/zoom, fits map to bounds +- `height`/`width`: Map container dimensions + +### Tile Layers + +Tile layers provide the base map imagery. The most common is OpenStreetMap: + +```python +rxe.map.tile_layer( + url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", + attribution='© OpenStreetMap contributors' +) +``` + + +### Markers + +Add point markers to specific locations: + +```python demo exec +import reflex as rx +import reflex_enterprise as rxe + +def markers_example(): + return rxe.map( + rxe.map.tile_layer( + url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", + attribution='© OpenStreetMap contributors' + ), + rxe.map.marker( + rxe.map.popup( + rx.vstack( + rx.text("Big Ben", weight="bold"), + rx.text("Famous clock tower in London"), + spacing="2" + ) + ), + position=rxe.map.latlng(lat=51.4994, lng=-0.1245), + ), + rxe.map.marker( + rxe.map.popup("London Eye"), + position=rxe.map.latlng(lat=51.5033, lng=-0.1196), + ), + id="markers-map", + center=rxe.map.latlng(lat=51.501, lng=-0.122), + zoom=14, + height="400px", + width="100%", + ) +``` + +### Vector Layers + +Draw shapes and areas on the map: + +```python demo exec +import reflex as rx +import reflex_enterprise as rxe + +def vectors_example(): + return rxe.map( + rxe.map.tile_layer( + url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", + attribution='© OpenStreetMap contributors' + ), + # Circle (radius in meters) + rxe.map.circle( + center=rxe.map.latlng(lat=51.505, lng=-0.09), + radius=500, + path_options=rxe.map.path_options( + color="#ff0000", + fill_color="#ff3333", + fill_opacity=0.3, + weight=2 + ) + ), + # Polygon + rxe.map.polygon( + positions=[ + rxe.map.latlng(lat=51.515, lng=-0.08), + rxe.map.latlng(lat=51.515, lng=-0.07), + rxe.map.latlng(lat=51.520, lng=-0.07), + rxe.map.latlng(lat=51.520, lng=-0.08), + ], + path_options=rxe.map.path_options( + color="#0000ff", + fill_color="#3333ff", + fill_opacity=0.3 + ) + ), + # Polyline + rxe.map.polyline( + positions=[ + rxe.map.latlng(lat=51.500, lng=-0.095), + rxe.map.latlng(lat=51.510, lng=-0.085), + rxe.map.latlng(lat=51.515, lng=-0.095), + ], + path_options=rxe.map.path_options( + color="#00ff00", + weight=4 + ) + ), + id="vectors-map", + center=rxe.map.latlng(lat=51.510, lng=-0.08), + zoom=13, + height="400px", + width="100%", + ) +``` + +## Interactive Features + +### Event Handling + +Maps support comprehensive event handling for user interactions: + +```python demo exec +import reflex as rx +import reflex_enterprise as rxe + +class InteractiveMapState(rx.State): + last_click: str = "No clicks yet" + current_zoom: float = 13.0 + + def handle_map_click(self, event): + lat = event.get("latlng", {}).get("lat", 0) + lng = event.get("latlng", {}).get("lng", 0) + self.last_click = f"Clicked at: {lat:.4f}, {lng:.4f}" + + def handle_zoom_change(self, event): + self.current_zoom = float(event.get("target", {}).get("_zoom", 13.0)) + +def interactive_example(): + return rx.vstack( + rx.text(f"Last click: {InteractiveMapState.last_click}"), + rx.text(f"Current zoom: {InteractiveMapState.current_zoom}"), + rxe.map( + rxe.map.tile_layer( + url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", + attribution='© OpenStreetMap contributors' + ), + id="interactive-map", + center=rxe.map.latlng(lat=51.505, lng=-0.09), + zoom=InteractiveMapState.current_zoom, + height="350px", + width="100%", + on_click=InteractiveMapState.handle_map_click, + on_zoom=InteractiveMapState.handle_zoom_change, + ), + spacing="3" + ) +``` + +### Map Controls + +Add UI controls for enhanced user interaction: + +```python demo exec +import reflex as rx +import reflex_enterprise as rxe + +def controls_example(): + return rxe.map( + rxe.map.tile_layer( + url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", + attribution='© OpenStreetMap contributors' + ), + rxe.map.zoom_control(position="topright"), + rxe.map.scale_control(position="bottomleft"), + rxe.map.attribution_control(position="bottomright"), + id="controls-map", + center=rxe.map.latlng(lat=51.505, lng=-0.09), + zoom=13, + height="400px", + width="100%", + ) +``` + +## Helper Functions + +### Coordinate Creation + +```python +# Create latitude/longitude coordinates +center = rxe.map.latlng(lat=51.505, lng=-0.09, nround=4) + +# Create bounds +bounds = rxe.map.latlng_bounds( + corner1_lat=51.49, corner1_lng=-0.11, + corner2_lat=51.52, corner2_lng=-0.07 +) +``` + +## Map API + +The Map API provides programmatic control over your maps, allowing you to manipulate the map programmatically from your Reflex state methods. + +### Getting the API Reference + +To access the Map API, you need to get a reference to your map using its ID: + +```python +map_api = rxe.map.api("my-map-id") +``` + +### Interactive Demo + +Here are some commonly used API methods demonstrated in action: + +```python demo exec +import reflex as rx +import reflex_enterprise as rxe + +map_api = rxe.map.api("api-demo-map") + +class MapAPIState(rx.State): + current_location: str = "London" + + def fly_to_london(self): + yield map_api.fly_to([51.505, -0.09], 13) + self.current_location = "London" + + def fly_to_paris(self): + yield map_api.fly_to([48.8566, 2.3522], 13) + self.current_location = "Paris" + +def map_api_example(): + return rx.vstack( + rx.text(f"Current location: {MapAPIState.current_location}"), + rx.hstack( + rx.button("Fly to London", on_click=MapAPIState.fly_to_london), + rx.button("Fly to Paris", on_click=MapAPIState.fly_to_paris), + rx.button("Zoom Out", on_click=map_api.set_zoom(8)), + rx.button("Log Center", on_click=map_api.get_center(callback=rx.console_log)), + spacing="2" + ), + rxe.map( + rxe.map.tile_layer( + url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", + attribution='© OpenStreetMap contributors' + ), + id="api-demo-map", + center=rxe.map.latlng(lat=51.505, lng=-0.09), + zoom=13.0, + height="350px", + width="100%", + ), + spacing="3" + ) +``` + +### Common API Methods + +**View Control:** +- `fly_to(latlng, zoom, options)` - Smooth animated movement to location +- `set_view(latlng, zoom, options)` - Instant movement to location +- `set_zoom(zoom)` - Change zoom level +- `zoom_in()` / `zoom_out()` - Zoom by one level +- `fit_bounds(bounds, options)` - Fit map to specific bounds + +**Location Services:** +- `locate(options)` - Get user's current location +- `stop_locate()` - Stop location tracking + +**Information Retrieval:** +- `get_center(callback)` - Get current map center +- `get_zoom(callback)` - Get current zoom level +- `get_bounds(callback)` - Get current map bounds +- `get_size(callback)` - Get map container size + +**Layer Management:** +- `add_layer(layer)` - Add a layer to the map +- `remove_layer(layer)` - Remove a layer from the map +- `has_layer(layer)` - Check if layer exists on map + +### Full Leaflet API Access + +```md alert info +# The Map API provides access to the complete Leaflet map API. Any method available on a Leaflet map instance can be called through the MapAPI instance. +Function names are automatically converted from snake_case (Python) to camelCase (JavaScript). +``` + +This means you can use any method from the [Leaflet Map documentation](https://leafletjs.com/reference.html#map). For example: + +**Python (snake_case) → JavaScript (camelCase):** +- `map_api.pan_to(latlng)` → `map.panTo(latlng)` +- `map_api.set_max_bounds(bounds)` → `map.setMaxBounds(bounds)` +- `map_api.get_pixel_bounds()` → `map.getPixelBounds()` +- `map_api.container_point_to_lat_lng(point)` → `map.containerPointToLatLng(point)` + +### Advanced Example + +```python demo exec +import reflex as rx +import reflex_enterprise as rxe + +class AdvancedMapState(rx.State): + constraints_applied: bool = False + location_tracking: bool = False + location_status: str = "Location tracking disabled" + + def set_location_status(self, status: str): + self.location_status = status + + def setup_map_constraints(self): + map_api = rxe.map.api("advanced-demo-map") + + # Set maximum bounds (restrict panning to London area) + max_bounds = rxe.map.latlng_bounds( + corner1_lat=51.4, corner1_lng=-0.3, + corner2_lat=51.6, corner2_lng=0.1 + ) + yield map_api.set_max_bounds(max_bounds) + + # Set min/max zoom levels + yield map_api.set_min_zoom(10) + yield map_api.set_max_zoom(16) + + # Disable scroll wheel zoom + yield map_api.scroll_wheel_zoom(False) + + self.constraints_applied = True + + def remove_constraints(self): + map_api = rxe.map.api("advanced-demo-map") + + # Remove bounds restriction + yield map_api.set_max_bounds(None) + + # Reset zoom limits + yield map_api.set_min_zoom(1) + yield map_api.set_max_zoom(18) + + # Re-enable scroll wheel zoom + yield map_api.scroll_wheel_zoom(True) + + self.constraints_applied = False + + def toggle_location_tracking(self): + map_api = rxe.map.api("advanced-demo-map") + + if self.location_tracking == False: + # Start location tracking + locate_options = rxe.map.locate_options( + set_view=True, + max_zoom=16, + timeout=10000, + enable_high_accuracy=True, + watch=False # Single location request + ) + yield map_api.locate(locate_options) + self.location_tracking = True + self.location_status = "Requesting location..." + else: + # Stop location tracking + yield map_api.stop_locate() + self.location_tracking = False + self.location_status = "Location tracking disabled" + +def advanced_example(): + return rx.vstack( + rx.hstack( + rx.button( + rx.cond(AdvancedMapState.constraints_applied, "Remove Constraints", "Apply Constraints"), + on_click=rx.cond(AdvancedMapState.constraints_applied, AdvancedMapState.remove_constraints, AdvancedMapState.setup_map_constraints), + color_scheme="blue" + ), + rx.button( + rx.cond(AdvancedMapState.location_tracking, "Disable Location", "Enable Location"), + on_click=AdvancedMapState.toggle_location_tracking, + color_scheme="green" + ), + spacing="3" + ), + rx.text(f"Status: {AdvancedMapState.location_status}"), + rx.text( + rx.cond( + AdvancedMapState.constraints_applied, + "Constraints: Applied (restricted to London area, zoom 10-16, no scroll wheel)", + "Constraints: None" + ) + ), + rxe.map( + rxe.map.tile_layer( + url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", + attribution='© OpenStreetMap contributors' + ), + rxe.map.marker( + rxe.map.popup("Try panning and zooming when constraints are applied!"), + position=rxe.map.latlng(lat=51.505, lng=-0.09), + ), + id="advanced-demo-map", + center=rxe.map.latlng(lat=51.505, lng=-0.09), + zoom=12.0, + height="400px", + width="100%", + on_locationfound=lambda e: AdvancedMapState.set_location_status("Location found!"), + on_locationerror=lambda e: AdvancedMapState.set_location_status("Location error - permission denied or unavailable"), + ), + spacing="3" + ) +``` + +### Callback Handling + +Many API methods that retrieve information require callbacks to handle the results: + +```python +class CallbackMapState(rx.State): + map_info: str = "" + + def handle_center_result(self, result): + lat = result.get("lat", 0) + lng = result.get("lng", 0) + self.map_info = f"Center: {lat:.4f}, {lng:.4f}" + + def handle_bounds_result(self, result): + # result will contain bounds information + self.map_info = f"Bounds: {result}" + + def get_map_info(self): + map_api = rxe.map.api("info-map") + yield map_api.get_center(self.handle_center_result) + # or + yield map_api.get_bounds(self.handle_bounds_result) +``` + +## Available Events + +The map components support a comprehensive set of events: + +**Map Events:** +- `on_click`, `on_dblclick` - Mouse click events +- `on_zoom`, `on_zoom_start`, `on_zoom_end` - Zoom events +- `on_move`, `on_move_start`, `on_move_end` - Pan events +- `on_resize` - Map container resize +- `on_load`, `on_unload` - Map lifecycle + +**Location Events:** +- `on_locationfound`, `on_locationerror` - Geolocation + +**Layer Events:** +- `on_layeradd`, `on_layerremove` - Layer management + +**Popup Events:** +- `on_popupopen`, `on_popupclose` - Popup lifecycle +- `on_tooltipopen`, `on_tooltipclose` - Tooltip lifecycle + +## Common Patterns + +### Dynamic Markers + +```python +class DynamicMapState(rx.State): + markers: list[dict] = [ + {"lat": 51.505, "lng": -0.09, "title": "London"}, + {"lat": 48.8566, "lng": 2.3522, "title": "Paris"}, + {"lat": 52.5200, "lng": 13.4050, "title": "Berlin"}, + ] + +def dynamic_markers(): + return rxe.map( + rxe.map.tile_layer(url="..."), + rx.foreach( + DynamicMapState.markers, + lambda marker: rxe.map.marker( + rxe.map.popup(marker["title"]), + position=rxe.map.latlng( + lat=marker["lat"], + lng=marker["lng"] + ) + ) + ), + # ... map configuration + ) +``` + + +## Best Practices + +1. **Always include attribution** for tile providers +2. **Set reasonable zoom levels** (typically 1-18) +3. **Use bounds for multiple markers** instead of arbitrary center/zoom +4. **Handle loading states** for dynamic map content +5. **Optimize marker rendering** for large datasets using clustering +6. **Test on mobile devices** for touch interactions + +--- + +[← Back to main documentation]({enterprise.overview.path}) diff --git a/docs/enterprise/overview.md b/docs/enterprise/overview.md new file mode 100644 index 00000000000..fb58a235ea5 --- /dev/null +++ b/docs/enterprise/overview.md @@ -0,0 +1,295 @@ +--- +title: Reflex Enterprise +--- + +# Reflex Enterprise + +```python exec +from pcweb.pages.docs import enterprise +import reflex as rx +try: + import reflex_enterprise as rxe + from reflex_enterprise.components.ag_grid.resource import RendererParams +except ImportError: + rxe = None + RendererParams = None +``` + +Reflex Enterprise is a package containing paid features built on top of Reflex. + +```md alert info +# Despite being an enterprise package, free users can use the components from this package. A badge "Built with Reflex" will be shown in the bottom right corner of the app. +For more information on the badge, visit [Built with Reflex]({enterprise.built_with_reflex.path}). +``` + +## Installation + +`reflex-enterprise` must be installed alongside `reflex` to access the enterprise features. + +You can install it from pypi with the following command: + +```bash +pip install reflex-enterprise +``` + +## Features + +```python exec +# Create master data organized by category +categories_data = [ + { + "category": "Configuration", + "description": "Core enterprise features for deployment and branding", + "count": 2, + "components": [ + { + "feature": "show_built_with_reflex", + "description": "Toggle the 'Built with Reflex' badge in your app", + "cloud_tier": "Enterprise", + "self_hosted_tier": "Enterprise", + "link": "/docs/enterprise/built-with-reflex", + }, + { + "feature": "use_single_port", + "description": "Enable single-port deployment by proxying backend to frontend", + "cloud_tier": "Free", + "self_hosted_tier": "Free", + "link": "/docs/enterprise/single-port-proxy", + }, + ] + }, + { + "category": "AGGrid and AGChart", + "description": "Advanced data visualization and grid components", + "count": 2, + "components": [ + { + "feature": "AgGrid", + "description": "Advanced data grid with enterprise features (sorting, filtering, grouping)", + "cloud_tier": "Free", + "self_hosted_tier": "Free", + "link": "/docs/enterprise/ag-grid", + }, + { + "feature": "AGCharts", + "description": "Interactive charts and data visualization components", + "cloud_tier": "Free", + "self_hosted_tier": "Free", + "link": "/docs/enterprise/ag-chart", + }, + ] + }, + { + "category": "Interactive Components", + "description": "Interactive UI features including drag-and-drop and mapping", + "count": 2, + "components": [ + { + "feature": "Drag and Drop", + "description": "Drag and drop functionality for interactive UI elements", + "cloud_tier": "Free", + "self_hosted_tier": "Free", + "link": "/docs/enterprise/drag-and-drop", + }, + { + "feature": "Mapping", + "description": "Interactive maps with markers, layers, and controls", + "cloud_tier": "Free", + "self_hosted_tier": "Free", + "link": "/docs/enterprise/map", + }, + ] + }, + { + "category": "Mantine", + "description": "Rich UI components from Mantine library", + "count": 15, + "components": [ + { + "feature": "Autocomplete", + "description": "Auto-completing text input with dropdown suggestions", + "cloud_tier": "Free", + "self_hosted_tier": "Free", + "link": "/docs/enterprise/mantine/autocomplete", + }, + { + "feature": "Combobox", + "description": "Searchable dropdown with custom options and filtering", + "cloud_tier": "Free", + "self_hosted_tier": "Free", + "link": "/docs/enterprise/mantine/combobox", + }, + { + "feature": "Multi Select", + "description": "Multi-selection dropdown with tags and search", + "cloud_tier": "Free", + "self_hosted_tier": "Free", + "link": "/docs/enterprise/mantine/multi-select", + }, + { + "feature": "Tags Input", + "description": "Input field for creating and managing tags", + "cloud_tier": "Free", + "self_hosted_tier": "Free", + "link": "/docs/enterprise/mantine/tags-input", + }, + { + "feature": "Json Input", + "description": "JSON editor with syntax highlighting and validation", + "cloud_tier": "Free", + "self_hosted_tier": "Free", + "link": "/docs/enterprise/mantine/json-input", + }, + { + "feature": "Pill", + "description": "Small rounded elements for tags, badges, and labels", + "cloud_tier": "Free", + "self_hosted_tier": "Free", + "link": "/docs/enterprise/mantine/pill", + }, + { + "feature": "Tree", + "description": "Hierarchical tree view with expandable nodes", + "cloud_tier": "Free", + "self_hosted_tier": "Free", + "link": "/docs/enterprise/mantine/tree", + }, + { + "feature": "Timeline", + "description": "Timeline component for displaying chronological events", + "cloud_tier": "Free", + "self_hosted_tier": "Free", + "link": "/docs/enterprise/mantine/timeline", + }, + { + "feature": "Number Formatter", + "description": "Format and display numbers with customizable formatting", + "cloud_tier": "Free", + "self_hosted_tier": "Free", + "link": "/docs/enterprise/mantine/number-formatter", + }, + { + "feature": "Ring Progress", + "description": "Circular progress indicator with customizable styling", + "cloud_tier": "Free", + "self_hosted_tier": "Free", + "link": "/docs/enterprise/mantine/ring-progress", + }, + { + "feature": "Semi Circle Progress", + "description": "Semi-circular progress indicator for dashboards", + "cloud_tier": "Free", + "self_hosted_tier": "Free", + "link": "/docs/enterprise/mantine/semi-circle-progress", + }, + { + "feature": "Loading Overlay", + "description": "Loading overlay with spinner for async operations", + "cloud_tier": "Free", + "self_hosted_tier": "Free", + "link": "/docs/enterprise/mantine/loading-overlay", + }, + { + "feature": "Spoiler", + "description": "Collapsible content container with show/hide toggle", + "cloud_tier": "Free", + "self_hosted_tier": "Free", + "link": "/docs/enterprise/mantine/spoiler", + }, + { + "feature": "Collapse", + "description": "Animated collapsible content with smooth transitions", + "cloud_tier": "Free", + "self_hosted_tier": "Free", + "link": "/docs/enterprise/mantine/collapse", + }, + ] + }, +] + +if rxe is not None: + @rxe.arrow_func + def custom_link_renderer(params: RendererParams): + """Custom cell renderer for links in AG Grid.""" + return rx.link( + params.value, + href=params.data.link, + ) + + grid = rxe.ag_grid( + column_defs=[ + { + "field": "category", + "header_name": "Category", + "cell_renderer": "agGroupCellRenderer", + "suppress_menu": True, + "width": 220, + }, + { + "field": "description", + "width": 500, + }, + { + "field": "count", + "header_name": "Components", + "width": 150, + }, + ], + row_data=categories_data, + master_detail=True, + detail_cell_renderer_params={ + "detail_grid_options": { + "column_defs": [ + { + "field": "feature", + "header_name": "Component/Feature", + "cell_renderer": custom_link_renderer, + "width": 250 + }, + {"field": "description", "header_name": "Description", "width": 350}, + {"field": "cloud_tier", "header_name": "Cloud Tier", "width": 120}, + {"field": "self_hosted_tier", "header_name": "Self-hosted Tier", "width": 140}, + ], + "suppress_context_menu": True, + "row_height": 35, + }, + "get_detail_row_data": lambda params: rx.vars.function.FunctionStringVar( + "params.successCallback" + ).call(params.data.components), + }, + id="features-grid", + width="100%", + detail_row_height=200, + detail_row_auto_height=True, + height="400px", + loading=False, +) + grid.api.set_grid_option("suppressContextMenu", True) +else: + grid = rx.text("Reflex Enterprise not available. Install with: pip install reflex-enterprise") +``` + +```python eval +grid +``` + +## Usage of reflex_enterprise. + +Using `rxe.App` as your `app` is required to use any of the components provided by the enterprise package, as well as config options provided by `rxe.Config`. + +### In the main file + +Instead of the usual `rx.App()` to create your app, use the following: +```python +import reflex_enterprise as rxe +app = rxe.App() +``` + +### In rxconfig.py +```python +import reflex_enterprise as rxe +config = rxe.Config( + app_name="MyApp", + ... # you can pass all rx.Config arguments as well as the one specific to rxe.Config +) +``` \ No newline at end of file diff --git a/docs/enterprise/react_flow/basic_flow.md b/docs/enterprise/react_flow/basic_flow.md new file mode 100644 index 00000000000..b057fdc918a --- /dev/null +++ b/docs/enterprise/react_flow/basic_flow.md @@ -0,0 +1,96 @@ +# Basic Flow Example + +This example demonstrates a simple flow diagram with three nodes and two edges, showing how nodes can be connected and how edges can be animated. + +### Nodes + +- Input Node – starting point +- Default Node – standard content node +- Output Node – endpoint of the flow + +### Edges + +- Input → Default (animated) +- Default → Output + +### Interactivity + +- Nodes can be moved +- Edges update dynamically +- Users can drag from handles to create new edges +- Zoom, pan, and mini-map controls are available + +### Visual Layout + +- Flow fits viewport automatically +- Background grid for orientation +- Light and dark color modes supported + +### Example Flow + +```python demo exec +import reflex as rx +import reflex_enterprise as rxe +from reflex_enterprise.components.flow.types import Node, Edge + +# Common style for all nodes +node_style = { + "backgroundColor": "#ffcc00", + "color": "#000000", + "padding": "10px", + "borderRadius": "5px" +} + +class FlowState(rx.State): + nodes: list[Node] = [ + { + "id": "1", + "type": "input", + "position": {"x": 100, "y": 100}, + "data": {"label": "Input Node"}, + "style": node_style + }, + { + "id": "2", + "type": "default", + "position": {"x": 300, "y": 200}, + "data": {"label": "Default Node"}, + "style": node_style + }, + { + "id": "3", + "type": "output", + "position": {"x": 500, "y": 100}, + "data": {"label": "Output Node"}, + "style": node_style + }, + ] + + edges: list[Edge] = [ + {"id": "e1-2", "source": "1", "target": "2", "animated": True}, + {"id": "e2-3", "source": "2", "target": "3"}, + ] + + +def flow_example(): + return rx.box( + rxe.flow( + # Core flow components + rxe.flow.controls(), + rxe.flow.background(), + rxe.flow.mini_map(), + + # Flow configuration + default_nodes=FlowState.nodes, + default_edges=FlowState.edges, + nodes=FlowState.nodes, + edges=FlowState.edges, + + # Visual settings + fit_view=True, + attribution_position="bottom-right", + ), + height="100vh", + width="100vw", + ) +``` diff --git a/docs/enterprise/react_flow/components.md b/docs/enterprise/react_flow/components.md new file mode 100644 index 00000000000..04ba0536166 --- /dev/null +++ b/docs/enterprise/react_flow/components.md @@ -0,0 +1,92 @@ +# Flow Components + +This page documents the main components provided by the `rxe.flow` library. + +## rxe.flow.provider + +The `FlowProvider` component is a context provider that makes it possible to access a flow’s internal state outside of the `` component. Many of the hooks we provide rely on this component to work. + +**Props:** + +- `initial_nodes`: `Sequence[Node]` - These nodes are used to initialize the flow. They are not dynamic. +- `default_edges`: `Sequence[Edge]` - These edges are used to initialize the flow. They are not dynamic. +- `initial_width`: `float` - The initial width is necessary to be able to use fitView on the server. +- `initial_height`: `float` - The initial height is necessary to be able to use fitView on the server. +- `fit_view`: `bool` - When true, the flow will be zoomed and panned to fit all the nodes initially provided. +- `initial_fit_view_options`: `FitViewOptions` - You can provide an object of options to customize the initial fitView behavior. +- `initial_min_zoom`: `float` - Initial minimum zoom level. +- `initial_max_zoom`: `float` - Initial maximum zoom level. +- `node_origin`: `NodeOrigin` - The origin of the node to use when placing it in the flow or looking up its x and y position. +- `node_extent`: `CoordinateExtent` - The boundary a node can be moved in. + +## rxe.flow + +The `Flow` component is the main component that renders the flow. It takes in nodes and edges, and provides event handlers for user interactions. + +**Props:** + +- `nodes`: `Sequence[Node]` - An array of nodes to render in a controlled flow. +- `edges`: `Sequence[Edge]` - An array of edges to render in a controlled flow. +- `default_nodes`: `Sequence[Node]` - The initial nodes to render in an uncontrolled flow. +- `default_edges`: `Sequence[Edge]` - The initial edges to render in an uncontrolled flow. +- `node_types`: `Mapping[str, Any]` - Custom node types. +- `edge_types`: `Mapping[str, Any]` - Custom edge types. +- `on_nodes_change`: Event handler for when nodes change. +- `on_edges_change`: Event handler for when edges change. +- `on_connect`: Event handler for when a connection is made. +- `fit_view`: `bool` - When true, the flow will be zoomed and panned to fit all the nodes initially provided. +- `fit_view_options`: `FitViewOptions` - Options for `fit_view`. +- `style`: The style of the component. + +## rxe.flow.background + +The `Background` component renders a background for the flow. It can be a pattern of lines, dots, or a cross. + +**Props:** + +- `color`: `str` - Color of the pattern. +- `bg_color`: `str` - Color of the background. +- `variant`: `Literal["lines", "dots", "cross"]` - The type of pattern to render. +- `gap`: `float | tuple[float, float]` - The gap between patterns. +- `size`: `float` - The size of the pattern elements. + +**Example:** + +```python +rxe.flow.background(variant="dots", gap=20, size=1) +``` + +## rxe.flow.controls + +The `Controls` component renders a panel with buttons to zoom in, zoom out, fit the view, and lock the viewport. + +**Props:** + +- `show_zoom`: `bool` - Whether to show the zoom buttons. +- `show_fit_view`: `bool` - Whether to show the fit view button. +- `show_interactive`: `bool` - Whether to show the lock button. +- `position`: `PanelPosition` - The position of the controls on the pane. + +**Example:** + +```python +rxe.flow.controls() +``` + +## rxe.flow.mini_map + +The `MiniMap` component renders a small overview of your flow. + +**Props:** + +- `node_color`: `str | Any` - Color of nodes on minimap. +- `node_stroke_color`: `str | Any` - Stroke color of nodes on minimap. +- `pannable`: `bool` - Determines whether you can pan the viewport by dragging inside the minimap. +- `zoomable`: `bool` - Determines whether you can zoom the viewport by scrolling inside the minimap. +- `position`: `PanelPosition` - Position of minimap on pane. + +**Example:** + +```python +rxe.flow.mini_map(pannable=True, zoomable=True) +``` diff --git a/docs/enterprise/react_flow/edges.md b/docs/enterprise/react_flow/edges.md new file mode 100644 index 00000000000..25142f70aca --- /dev/null +++ b/docs/enterprise/react_flow/edges.md @@ -0,0 +1,217 @@ +# Edges + +Edges connect nodes together in a flow. This page explains how to define, customize, and interact with edges in Reflex Flow. + +## The Edge Type + +An edge is represented as a Python dictionary with the following fields: + +- `id` (`str`) – Unique identifier for the edge. +- `source` (`str`) – ID of the source node. +- `target` (`str`) – ID of the target node. +- `type` (`str`) – Edge type defined in `edge_types`. +- `sourceHandle` (`str | None`) – Optional source handle ID. +- `targetHandle` (`str | None`) – Optional target handle ID. +- `animated` (`bool`) – Whether the edge should animate. +- `hidden` (`bool`) – Whether the edge is hidden. +- `deletable` (`bool`) – Whether the edge can be removed. +- `selectable` (`bool`) – Whether the edge can be selected. +- `data` (`dict`) – Arbitrary metadata. +- `label` (`Any`) – Label rendered along the edge. +- `style` (`dict`) – Custom styles. +- `className` (`str`) – CSS class for the edge. + +## Basic Edge Types + +Reflex Flow comes with several built-in edge types: + +### Default Edge Types + +```python +edges: list[Edge] = [ + {"id": "e1", "source": "1", "target": "2", "type": "default"}, + {"id": "e2", "source": "2", "target": "3", "type": "straight"}, + {"id": "e3", "source": "3", "target": "4", "type": "step"}, + {"id": "e4", "source": "4", "target": "5", "type": "smoothstep"}, + {"id": "e5", "source": "5", "target": "6", "type": "bezier"}, +] +``` + +- **default** – Standard curved edge +- **straight** – Direct line between nodes +- **step** – Right-angled path with steps +- **smoothstep** – Smooth right-angled path +- **bezier** – Curved bezier path + +## Edge Styling + +### Basic Styling + +Add visual styling to edges using the `style` property: + +```python +edges: list[Edge] = [ + { + "id": "styled-edge", + "source": "1", + "target": "2", + "style": { + "stroke": "#ff6b6b", + "strokeWidth": 3, + } + } +] +``` + +### Animated Edges + +Make edges animate with flowing dots: + +```python +edges: list[Edge] = [ + { + "id": "animated-edge", + "source": "1", + "target": "2", + "animated": True, + "style": {"stroke": "#4dabf7"} + } +] +``` + +### Edge Labels + +Add text labels to edges: + +```python +edges: list[Edge] = [ + { + "id": "labeled-edge", + "source": "1", + "target": "2", + "label": "Connection", + "style": {"stroke": "#51cf66"} + } +] +``` + +# Custom Edges + +React Flow in Reflex also allows you to define custom edge types. This is useful when you want edges to carry extra functionality (like buttons, labels, or dynamic styling) beyond the default straight or bezier connectors. + +```python demo exec +import reflex as rx +import reflex_enterprise as rxe +from reflex_enterprise.components.flow.types import ( + ConnectionInProgress, + Edge, + NoConnection, + Node, + Position, +) + +class SimpleEdgeDemoState(rx.State): + nodes: list[Node] = [ + {"id": "1", "position": {"x": 0, "y": 0}, "data": {"label": "Node A"}, "style": {"color": "#000000",}}, + {"id": "2", "position": {"x": 250, "y": 150}, "data": {"label": "Node B"}, "style": {"color": "#000000",}}, + ] + edges: list[Edge] = [ + {"id": "e1-2", "source": "1", "target": "2", "type": "button"} + ] + + @rx.event + def set_nodes(self, nodes: list[Node]): + self.nodes = nodes + + @rx.event + def set_edges(self, edges: list[Edge]): + self.edges = edges + + @rx.event + def handle_connect_end( + self, + connection_status: NoConnection | ConnectionInProgress, + ): + if not connection_status["isValid"]: + new_edge = { + "id": f"{connection_status['fromNode']['id']}-{connection_status['toNode']['id']}", + "source": connection_status["fromNode"]["id"], + "target": connection_status["toNode"]["id"], + "type": "button", + } + self.edges.append(new_edge) + + + +@rx.memo +def button_edge( + id: rx.Var[str], + sourceX: rx.Var[float], + sourceY: rx.Var[float], + targetX: rx.Var[float], + targetY: rx.Var[float], + sourcePosition: rx.Var[Position], + targetPosition: rx.Var[Position], + markerEnd: rx.Var[str], +): + bezier_path = rxe.components.flow.util.get_bezier_path( + source_x=sourceX, + source_y=sourceY, + target_x=targetX, + target_y=targetY, + source_position=sourcePosition, + target_position=targetPosition, + ) + + mid_x = bezier_path.label_x + mid_y = bezier_path.label_y + + return rx.fragment( + rxe.flow.base_edge(path=bezier_path.path, markerEnd=markerEnd), + rxe.flow.edge_label_renderer( + rx.el.div( + rx.el.button( + "×", + class_name=("w-[30px] h-[30px] border-2 border-gray-200 bg-gray-200 text-black rounded-full text-[12px] pt-0 cursor-pointer hover:bg-gray-400 hover:text-white"), + on_click=rx.run_script( + rxe.flow.api.set_edges( + rx.vars.FunctionStringVar.create( + "Array.prototype.filter.call" + ).call( + rxe.flow.api.get_edges(), + rx.Var(f"((edge) => edge.id !== {id})"), + ), + ) + ), + style={ + "position": "absolute", + "left": f"{mid_x}px", + "top": f"{mid_y}px", + "transform": "translate(-50%, -50%)", + "pointerEvents": "all", + }, + ), + ) + ), + ) + +def very_simple_custom_edge_example(): + return rx.box( + rxe.flow( + rxe.flow.background(), + default_nodes=SimpleEdgeDemoState.nodes, + default_edges=SimpleEdgeDemoState.edges, + nodes=SimpleEdgeDemoState.nodes, + edges=SimpleEdgeDemoState.edges, + on_connect=lambda connection: SimpleEdgeDemoState.set_edges( + rxe.flow.util.add_edge(connection, SimpleEdgeDemoState.edges) + ), + on_connect_end=lambda status, event: SimpleEdgeDemoState.handle_connect_end(status), + edge_types={"button": button_edge}, + fit_view=True, + ), + height="100vh", + width="100%", + ) + +``` diff --git a/docs/enterprise/react_flow/examples.md b/docs/enterprise/react_flow/examples.md new file mode 100644 index 00000000000..72d4a0e76aa --- /dev/null +++ b/docs/enterprise/react_flow/examples.md @@ -0,0 +1,235 @@ +# Example React Flow Components + +This section showcases examples of interactive flow components built with Reflex and Reflex Enterprise. Learn how to create dynamic nodes, edges, and custom behaviors for building flow diagrams in your React apps. + +## Add Node on Edge Drop + +In this example, we demonstrate how to dynamically add nodes to a flow when a connection is dropped onto the canvas. When the user drops a connection, a new node is created at the drop point, and an edge is added between the source node and the new node. + + +```python demo exec +import reflex as rx + +import reflex_enterprise as rxe +from reflex_enterprise.components.flow.types import ( + ConnectionInProgress, + Edge, + NoConnection, + Node, + XYPosition, +) + +node_style = { + "color": "#000000", +} + +initial_nodes: list[Node] = [ + { + "id": "0", + "type": "input", + "data": {"label": "Node"}, + "position": {"x": 0, "y": 50}, + "style": node_style + }, +] + + +class AddNodesOnEdgeDropState(rx.State): + nodes: rx.Field[list[Node]] = rx.field(default_factory=lambda: initial_nodes) + edges: rx.Field[list[Edge]] = rx.field(default_factory=list) + node_id: int = 1 + + @rx.event + def increment(self): + self.node_id += 1 + + @rx.event + def set_nodes(self, nodes: list[Node]): + self.nodes = nodes + + @rx.event + def set_edges(self, edges: list[Edge]): + self.edges = edges + + @rx.event + def handle_connect_end( + self, + connection_status: NoConnection | ConnectionInProgress, + event: rx.event.PointerEventInfo, + flow_position: XYPosition, + ): + if not connection_status["isValid"]: + node_id = str(self.node_id) + self.increment() + self.nodes.append( + { + "id": node_id, + "position": flow_position, + "data": {"label": f"Node {node_id}"}, + "origin": (0.5, 0.0), + "style": node_style + } + ) + self.edges.append( + { + "id": node_id, + "source": connection_status["fromNode"]["id"], + "target": node_id, + "style": node_style + } + ) + +def add_node_on_edge_drop(): + return rx.box( + rxe.flow.provider( + rxe.flow( + rxe.flow.controls(), + rxe.flow.mini_map(), + rxe.flow.background(), + on_connect=lambda connection: AddNodesOnEdgeDropState.set_edges( + rxe.flow.util.add_edge(connection, AddNodesOnEdgeDropState.edges) + ), + on_connect_end=( + lambda connection_status, event: ( + AddNodesOnEdgeDropState.handle_connect_end( + connection_status, + event, + rxe.flow.api.screen_to_flow_position( + x=event.client_x, + y=event.client_y, + ), + ) + ) + ), + nodes=AddNodesOnEdgeDropState.nodes, + edges=AddNodesOnEdgeDropState.edges, + default_nodes=AddNodesOnEdgeDropState.nodes, + default_edges=AddNodesOnEdgeDropState.edges, + on_nodes_change=lambda changes: AddNodesOnEdgeDropState.set_nodes( + rxe.flow.util.apply_node_changes( + AddNodesOnEdgeDropState.nodes, changes + ) + ), + on_edges_change=lambda changes: AddNodesOnEdgeDropState.set_edges( + rxe.flow.util.apply_edge_changes( + AddNodesOnEdgeDropState.edges, changes + ) + ), + fit_view=True, + fit_view_options={"padding": 2}, + node_origin=(0.5, 0.0), + ) + ), + height="100vh", + width="100vw", + ) + +``` + +## Connection Limit on Custom Node + +This example demonstrates how to create a custom node with a connection limit on its handle. The handle can be configured to allow a specific number of connections, or no connections at all, using the isConnectable property. This is useful when you want to restrict the number of connections a node can have. + +```python demo exec +import reflex as rx + +import reflex_enterprise as rxe +from reflex_enterprise.components.flow.types import Edge, HandleType, Node, Position + +node_style = { + "color": "#000000", +} + +class ConnectionLimitState(rx.State): + nodes: rx.Field[list[Node]] = rx.field( + default_factory=lambda: [ + { + "id": "1", + "type": "input", + "data": {"label": "Node 1"}, + "position": {"x": 0, "y": 25}, + "sourcePosition": "right", + "style": node_style + }, + { + "id": "2", + "type": "custom", + "data": {}, + "position": {"x": 250, "y": 50}, + "style": node_style + }, + { + "id": "3", + "type": "input", + "data": {"label": "Node 2"}, + "position": {"x": 0, "y": 100}, + "sourcePosition": "right", + "style": node_style + }, + ] + ) + edges: rx.Field[list[Edge]] = rx.field(default_factory=list) + + @rx.event + def set_nodes(self, nodes: list[Node]): + self.nodes = nodes + + @rx.event + def set_edges(self, edges: list[Edge]): + self.edges = edges + + +@rx.memo +def custom_handle( + type: rx.Var[HandleType], position: rx.Var[Position], connection_count: rx.Var[int] +): + connections = rxe.flow.api.get_node_connections() + return rxe.flow.handle( + type=type, + position=position, + connection_count=connection_count, + is_connectable=connections.length() < connection_count.guess_type(), + ) + + +@rx.memo +def custom_node(): + return rx.el.div( + custom_handle(type="target", position="left", connection_count=1), + rx.el.div("← Only one edge allowed"), + class_name="border border-1 p-2 rounded-sm", + border_color=rx.color_mode_cond("black", ""), + color="black", + bg="white", + ) + + +def connection_limit(): + return rx.box( + rxe.flow( + rxe.flow.background(), + nodes=ConnectionLimitState.nodes, + edges=ConnectionLimitState.edges, + default_nodes=ConnectionLimitState.nodes, + default_edges=ConnectionLimitState.edges, + on_nodes_change=lambda changes: ConnectionLimitState.set_nodes( + rxe.flow.util.apply_node_changes(ConnectionLimitState.nodes, changes) + ), + on_edges_change=lambda changes: ConnectionLimitState.set_edges( + rxe.flow.util.apply_edge_changes(ConnectionLimitState.edges, changes) + ), + on_connect=lambda connection: ConnectionLimitState.set_edges( + rxe.flow.util.add_edge(connection, ConnectionLimitState.edges) + ), + node_types={ + "custom": rx.vars.function.ArgsFunctionOperation.create( + (), custom_node() + ) + }, + color_mode="light", + fit_view=True, + ), + height="100vh", + width="100vw", + ) +``` diff --git a/docs/enterprise/react_flow/hooks.md b/docs/enterprise/react_flow/hooks.md new file mode 100644 index 00000000000..e9d12d95369 --- /dev/null +++ b/docs/enterprise/react_flow/hooks.md @@ -0,0 +1,33 @@ +# Hooks (API) + +The `rxe.flow.api` module provides hooks to interact with the Flow instance. These hooks are wrappers around the `useReactFlow` hook from React Flow. + +## Node Hooks + +- `get_nodes()`: Returns an array of all nodes in the flow. +- `set_nodes(nodes)`: Sets the nodes in the flow. +- `add_nodes(nodes)`: Adds nodes to the flow. +- `get_node(id)`: Returns a node by its ID. +- `update_node(id, node_update, replace=False)`: Updates a node in the flow. +- `update_node_data(id, data_update, replace=False)`: Updates a node's data in the flow. + +## Edge Hooks + +- `get_edges()`: Returns an array of all edges in the flow. +- `set_edges(edges)`: Sets the edges in the flow. +- `add_edges(edges)`: Adds edges to the flow. +- `get_edge(id)`: Returns an edge by its ID. +- `update_edge(id, edge_update, replace=False)`: Updates an edge in the flow. +- `update_edge_data(id, data_update, replace=False)`: Updates an edge's data in the flow. + +## Viewport Hooks + +- `screen_to_flow_position(x, y, snap_to_grid=False)`: Translates a screen pixel position to a flow position. +- `flow_to_screen_position(x, y)`: Translates a position inside the flow’s canvas to a screen pixel position. + +## Other Hooks + +- `to_object()`: Converts the React Flow state to a JSON object. +- `get_intersecting_nodes(node, partially=True, nodes=None)`: Find all the nodes currently intersecting with a given node or rectangle. +- `get_node_connections(id=None, handle_type=None, handle_id=None)`: This hook returns an array of connections on a specific node, handle type ("source", "target") or handle ID. +- `get_connection()`: Returns the current connection state when there is an active connection interaction. diff --git a/docs/enterprise/react_flow/interactivity.md b/docs/enterprise/react_flow/interactivity.md new file mode 100644 index 00000000000..52691360839 --- /dev/null +++ b/docs/enterprise/react_flow/interactivity.md @@ -0,0 +1,71 @@ +# Adding Interactivity to Your Flow + +This guide shows how to create an interactive flow in Reflex, allowing you to select, drag, and connect nodes and edges. + +## Define the State + +We start by defining the nodes and edges of the flow. The `FlowState` class holds the nodes and edges as state variables and includes event handlers to respond to changes. + +```python +import reflex as rx +import reflex_enterprise as rxe +from reflex_enterprise.components.flow.types import Node, Edge + +class FlowState(rx.State): + nodes: list[Node] = [ + {"id": "1", "type": "input", "position": {"x": 100, "y": 100}, "data": {"label": "Node 1"}}, + {"id": "2", "type": "default", "position": {"x": 300, "y": 200}, "data": {"label": "Node 2"}}, + ] + + edges: list[Edge] = [ + {"id": "e1-2", "source": "1", "target": "2", "label": "Connection", "type": "step"} + ] +``` + +# Add Event Handlers + +Event handlers allow the flow to respond to user interactions such as dragging nodes, updating edges, or creating new connections. + +```python +@rx.event +def set_nodes(self, nodes: list[Node]): + self.nodes = nodes + +@rx.event +def set_edges(self, edges: list[Edge]): + self.edges = edges + +``` + +- set_nodes updates nodes when they are moved or edited. + +- set_edges updates edges when they are modified or deleted. + + +## Render the Interactive Flow + +Finally, we render the flow using **rxe.flow**, passing in the state and event handlers. Additional UI features include zoom/pan controls, a background grid, and a mini-map for navigation. + +```python +def interactive_flow(): + return rx.box( + rxe.flow( + rxe.flow.controls(), + rxe.flow.background(), + rxe.flow.mini_map(), + nodes=FlowState.nodes, + edges=FlowState.edges, + on_nodes_change=lambda node_changes: FlowState.set_nodes( + rxe.flow.util.apply_node_changes(FlowState.nodes, node_changes) + ), + on_edges_change=lambda edge_changes: FlowState.set_edges( + rxe.flow.util.apply_edge_changes(FlowState.edges, edge_changes) + ), + fit_view=True, + + attribution_position="bottom-right", + ), + height="100vh", + width="100vw", + ) +``` diff --git a/docs/enterprise/react_flow/nodes.md b/docs/enterprise/react_flow/nodes.md new file mode 100644 index 00000000000..398afdfdd2e --- /dev/null +++ b/docs/enterprise/react_flow/nodes.md @@ -0,0 +1,277 @@ +# Nodes + +Nodes are the fundamental building blocks of a flow. This page explains how to define and customize nodes in Reflex Flow. + +## The Node Type + +A node is represented as a Python dictionary with the following fields: + +- `id` (`str`) – Unique identifier for the node. +- `position` (`dict`) – Position of the node with `x` and `y` coordinates. +- `data` (`dict`) – Arbitrary data passed to the node component. +- `type` (`str`) – Node type defined in `node_types`. +- `sourcePosition` (`str`) – Controls source handle position ("top", "right", "bottom", "left"). +- `targetPosition` (`str`) – Controls target handle position ("top", "right", "bottom", "left"). +- `hidden` (`bool`) – Whether the node is visible on the canvas. +- `selected` (`bool`) – Whether the node is currently selected. +- `draggable` (`bool`) – Whether the node can be dragged. +- `selectable` (`bool`) – Whether the node can be selected. +- `connectable` (`bool`) – Whether the node can be connected to other nodes. +- `deletable` (`bool`) – Whether the node can be deleted. +- `width` (`float`) – Width of the node. +- `height` (`float`) – Height of the node. +- `parentId` (`str`) – Parent node ID for creating sub-flows. +- `style` (`dict`) – Custom styles for the node. +- `className` (`str`) – CSS class name for the node. + +## Built-in Node Types + +Reflex Flow includes several built-in node types: + +```python +nodes: list[Node] = [ + {"id": "1", "type": "input", "position": {"x": 100, "y": 100}, "data": {"label": "Start"}}, + {"id": "2", "type": "default", "position": {"x": 300, "y": 100}, "data": {"label": "Process"}}, + {"id": "3", "type": "output", "position": {"x": 500, "y": 100}, "data": {"label": "End"}}, +] +``` + +- **input** – Entry point with only source handles +- **default** – Standard node with both source and target handles +- **output** – Exit point with only target handles + +## Basic Node Configuration + +### Node Positioning + +```python +node = { + "id": "positioned-node", + "type": "default", + "position": {"x": 250, "y": 150}, + "data": {"label": "Positioned Node"} +} +``` + +### Node Styling + +```python +styled_node = { + "id": "styled-node", + "type": "default", + "position": {"x": 100, "y": 200}, + "data": {"label": "Custom Style"}, + "style": { + "background": "#ff6b6b", + "color": "white", + "border": "2px solid #ff5252", + "borderRadius": "8px", + "padding": "10px" + } +} +``` + +### Handle Positioning + +```python +node_with_handles = { + "id": "handle-node", + "type": "default", + "position": {"x": 300, "y": 300}, + "data": {"label": "Custom Handles"}, + "sourcePosition": "right", + "targetPosition": "left" +} +``` + +# Custom Nodes + +Creating custom nodes is as easy as building a regular React component and passing it to the `node_types`. Since they’re standard React components, you can display any content and implement any functionality you need. Plus, you’ll have access to a range of props that allow you to extend and customize the default node behavior. + +Below is an example custom node using a `color picker` component. + +```python demo exec + +from typing import Any + +import reflex as rx + +import reflex_enterprise as rxe +from reflex_enterprise.components.flow.types import Connection, Edge, Node + + +class CustomNodeState(rx.State): + bg_color: rx.Field[str] = rx.field(default="#c9f1dd") + nodes: rx.Field[list[Node]] = rx.field( + default_factory=lambda: [ + { + "id": "1", + "type": "input", + "data": {"label": "An input node"}, + "position": {"x": 0, "y": 50}, + "sourcePosition": "right", + "style": {"color": "#000000",} + }, + { + "id": "2", + "type": "selectorNode", + "data": { + "color": "#c9f1dd", + }, + "position": {"x": 300, "y": 50}, + }, + { + "id": "3", + "type": "output", + "data": {"label": "Output A"}, + "position": {"x": 650, "y": 25}, + "targetPosition": "left", + "style": {"color": "#000000",} + }, + { + "id": "4", + "type": "output", + "data": {"label": "Output B"}, + "position": {"x": 650, "y": 100}, + "targetPosition": "left", + "style": {"color": "#000000",} + }, + ] + ) + edges: rx.Field[list[Edge]] = rx.field( + default_factory=lambda: [ + { + "id": "e1-2", + "source": "1", + "target": "2", + "animated": True, + }, + { + "id": "e2a-3", + "source": "2", + "target": "3", + "animated": True, + }, + { + "id": "e2b-4", + "source": "2", + "target": "4", + "animated": True, + }, + ] + ) + + @rx.event + def on_change_color(self, color: str): + self.nodes = [ + node + if node["id"] != "2" or "data" not in node + else {**node, "data": {**node["data"], "color": color}} + for node in self.nodes + ] + self.bg_color = color + + @rx.event + def set_nodes(self, nodes: list[Node]): + self.nodes = nodes + + @rx.event + def set_edges(self, edges: list[Edge]): + self.edges = edges + + +@rx.memo +def color_selector_node(data: rx.Var[dict], isConnectable: rx.Var[bool]): + data = data.to(dict) + return rx.el.div( + rxe.flow.handle( + type="target", + position="left", + is_connectable=isConnectable, + ), + rx.el.div( + "Custom Color Picker Node: ", + rx.el.strong(data["color"]), + ), + rx.el.input( + class_name="nodrag", + type="color", + on_change=CustomNodeState.on_change_color, + default_value=data["color"], + ), + rxe.flow.handle( + type="source", + position="right", + is_connectable=isConnectable, + ), + class_name="border border-1 p-2 rounded-sm", + border_color=rx.color_mode_cond("black", ""), + color="black", + bg="white", + ) + + +def node_stroke_color(node: rx.vars.ObjectVar[Node]): + return rx.match( + node["type"], + ("input", "#0041d0"), + ( + "selectorNode", + CustomNodeState.bg_color, + ), + ("output", "#ff0072"), + None, + ) + + +def node_color(node: rx.vars.ObjectVar[Node]): + return rx.match( + node["type"], + ( + "selectorNode", + CustomNodeState.bg_color, + ), + "#fff", + ) + +def custom_node(): + return rx.box( + rxe.flow( + rxe.flow.background(bg_color=CustomNodeState.bg_color), + rxe.flow.mini_map( + node_stroke_color=rx.vars.function.ArgsFunctionOperation.create( + ("node",), node_stroke_color(rx.Var("node").to(Node)) + ), + node_color=rx.vars.function.ArgsFunctionOperation.create( + ("node",), node_color(rx.Var("node").to(Node)) + ), + ), + rxe.flow.controls(), + default_nodes=CustomNodeState.nodes, + default_edges=CustomNodeState.edges, + nodes=CustomNodeState.nodes, + edges=CustomNodeState.edges, + on_nodes_change=lambda changes: CustomNodeState.set_nodes( + rxe.flow.util.apply_node_changes(CustomNodeState.nodes, changes) + ), + on_edges_change=lambda changes: CustomNodeState.set_edges( + rxe.flow.util.apply_edge_changes(CustomNodeState.edges, changes) + ), + on_connect=lambda connection: CustomNodeState.set_edges( + rxe.flow.util.add_edge( + connection.to(dict).merge({"animated": True}).to(Connection), + CustomNodeState.edges, + ) + ), + node_types={"selectorNode": color_selector_node}, + color_mode="light", + snap_grid=(20, 20), + default_viewport={"x": 0, "y": 0, "zoom": 1.5}, + snap_to_grid=True, + attribution_position="bottom-left", + fit_view=True, + ), + height="100vh", + width="100vw", + ) +``` diff --git a/docs/enterprise/react_flow/overview.md b/docs/enterprise/react_flow/overview.md new file mode 100644 index 00000000000..8c4e183642c --- /dev/null +++ b/docs/enterprise/react_flow/overview.md @@ -0,0 +1,23 @@ +# Overview + +At its core, a flow diagram is an interactive graph composed of nodes connected by edges. To help understand the key concepts, let’s go over the main components of a flow. + +### Nodes + +Nodes are the building blocks of a flow. While there are a few default node types available, the real power comes from customizing them. You can design nodes to include interactive elements, display dynamic data, or support multiple connection points. The framework provides the foundation—you provide the functionality and style. + +### Handles + +Handles are the points on a node where edges attach. They typically appear on the top, bottom, left, or right sides of a node, but can be positioned and styled freely. Nodes can have multiple handles, allowing for complex connection setups. + +### Edges + +Edges are the connections between nodes. Each edge requires a source node and a target node. Edges can be styled and customized, and nodes with multiple handles can support multiple edges. Custom edges can include interactive elements, specialized routing, or unique visual styles beyond simple lines. + +### Connection Line + +When creating a new edge, you can click and drag from one handle to another. While dragging, the placeholder edge is called a connection line. Connection lines behave like edges and can be customized in appearance and behavior. + +### Viewport + +The viewport is the visible area containing the flow. Each node has x- and y-coordinates representing its position. Moving the viewport changes these coordinates, and zooming in or out adjusts the zoom level. The viewport ensures the diagram remains navigable and interactive. diff --git a/docs/enterprise/react_flow/theming.md b/docs/enterprise/react_flow/theming.md new file mode 100644 index 00000000000..3aa8a1d2fa1 --- /dev/null +++ b/docs/enterprise/react_flow/theming.md @@ -0,0 +1,74 @@ +# Theming + +You can customize the appearance of the Flow component using CSS. The Flow component comes with a default theme, which you can override with your own styles. + +## CSS Variables + +The Flow component uses CSS variables for theming. You can override these variables to change the appearance of the flow. Here are some of the most common variables: + +```css +.react-flow { + --xy-background-color: #f7f9fb; + --xy-node-border-default: 1px solid #ededed; + --xy-node-boxshadow-default: 0px 3.54px 4.55px 0px #00000005, + 0px 3.54px 4.55px 0px #0000000d, 0px 0.51px 1.01px 0px #0000001a; + --xy-node-border-radius-default: 8px; + --xy-handle-background-color-default: #ffffff; + --xy-handle-border-color-default: #aaaaaa; + --xy-edge-label-color-default: #505050; +} +``` + +## Custom Stylesheets + +You can add custom stylesheets to your app to override the default styles. To do this, add the `stylesheets` prop to your `rxe.App` or `rx.App` instance: + +```python +app = rxe.App( + stylesheets=[ + "/css/my-custom-styles.css", + ], +) +``` + +Then, create a file `assets/css/my-custom-styles.css` in your project and add your custom styles there. + +## Customizing Node and Edge Styles + +You can also apply custom styles to individual nodes and edges using the `style` and `className` props. + +### Using the style prop + +You can pass a style dictionary to the `style` prop of a node or edge: + +```python +node = { + "id": "1", + "position": {"x": 100, "y": 100}, + "data": {"label": "Node 1"}, + "style": {"backgroundColor": "#ffcc00"}, +} +``` + +### Using the className prop + +You can also pass a class name to the `className` prop and define the styles in your CSS file: + +```python +# In your python code +node = { + "id": "1", + "position": {"x": 100, "y": 100}, + "data": {"label": "Node 1"}, + "className": "my-custom-node", +} +``` + +```css +/* In your CSS file */ +.my-custom-node { + background-color: #ffcc00; + border: 2px solid #ff9900; + border-radius: 10px; +} +``` diff --git a/docs/enterprise/react_flow/utils.md b/docs/enterprise/react_flow/utils.md new file mode 100644 index 00000000000..1f013857428 --- /dev/null +++ b/docs/enterprise/react_flow/utils.md @@ -0,0 +1,29 @@ +# Utility Functions + +The `rxe.flow.util` module provides utility functions for working with Flow components. + +## Path Utilities + +These functions are used to calculate the path for an edge. + +- `get_simple_bezier_path(source_x, source_y, target_x, target_y, source_position="bottom", target_position="top")`: Returns everything you need to render a simple bezier edge between two nodes. +- `get_bezier_path(source_x, source_y, target_x, target_y, source_position="bottom", target_position="top", curvature=0.5)`: Returns everything you need to render a bezier edge between two nodes. +- `get_straight_path(source_x, source_y, target_x, target_y)`: Calculates the straight line path between two points. +- `get_smooth_step_path(source_x, source_y, target_x, target_y, source_position="bottom", target_position="top", border_radius=5, center_x=None, center_y=None, offset=20, step_position=0.5)`: Returns everything you need to render a stepped path between two nodes. + +## Change Handlers + +These functions are used to apply changes to nodes and edges from the `on_nodes_change` and `on_edges_change` event handlers. + +- `apply_node_changes(nodes, changes)`: Applies changes to nodes in the flow. +- `apply_edge_changes(edges, changes)`: Applies changes to edges in the flow. + +## Edge and Connection Utilities + +- `add_edge(params, edges)`: Creates a new edge in the flow. + +## Graph Utilities + +- `get_incomers(node_id, nodes, edges)`: Returns all incoming nodes connected to the given node. +- `get_outgoers(node_id, nodes, edges)`: Returns all outgoing nodes connected to the given node. +- `get_connected_edges(nodes, edges)`: Returns all edges connected to the given nodes. diff --git a/docs/enterprise/single-port-proxy.md b/docs/enterprise/single-port-proxy.md new file mode 100644 index 00000000000..078b9a703ba --- /dev/null +++ b/docs/enterprise/single-port-proxy.md @@ -0,0 +1,15 @@ +# Single Port Proxy + +Enable single-port deployment by proxying the backend to the frontend port. + +## Configuration + +```python +import reflex_enterprise as rxe + +config = rxe.Config( + use_single_port=True, +) +``` + +This allows your application to run on a single port, which is useful for deployment scenarios where you can only expose one port. \ No newline at end of file diff --git a/docs/es/README.md b/docs/es/README.md deleted file mode 100644 index 9f9ab69d628..00000000000 --- a/docs/es/README.md +++ /dev/null @@ -1,247 +0,0 @@ -
-Reflex Logo -
- -### **✨ Aplicaciones web personalizables y eficaces en Python puro. Despliega tu aplicación en segundos. ✨** - -[![PyPI version](https://badge.fury.io/py/reflex.svg)](https://badge.fury.io/py/reflex) -![Versiones](https://img.shields.io/pypi/pyversions/reflex.svg) -[![Documentación](https://img.shields.io/badge/Documentation%20-Introduction%20-%20%23007ec6)](https://reflex.dev/docs/getting-started/introduction) -[![PyPI Downloads](https://static.pepy.tech/badge/reflex)](https://pepy.tech/projects/reflex) -[![Discord](https://img.shields.io/discord/1029853095527727165?color=%237289da&label=Discord)](https://discord.gg/T5WSbC2YtQ) - -
- ---- - -[English](https://github.com/reflex-dev/reflex/blob/main/README.md) | [简体中文](https://github.com/reflex-dev/reflex/blob/main/docs/zh/zh_cn/README.md) | [繁體中文](https://github.com/reflex-dev/reflex/blob/main/docs/zh/zh_tw/README.md) | [Türkçe](https://github.com/reflex-dev/reflex/blob/main/docs/tr/README.md) | [हिंदी](https://github.com/reflex-dev/reflex/blob/main/docs/in/README.md) | [Português (Brasil)](https://github.com/reflex-dev/reflex/blob/main/docs/pt/pt_br/README.md) | [Italiano](https://github.com/reflex-dev/reflex/blob/main/docs/it/README.md) | [Español](https://github.com/reflex-dev/reflex/blob/main/docs/es/README.md) | [한국어](https://github.com/reflex-dev/reflex/blob/main/docs/kr/README.md) | [日本語](https://github.com/reflex-dev/reflex/blob/main/docs/ja/README.md) | [Deutsch](https://github.com/reflex-dev/reflex/blob/main/docs/de/README.md) | [Persian (پارسی)](https://github.com/reflex-dev/reflex/blob/main/docs/pe/README.md) | [Tiếng Việt](https://github.com/reflex-dev/reflex/blob/main/docs/vi/README.md) - ---- - -# Reflex - -Reflex es una biblioteca para construir aplicaciones web full-stack en Python puro. - -Características clave: - -- **Python puro** - Escribe el frontend y backend de tu aplicación en Python, sin necesidad de aprender JavaScript. -- **Flexibilidad total** - Reflex es fácil para empezar, pero también puede escalar a aplicaciones complejas. -- **Despliegue instantáneo** - Después de construir, despliega tu aplicación con un [solo comando](https://reflex.dev/docs/hosting/deploy-quick-start/) u hospédala en tu propio servidor. - -Consulta nuestra [página de arquitectura](https://reflex.dev/blog/2024-03-21-reflex-architecture/#the-reflex-architecture) para aprender cómo funciona Reflex en detalle. - -## ⚙️ Instalación - -Abra un terminal y ejecute (Requiere Python 3.10+): - -```bash -pip install reflex -``` - -## 🥳 Crea tu primera aplicación - -Al instalar `reflex` también se instala la herramienta de línea de comandos `reflex`. - -Compruebe que la instalación se ha realizado correctamente creando un nuevo proyecto. (Sustituye `my_app_name` por el nombre de tu proyecto): - -```bash -mkdir my_app_name -cd my_app_name -reflex init -``` - -Este comando inicializa una plantilla en tu nuevo directorio. - -Puedes iniciar esta aplicación en modo de desarrollo: - -```bash -reflex run -``` - -Debería ver su aplicación ejecutándose en http://localhost:3000. - -Ahora puede modificar el código fuente en `my_app_name/my_app_name.py`. Reflex se actualiza rápidamente para que pueda ver los cambios al instante cuando guarde el código. - -## 🫧 Ejemplo de una Aplicación - -Veamos un ejemplo: crearemos una UI de generación de imágenes en torno a [DALL·E](https://platform.openai.com/docs/guides/images/image-generation?context=node). Para simplificar, solo llamamos a la [API de OpenAI](https://platform.openai.com/docs/api-reference/authentication), pero podrías reemplazar esto con un modelo ML ejecutado localmente. - -  - -
-Un envoltorio frontend para DALL·E, mostrado en el proceso de generar una imagen. -
- -  - -Aquí está el código completo para crear esto. ¡Todo esto se hace en un archivo de Python! - -```python -import reflex as rx -import openai - -openai_client = openai.OpenAI() - -class State(rx.State): - """El estado de la aplicación""" - prompt = "" - image_url = "" - processing = False - complete = False - - def get_image(self): - """Obtiene la imagen desde la consulta.""" - if self.prompt == "": - return rx.window_alert("Prompt Empty") - - self.processing, self.complete = True, False - yield - response = openai_client.images.generate( - prompt=self.prompt, n=1, size="1024x1024" - ) - self.image_url = response.data[0].url - self.processing, self.complete = False, True - - -def index(): - return rx.center( - rx.vstack( - rx.heading("DALL-E", font_size="1.5em"), - rx.input( - placeholder="Enter a prompt..", - on_blur=State.set_prompt, - width="25em", - ), - rx.button( - "Generate Image", - on_click=State.get_image, - width="25em", - loading=State.processing - ), - rx.cond( - State.complete, - rx.image(src=State.image_url, width="20em"), - ), - align="center", - ), - width="100%", - height="100vh", - ) - -# Agrega el estado y la pagina a la aplicación -app = rx.App() -app.add_page(index, title="Reflex:DALL-E") -``` - -## Vamos a analizarlo. - -
-Explicando las diferencias entre las partes del backend y frontend de la aplicación DALL-E. -
- -### **Reflex UI** - -Empezemos por la interfaz de usuario (UI). - -```python -def index(): - return rx.center( - ... - ) -``` - -Esta función `index` define el frontend de la aplicación. - -Utilizamos diferentes componentes como `center`, `vstack`, `input`, y `button` para construir el frontend. Los componentes pueden anidarse unos dentro de otros para crear diseños complejos. Además, puedes usar argumentos de tipo keyword para darles estilo con toda la potencia de CSS. - -Reflex viene con [mas de 60 componentes incorporados](https://reflex.dev/docs/library) para ayudarle a empezar. Estamos añadiendo activamente más componentes y es fácil [crear sus propios componentes](https://reflex.dev/docs/wrapping-react/overview/). - -### **Estado** - -Reflex representa su UI como una función de su estado (State). - -```python -class State(rx.State): - """El estado de la aplicación""" - prompt = "" - image_url = "" - processing = False - complete = False -``` - -El estado (State) define todas las variables (llamadas vars) de una aplicación que pueden cambiar y las funciones que las modifican. - -Aquí el estado se compone de `prompt` e `image_url`. También están los booleanos `processing` y `complete` para indicar cuando se deshabilite el botón (durante la generación de la imagen) y cuando se muestre la imagen resultante. - -### **Manejadores de Evento** - -```python -def get_image(self): - """Obtiene la imagen desde la consulta.""" - if self.prompt == "": - return rx.window_alert("Prompt Empty") - - self.processing, self.complete = True, False - yield - response = openai_client.images.generate( - prompt=self.prompt, n=1, size="1024x1024" - ) - self.image_url = response.data[0].url - self.processing, self.complete = False, True -``` - -Dentro del estado, definimos funciones llamadas manejadores de eventos que cambian las variables de estado. Los Manejadores de Evento son la manera que podemos modificar el estado en Reflex. Pueden ser activados en respuesta a las acciones del usuario, como hacer clic en un botón o escribir en un cuadro de texto. Estas acciones se llaman eventos. - -Nuestra aplicación DALL·E tiene un manipulador de eventos, `get_image` que recibe esta imagen del OpenAI API. El uso de `yield` en medio de un manipulador de eventos hará que la UI se actualice. De lo contrario, la interfaz se actualizará al final del manejador de eventos. - -### **Enrutamiento** - -Por último, definimos nuestra app. - -```python -app = rx.App() -``` - -Añadimos una página desde la raíz (root) de la aplicación al componente de índice (index). También agregamos un título que se mostrará en la vista previa de la página/pestaña del navegador. - -```python -app.add_page(index, title="DALL-E") -``` - -Puedes crear una aplicación multipágina añadiendo más páginas. - -## 📑 Recursos - -
- -📑 [Docs](https://reflex.dev/docs/getting-started/introduction)   |   🗞️ [Blog](https://reflex.dev/blog)   |   📱 [Librería de componentes](https://reflex.dev/docs/library)   |   🖼️ [Templates](https://reflex.dev/templates/)   |   🛸 [Despliegue](https://reflex.dev/docs/hosting/deploy-quick-start)   - -
- -## ✅ Estado - -Reflex se lanzó en diciembre de 2022 con el nombre de Pynecone. - -A partir de 2025, [Reflex Cloud](https://cloud.reflex.dev) se ha lanzado para proporcionar la mejor experiencia de alojamiento para aplicaciones Reflex. Continuaremos desarrollándolo e implementando más características. - -¡Reflex tiene nuevas versiones y características cada semana! Asegúrate de :star: marcar como favorito y :eyes: seguir este repositorio para mantenerte actualizado. - -## Contribuciones - -¡Aceptamos contribuciones de cualquier tamaño! A continuación encontrará algunas buenas formas de iniciarse en la comunidad Reflex. - -- **Únete a nuestro Discord**: Nuestro [Discord](https://discord.gg/T5WSbC2YtQ) es el mejor lugar para obtener ayuda en su proyecto Reflex y discutir cómo puedes contribuir. -- **Discusiones de GitHub**: Una excelente manera de hablar sobre las características que deseas agregar o las cosas que te resultan confusas o necesitan aclaración. -- **GitHub Issues**: Las incidencias son una forma excelente de informar de errores. Además, puedes intentar resolver un problema existente y enviar un PR. - -Buscamos colaboradores, sin importar su nivel o experiencia. Para contribuir consulta [CONTRIBUTING.md](https://github.com/reflex-dev/reflex/blob/main/CONTRIBUTING.md) - -## Todo Gracias A Nuestros Contribuidores: - - - - - -## Licencia - -Reflex es de código abierto y está licenciado bajo la [Apache License 2.0](/LICENSE). diff --git a/docs/events/background_events.md b/docs/events/background_events.md new file mode 100644 index 00000000000..f8d0d38874c --- /dev/null +++ b/docs/events/background_events.md @@ -0,0 +1,159 @@ +```python exec +import reflex as rx +from pcweb import constants, styles +``` + +# Background Tasks + +A background task is a special type of `EventHandler` that may run +concurrently with other `EventHandler` functions. This enables long-running +tasks to execute without blocking UI interactivity. + +A background task is defined by decorating an async `State` method with +`@rx.event(background=True)`. + +```md alert warning +# `@rx.event(background=True)` used to be called `@rx.background`. +In Reflex version 0.6.5 and later, the `@rx.background` decorator has been renamed to `@rx.event(background=True)`. +``` + +Whenever a background task needs to interact with the state, **it must enter an +`async with self` context block** which refreshes the state and takes an +exclusive lock to prevent other tasks or event handlers from modifying it +concurrently. Because other `EventHandler` functions may modify state while the +task is running, **outside of the context block, Vars accessed by the background +task may be _stale_**. Attempting to modify the state from a background task +outside of the context block will raise an `ImmutableStateError` exception. + +In the following example, the `my_task` event handler is decorated with +`@rx.event(background=True)` and increments the `counter` variable every half second, as +long as certain conditions are met. While it is running, the UI remains +interactive and continues to process events normally. + +```md alert info +# Background events are similar to simple Task Queues like [Celery](https://www.fullstackpython.com/celery.html) allowing asynchronous events. +``` + +```python demo exec id=background_demo +import asyncio +import reflex as rx + + +class MyTaskState(rx.State): + counter: int = 0 + max_counter: int = 10 + running: bool = False + _n_tasks: int = 0 + + @rx.event + def set_max_counter(self, value: str): + self.max_counter = int(value) + + @rx.event(background=True) + async def my_task(self): + async with self: + # The latest state values are always available inside the context + if self._n_tasks > 0: + # only allow 1 concurrent task + return + + # State mutation is only allowed inside context block + self._n_tasks += 1 + + while True: + async with self: + # Check for stopping conditions inside context + if self.counter >= self.max_counter: + self.running = False + if not self.running: + self._n_tasks -= 1 + return + + self.counter += 1 + + # Await long operations outside the context to avoid blocking UI + await asyncio.sleep(0.5) + + @rx.event + def toggle_running(self): + self.running = not self.running + if self.running: + return MyTaskState.my_task + + @rx.event + def clear_counter(self): + self.counter = 0 + + +def background_task_example(): + return rx.hstack( + rx.heading(MyTaskState.counter, " /"), + rx.input( + value=MyTaskState.max_counter, + on_change=MyTaskState.set_max_counter, + width="8em", + ), + rx.button( + rx.cond(~MyTaskState.running, "Start", "Stop"), + on_click=MyTaskState.toggle_running, + ), + rx.button( + "Reset", + on_click=MyTaskState.clear_counter, + ), + ) +``` + +## Terminating Background Tasks on Page Close or Navigation + +Sometimes, background tasks may continue running even after the user navigates away from the page or closes the browser tab. To handle such cases, you can check if the websocket associated with the state is disconnected and terminate the background task when necessary. + +The solution involves checking if the client_token is still valid in the app.event_namespace.token_to_sid mapping. If the session is lost (e.g., the user navigates away or closes the page), the background task will stop. + +```python +import asyncio +import reflex as rx + +class State(rx.State): + @rx.event(background=True) + async def loop_function(self): + while True: + if self.router.session.client_token not in app.event_namespace.token_to_sid: + print("WebSocket connection closed or user navigated away. Stopping background task.") + break + + print("Running background task...") + await asyncio.sleep(2) + + +@rx.page(on_load=State.loop_function) +def index(): + return rx.text("Hello, this page will manage background tasks and stop them when the page is closed or navigated away.") + +``` + +## Task Lifecycle + +When a background task is triggered, it starts immediately, saving a reference to +the task in `app.background_tasks`. When the task completes, it is removed from +the set. + +Multiple instances of the same background task may run concurrently, and the +framework makes no attempt to avoid duplicate tasks from starting. + +It is up to the developer to ensure that duplicate tasks are not created under +the circumstances that are undesirable. In the example above, the `_n_tasks` +backend var is used to control whether `my_task` will enter the increment loop, +or exit early. + + + + +## Background Task Limitations + +Background tasks mostly work like normal `EventHandler` methods, with certain exceptions: + +- Background tasks must be `async` functions. +- Background tasks cannot modify the state outside of an `async with self` context block. +- Background tasks may read the state outside of an `async with self` context block, but the value may be stale. +- Background tasks may not be directly called from other event handlers or background tasks. Instead use `yield` or `return` to trigger the background task. diff --git a/docs/events/chaining_events.md b/docs/events/chaining_events.md new file mode 100644 index 00000000000..1d14ff16eee --- /dev/null +++ b/docs/events/chaining_events.md @@ -0,0 +1,94 @@ +```python exec +import reflex as rx +``` + +# Chaining events + +## Calling Event Handlers From Event Handlers + +You can call other event handlers from event handlers to keep your code modular. Just use the `self.call_handler` syntax to run another event handler. As always, you can yield within your function to send incremental updates to the frontend. + +```python demo exec id=call-handler +import asyncio + +class CallHandlerState(rx.State): + count: int = 0 + progress: int = 0 + + @rx.event + async def run(self): + # Reset the count. + self.set_progress(0) + yield + + # Count to 10 while showing progress. + for i in range(10): + # Wait and increment. + await asyncio.sleep(0.5) + self.count += 1 + + # Update the progress. + self.set_progress(i + 1) + + # Yield to send the update. + yield + + +def call_handler_example(): + return rx.vstack( + rx.badge(CallHandlerState.count, font_size="1.5em", color_scheme="green"), + rx.progress(value=CallHandlerState.progress, max=10, width="100%"), + rx.button("Run", on_click=CallHandlerState.run), + ) +``` + +## Returning Events From Event Handlers + +So far, we have only seen events that are triggered by components. However, an event handler can also return events. + +In Reflex, event handlers run synchronously, so only one event handler can run at a time, and the events in the queue will be blocked until the current event handler finishes.The difference between returning an event and calling an event handler is that returning an event will send the event to the frontend and unblock the queue. + +```md alert info +# Be sure to use the class name `State` (or any substate) rather than `self` when returning events. +``` + +Try entering an integer in the input below then clicking out. + +```python demo exec id=collatz +class CollatzState(rx.State): + count: int = 1 + + @rx.event + def start_collatz(self, count: str): + """Run the collatz conjecture on the given number.""" + self.count = abs(int(count if count else 1)) + return CollatzState.run_step + + @rx.event + async def run_step(self): + """Run a single step of the collatz conjecture.""" + + while self.count > 1: + + await asyncio.sleep(0.5) + + if self.count % 2 == 0: + # If the number is even, divide by 2. + self.count //= 2 + else: + # If the number is odd, multiply by 3 and add 1. + self.count = self.count * 3 + 1 + yield + + +def collatz_example(): + return rx.vstack( + rx.badge(CollatzState.count, font_size="1.5em", color_scheme="green"), + rx.input(on_blur=CollatzState.start_collatz), + ) + +``` + +In this example, we run the [Collatz Conjecture](https://en.wikipedia.org/wiki/Collatz_conjecture) on a number entered by the user. + +When the `on_blur` event is triggered, the event handler `start_collatz` is called. It sets the initial count, then calls `run_step` which runs until the count reaches `1`. diff --git a/docs/events/decentralized_event_handlers.md b/docs/events/decentralized_event_handlers.md new file mode 100644 index 00000000000..2ac8fcb747a --- /dev/null +++ b/docs/events/decentralized_event_handlers.md @@ -0,0 +1,149 @@ +```python exec +import reflex as rx +``` + +# Decentralized Event Handlers + +## Overview + +Decentralized event handlers allow you to define event handlers outside of state classes, providing more flexible code organization. This feature was introduced in Reflex v0.7.10 and enables a more modular approach to event handling. + +With decentralized event handlers, you can: +- Organize event handlers by feature rather than by state class +- Separate UI logic from state management +- Create more maintainable and scalable applications + +## Basic Usage + +To create a decentralized event handler, use the `@rx.event` decorator on a function that takes a state instance as its first parameter: + +```python demo exec +import reflex as rx + +class MyState(rx.State): + count: int = 0 + +@rx.event +def increment(state: MyState, amount: int): + state.count += amount + +def decentralized_event_example(): + return rx.vstack( + rx.heading(f"Count: {MyState.count}"), + rx.hstack( + rx.button("Increment by 1", on_click=increment(1)), + rx.button("Increment by 5", on_click=increment(5)), + rx.button("Increment by 10", on_click=increment(10)), + ), + spacing="4", + align="center", + ) +``` + +In this example: +1. We define a `MyState` class with a `count` variable +2. We create a decentralized event handler `increment` that takes a `MyState` instance as its first parameter +3. We use the event handler in buttons, passing different amounts to increment by + +## Compared to Traditional Event Handlers + +Here's a comparison between traditional event handlers defined within state classes and decentralized event handlers: + +```python box +# Traditional event handler within a state class +class TraditionalState(rx.State): + count: int = 0 + + @rx.event + def increment(self, amount: int = 1): + self.count += amount + +# Usage in components +rx.button("Increment", on_click=TraditionalState.increment(5)) + +# Decentralized event handler outside the state class +class DecentralizedState(rx.State): + count: int = 0 + +@rx.event +def increment(state: DecentralizedState, amount: int = 1): + state.count += amount + +# Usage in components +rx.button("Increment", on_click=increment(5)) +``` + +Key differences: +- Traditional event handlers use `self` to reference the state instance +- Decentralized event handlers explicitly take a state instance as the first parameter +- Both approaches use the same syntax for triggering events in components +- Both can be decorated with `@rx.event` respectively + +## Best Practices + +### When to Use Decentralized Event Handlers + +Decentralized event handlers are particularly useful in these scenarios: + +1. **Large applications** with many event handlers that benefit from better organization +2. **Feature-based organization** where you want to group related event handlers together +3. **Separation of concerns** when you want to keep state definitions clean and focused + +### Type Annotations + +Always use proper type annotations for your state parameter and any additional parameters: + +```python box +@rx.event +def update_user(state: UserState, name: str, age: int): + state.name = name + state.age = age +``` + +### Naming Conventions + +Follow these naming conventions for clarity: + +1. Use descriptive names that indicate the action being performed +2. Use the state class name as the type annotation for the first parameter +3. Name the state parameter consistently across your codebase (e.g., always use `state` or the first letter of the state class) + +### Organization + +Consider these approaches for organizing decentralized event handlers: + +1. Group related event handlers in the same file +2. Place event handlers near the state classes they modify +3. For larger applications, create a dedicated `events` directory with files organized by feature + +```python box +# Example organization in a larger application +# events/user_events.py +@rx.event +def update_user(state: UserState, name: str, age: int): + state.name = name + state.age = age + +@rx.event +def delete_user(state: UserState): + state.name = "" + state.age = 0 +``` + +### Combining with Other Event Features + +Decentralized event handlers work seamlessly with other Reflex event features: + +```python box +# Background event +@rx.event(background=True) +async def long_running_task(state: AppState): + # Long-running task implementation + pass + +# Event chaining +@rx.event +def process_form(state: FormState, data: dict): + # Process form data + return validate_data # Chain to another event +``` diff --git a/docs/events/event_actions.md b/docs/events/event_actions.md new file mode 100644 index 00000000000..1d431eddc8b --- /dev/null +++ b/docs/events/event_actions.md @@ -0,0 +1,252 @@ +```python exec +import reflex as rx +import datetime +``` + +# Event Actions + +In Reflex, an event action is a special behavior that occurs during or after +processing an event on the frontend. + +Event actions can modify how the browser handles DOM events or throttle and +debounce events before they are processed by the backend. + +An event action is specified by accessing attributes and methods present on all +EventHandlers and EventSpecs. + +## DOM Event Propagation + +_Added in v0.3.2_ + +### prevent_default + +The `.prevent_default` action prevents the default behavior of the browser for +the action. This action can be added to any existing event, or it can be used on its own by +specifying `rx.prevent_default` as an event handler. + +A common use case for this is to prevent navigation when clicking a link. + +```python demo +rx.link("This Link Does Nothing", href="https://reflex.dev/", on_click=rx.prevent_default) +``` + +```python demo exec +class LinkPreventDefaultState(rx.State): + status: bool = False + + @rx.event + def toggle_status(self): + self.status = not self.status + +def prevent_default_example(): + return rx.vstack( + rx.heading(f"The value is {LinkPreventDefaultState.status}"), + rx.link( + "Toggle Value", + href="https://reflex.dev/", + on_click=LinkPreventDefaultState.toggle_status.prevent_default, + ), + ) +``` + +### stop_propagation + +The `.stop_propagation` action stops the event from propagating to parent elements. + +This action is often used when a clickable element contains nested buttons that +should not trigger the parent element's click event. + +In the following example, the first button uses `.stop_propagation` to prevent +the click event from propagating to the outer vstack. The second button does not +use `.stop_propagation`, so the click event will also be handled by the on_click +attached to the outer vstack. + +```python demo exec +class StopPropagationState(rx.State): + where_clicked: list[str] = [] + + @rx.event + def handle_click(self, where: str): + self.where_clicked.append(where) + + @rx.event + def handle_reset(self): + self.where_clicked = [] + +def stop_propagation_example(): + return rx.vstack( + rx.button( + "btn1 - Stop Propagation", + on_click=StopPropagationState.handle_click("btn1").stop_propagation, + ), + rx.button( + "btn2 - Normal Propagation", + on_click=StopPropagationState.handle_click("btn2"), + ), + rx.foreach(StopPropagationState.where_clicked, rx.text), + rx.button( + "Reset", + on_click=StopPropagationState.handle_reset.stop_propagation, + ), + padding="2em", + border=f"1px dashed {rx.color('accent', 5)}", + on_click=StopPropagationState.handle_click("outer") + ) +``` + +## Throttling and Debounce + +_Added in v0.5.0_ + +For events that are fired frequently, it can be useful to throttle or debounce +them to avoid network latency and improve performance. These actions both take a +single argument which specifies the delay time in milliseconds. + +### throttle + +The `.throttle` action limits the number of times an event is processed within a +a given time period. It is useful for `on_scroll` and `on_mouse_move` events which are +fired very frequently, causing lag when handling them in the backend. + +```md alert warning +# Throttled events are discarded. + +There is no eventual delivery of any event that is triggered while the throttle +period is active. Throttle is not appropriate for events when the final payload +contains data that must be processed, like `on_change`. +``` + +In the following example, the `on_scroll` event is throttled to only fire every half second. + +```python demo exec +class ThrottleState(rx.State): + last_scroll: datetime.datetime | None + + @rx.event + def handle_scroll(self): + self.last_scroll = datetime.datetime.now(datetime.timezone.utc) + +def scroll_box(): + return rx.scroll_area( + rx.heading("Scroll Me"), + *[rx.text(f"Item {i}") for i in range(100)], + height="75px", + width="50%", + border=f"1px solid {rx.color('accent', 5)}", + on_scroll=ThrottleState.handle_scroll.throttle(500), + ) + +def throttle_example(): + return ( + scroll_box(), + rx.text( + f"Last Scroll Event: ", + rx.moment(ThrottleState.last_scroll, format="HH:mm:ss.SSS"), + ), + ) +``` + +```md alert info +# Event Actions are Chainable + +Event actions can be chained together to create more complex event handling +behavior. For example, you can throttle an event and prevent its default +behavior in the same event handler: `on_click=MyState.handle_click.throttle(500).prevent_default`. +``` + +### debounce + +The `.debounce` action delays the processing of an event until the specified +timeout occurs. If another event is triggered during the timeout, the timer is +reset and the original event is discarded. + +Debounce is useful for handling the final result of a series of events, such as +moving a slider. + +```md alert warning +# Debounced events are discarded. + +When a new event is triggered during the debounce period, the original event is +discarded. Debounce is not appropriate for events where each payload contains +unique data that must be processed, like `on_key_down`. +``` + +In the following example, the slider's `on_change` handler, `update_value`, is +only triggered on the backend when the slider value has not changed for half a +second. + +```python demo exec +class DebounceState(rx.State): + settled_value: int = 50 + + @rx.event + def update_value(self, value: list[int | float]): + self.settled_value = value[0] + + +def debounced_slider(): + return rx.slider( + key=rx.State.router.session.session_id, + default_value=[DebounceState.settled_value], + on_change=DebounceState.update_value.debounce(500), + width="100%", + ) + +def debounce_example(): + return rx.vstack( + debounced_slider(), + rx.text(f"Settled Value: {DebounceState.settled_value}"), + ) +``` + +```md alert info +# Why set key on the slider? + +Setting `key` to the `session_id` with a dynamic `default_value` ensures that +when the page is refreshed, the component will be re-rendered to reflect the +updated default_value from the state. + +Without the `key` set, the slider would always display the original +`settled_value` after a page reload, instead of its current value. +``` + +## Temporal Events + +_Added in [v0.6.6](https://github.com/reflex-dev/reflex/releases/tag/v0.6.6)_ + +### temporal + +The `.temporal` action prevents events from being queued when the backend is down. +This is useful for non-critical events where you do not want them to pile up if there is +a temporary connection issue. + +```md alert warning +# Temporal events are discarded when the backend is down. + +When the backend is unavailable, events with the `.temporal` action will be +discarded rather than queued for later processing. Only use this for events +where it is acceptable to lose some interactions during connection issues. +``` + +In the following example, the `rx.moment` component with `interval` and `on_change` uses `.temporal` to +prevent periodic updates from being queued when the backend is down: + +```python demo exec +class TemporalState(rx.State): + current_time: str = "" + + @rx.event + def update_time(self): + self.current_time = datetime.datetime.now().strftime("%H:%M:%S") + +def temporal_example(): + return rx.vstack( + rx.heading("Current Time:"), + rx.heading(TemporalState.current_time), + rx.moment( + interval=1000, + on_change=TemporalState.update_time.temporal, + ), + rx.text("Time updates will not be queued if the backend is down."), + ) +``` diff --git a/docs/events/event_arguments.md b/docs/events/event_arguments.md new file mode 100644 index 00000000000..42621f5856e --- /dev/null +++ b/docs/events/event_arguments.md @@ -0,0 +1,123 @@ +```python exec +import reflex as rx +``` + +# Event Arguments + +The event handler signature needs to match the event trigger definition argument count. If the event handler takes two arguments, the event trigger must be able to provide two arguments. + +Here is a simple example: + +```python demo exec + +class EventArgStateSlider(rx.State): + value: int = 50 + + @rx.event + def set_end(self, value: list[int | float]): + self.value = value[0] + + +def slider_max_min_step(): + return rx.vstack( + rx.heading(EventArgStateSlider.value), + rx.slider( + default_value=40, + on_value_commit=EventArgStateSlider.set_end, + ), + width="100%", + ) + +``` + +The event trigger here is `on_value_commit` and it is called when the value changes at the end of an interaction. This event trigger passes one argument, which is the value of the slider. The event handler which is triggered by the event trigger must therefore take one argument, which is `value` here. + +Here is a form example: + +```python demo exec + +class EventArgState(rx.State): + form_data: dict = {} + + @rx.event + def handle_submit(self, form_data: dict): + """Handle the form submit.""" + self.form_data = form_data + + +def event_arg_example(): + return rx.vstack( + rx.form( + rx.vstack( + rx.input( + placeholder="Name", + name="name", + ), + rx.checkbox("Checked", name="check"), + rx.button("Submit", type="submit"), + ), + on_submit=EventArgState.handle_submit, + reset_on_submit=True, + ), + rx.divider(), + rx.heading("Results"), + rx.text(EventArgState.form_data.to_string()), + ) +``` + +In this example the event trigger is the `on_submit` event of the form. The event handler is `handle_submit`. The `on_submit` event trigger passes one argument, the form data as a dictionary, to the `handle_submit` event handler. The `handle_submit` event handler must take one argument because the `on_submit` event trigger passes one argument. + +When the number of args accepted by an EventHandler differs from that provided by the event trigger, an `EventHandlerArgMismatch` error will be raised. + +## Pass Additional Arguments to Event Handlers + +In some use cases, you want to pass additional arguments to your event handlers. To do this you can bind an event trigger to a lambda, which can call your event handler with the arguments you want. + +Try typing a color in an input below and clicking away from it to change the color of the input. + +```python demo exec +class ArgState(rx.State): + colors: list[str] = ["rgba(245,168,152)", "MediumSeaGreen", "#DEADE3"] + + @rx.event + def change_color(self, color: str, index: int): + self.colors[index] = color + +def event_arguments_example(): + return rx.hstack( + rx.input(default_value=ArgState.colors[0], on_blur=lambda c: ArgState.change_color(c, 0), bg=ArgState.colors[0]), + rx.input(default_value=ArgState.colors[1], on_blur=lambda c: ArgState.change_color(c, 1), bg=ArgState.colors[1]), + rx.input(default_value=ArgState.colors[2], on_blur=lambda c: ArgState.change_color(c, 2), bg=ArgState.colors[2]), + ) + +``` + +In this case, in we want to pass two arguments to the event handler `change_color`, the color and the index of the color to change. + +The `on_blur` event trigger passes the text of the input as an argument to the lambda, and the lambda calls the `change_color` event handler with the text and the index of the input. + +When the number of args accepted by a lambda differs from that provided by the event trigger, an `EventFnArgMismatch` error will be raised. + +```md alert warning +# Event Handler Parameters should provide type annotations. + +Like state vars, be sure to provide the right type annotations for the parameters in an event handler. +``` + +## Events with Partial Arguments (Advanced) + +_Added in v0.5.0_ + +Event arguments in Reflex are passed positionally. Any additional arguments not +passed to an EventHandler will be filled in by the event trigger when it is +fired. + +The following two code samples are equivalent: + +```python +# Use a lambda to pass event trigger args to the EventHandler. +rx.text(on_blur=lambda v: MyState.handle_update("field1", v)) + +# Create a partial that passes event trigger args for any args not provided to the EventHandler. +rx.text(on_blur=MyState.handle_update("field1")) +``` diff --git a/docs/events/events_overview.md b/docs/events/events_overview.md new file mode 100644 index 00000000000..e67aa3bf5ef --- /dev/null +++ b/docs/events/events_overview.md @@ -0,0 +1,52 @@ +```python exec +import reflex as rx + +from pcweb.pages.docs.library import library +``` + +# Events Overview + +Events are composed of two parts: Event Triggers and Event Handlers. + +- **Events Handlers** are how the State of a Reflex application is updated. They are triggered by user interactions with the UI, such as clicking a button or hovering over an element. Events can also be triggered by the page loading or by other events. + +- **Event triggers** are component props that create an event to be sent to an event handler. +Each component supports a set of events triggers. They are described in each [component's documentation]({library.path}) in the event trigger section. + + +## Example +Lets take a look at an example below. Try mousing over the heading to change the word. + +```python demo exec +class WordCycleState(rx.State): + # The words to cycle through. + text: list[str] = ["Welcome", "to", "Reflex", "!"] + + # The index of the current word. + index: int = 0 + + @rx.event + def next_word(self): + self.index = (self.index + 1) % len(self.text) + + @rx.var + def get_text(self) -> str: + return self.text[self.index] + +def event_triggers_example(): + return rx.heading( + WordCycleState.get_text, + on_mouse_over=WordCycleState.next_word, + color="green", + ) + +``` + +In this example, the heading component has the **event trigger**, `on_mouse_over`. +Whenever the user hovers over the heading, the `next_word` **event handler** will be called to cycle the word. Once the handler returns, the UI will be updated to reflect the new state. + +Adding the `@rx.event` decorator above the event handler is strongly recommended. This decorator enables proper static type checking, which ensures event handlers receive the correct number and types of arguments. + +# What's in this section? + +In the event section of the documentation, you will explore the different types of events supported by Reflex, along with the different ways to call them. diff --git a/docs/events/page_load_events.md b/docs/events/page_load_events.md new file mode 100644 index 00000000000..3c4e9b68678 --- /dev/null +++ b/docs/events/page_load_events.md @@ -0,0 +1,40 @@ +```python exec +import reflex as rx +``` + +# Page Load Events + +You can also specify a function to run when the page loads. This can be useful for fetching data once vs on every render or state change. +In this example, we fetch data when the page loads: + +```python +class State(rx.State): + data: Dict[str, Any] + + @rx.event + def get_data(self): + # Fetch data + self.data = fetch_data() + +@rx.page(on_load=State.get_data) +def index(): + return rx.text('A Beautiful App') +``` + +Another example would be checking if the user is authenticated when the page loads. If the user is not authenticated, we redirect them to the login page. If they are authenticated, we don't do anything, letting them access the page. This `on_load` event would be placed on every page that requires authentication to access. + +```python +class State(rx.State): + authenticated: bool + + @rx.event + def check_auth(self): + # Check if user is authenticated + self.authenticated = check_auth() + if not self.authenticated: + return rx.redirect('/login') + +@rx.page(on_load=State.check_auth) +def index(): + return rx.text('A Beautiful App') +``` diff --git a/docs/events/setters.md b/docs/events/setters.md new file mode 100644 index 00000000000..243926b0e76 --- /dev/null +++ b/docs/events/setters.md @@ -0,0 +1,57 @@ +```python exec +import reflex as rx +``` + +# Setters + +Every base var has a built-in event handler to set it's value for convenience, called `set_VARNAME`. + +Say you wanted to change the value of the select component. You could write your own event handler to do this: + +```python demo exec + +options: list[str] = ["1", "2", "3", "4"] +class SetterState1(rx.State): + selected: str = "1" + + @rx.event + def change(self, value): + self.selected = value + + +def code_setter(): + return rx.vstack( + rx.badge(SetterState1.selected, color_scheme="green"), + rx.select( + options, + on_change= lambda value: SetterState1.change(value), + ) + ) + +``` + +Or you could could use a built-in setter for conciseness. + +```python demo exec + +options: list[str] = ["1", "2", "3", "4"] +class SetterState2(rx.State): + selected: str = "1" + + @rx.event + def set_selected(self, selected: str): + self.selected = selected + +def code_setter_2(): + return rx.vstack( + rx.badge(SetterState2.selected, color_scheme="green"), + rx.select( + options, + on_change= SetterState2.set_selected, + ) + ) +``` + +In this example, the setter for `selected` is `set_selected`. Both of these examples are equivalent. + +Setters are a great way to make your code more concise. But if you want to do something more complicated, you can always write your own function in the state. diff --git a/docs/events/special_events.md b/docs/events/special_events.md new file mode 100644 index 00000000000..0b6ae665e70 --- /dev/null +++ b/docs/events/special_events.md @@ -0,0 +1,28 @@ +```python exec +import reflex as rx + +from pcweb.pages.docs import api_reference +``` + +# Special Events + +Reflex also has built-in special events can be found in the [reference]({api_reference.special_events.path}). + +For example, an event handler can trigger an alert on the browser. + +```python demo exec +class SpecialEventsState(rx.State): + @rx.event + def alert(self): + return rx.window_alert("Hello World!") + +def special_events_example(): + return rx.button("Alert", on_click=SpecialEventsState.alert) +``` + +Special events can also be triggered directly in the UI by attaching them to an event trigger. + +```python +def special_events_example(): + return rx.button("Alert", on_click=rx.window_alert("Hello World!")) +``` diff --git a/docs/events/yield_events.md b/docs/events/yield_events.md new file mode 100644 index 00000000000..78bdb1c02c6 --- /dev/null +++ b/docs/events/yield_events.md @@ -0,0 +1,107 @@ +```python exec +import reflex as rx + +``` + +# Yielding Updates + +A regular event handler will send a `StateUpdate` when it has finished running. This works fine for basic event, but sometimes we need more complex logic. To update the UI multiple times in an event handler, we can `yield` when we want to send an update. + +To do so, we can use the Python keyword `yield`. For every yield inside the function, a `StateUpdate` will be sent to the frontend with the changes up to this point in the execution of the event handler. + +This example below shows how to yield 100 updates to the UI. + +```python demo exec + +class MultiUpdateState(rx.State): + count: int = 0 + + @rx.event + def timed_update(self): + for i in range(100): + self.count += 1 + yield + + +def multi_update(): + return rx.vstack( + rx.text(MultiUpdateState.count), + rx.button("Start", on_click=MultiUpdateState.timed_update) +) + +``` + +Here is another example of yielding multiple updates with a loading icon. + +```python demo exec + +import asyncio + +class ProgressExampleState(rx.State): + count: int = 0 + show_progress: bool = False + + @rx.event + async def increment(self): + self.show_progress = True + yield + # Think really hard. + await asyncio.sleep(0.5) + self.count += 1 + self.show_progress = False + +def progress_example(): + return rx.button( + ProgressExampleState.count, + on_click=ProgressExampleState.increment, + loading=ProgressExampleState.show_progress, + ) + +``` + +```md video https://youtube.com/embed/ITOZkzjtjUA?start=6463&end=6835 +# Video: Asyncio with Yield +``` + +## Yielding Other Events + +Events can also yield other events. This is useful when you want to chain events together. To do this, you can yield the event handler function itself. + +```md alert +# Reference other Event Handler via class + +When chaining another event handler with `yield`, access it via the state class, not `self`. +``` + +```python demo exec + +import asyncio + +class YieldEventsState(rx.State): + count: int = 0 + show_progress: bool = False + + @rx.event + async def add_five(self): + self.show_progress = True + yield + # Think really hard. + await asyncio.sleep(1) + self.count += 5 + self.show_progress = False + + @rx.event + async def increment(self): + yield YieldEventsState.add_five + yield YieldEventsState.add_five + yield YieldEventsState.add_five + + +def multiple_yield_example(): + return rx.button( + YieldEventsState.count, + on_click=YieldEventsState.increment, + loading=YieldEventsState.show_progress, + ) + +``` diff --git a/docs/getting_started/basics.md b/docs/getting_started/basics.md new file mode 100644 index 00000000000..baf4d69b420 --- /dev/null +++ b/docs/getting_started/basics.md @@ -0,0 +1,406 @@ +```python exec +from pcweb.pages.docs import components, getting_started +from pcweb.pages.docs.library import library +from pcweb.pages.docs.custom_components import custom_components +from pcweb.pages import docs +import reflex as rx +``` + +# Reflex Basics + +This page gives an introduction to the most common concepts that you will use to build Reflex apps. + +```md section +# You will learn how to: + +- Create and nest components +- Customize and style components +- Distinguish between compile-time and runtime +- Display data that changes over time +- Respond to events and update the screen +- Render conditions and lists +- Create pages and navigate between them +``` + +[Install]({docs.getting_started.installation.path}) `reflex` using pip. + +```bash +pip install reflex +``` + +Import the `reflex` library to get started. + +```python +import reflex as rx +``` + +## Creating and nesting components + +[Components]({docs.ui.overview.path}) are the building blocks for your app's user interface (UI). They are the visual elements that make up your app, like buttons, text, and images. Reflex has a wide selection of [built-in components]({library.path}) to get you started quickly. + +Components are created using functions that return a component object. + +```python demo exec +def my_button(): + return rx.button("Click Me") +``` + +Components can be nested inside each other to create complex UIs. + +To nest components as children, pass them as positional arguments to the parent component. In the example below, the `rx.text` and `my_button` components are children of the `rx.box` component. + +```python demo exec +def my_page(): + return rx.box( + rx.text("This is a page"), + # Reference components defined in other functions. + my_button() + ) +``` + +You can also use any base HTML element through the [`rx.el`]({docs.library.other.html.path}) namespace. This allows you to use standard HTML elements directly in your Reflex app when you need more control or when a specific component isn't available in the Reflex component library. + +```python demo exec +def my_div(): + return rx.el.div( + rx.el.p("Use base html!"), + ) +``` + +If you need a component not provided by Reflex, you can check the [3rd party ecosystem]({custom_components.path}) or [wrap your own React component]({docs.wrapping_react.library_and_tags.path}). + +## Customizing and styling components + +Components can be customized using [props]({docs.components.props.path}), which are passed in as keyword arguments to the component function. + +Each component has props that are specific to that component. Check the docs for the component you are using to see what props are available. + +```python demo exec +def half_filled_progress(): + return rx.progress(value=50) +``` + +In addition to component-specific props, components can also be styled using CSS properties passed as props. + +```python demo exec +def round_button(): + return rx.button("Click Me", border_radius="15px", font_size="18px") +``` + +```md alert +Use the `snake_case` version of the CSS property name as the prop name. +``` + +See the [styling guide]({docs.styling.overview.path}) for more information on how to style components + +In summary, components are made up of children and props. + +```md definition +# Children + +- Text or other Reflex components nested inside a component. +- Passed as **positional arguments**. + +# Props + +- Attributes that affect the behavior and appearance of a component. +- Passed as **keyword arguments**. +``` + +## Displaying data that changes over time + +Apps need to store and display data that changes over time. Reflex handles this through [State]({docs.state.overview.path}), which is a Python class that stores variables that can change when the app is running, as well as the functions that can change those variables. + +To define a state class, subclass `rx.State` and define fields that store the state of your app. The state variables ([vars]({docs.vars.base_vars.path})) should have a type annotation, and can be initialized with a default value. + +```python +class MyState(rx.State): + count: int = 0 +``` + +### Referencing state vars in components + +To reference a state var in a component, you can pass it as a child or prop. The component will automatically update when the state changes. + +Vars are referenced through class attributes on your state class. For example, to reference the `count` var in a component, use `MyState.count`. + +```python demo exec +class MyState(rx.State): + count: int = 0 + color: str = "red" + +def counter(): + return rx.hstack( + # The heading `color` prop is set to the `color` var in MyState. + rx.heading("Count: ", color=MyState.color), + # The `count` var in `MyState` is passed as a child to the heading component. + rx.heading(MyState.count), + ) +``` + +Vars can be referenced in multiple components, and will automatically update when the state changes. + +## Responding to events and updating the screen + +So far, we've defined state vars but we haven't shown how to change them. All state changes are handled through functions in the state class, called [event handlers]({docs.events.events_overview.path}). + +```md alert +Event handlers are the ONLY way to change state in Reflex. +``` + +Components have special props, such as `on_click`, called event triggers that can be used to make components interactive. Event triggers connect components to event handlers, which update the state. + +```python demo exec +class CounterState(rx.State): + count: int = 0 + + @rx.event + def increment(self): + self.count += 1 + +def counter_increment(): + return rx.hstack( + rx.heading(CounterState.count), + rx.button("Increment", on_click=CounterState.increment) + ) +``` + +When an event trigger is activated, the event handler is called, which updates the state. The UI is automatically re-rendered to reflect the new state. + + +```md alert info +# What is the `@rx.event` decorator? +Adding the `@rx.event` decorator above the event handler is strongly recommended. This decorator enables proper static type checking, which ensures event handlers receive the correct number and types of arguments. This was introduced in Reflex version 0.6.5. +``` + +### Event handlers with arguments + +Event handlers can also take in arguments. For example, the `increment` event handler can take an argument to increment the count by a specific amount. + +```python demo exec +class CounterState2(rx.State): + count: int = 0 + + @rx.event + def increment(self, amount: int): + self.count += amount + +def counter_variable(): + return rx.hstack( + rx.heading(CounterState2.count), + rx.button("Increment by 1", on_click=lambda: CounterState2.increment(1)), + rx.button("Increment by 5", on_click=lambda: CounterState2.increment(5)), + ) +``` + +The `on_click` event trigger doesn't pass any arguments here, but some event triggers do. For example, the `on_blur` event trigger passes the text of an input as an argument to the event handler. + +```python demo exec +class TextState(rx.State): + text: str = "" + + @rx.event + def update_text(self, new_text: str): + self.text = new_text + +def text_input(): + return rx.vstack( + rx.heading(TextState.text), + rx.input(default_value=TextState.text, on_blur=TextState.update_text), + ) +``` + +```md alert +Make sure that the event handler has the same number of arguments as the event trigger, or an error will be raised. +``` + +## Compile-time vs. runtime (IMPORTANT) + +Before we dive deeper into state, it's important to understand the difference between compile-time and runtime in Reflex. + +When you run your app, the frontend gets compiled to Javascript code that runs in the browser (compile-time). The backend stays in Python and runs on the server during the lifetime of the app (runtime). + +### When can you not use pure Python? + +We cannot compile arbitrary Python code, only the components that you define. What this means importantly is that you cannot use arbitrary Python operations and functions on state vars in components. + +However, since any event handlers in your state are on the backend, you **can use any Python code or library** within your state. + +### Examples that work + +Within an event handler, use any Python code or library. + +```python demo exec +def check_even(num: int): + return num % 2 == 0 + +class MyState3(rx.State): + count: int = 0 + text: str = "even" + + @rx.event + def increment(self): + # Use any Python code within state. + # Even reference functions defined outside the state. + if check_even(self.count): + self.text = "even" + else: + self.text = "odd" + self.count += 1 + +def count_and_check(): + return rx.box( + rx.heading(MyState3.text), + rx.button("Increment", on_click=MyState3.increment) + ) +``` + +Use any Python function within components, as long as it is defined at compile time (i.e. does not reference any state var) + +```python demo exec +def show_numbers(): + return rx.vstack( + *[ + rx.hstack(i, check_even(i)) + for i in range(10) + ] + ) +``` + +### Examples that don't work + +You cannot do an `if` statement on vars in components, since the value is not known at compile time. + +```python +class BadState(rx.State): + count: int = 0 + +def count_if_even(): + return rx.box( + rx.heading("Count: "), + # This will raise a compile error, as BadState.count is a var and not known at compile time. + rx.text(BadState.count if BadState.count % 2 == 0 else "Odd"), + # Using an if statement with a var as a prop will NOT work either. + rx.text("hello", color="red" if BadState.count % 2 == 0 else "blue"), + ) +``` + +You cannot do a `for` loop over a list of vars. + +```python +class BadState(rx.State): + items: list[str] = ["Apple", "Banana", "Cherry"] + +def loop_over_list(): + return rx.box( + # This will raise a compile error, as BadState.items is a list and not known at compile time. + *[rx.text(item) for item in BadState.items] + ) +``` + +You cannot do arbitrary Python operations on state vars in components. + +```python +class BadTextState(rx.State): + text: str = "Hello world" + +def format_text(): + return rx.box( + # Python operations such as `len` will not work on state vars. + rx.text(len(BadTextState.text)), + ) +``` + +In the next sections, we will show how to handle these cases. + +## Conditional rendering + +As mentioned above, you cannot use Python `if/else` statements with state vars in components. Instead, use the [`rx.cond`]({docs.components.conditional_rendering.path}) function to conditionally render components. + +```python demo exec +class LoginState(rx.State): + logged_in: bool = False + + @rx.event + def toggle_login(self): + self.logged_in = not self.logged_in + +def show_login(): + return rx.box( + rx.cond( + LoginState.logged_in, + rx.heading("Logged In"), + rx.heading("Not Logged In"), + ), + rx.button("Toggle Login", on_click=LoginState.toggle_login) + ) +``` + +## Rendering lists + +To iterate over a var that is a list, use the [`rx.foreach`]({docs.components.rendering_iterables.path}) function to render a list of components. + +Pass the list var and a function that returns a component as arguments to `rx.foreach`. + +```python demo exec +class ListState(rx.State): + items: list[str] = ["Apple", "Banana", "Cherry"] + +def render_item(item: rx.Var[str]): + """Render a single item.""" + # Note that item here is a Var, not a str! + return rx.list.item(item) + +def show_fruits(): + return rx.box( + rx.foreach(ListState.items, render_item), + ) +``` + +The function that renders each item takes in a `Var`, since this will get compiled up front. + +## Var Operations + +You can't use arbitrary Python operations on state vars in components, but Reflex has [var operations]({docs.vars.var_operations.path}) that you can use to manipulate state vars. + +For example, to check if a var is even, you can use the `%` and `==` var operations. + +```python demo exec +class CountEvenState(rx.State): + count: int = 0 + + @rx.event + def increment(self): + self.count += 1 + +def count_if_even(): + return rx.box( + rx.heading("Count: "), + rx.cond( + # Here we use the `%` and `==` var operations to check if the count is even. + CountEvenState.count % 2 == 0, + rx.text("Even"), + rx.text("Odd"), + ), + rx.button("Increment", on_click=CountEvenState.increment), + ) +``` + +## App and Pages + +Reflex apps are created by instantiating the `rx.App` class. Pages are linked to specific URL routes, and are created by defining a function that returns a component. + +```python +def index(): + return rx.text('Root Page') + +rx.app = rx.App() +app.add_page(index, route="/") +``` + +## Next Steps + +Now that you have a basic understanding of how Reflex works, the next step is to start coding your own apps. Try one of the following tutorials: + +- [Dashboard Tutorial]({getting_started.dashboard_tutorial.path}) +- [Chatapp Tutorial]({getting_started.chatapp_tutorial.path}) diff --git a/docs/getting_started/chat_tutorial_style.py b/docs/getting_started/chat_tutorial_style.py new file mode 100644 index 00000000000..fe8ff74d610 --- /dev/null +++ b/docs/getting_started/chat_tutorial_style.py @@ -0,0 +1,27 @@ +# Common styles for questions and answers. +import reflex as rx + +shadow = "rgba(0, 0, 0, 0.15) 0px 2px 8px" +chat_margin = "20%" +message_style = { + "padding": "1em", + "border_radius": "5px", + "margin_y": "0.5em", + "box_shadow": shadow, + "max_width": "30em", + "display": "inline-block", +} + +# Set specific styles for questions and answers. +question_style = message_style | { + "background_color": rx.color("gray", 4), + "margin_left": chat_margin, +} +answer_style = message_style | { + "background_color": rx.color("accent", 8), + "margin_right": chat_margin, +} + +# Styles for the action bar. +input_style = {"border_width": "1px", "box_shadow": shadow, "width": "350px"} +button_style = {"background_color": rx.color("accent", 10), "box_shadow": shadow} diff --git a/docs/getting_started/chat_tutorial_utils.py b/docs/getting_started/chat_tutorial_utils.py new file mode 100644 index 00000000000..9b3f27988c8 --- /dev/null +++ b/docs/getting_started/chat_tutorial_utils.py @@ -0,0 +1,86 @@ +from __future__ import annotations + +import os + +import openai +import reflex as rx + +OPENAI_API_KEY = os.environ.get("OPENAI_API_KEY") + + +class ChatappState(rx.State): + # The current question being asked. + question: str + + # Keep track of the chat history as a list of (question, answer) tuples. + chat_history: list[tuple[str, str]] + + def set_question(self, q: str): + self.question = q + + def set_question1(self, q: str): + self.question = q + + def set_question2(self, q: str): + self.question = q + + def set_question3(self, q: str): + self.question = q + + def answer(self) -> None: + # Our chatbot is not very smart right now... + answer = "I don't know!" + self.chat_history.append((self.question, answer)) + + def answer2(self) -> None: + # Our chatbot is not very smart right now... + answer = "I don't know!" + self.chat_history.append((self.question, answer)) + # Clear the question input. + self.question = "" + + async def answer3(self): + import asyncio + + # Our chatbot is not very smart right now... + answer = "I don't know!" + self.chat_history.append((self.question, "")) + + # Clear the question input. + self.question = "" + # Yield here to clear the frontend input before continuing. + yield + + for i in range(len(answer)): + await asyncio.sleep(0.1) + self.chat_history[-1] = (self.chat_history[-1][0], answer[: i + 1]) + yield + + async def answer4(self): + # Our chatbot has some brains now! + client = openai.AsyncOpenAI(api_key=OPENAI_API_KEY) + session = await client.chat.completions.create( + model="gpt-4o-mini", + messages=[{"role": "user", "content": self.question}], + stop=None, + temperature=0.7, + stream=True, + ) + + # Add to the answer as the chatbot responds. + answer = "" + self.chat_history.append((self.question, answer)) + + # Clear the question input. + self.question = "" + # Yield here to clear the frontend input before continuing. + yield + + async for item in session: + if hasattr(item.choices[0].delta, "content"): + if item.choices[0].delta.content is None: + # presence of 'None' indicates the end of the response + break + answer += item.choices[0].delta.content + self.chat_history[-1] = (self.chat_history[-1][0], answer) + yield diff --git a/docs/getting_started/chatapp_tutorial.md b/docs/getting_started/chatapp_tutorial.md new file mode 100644 index 00000000000..fc2c0e36fd2 --- /dev/null +++ b/docs/getting_started/chatapp_tutorial.md @@ -0,0 +1,807 @@ +```python exec +import os + +import reflex as rx +import openai + +from pcweb.constants import CHAT_APP_URL +from pcweb import constants +from pcweb.pages.docs import components +from pcweb.pages.docs import styling +from pcweb.pages.docs import library +from pcweb.pages.docs import events +from pcweb.pages.docs import state +from pcweb.pages.docs import hosting + +from docs.getting_started import chat_tutorial_style as style +from docs.getting_started.chat_tutorial_utils import ChatappState + +# If it's in environment, no need to hardcode (openai SDK will pick it up) +if "OPENAI_API_KEY" not in os.environ: + openai.api_key = "YOUR_OPENAI_KEY" + +``` + +# Interactive Tutorial: AI Chat App + +This tutorial will walk you through building an AI chat app with Reflex. This app is fairly complex, but don't worry - we'll break it down into small steps. + +You can find the full source code for this app [here]({CHAT_APP_URL}). + +### What You'll Learn + +In this tutorial you'll learn how to: + +1. Install `reflex` and set up your development environment. +2. Create components to define and style your UI. +3. Use state to add interactivity to your app. +4. Deploy your app to share with others. + + + + +## Setting up Your Project + +```md video https://youtube.com/embed/ITOZkzjtjUA?start=175&end=445 +# Video: Example of Setting up the Chat App +``` + +We will start by creating a new project and setting up our development environment. First, create a new directory for your project and navigate to it. + +```bash +~ $ mkdir chatapp +~ $ cd chatapp +``` + +Next, we will create a virtual environment for our project. This is optional, but recommended. In this example, we will use [venv]({constants.VENV_URL}) to create our virtual environment. + +```bash +chatapp $ python3 -m venv venv +$ source venv/bin/activate +``` + +Now, we will install Reflex and create a new project. This will create a new directory structure in our project directory. + +> **Note:** When prompted to select a template, choose option 0 for a blank project. + + +```bash +chatapp $ pip install reflex +chatapp $ reflex init +────────────────────────────────── Initializing chatapp ─────────────────────────────────── +Success: Initialized chatapp +chatapp $ ls +assets chatapp rxconfig.py venv +``` + +```python eval +rx.box(height="20px") +``` +You can run the template app to make sure everything is working. + +```bash +chatapp $ reflex run +─────────────────────────────────── Starting Reflex App ─────────────────────────────────── +Compiling: ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 100% 1/1 0:00:00 +─────────────────────────────────────── App Running ─────────────────────────────────────── +App running at: http://localhost:3000 +``` + +```python eval +rx.box(height="20px") +``` + +You should see your app running at [http://localhost:3000]({"http://localhost:3000"}). + +Reflex also starts the backend server which handles all the state management and communication with the frontend. You can test the backend server is running by navigating to [http://localhost:8000/ping]({"http://localhost:8000/ping"}). + +Now that we have our project set up, in the next section we will start building our app! + + + + +## Basic Frontend + +Let's start with defining the frontend for our chat app. In Reflex, the frontend can be broken down into independent, reusable components. See the [components docs]({components.props.path}) for more information. + +### Display A Question And Answer + +We will modify the `index` function in `chatapp/chatapp.py` file to return a component that displays a single question and answer. + +```python demo box +rx.container( + rx.box( + "What is Reflex?", + # The user's question is on the right. + text_align="right", + ), + rx.box( + "A way to build web apps in pure Python!", + # The answer is on the left. + text_align="left", + ), +) +``` + +```python +# chatapp.py + +import reflex as rx + +def index() -> rx.Component: + return rx.container( + rx.box( + "What is Reflex?", + # The user's question is on the right. + text_align="right", + ), + rx.box( + "A way to build web apps in pure Python!", + # The answer is on the left. + text_align="left", + ), + ) + + +# Add state and page to the app. +app = rx.App() +app.add_page(index) +``` + +Components can be nested inside each other to create complex layouts. Here we create a parent container that contains two boxes for the question and answer. + +We also add some basic styling to the components. Components take in keyword arguments, called [props]({components.props.path}), that modify the appearance and functionality of the component. We use the `text_align` prop to align the text to the left and right. + +### Reusing Components + +Now that we have a component that displays a single question and answer, we can reuse it to display multiple questions and answers. We will move the component to a separate function `question_answer` and call it from the `index` function. + +```python exec +def qa(question: str, answer: str) -> rx.Component: + return rx.box( + rx.box(question, text_align="right"), + rx.box(answer, text_align="left"), + margin_y="1em", + ) + + +qa_pairs = [ + ("What is Reflex?", "A way to build web apps in pure Python!"), + ( + "What can I make with it?", + "Anything from a simple website to a complex web app!", + ), +] + + +def chat() -> rx.Component: + qa_pairs = [ + ("What is Reflex?", "A way to build web apps in pure Python!"), + ( + "What can I make with it?", + "Anything from a simple website to a complex web app!", + ), + ] + return rx.box(*[qa(question, answer) for question, answer in qa_pairs]) +``` + +```python demo box +rx.container(chat()) +``` + +```python +def qa(question: str, answer: str) -> rx.Component: + return rx.box( + rx.box(question, text_align="right"), + rx.box(answer, text_align="left"), + margin_y="1em", + ) + + +def chat() -> rx.Component: + qa_pairs = [ + ("What is Reflex?", "A way to build web apps in pure Python!"), + ("What can I make with it?", "Anything from a simple website to a complex web app!"), + ] + return rx.box(*[qa(question, answer) for question, answer in qa_pairs]) + + +def index() -> rx.Component: + return rx.container(chat()) +``` + +### Chat Input + +Now we want a way for the user to input a question. For this, we will use the [input]({library.forms.input.path}) component to have the user add text and a [button]({library.forms.button.path}) component to submit the question. + +```python exec +def action_bar() -> rx.Component: + return rx.hstack( + rx.input(placeholder="Ask a question"), + rx.button("Ask"), + ) +``` + +```python demo box +rx.container( + chat(), + action_bar(), +) +``` + +```python +def action_bar() -> rx.Component: + return rx.hstack( + rx.input(placeholder="Ask a question"), + rx.button("Ask"), + ) + +def index() -> rx.Component: + return rx.container( + chat(), + action_bar(), + ) +``` + +### Styling + +Let's add some styling to the app. More information on styling can be found in the [styling docs]({styling.overview.path}). To keep our code clean, we will move the styling to a separate file `chatapp/style.py`. + +```python +# style.py +import reflex as rx + +# Common styles for questions and answers. +shadow = "rgba(0, 0, 0, 0.15) 0px 2px 8px" +chat_margin = "20%" +message_style = dict( + padding="1em", + border_radius="5px", + margin_y="0.5em", + box_shadow=shadow, + max_width="30em", + display="inline-block", +) + +# Set specific styles for questions and answers. +question_style = message_style | dict(margin_left=chat_margin, background_color=rx.color("gray", 4)) +answer_style = message_style | dict(margin_right=chat_margin, background_color=rx.color("accent", 8)) + +# Styles for the action bar. +input_style = dict( + border_width="1px", padding="0.5em", box_shadow=shadow,width="350px" +) +button_style = dict(background_color=rx.color("accent", 10), box_shadow=shadow) +``` + +We will import the styles in `chatapp.py` and use them in the components. At this point, the app should look like this: + +```python exec +def qa4(question: str, answer: str) -> rx.Component: + return rx.box( + rx.box(rx.text(question, style=style.question_style), text_align="right"), + rx.box(rx.text(answer, style=style.answer_style), text_align="left"), + margin_y="1em", + width="100%", + ) + + +def chat4() -> rx.Component: + qa_pairs = [ + ("What is Reflex?", "A way to build web apps in pure Python!"), + ( + "What can I make with it?", + "Anything from a simple website to a complex web app!", + ), + ] + return rx.box(*[qa4(question, answer) for question, answer in qa_pairs]) + + +def action_bar4() -> rx.Component: + return rx.hstack( + rx.input(placeholder="Ask a question", style=style.input_style), + rx.button("Ask", style=style.button_style), + ) +``` + +```python demo box +rx.center( + rx.vstack( + chat4(), + action_bar4(), + align="center", + ) +) +``` + +```python +# chatapp.py +import reflex as rx + +from chatapp import style + + +def qa(question: str, answer: str) -> rx.Component: + return rx.box( + rx.box(rx.text(question, style=style.question_style), text_align="right"), + rx.box(rx.text(answer, style=style.answer_style), text_align="left"), + margin_y="1em", + width="100%", + ) + +def chat() -> rx.Component: + qa_pairs = [ + ("What is Reflex?", "A way to build web apps in pure Python!"), + ("What can I make with it?", "Anything from a simple website to a complex web app!"), + ] + return rx.box(*[qa(question, answer) for question, answer in qa_pairs]) + + +def action_bar() -> rx.Component: + return rx.hstack( + rx.input(placeholder="Ask a question", style=style.input_style), + rx.button("Ask", style=style.button_style), + ) + + +def index() -> rx.Component: + return rx.center( + rx.vstack( + chat(), + action_bar(), + align="center", + ) + ) + + +app = rx.App() +app.add_page(index) +``` + +The app is looking good, but it's not very useful yet! In the next section, we will add some functionality to the app. + + + + + + +## State + +Now let’s make the chat app interactive by adding state. The state is where we define all the variables that can change in the app and all the functions that can modify them. You can learn more about state in the [state docs]({state.overview.path}). + +### Defining State + +We will create a new file called `state.py` in the `chatapp` directory. Our state will keep track of the current question being asked and the chat history. We will also define an event handler `answer` which will process the current question and add the answer to the chat history. + +```python +# state.py +import reflex as rx + + +class State(rx.State): + + # The current question being asked. + question: str + + # Keep track of the chat history as a list of (question, answer) tuples. + chat_history: list[tuple[str, str]] + + @rx.event + def answer(self): + # Our chatbot is not very smart right now... + answer = "I don't know!" + self.chat_history.append((self.question, answer)) + +``` + +### Binding State to Components + +Now we can import the state in `chatapp.py` and reference it in our frontend components. We will modify the `chat` component to use the state instead of the current fixed questions and answers. + +```python exec +def qa(question: str, answer: str) -> rx.Component: + return rx.box( + rx.box(rx.text(question, style=style.question_style), text_align="right"), + rx.box(rx.text(answer, style=style.answer_style), text_align="left"), + margin_y="1em", + width="100%", + ) + + +def chat1() -> rx.Component: + return rx.box( + rx.foreach( + ChatappState.chat_history, lambda messages: qa(messages[0], messages[1]) + ) + ) + + +def action_bar1() -> rx.Component: + return rx.hstack( + rx.input( + placeholder="Ask a question", + on_change=ChatappState.set_question, + style=style.input_style, + ), + rx.button("Ask", on_click=ChatappState.answer, style=style.button_style), + ) +``` + +```python demo box +rx.container( + chat1(), + action_bar1(), +) +``` + +```python +# chatapp.py +from chatapp.state import State + + +def chat() -> rx.Component: + return rx.box( + rx.foreach( + State.chat_history, + lambda messages: qa(messages[0], messages[1]) + ) + ) + + + +def action_bar() -> rx.Component: + return rx.hstack( + rx.input(placeholder="Ask a question", on_change=State.set_question1, style=style.input_style), + rx.button("Ask", on_click=State.answer, style=style.button_style), + ) +``` + +Normal Python `for` loops don't work for iterating over state vars because these values can change and aren't known at compile time. Instead, we use the [foreach]({library.dynamic_rendering.foreach.path}) component to iterate over the chat history. + +We also bind the input's `on_change` event to the `set_question` event handler, which will update the `question` state var while the user types in the input. We bind the button's `on_click` event to the `answer` event handler, which will process the question and add the answer to the chat history. The `set_question` event handler is a built-in implicitly defined event handler. Every base var has one. Learn more in the [events docs]({events.setters.path}) under the Setters section. + +### Clearing the Input + +Currently the input doesn't clear after the user clicks the button. We can fix this by binding the value of the input to `question`, with `value=State.question`, and clear it when we run the event handler for `answer`, with `self.question = ''`. + +```python exec +def action_bar2() -> rx.Component: + return rx.hstack( + rx.input( + value=ChatappState.question, + placeholder="Ask a question", + on_change=ChatappState.set_question, + style=style.input_style, + ), + rx.button("Ask", on_click=ChatappState.answer2, style=style.button_style), + ) +``` + +```python demo box +rx.container( + chat1(), + action_bar2(), +) +``` + +```python +# chatapp.py +def action_bar() -> rx.Component: + return rx.hstack( + rx.input( + value=State.question, + placeholder="Ask a question", + on_change=State.set_question2, + style=style.input_style), + rx.button("Ask", on_click=State.answer, style=style.button_style), + ) +``` + +```python +# state.py +@rx.event +def answer(self): + # Our chatbot is not very smart right now... + answer = "I don't know!" + self.chat_history.append((self.question, answer)) + self.question = "" +``` + +### Streaming Text + +Normally state updates are sent to the frontend when an event handler returns. However, we want to stream the text from the chatbot as it is generated. We can do this by yielding from the event handler. See the [yield events docs]({events.yield_events.path}) for more info. + +```python exec +def action_bar3() -> rx.Component: + return rx.hstack( + rx.input( + value=ChatappState.question, + placeholder="Ask a question", + on_change=ChatappState.set_question, + style=style.input_style, + ), + rx.button("Ask", on_click=ChatappState.answer3, style=style.button_style), + ) +``` + +```python demo box +rx.container( + chat1(), + action_bar3(), +) +``` + +```python +# state.py +import asyncio + +async def answer(self): + # Our chatbot is not very smart right now... + answer = "I don't know!" + self.chat_history.append((self.question, "")) + + # Clear the question input. + self.question = "" + # Yield here to clear the frontend input before continuing. + yield + + for i in range(len(answer)): + # Pause to show the streaming effect. + await asyncio.sleep(0.1) + # Add one letter at a time to the output. + self.chat_history[-1] = (self.chat_history[-1][0], answer[:i + 1]) + yield +``` + +In the next section, we will finish our chatbot by adding AI! + + + +## Final App + +We will use OpenAI's API to give our chatbot some intelligence. + +### Configure the OpenAI API Key + +First, ensure you have an active OpenAI subscription. +Next, install the latest openai package: +```bash +pip install --upgrade openai +``` + +Direct Configuration of API in Code + +Update the state.py file to include your API key directly: + +```python +# state.py +import os +from openai import AsyncOpenAI + +import reflex as rx + +# Initialize the OpenAI client +client = AsyncOpenAI(api_key="YOUR_OPENAI_API_KEY") # Replace with your actual API key + +``` + +### Using the API + +Making your chatbot intelligent requires connecting to a language model API. This section explains how to integrate with OpenAI's API to power your chatbot's responses. + +1. First, the user types a prompt that is updated via the `on_change` event handler. +2. Next, when a prompt is ready, the user can choose to submit it by clicking the `Ask` button which in turn triggers the `State.answer` method inside our `state.py` file. +3. Finally, if the method is triggered, the `prompt` is sent via a request to OpenAI client and returns an answer that we can trim and use to update the chat history! + + +```python +# chatapp.py +def action_bar() -> rx.Component: + return rx.hstack( + rx.input( + value=State.question, + placeholder="Ask a question", + # on_change event updates the input as the user types a prompt. + on_change=State.set_question3, + style=style.input_style), + + # on_click event triggers the API to send the prompt to OpenAI. + rx.button("Ask", on_click=State.answer, style=style.button_style), + ) +``` + +```python +# state.py +import os + +from openai import AsyncOpenAI + +@rx.event +async def answer(self): + # Our chatbot has some brains now! + client = AsyncOpenAI(api_key=os.environ["OPENAI_API_KEY"]) + + session = await client.chat.completions.create( + model="gpt-4o-mini", + messages=[ + \{"role": "user", "content": self.question} + ], + stop=None, + temperature=0.7, + stream=True, + ) + + # Add to the answer as the chatbot responds. + answer = "" + self.chat_history.append((self.question, answer)) + + # Clear the question input. + self.question = "" + # Yield here to clear the frontend input before continuing. + yield + + async for item in session: + if hasattr(item.choices[0].delta, "content"): + if item.choices[0].delta.content is None: + # presence of 'None' indicates the end of the response + break + answer += item.choices[0].delta.content + self.chat_history[-1] = (self.chat_history[-1][0], answer) + yield +``` + +Finally, we have our chatbot! + +### Final Code + +This application is a simple, interactive chatbot built with Reflex that leverages OpenAI's API for intelligent responses. The chatbot features a clean interface with streaming responses for a natural conversation experience. + +Key Features + +1. Real-time streaming responses +2. Clean, visually distinct chat bubbles for questions and answers +3. Simple input interface with question field and submit button + +Project Structure + +Below is the full chatbot code with a commented title that corresponds to the filename. + +```text +chatapp/ +├── chatapp.py # UI components and app setup +├── state.py # State management and API integration +└── style.py # Styling definitions +``` + +The `chatapp.py` file: + +```python +import reflex as rx +from chatapp import style +from chatapp.state import State + +def qa(question: str, answer: str) -> rx.Component: + return rx.box( + rx.box(rx.text(question, style=style.question_style), text_align="right"), + rx.box(rx.text(answer, style=style.answer_style), text_align="left"), + margin_y="1em", + ) + +def chat() -> rx.Component: + return rx.box( + rx.foreach( + State.chat_history, + lambda messages: qa(messages[0], messages[1]), + ) + ) + +def action_bar() -> rx.Component: + return rx.hstack( + rx.input( + value=State.question, + placeholder="Ask a question", + on_change=State.set_question, + style=style.input_style, + ), + rx.button( + "Ask", + on_click=State.answer, + style=style.button_style, + ), + ) + +def index() -> rx.Component: + return rx.center( + rx.vstack( + chat(), + action_bar(), + align="center", + ) + ) + +app = rx.App() +app.add_page(index) +``` + + +The `state.py` file: + +```python +import os +from openai import AsyncOpenAI +import reflex as rx + +class State(rx.State): + question: str + chat_history: list[tuple[str, str]] = [] + + async def answer(self): + client = AsyncOpenAI(api_key=os.environ["OPENAI_API_KEY"]) + + # Start streaming completion from OpenAI + session = await client.chat.completions.create( + model="gpt-4o-mini", + messages=[ + \{"role": "user", "content": self.question} + ], + temperature=0.7, + stream=True, + ) + + # Initialize response and update UI + answer = "" + self.chat_history.append((self.question, answer)) + self.question = "" + yield + + # Process streaming response + async for item in session: + if hasattr(item.choices[0].delta, "content"): + if item.choices[0].delta.content is None: + break + answer += item.choices[0].delta.content + self.chat_history[-1] = (self.chat_history[-1][0], answer) + yield +``` + + +The `style.py` file: + +```python +import reflex as rx + +# Common style base +shadow = "rgba(0, 0, 0, 0.15) 0px 2px 8px" +chat_margin = "20%" +message_style = dict( + padding="1em", + border_radius="5px", + margin_y="0.5em", + box_shadow=shadow, + max_width="30em", + display="inline-block", +) + +# Styles for questions and answers +question_style = message_style | dict( + margin_left=chat_margin, + background_color=rx.color("gray", 4), +) +answer_style = message_style | dict( + margin_right=chat_margin, + background_color=rx.color("accent", 8), +) + +# Styles for input elements +input_style = dict(border_width="1px", padding="0.5em", box_shadow=shadow, width="350px") +button_style = dict(background_color=rx.color("accent", 10), box_shadow=shadow) +``` + + +### Next Steps + +Congratulations! You have built your first chatbot. From here, you can read through the rest of the documentations to learn about Reflex in more detail. The best way to learn is to build something, so try to build your own app using this as a starting point! + +### One More Thing + +With our hosting service, you can deploy this app with a single command within minutes. Check out our [Hosting Quick Start]({hosting.deploy_quick_start.path}). diff --git a/docs/getting_started/dashboard_tutorial.md b/docs/getting_started/dashboard_tutorial.md new file mode 100644 index 00000000000..7866cd40912 --- /dev/null +++ b/docs/getting_started/dashboard_tutorial.md @@ -0,0 +1,1840 @@ +```python exec +import reflex as rx +from pcweb.pages import docs +``` + +# Tutorial: Data Dashboard + +During this tutorial you will build a small data dashboard, where you can input data and it will be rendered in table and a graph. This tutorial does not assume any existing Reflex knowledge, but we do recommend checking out the quick [Basics Guide]({docs.getting_started.basics.path}) first. + +The techniques you’ll learn in the tutorial are fundamental to building any Reflex app, and fully understanding it will give you a deep understanding of Reflex. + + +This tutorial is divided into several sections: + +- **Setup for the Tutorial**: A starting point to follow the tutorial +- **Overview**: The fundamentals of Reflex UI (components and props) +- **Showing Dynamic Data**: How to use State to render data that will change in your app. +- **Add Data to your App**: Using a Form to let a user add data to your app and introduce event handlers. +- **Plotting Data in a Graph**: How to use Reflex's graphing components. +- **Final Cleanup and Conclusion**: How to further customize your app and add some extra styling to it. + +### What are you building? + +In this tutorial, you are building an interactive data dashboard with Reflex. + +You can see what the finished app and code will look like here: + + +```python exec +from collections import Counter + +class User(rx.Base): + """The user model.""" + + name: str + email: str + gender: str + +class State5(rx.State): + users: list[User] = [ + User(name="Danilo Sousa", email="danilo@example.com", gender="Male"), + User(name="Zahra Ambessa", email="zahra@example.com", gender="Female"), + ] + users_for_graph: list[dict] = [] + + def add_user(self, form_data: dict): + self.users.append(User(**form_data)) + self.transform_data() + + return rx.toast.info( + f"User {form_data['name']} has been added.", + position="bottom-right", + ) + + def transform_data(self): + """Transform user gender group data into a format suitable for visualization in graphs.""" + # Count users of each gender group + gender_counts = Counter(user.gender for user in self.users) + + # Transform into list of dict so it can be used in the graph + self.users_for_graph = [ + { + "name": gender_group, + "value": count + } + for gender_group, count in gender_counts.items() + ] + + +def show_user5(user: User): + """Show a user in a table row.""" + return rx.table.row( + rx.table.cell(user.name), + rx.table.cell(user.email), + rx.table.cell(user.gender), + style={"_hover": {"bg": rx.color("gray", 3)}}, + align="center", + ) + +def add_customer_button5() -> rx.Component: + return rx.dialog.root( + rx.dialog.trigger( + rx.button( + rx.icon("plus", size=26), + rx.text("Add User", size="4"), + ), + ), + rx.dialog.content( + rx.dialog.title( + "Add New User", + ), + rx.dialog.description( + "Fill the form with the user's info", + ), + rx.form( + rx.flex( + rx.input( + placeholder="User Name", name="name", required=True + ), + rx.input( + placeholder="user@reflex.dev", + name="email", + ), + rx.select( + ["Male", "Female"], + placeholder="male", + name="gender", + ), + rx.flex( + rx.dialog.close( + rx.button( + "Cancel", + variant="soft", + color_scheme="gray", + ), + ), + rx.dialog.close( + rx.button( + "Submit", type="submit" + ), + ), + spacing="3", + justify="end", + ), + direction="column", + spacing="4", + ), + on_submit=State5.add_user, + reset_on_submit=False, + ), + max_width="450px", + ), + ) + +def graph5(): + return rx.recharts.bar_chart( + rx.recharts.bar( + data_key="value", + stroke=rx.color("accent", 9), + fill=rx.color("accent", 8), + ), + rx.recharts.x_axis(data_key="name"), + rx.recharts.y_axis(), + data=State5.users_for_graph, + width="100%", + height=250, + ) +``` + +```python eval +rx.vstack( + add_customer_button5(), + rx.table.root( + rx.table.header( + rx.table.row( + rx.table.column_header_cell("Name"), + rx.table.column_header_cell("Email"), + rx.table.column_header_cell("Gender"), + ), + ), + rx.table.body( + rx.foreach( + State5.users, show_user5 + ), + ), + variant="surface", + size="3", + width="100%", + ), + graph5(), + align="center", + width="100%", + on_mouse_enter=State5.transform_data, + border_width="2px", + border_radius="10px", + padding="1em", + ) +``` + +```python +import reflex as rx +from collections import Counter + +class User(rx.Base): + """The user model.""" + + name: str + email: str + gender: str + + +class State(rx.State): + users: list[User] = [ + User(name="Danilo Sousa", email="danilo@example.com", gender="Male"), + User(name="Zahra Ambessa", email="zahra@example.com", gender="Female"), + ] + users_for_graph: list[dict] = [] + + def add_user(self, form_data: dict): + self.users.append(User(**form_data)) + self.transform_data() + + def transform_data(self): + """Transform user gender group data into a format suitable for visualization in graphs.""" + # Count users of each gender group + gender_counts = Counter(user.gender for user in self.users) + + # Transform into list of dict so it can be used in the graph + self.users_for_graph = [ + { + "name": gender_group, + "value": count + } + for gender_group, count in gender_counts.items() + ] + + +def show_user(user: User): + """Show a user in a table row.""" + return rx.table.row( + rx.table.cell(user.name), + rx.table.cell(user.email), + rx.table.cell(user.gender), + style={ + "_hover": { + "bg": rx.color("gray", 3) + } + }, + align="center", + ) + +def add_customer_button() -> rx.Component: + return rx.dialog.root( + rx.dialog.trigger( + rx.button( + rx.icon("plus", size=26), + rx.text("Add User", size="4"), + ), + ), + rx.dialog.content( + rx.dialog.title( + "Add New User", + ), + rx.dialog.description( + "Fill the form with the user's info", + ), + rx.form( + rx.flex( + rx.input( + placeholder="User Name", name="name", required=True + ), + rx.input( + placeholder="user@reflex.dev", + name="email", + ), + rx.select( + ["Male", "Female"], + placeholder="male", + name="gender", + ), + rx.flex( + rx.dialog.close( + rx.button( + "Cancel", + variant="soft", + color_scheme="gray", + ), + ), + rx.dialog.close( + rx.button( + "Submit", type="submit" + ), + ), + spacing="3", + justify="end", + ), + direction="column", + spacing="4", + ), + on_submit=State.add_user, + reset_on_submit=False, + ), + max_width="450px", + ), + ) + +def graph(): + return rx.recharts.bar_chart( + rx.recharts.bar( + data_key="value", + stroke=rx.color("accent", 9), + fill=rx.color("accent", 8), + ), + rx.recharts.x_axis(data_key="name"), + rx.recharts.y_axis(), + data=State.users_for_graph, + width="100%", + height=250, + ) + +def index() -> rx.Component: + return rx.vstack( + add_customer_button(), + rx.table.root( + rx.table.header( + rx.table.row( + rx.table.column_header_cell("Name"), + rx.table.column_header_cell("Email"), + rx.table.column_header_cell("Gender"), + ), + ), + rx.table.body( + rx.foreach( + State.users, show_user + ), + ), + variant="surface", + size="3", + width="100%", + ), + graph(), + align="center", + width="100%", + ) + + +app = rx.App( + theme=rx.theme( + radius="full", accent_color="grass" + ), +) + +app.add_page( + index, + title="Customer Data App", + description="A simple app to manage customer data.", + on_load=State.transform_data, +) +``` + +Don't worry if you don't understand the code above, in this tutorial we are going to walk you through the whole thing step by step. + + +## Setup for the tutorial + +Check out the [installation docs]({docs.getting_started.installation.path}) to get Reflex set up on your machine. Follow these to create a folder called `dashboard_tutorial`, which you will `cd` into and `pip install reflex`. + +We will choose template `0` when we run `reflex init` to get the blank template. Finally run `reflex run` to start the app and confirm everything is set up correctly. + + +## Overview + +Now that you’re set up, let’s get an overview of Reflex! + +### Inspecting the starter code + +Within our `dashboard_tutorial` folder we just `cd`'d into, there is a `rxconfig.py` file that contains the configuration for our Reflex app. (Check out the [config docs]({docs.advanced_onboarding.configuration.path}) for more information) + +There is also an `assets` folder where static files such as images and stylesheets can be placed to be referenced within your app. ([asset docs]({docs.assets.overview.path}) for more information) + +Most importantly there is a folder also called `dashboard_tutorial` which contains all the code for your app. Inside of this folder there is a file named `dashboard_tutorial.py`. To begin this tutorial we will delete all the code in this file so that we can start from scratch and explain every step as we go. + +The first thing we need to do is import `reflex`. Once we have done this we can create a component, which is a reusable piece of user interface code. Components are used to render, manage, and update the UI elements in your application. + +Let's look at the example below. Here we have a function called `index` that returns a `text` component (an in-built Reflex UI component) that displays the text "Hello World!". + +Next we define our app using `app = rx.App()` and add the component we just defined (`index`) to a page using `app.add_page(index)`. The function name (in this example `index`) which defines the component, must be what we pass into the `add_page`. The definition of the app and adding a component to a page are required for every Reflex app. + +```python +import reflex as rx + + +def index() -> rx.Component: + return rx.text("Hello World!") + +app = rx.App() +app.add_page(index) +``` + +This code will render a page with the text "Hello World!" when you run your app like below: + +```python eval +rx.text("Hello World!", + border_width="2px", + border_radius="10px", + padding="1em" +) +``` + +```md alert info +For the rest of the tutorial the `app=rx.App()` and `app.add_page` will be implied and not shown in the code snippets. +``` + +### Creating a table + +Let's create a new component that will render a table. We will use the `table` component to do this. The `table` component has a `root`, which takes in a `header` and a `body`, which in turn take in `row` components. The `row` component takes in `cell` components which are the actual data that will be displayed in the table. + +```python eval +rx.table.root( + rx.table.header( + rx.table.row( + rx.table.column_header_cell("Name"), + rx.table.column_header_cell("Email"), + rx.table.column_header_cell("Gender"), + ), + ), + rx.table.body( + rx.table.row( + rx.table.cell("Danilo Sousa"), + rx.table.cell("danilo@example.com"), + rx.table.cell("Male"), + ), + rx.table.row( + rx.table.cell("Zahra Ambessa"), + rx.table.cell("zahra@example.com"), + rx.table.cell("Female"), + ), + ), + border_width="2px", + border_radius="10px", + padding="1em", + ) +``` + +```python +def index() -> rx.Component: + return rx.table.root( + rx.table.header( + rx.table.row( + rx.table.column_header_cell("Name"), + rx.table.column_header_cell("Email"), + rx.table.column_header_cell("Gender"), + ), + ), + rx.table.body( + rx.table.row( + rx.table.cell("Danilo Sousa"), + rx.table.cell("danilo@example.com"), + rx.table.cell("Male"), + ), + rx.table.row( + rx.table.cell("Zahra Ambessa"), + rx.table.cell("zahra@example.com"), + rx.table.cell("Female"), + ), + ), + ) +``` + +Components in Reflex have `props`, which can be used to customize the component and are passed in as keyword arguments to the component function. + +The `rx.table.root` component has for example the `variant` and `size` props, which customize the table as seen below. + +```python eval +rx.table.root( + rx.table.header( + rx.table.row( + rx.table.column_header_cell("Name"), + rx.table.column_header_cell("Email"), + rx.table.column_header_cell("Gender"), + ), + ), + rx.table.body( + rx.table.row( + rx.table.cell("Danilo Sousa"), + rx.table.cell("danilo@example.com"), + rx.table.cell("Male"), + ), + rx.table.row( + rx.table.cell("Zahra Ambessa"), + rx.table.cell("zahra@example.com"), + rx.table.cell("Female"), + ), + ), + variant="surface", + size="3", + border_width="2px", + border_radius="10px", + padding="1em", + ) +``` + +```python +def index() -> rx.Component: + return rx.table.root( + rx.table.header( + rx.table.row( + rx.table.column_header_cell("Name"), + rx.table.column_header_cell("Email"), + rx.table.column_header_cell("Gender"), + ), + ), + rx.table.body( + rx.table.row( + rx.table.cell("Danilo Sousa"), + rx.table.cell("danilo@example.com"), + rx.table.cell("Male"), + ), + rx.table.row( + rx.table.cell("Zahra Ambessa"), + rx.table.cell("zahra@example.com"), + rx.table.cell("Female"), + ), + ), + variant="surface", + size="3", + ) +``` + +## Showing dynamic data (State) + +Up until this point all the data we are showing in the app is static. This is not very useful for a data dashboard. We need to be able to show dynamic data that can be added to and updated. + +This is where `State` comes in. `State` is a Python class that stores variables that can change when the app is running, as well as the functions that can change those variables. + +To define a state class, subclass `rx.State` and define fields that store the state of your app. The state variables (vars) should have a type annotation, and can be initialized with a default value. Check out the [basics]({docs.getting_started.basics.path}) section for a simple example of how state works. + + +In the example below we define a `State` class called `State` that has a variable called `users` that is a list of lists of strings. Each list in the `users` list represents a user and contains their name, email and gender. + +```python +class State(rx.State): + users: list[list[str]] = [ + ["Danilo Sousa", "danilo@example.com", "Male"], + ["Zahra Ambessa", "zahra@example.com", "Female"], + ] +``` + +To iterate over a state var that is a list, we use the [`rx.foreach`]({docs.components.rendering_iterables.path}) function to render a list of components. The `rx.foreach` component takes an `iterable` (list, tuple or dict) and a `function` that renders each item in the `iterable`. + +```md alert info +# Why can we not just splat this in a `for` loop +You might be wondering why a `foreach` is even needed to render this state variable and why we cannot just splat a `for` loop. Check out this [documentation]({docs.getting_started.basics.path}#compile-time-vs.-runtime-(important)) to learn why. +``` + +Here the render function is `show_user` which takes in a single user and returns a `table.row` component that displays the users name, email and gender. + +```python exec +class State1(rx.State): + users: list[list[str]] = [ + ["Danilo Sousa", "danilo@example.com", "Male"], + ["Zahra Ambessa", "zahra@example.com", "Female"], + ] + +def show_user1(person: list): + """Show a person in a table row.""" + return rx.table.row( + rx.table.cell(person[0]), + rx.table.cell(person[1]), + rx.table.cell(person[2]), + ) +``` + +```python eval +rx.table.root( + rx.table.header( + rx.table.row( + rx.table.column_header_cell("Name"), + rx.table.column_header_cell("Email"), + rx.table.column_header_cell("Gender"), + ), + ), + rx.table.body( + rx.foreach( + State1.users, show_user1 + ), + ), + variant="surface", + size="3", + border_width="2px", + border_radius="10px", + padding="1em", +) +``` + + +```python +class State(rx.State): + users: list[list[str]] = [ + ["Danilo Sousa", "danilo@example.com", "Male"], + ["Zahra Ambessa", "zahra@example.com", "Female"], + ] + +def show_user(person: list): + """Show a person in a table row.""" + return rx.table.row( + rx.table.cell(person[0]), + rx.table.cell(person[1]), + rx.table.cell(person[2]), + ) + +def index() -> rx.Component: + return rx.table.root( + rx.table.header( + rx.table.row( + rx.table.column_header_cell("Name"), + rx.table.column_header_cell("Email"), + rx.table.column_header_cell("Gender"), + ), + ), + rx.table.body( + rx.foreach( + State.users, show_user + ), + ), + variant="surface", + size="3", +) +``` + +As you can see the output above looks the same as before, except now the data is no longer static and can change with user input to the app. + +### Using a proper class structure for our data + +So far our data has been defined in a list of lists, where the data is accessed by index i.e. `user[0]`, `user[1]`. This is not very maintainable as our app gets bigger. + +A better way to structure our data in Reflex is to use a class to represent a user. This way we can access the data using attributes i.e. `user.name`, `user.email`. + +In Reflex when we create these classes to showcase our data, the class must inherit from `rx.Base`. + +`rx.Base` is also necessary if we want to have a state var that is an iterable with different types. For example if we wanted to have `age` as an `int` we would have to use `rx.base` as we could not do this with a state var defined as `list[list[str]]`. + +The `show_user` render function is also updated to access the data by named attributes, instead of indexing. + +```python exec +class User(rx.Base): + """The user model.""" + + name: str + email: str + gender: str + + +class State2(rx.State): + users: list[User] = [ + User(name="Danilo Sousa", email="danilo@example.com", gender="Male"), + User(name="Zahra Ambessa", email="zahra@example.com", gender="Female"), + ] + +def show_user2(user: User): + """Show a person in a table row.""" + return rx.table.row( + rx.table.cell(user.name), + rx.table.cell(user.email), + rx.table.cell(user.gender), + ) +``` + +```python eval +rx.table.root( + rx.table.header( + rx.table.row( + rx.table.column_header_cell("Name"), + rx.table.column_header_cell("Email"), + rx.table.column_header_cell("Gender"), + ), + ), + rx.table.body( + rx.foreach( + State2.users, show_user2 + ), + ), + variant="surface", + size="3", + border_width="2px", + border_radius="10px", + padding="1em", +) +``` + + +```python +class User(rx.Base): + """The user model.""" + + name: str + email: str + gender: str + + +class State(rx.State): + users: list[User] = [ + User(name="Danilo Sousa", email="danilo@example.com", gender="Male"), + User(name="Zahra Ambessa", email="zahra@example.com", gender="Female"), + ] + +def show_user(user: User): + """Show a person in a table row.""" + return rx.table.row( + rx.table.cell(user.name), + rx.table.cell(user.email), + rx.table.cell(user.gender), + ) + +def index() -> rx.Component: + return rx.table.root( + rx.table.header( + rx.table.row( + rx.table.column_header_cell("Name"), + rx.table.column_header_cell("Email"), + rx.table.column_header_cell("Gender"), + ), + ), + rx.table.body( + rx.foreach( + State.users, show_user + ), + ), + variant="surface", + size="3", +) +``` + + +Next let's add a form to the app so we can add new users to the table. + + +## Using a Form to Add Data + +We build a form using `rx.form`, which takes several components such as `rx.input` and `rx.select`, which represent the form fields that allow you to add information to submit with the form. Check out the [form]({docs.library.forms.form.path}) docs for more information on form components. + +The `rx.input` component takes in several props. The `placeholder` prop is the text that is displayed in the input field when it is empty. The `name` prop is the name of the input field, which gets passed through in the dictionary when the form is submitted. The `required` prop is a boolean that determines if the input field is required. + +The `rx.select` component takes in a list of options that are displayed in the dropdown. The other props used here are identical to the `rx.input` component. + +```python demo +rx.form( + rx.input( + placeholder="User Name", name="name", required=True + ), + rx.input( + placeholder="user@reflex.dev", + name="email", + ), + rx.select( + ["Male", "Female"], + placeholder="Male", + name="gender", + ), +) +``` + +This form is all very compact as you can see from the example, so we need to add some styling to make it look better. We can do this by adding a `vstack` component around the form fields. The `vstack` component stacks the form fields vertically. Check out the [layout]({docs.styling.layout.path}) docs for more information on how to layout your app. + + +```python demo +rx.form( + rx.vstack( + rx.input( + placeholder="User Name", name="name", required=True + ), + rx.input( + placeholder="user@reflex.dev", + name="email", + ), + rx.select( + ["Male", "Female"], + placeholder="Male", + name="gender", + ), + ), +) +``` + +Now you have probably realised that we have all the form fields, but we have no way to submit the form. We can add a submit button to the form by adding a `rx.button` component to the `vstack` component. The `rx.button` component takes in the text that is displayed on the button and the `type` prop which is the type of button. The `type` prop is set to `submit` so that the form is submitted when the button is clicked. + +In addition to this we need a way to update the `users` state variable when the form is submitted. All state changes are handled through functions in the state class, called [event handlers]({docs.events.events_overview.path}). + +Components have special props called event triggers, such as `on_submit`, that can be used to make components interactive. Event triggers connect components to event handlers, which update the state. Different event triggers expect the event handler that you hook them up to, to take in different arguments (and some do not take in any arguments). + +The `on_submit` event trigger of `rx.form` is hooked up to the `add_user` event handler that is defined in the `State` class. This event trigger expects to pass a `dict`, containing the form data, to the event handler that it is hooked up to. The `add_user` event handler takes in the form data as a dictionary and appends it to the `users` state variable. + + +```python +class State(rx.State): + + ... + + def add_user(self, form_data: dict): + self.users.append(User(**form_data)) + + +def form(): + return rx.form( + rx.vstack( + rx.input( + placeholder="User Name", name="name", required=True + ), + rx.input( + placeholder="user@reflex.dev", + name="email", + ), + rx.select( + ["Male", "Female"], + placeholder="Male", + name="gender", + ), + rx.button("Submit", type="submit"), + ), + on_submit=State.add_user, + reset_on_submit=True, + ) +``` + +Finally we must add the new `form()` component we have defined to the `index()` function so that the form is rendered on the page. + +Below is the full code for the app so far. If you try this form out you will see that you can add new users to the table by filling out the form and clicking the submit button. The form data will also appear as a toast (a small window in the corner of the page) on the screen when submitted. + + +```python exec +class State3(rx.State): + users: list[User] = [ + User(name="Danilo Sousa", email="danilo@example.com", gender="Male"), + User(name="Zahra Ambessa", email="zahra@example.com", gender="Female"), + ] + + def add_user(self, form_data: dict): + self.users.append(User(**form_data)) + + + return rx.toast.info( + f"User has been added: {form_data}.", + position="bottom-right", + ) + +def show_user(user: User): + """Show a person in a table row.""" + return rx.table.row( + rx.table.cell(user.name), + rx.table.cell(user.email), + rx.table.cell(user.gender), + ) + +def form(): + return rx.form( + rx.vstack( + rx.input( + placeholder="User Name", name="name", required=True + ), + rx.input( + placeholder="user@reflex.dev", + name="email", + ), + rx.select( + ["Male", "Female"], + placeholder="Male", + name="gender", + ), + rx.button("Submit", type="submit"), + ), + on_submit=State3.add_user, + reset_on_submit=True, + ) +``` + +```python eval +rx.vstack( + form(), + rx.table.root( + rx.table.header( + rx.table.row( + rx.table.column_header_cell("Name"), + rx.table.column_header_cell("Email"), + rx.table.column_header_cell("Gender"), + ), + ), + rx.table.body( + rx.foreach( + State3.users, show_user + ), + ), + variant="surface", + size="3", + ), + border_width="2px", + border_radius="10px", + padding="1em", +) +``` + +```python +class State(rx.State): + users: list[User] = [ + User(name="Danilo Sousa", email="danilo@example.com", gender="Male"), + User(name="Zahra Ambessa", email="zahra@example.com", gender="Female"), + ] + + def add_user(self, form_data: dict): + self.users.append(User(**form_data)) + + +def show_user(user: User): + """Show a person in a table row.""" + return rx.table.row( + rx.table.cell(user.name), + rx.table.cell(user.email), + rx.table.cell(user.gender), + ) + +def form(): + return rx.form( + rx.vstack( + rx.input( + placeholder="User Name", name="name", required=True + ), + rx.input( + placeholder="user@reflex.dev", + name="email", + ), + rx.select( + ["Male", "Female"], + placeholder="Male", + name="gender", + ), + rx.button("Submit", type="submit"), + ), + on_submit=State.add_user, + reset_on_submit=True, + ) + +def index() -> rx.Component: + return rx.vstack( + form(), + rx.table.root( + rx.table.header( + rx.table.row( + rx.table.column_header_cell("Name"), + rx.table.column_header_cell("Email"), + rx.table.column_header_cell("Gender"), + ), + ), + rx.table.body( + rx.foreach( + State.users, show_user + ), + ), + variant="surface", + size="3", + ), + ) +``` + + +### Putting the Form in an Overlay + +In Reflex, we like to make the user interaction as intuitive as possible. Placing the form we just constructed in an overlay creates a focused interaction by dimming the background, and ensures a cleaner layout when you have multiple action points such as editing and deleting as well. + +We will place the form inside of a `rx.dialog` component (also called a modal). The `rx.dialog.root` contains all the parts of a dialog, and the `rx.dialog.trigger` wraps the control that will open the dialog. In our case the trigger will be an `rx.button` that says "Add User" as shown below. + +```python +rx.dialog.trigger( + rx.button( + rx.icon("plus", size=26), + rx.text("Add User", size="4"), + ), +) +``` + +After the trigger we have the `rx.dialog.content` which contains everything within our dialog, including a title, a description and our form. The first way to close the dialog is without submitting the form and the second way is to close the dialog by submitting the form as shown below. This requires two `rx.dialog.close` components within the dialog. + +```python +rx.dialog.close( + rx.button( + "Cancel", + variant="soft", + color_scheme="gray", + ), +), +rx.dialog.close( + rx.button( + "Submit", type="submit" + ), +) +``` + +The total code for the dialog with the form in it is below. + +```python demo +rx.dialog.root( + rx.dialog.trigger( + rx.button( + rx.icon("plus", size=26), + rx.text("Add User", size="4"), + ), + ), + rx.dialog.content( + rx.dialog.title( + "Add New User", + ), + rx.dialog.description( + "Fill the form with the user's info", + ), + rx.form( + # flex is similar to vstack and used to layout the form fields + rx.flex( + rx.input( + placeholder="User Name", name="name", required=True + ), + rx.input( + placeholder="user@reflex.dev", + name="email", + ), + rx.select( + ["Male", "Female"], + placeholder="Male", + name="gender", + ), + rx.flex( + rx.dialog.close( + rx.button( + "Cancel", + variant="soft", + color_scheme="gray", + ), + ), + rx.dialog.close( + rx.button( + "Submit", type="submit" + ), + ), + spacing="3", + justify="end", + ), + direction="column", + spacing="4", + ), + on_submit=State3.add_user, + reset_on_submit=False, + ), + # max_width is used to limit the width of the dialog + max_width="450px", + ), +) +``` + +At this point we have an app that allows you to add users to a table by filling out a form. The form is placed in a dialog that can be opened by clicking the "Add User" button. We change the name of the component from `form` to `add_customer_button` and update this in our `index` component. The full app so far and code are below. + + +```python exec +def add_customer_button() -> rx.Component: + return rx.dialog.root( + rx.dialog.trigger( + rx.button( + rx.icon("plus", size=26), + rx.text("Add User", size="4"), + ), + ), + rx.dialog.content( + rx.dialog.title( + "Add New User", + ), + rx.dialog.description( + "Fill the form with the user's info", + ), + rx.form( + rx.flex( + rx.input( + placeholder="User Name", name="name", required=True + ), + rx.input( + placeholder="user@reflex.dev", + name="email", + ), + rx.select( + ["Male", "Female"], + placeholder="Male", + name="gender", + ), + rx.flex( + rx.dialog.close( + rx.button( + "Cancel", + variant="soft", + color_scheme="gray", + ), + ), + rx.dialog.close( + rx.button( + "Submit", type="submit" + ), + ), + spacing="3", + justify="end", + ), + direction="column", + spacing="4", + ), + on_submit=State3.add_user, + reset_on_submit=False, + ), + max_width="450px", + ), + ) +``` + +```python eval +rx.vstack( + add_customer_button(), + rx.table.root( + rx.table.header( + rx.table.row( + rx.table.column_header_cell("Name"), + rx.table.column_header_cell("Email"), + rx.table.column_header_cell("Gender"), + ), + ), + rx.table.body( + rx.foreach( + State3.users, show_user + ), + ), + variant="surface", + size="3", + ), + border_width="2px", + border_radius="10px", + padding="1em", +) +``` + +```python +class User(rx.Base): + """The user model.""" + + name: str + email: str + gender: str + + +class State(rx.State): + users: list[User] = [ + User(name="Danilo Sousa", email="danilo@example.com", gender="Male"), + User(name="Zahra Ambessa", email="zahra@example.com", gender="Female"), + ] + + def add_user(self, form_data: dict): + self.users.append(User(**form_data)) + + + +def show_user(user: User): + """Show a person in a table row.""" + return rx.table.row( + rx.table.cell(user.name), + rx.table.cell(user.email), + rx.table.cell(user.gender), + ) + +def add_customer_button() -> rx.Component: + return rx.dialog.root( + rx.dialog.trigger( + rx.button( + rx.icon("plus", size=26), + rx.text("Add User", size="4"), + ), + ), + rx.dialog.content( + rx.dialog.title( + "Add New User", + ), + rx.dialog.description( + "Fill the form with the user's info", + ), + rx.form( + rx.flex( + rx.input( + placeholder="User Name", name="name", required=True + ), + rx.input( + placeholder="user@reflex.dev", + name="email", + ), + rx.select( + ["Male", "Female"], + placeholder="Male", + name="gender", + ), + rx.flex( + rx.dialog.close( + rx.button( + "Cancel", + variant="soft", + color_scheme="gray", + ), + ), + rx.dialog.close( + rx.button( + "Submit", type="submit" + ), + ), + spacing="3", + justify="end", + ), + direction="column", + spacing="4", + ), + on_submit=State.add_user, + reset_on_submit=False, + ), + max_width="450px", + ), + ) + +def index() -> rx.Component: + return rx.vstack( + add_customer_button(), + rx.table.root( + rx.table.header( + rx.table.row( + rx.table.column_header_cell("Name"), + rx.table.column_header_cell("Email"), + rx.table.column_header_cell("Gender"), + ), + ), + rx.table.body( + rx.foreach( + State.users, show_user + ), + ), + variant="surface", + size="3", + ), + ) +``` + + +## Plotting Data in a Graph + +The last part of this tutorial is to plot the user data in a graph. We will use Reflex's built-in graphing library recharts to plot the number of users of each gender. + +### Transforming the data for the graph + +The graphing components in Reflex expect to take in a list of dictionaries. Each dictionary represents a data point on the graph and contains the x and y values. We will create a new event handler in the state called `transform_data` to transform the user data into the format that the graphing components expect. We must also create a new state variable called `users_for_graph` to store the transformed data, which will be used to render the graph. + + +```python +from collections import Counter + +class State(rx.State): + users: list[User] = [] + users_for_graph: list[dict] = [] + + def add_user(self, form_data: dict): + self.users.append(User(**form_data)) + self.transform_data() + + def transform_data(self): + """Transform user gender group data into a format suitable for visualization in graphs.""" + # Count users of each gender group + gender_counts = Counter(user.gender for user in self.users) + + # Transform into list of dict so it can be used in the graph + self.users_for_graph = [ + { + "name": gender_group, + "value": count + } + for gender_group, count in gender_counts.items() + ] +``` + +As we can see above the `transform_data` event handler uses the `Counter` class from the `collections` module to count the number of users of each gender. We then create a list of dictionaries from this which we set to the state var `users_for_graph`. + +Finally we can see that whenever we add a new user through submitting the form and running the `add_user` event handler, we call the `transform_data` event handler to update the `users_for_graph` state variable. + +### Rendering the graph + +We use the `rx.recharts.bar_chart` component to render the graph. We pass through the state variable for our graphing data as `data=State.users_for_graph`. We also pass in a `rx.recharts.bar` component which represents the bars on the graph. The `rx.recharts.bar` component takes in the `data_key` prop which is the key in the data dictionary that represents the y value of the bar. The `stroke` and `fill` props are used to set the color of the bars. + +The `rx.recharts.bar_chart` component also takes in `rx.recharts.x_axis` and `rx.recharts.y_axis` components which represent the x and y axes of the graph. The `data_key` prop of the `rx.recharts.x_axis` component is set to the key in the data dictionary that represents the x value of the bar. Finally we add `width` and `height` props to set the size of the graph. + +```python +def graph(): + return rx.recharts.bar_chart( + rx.recharts.bar( + data_key="value", + stroke=rx.color("accent", 9), + fill=rx.color("accent", 8), + ), + rx.recharts.x_axis(data_key="name"), + rx.recharts.y_axis(), + data=State.users_for_graph, + width="100%", + height=250, + ) +``` + +Finally we add this `graph()` component to our `index()` component so that the graph is rendered on the page. The code for the full app with the graph included is below. If you try this out you will see that the graph updates whenever you add a new user to the table. + +```python exec +from collections import Counter + +class State4(rx.State): + users: list[User] = [ + User(name="Danilo Sousa", email="danilo@example.com", gender="Male"), + User(name="Zahra Ambessa", email="zahra@example.com", gender="Female"), + ] + users_for_graph: list[dict] = [] + + def add_user(self, form_data: dict): + self.users.append(User(**form_data)) + self.transform_data() + + return rx.toast.info( + f"User {form_data['name']} has been added.", + position="bottom-right", + ) + + def transform_data(self): + """Transform user gender group data into a format suitable for visualization in graphs.""" + # Count users of each gender group + gender_counts = Counter(user.gender for user in self.users) + + # Transform into list of dict so it can be used in the graph + self.users_for_graph = [ + { + "name": gender_group, + "value": count + } + for gender_group, count in gender_counts.items() + ] + +def add_customer_button() -> rx.Component: + return rx.dialog.root( + rx.dialog.trigger( + rx.button( + rx.icon("plus", size=26), + rx.text("Add User", size="4"), + ), + ), + rx.dialog.content( + rx.dialog.title( + "Add New User", + ), + rx.dialog.description( + "Fill the form with the user's info", + ), + rx.form( + rx.flex( + rx.input( + placeholder="User Name", name="name", required=True + ), + rx.input( + placeholder="user@reflex.dev", + name="email", + ), + rx.select( + ["Male", "Female"], + placeholder="Male", + name="gender", + ), + rx.flex( + rx.dialog.close( + rx.button( + "Cancel", + variant="soft", + color_scheme="gray", + ), + ), + rx.dialog.close( + rx.button( + "Submit", type="submit" + ), + ), + spacing="3", + justify="end", + ), + direction="column", + spacing="4", + ), + on_submit=State4.add_user, + reset_on_submit=False, + ), + max_width="450px", + ), + ) + +def graph(): + return rx.recharts.bar_chart( + rx.recharts.bar( + data_key="value", + stroke=rx.color("accent", 9), + fill=rx.color("accent", 8), + ), + rx.recharts.x_axis(data_key="name"), + rx.recharts.y_axis(), + data=State4.users_for_graph, + width="100%", + height=250, + ) +``` + +```python eval +rx.vstack( + add_customer_button(), + rx.table.root( + rx.table.header( + rx.table.row( + rx.table.column_header_cell("Name"), + rx.table.column_header_cell("Email"), + rx.table.column_header_cell("Gender"), + ), + ), + rx.table.body( + rx.foreach( + State4.users, show_user + ), + ), + variant="surface", + size="3", + ), + graph(), + border_width="2px", + border_radius="10px", + padding="1em", +) +``` + +```python +from collections import Counter + +class State(rx.State): + users: list[User] = [ + User(name="Danilo Sousa", email="danilo@example.com", gender="Male"), + User(name="Zahra Ambessa", email="zahra@example.com", gender="Female"), + ] + users_for_graph: list[dict] = [] + + def add_user(self, form_data: dict): + self.users.append(User(**form_data)) + self.transform_data() + + def transform_data(self): + """Transform user gender group data into a format suitable for visualization in graphs.""" + # Count users of each gender group + gender_counts = Counter(user.gender for user in self.users) + + # Transform into list of dict so it can be used in the graph + self.users_for_graph = [ + { + "name": gender_group, + "value": count + } + for gender_group, count in gender_counts.items() + ] + + +def show_user(user: User): + """Show a person in a table row.""" + return rx.table.row( + rx.table.cell(user.name), + rx.table.cell(user.email), + rx.table.cell(user.gender), + ) + +def add_customer_button() -> rx.Component: + return rx.dialog.root( + rx.dialog.trigger( + rx.button( + rx.icon("plus", size=26), + rx.text("Add User", size="4"), + ), + ), + rx.dialog.content( + rx.dialog.title( + "Add New User", + ), + rx.dialog.description( + "Fill the form with the user's info", + ), + rx.form( + rx.flex( + rx.input( + placeholder="User Name", name="name", required=True + ), + rx.input( + placeholder="user@reflex.dev", + name="email", + ), + rx.select( + ["Male", "Female"], + placeholder="male", + name="gender", + ), + rx.flex( + rx.dialog.close( + rx.button( + "Cancel", + variant="soft", + color_scheme="gray", + ), + ), + rx.dialog.close( + rx.button( + "Submit", type="submit" + ), + ), + spacing="3", + justify="end", + ), + direction="column", + spacing="4", + ), + on_submit=State.add_user, + reset_on_submit=False, + ), + max_width="450px", + ), + ) + +def graph(): + return rx.recharts.bar_chart( + rx.recharts.bar( + data_key="value", + stroke=rx.color("accent", 9), + fill=rx.color("accent", 8), + ), + rx.recharts.x_axis(data_key="name"), + rx.recharts.y_axis(), + data=State.users_for_graph, + width="100%", + height=250, + ) + +def index() -> rx.Component: + return rx.vstack( + add_customer_button(), + rx.table.root( + rx.table.header( + rx.table.row( + rx.table.column_header_cell("Name"), + rx.table.column_header_cell("Email"), + rx.table.column_header_cell("Gender"), + ), + ), + rx.table.body( + rx.foreach( + State.users, show_user + ), + ), + variant="surface", + size="3", + ), + graph(), + ) +``` + +One thing you may have noticed about your app is that the graph does not appear initially when you run the app, and that you must add a user to the table for it to first appear. This occurs because the `transform_data` event handler is only called when a user is added to the table. In the next section we will explore a solution to this. + + +## Final Cleanup + +### Revisiting app.add_page + +At the beginning of this tutorial we mentioned that the `app.add_page` function is required for every Reflex app. This function is used to add a component to a page. + +The `app.add_page` currently looks like this `app.add_page(index)`. We could change the route that the page renders on by setting the `route` prop such as `route="/custom-route"`, this would change the route to `http://localhost:3000/custom-route` for this page. + +We can also set a `title` to be shown in the browser tab and a `description` as shown in search results. + +To solve the problem we had above about our graph not loading when the page loads, we can use `on_load` inside of `app.add_page` to call the `transform_data` event handler when the page loads. This would look like `on_load=State.transform_data`. Below see what our `app.add_page` would look like with some of the changes above added. + +```python eval +rx.vstack( + add_customer_button(), + rx.table.root( + rx.table.header( + rx.table.row( + rx.table.column_header_cell("Name"), + rx.table.column_header_cell("Email"), + rx.table.column_header_cell("Gender"), + ), + ), + rx.table.body( + rx.foreach( + State4.users, show_user + ), + ), + variant="surface", + size="3", + ), + graph(), + on_mouse_enter=State4.transform_data, + border_width="2px", + border_radius="10px", + padding="1em", +) +``` + +```python +app.add_page( + index, + title="Customer Data App", + description="A simple app to manage customer data.", + on_load=State.transform_data, +) +``` + +### Revisiting app=rx.App() + +At the beginning of the tutorial we also mentioned that we defined our app using `app=rx.App()`. We can also pass in some props to the `rx.App` component to customize the app. + +The most important one is `theme` which allows you to customize the look and feel of the app. The `theme` prop takes in an `rx.theme` component which has several props that can be set. + +The `radius` prop sets the global radius value for the app that is inherited by all components that have a `radius` prop. It can be overwritten locally for a specific component by manually setting the `radius` prop. + +The `accent_color` prop sets the accent color of the app. Check out other options for the accent color [here]({docs.library.other.theme.path}). + +To see other props that can be set at the app level check out this [documentation]({docs.styling.theming.path}) + +```python +app = rx.App( + theme=rx.theme( + radius="full", accent_color="grass" + ), +) +``` + +Unfortunately in this tutorial here we cannot actually apply this to the live example on the page, but if you copy and paste the code below into a reflex app locally you can see it in action. + + + +## Conclusion + +Finally let's make some final styling updates to our app. We will add some hover styling to the table rows and center the table inside the `show_user` with `style=\{"_hover": \{"bg": rx.color("gray", 3)}}, align="center"`. + +In addition, we will add some `width="100%"` and `align="center"` to the `index()` component to center the items on the page and ensure they stretch the full width of the page. + +Check out the full code and interactive app below: + +```python eval +rx.vstack( + add_customer_button5(), + rx.table.root( + rx.table.header( + rx.table.row( + rx.table.column_header_cell("Name"), + rx.table.column_header_cell("Email"), + rx.table.column_header_cell("Gender"), + ), + ), + rx.table.body( + rx.foreach( + State5.users, show_user5 + ), + ), + variant="surface", + size="3", + width="100%", + ), + graph5(), + align="center", + width="100%", + on_mouse_enter=State5.transform_data, + border_width="2px", + border_radius="10px", + padding="1em", + ) +``` + + +```python +import reflex as rx +from collections import Counter + +class User(rx.Base): + """The user model.""" + + name: str + email: str + gender: str + + +class State(rx.State): + users: list[User] = [ + User(name="Danilo Sousa", email="danilo@example.com", gender="Male"), + User(name="Zahra Ambessa", email="zahra@example.com", gender="Female"), + ] + users_for_graph: list[dict] = [] + + def add_user(self, form_data: dict): + self.users.append(User(**form_data)) + self.transform_data() + + def transform_data(self): + """Transform user gender group data into a format suitable for visualization in graphs.""" + # Count users of each gender group + gender_counts = Counter(user.gender for user in self.users) + + # Transform into list of dict so it can be used in the graph + self.users_for_graph = [ + { + "name": gender_group, + "value": count + } + for gender_group, count in gender_counts.items() + ] + + +def show_user(user: User): + """Show a user in a table row.""" + return rx.table.row( + rx.table.cell(user.name), + rx.table.cell(user.email), + rx.table.cell(user.gender), + style={ + "_hover": { + "bg": rx.color("gray", 3) + } + }, + align="center", + ) + +def add_customer_button() -> rx.Component: + return rx.dialog.root( + rx.dialog.trigger( + rx.button( + rx.icon("plus", size=26), + rx.text("Add User", size="4"), + ), + ), + rx.dialog.content( + rx.dialog.title( + "Add New User", + ), + rx.dialog.description( + "Fill the form with the user's info", + ), + rx.form( + rx.flex( + rx.input( + placeholder="User Name", name="name", required=True + ), + rx.input( + placeholder="user@reflex.dev", + name="email", + ), + rx.select( + ["Male", "Female"], + placeholder="male", + name="gender", + ), + rx.flex( + rx.dialog.close( + rx.button( + "Cancel", + variant="soft", + color_scheme="gray", + ), + ), + rx.dialog.close( + rx.button( + "Submit", type="submit" + ), + ), + spacing="3", + justify="end", + ), + direction="column", + spacing="4", + ), + on_submit=State.add_user, + reset_on_submit=False, + ), + max_width="450px", + ), + ) + +def graph(): + return rx.recharts.bar_chart( + rx.recharts.bar( + data_key="value", + stroke=rx.color("accent", 9), + fill=rx.color("accent", 8), + ), + rx.recharts.x_axis(data_key="name"), + rx.recharts.y_axis(), + data=State.users_for_graph, + width="100%", + height=250, + ) + +def index() -> rx.Component: + return rx.vstack( + add_customer_button(), + rx.table.root( + rx.table.header( + rx.table.row( + rx.table.column_header_cell("Name"), + rx.table.column_header_cell("Email"), + rx.table.column_header_cell("Gender"), + ), + ), + rx.table.body( + rx.foreach( + State.users, show_user + ), + ), + variant="surface", + size="3", + width="100%", + ), + graph(), + align="center", + width="100%", + ) + + +app = rx.App( + theme=rx.theme( + radius="full", accent_color="grass" + ), +) + +app.add_page( + index, + title="Customer Data App", + description="A simple app to manage customer data.", + on_load=State.transform_data, +) +``` + +And that is it for your first dashboard tutorial. In this tutorial we have created + +- a table to display user data +- a form to add new users to the table +- a dialog to showcase the form +- a graph to visualize the user data + +In addition to the above we have we have + +- explored state to allow you to show dynamic data that changes over time +- explored events to allow you to make your app interactive and respond to user actions +- added styling to the app to make it look better + + + +## Advanced Section (Hooking this up to a Database) + +Coming Soon! \ No newline at end of file diff --git a/docs/getting_started/installation.md b/docs/getting_started/installation.md new file mode 100644 index 00000000000..b412c738a61 --- /dev/null +++ b/docs/getting_started/installation.md @@ -0,0 +1,172 @@ +```python exec +from pcweb import constants +import reflex as rx +from pcweb.pages.gallery import gallery +app_name = "my_app_name" +default_url = "http://localhost:3000" +``` + +# Installation + +Reflex requires Python 3.10+. + + +```md video https://youtube.com/embed/ITOZkzjtjUA?start=758&end=1206 +# Video: Installation +``` + + +## Virtual Environment + +We **highly recommend** creating a virtual environment for your project. + +[uv]({constants.UV_URL}) is the recommended modern option. [venv]({constants.VENV_URL}), [conda]({constants.CONDA_URL}) and [poetry]({constants.POETRY_URL}) are some alternatives. + + +# Install Reflex on your system + +---md tabs + +--tab macOS/Linux +## Install on macOS/Linux + +We will go with [uv]({constants.UV_URL}) here. + + +### Prerequisites + +#### Install uv + +```bash +curl -LsSf https://astral.sh/uv/install.sh | sh +``` + +After installation, restart your terminal or run `source ~/.bashrc` (or `source ~/.zshrc` for zsh). + +Alternatively, install via [Homebrew, PyPI, or other methods](https://docs.astral.sh/uv/getting-started/installation/). + +**macOS (Apple Silicon) users:** Install [Rosetta 2](https://support.apple.com/en-us/HT211861). Run this command: + +`/usr/sbin/softwareupdate --install-rosetta --agree-to-license` + + +### Create the project directory + +Replace `{app_name}` with your project name. Switch to the new directory. + +```bash +mkdir {app_name} +cd {app_name} +``` + +### Initialize uv project + +```bash +uv init +``` + +### Add Reflex to the project + +```bash +uv add reflex +``` + +### Initialize the Reflex project + +```bash +uv run reflex init +``` + + +-- +--tab Windows +## Install on Windows + +For Windows users, we recommend using [Windows Subsystem for Linux (WSL)](https://learn.microsoft.com/en-us/windows/wsl/about) for optimal performance. + +**WSL users:** Refer to the macOS/Linux instructions above. + +For the rest of this section we will work with native Windows (non-WSL). + +We will go with [uv]({constants.UV_URL}) here. + +### Prerequisites + +#### Install uv + +```powershell +powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex" +``` + +After installation, restart your terminal (PowerShell or Command Prompt). + +Alternatively, install via [WinGet, Scoop, or other methods](https://docs.astral.sh/uv/getting-started/installation/). + +### Create the project directory + +Replace `{app_name}` with your project name. Switch to the new directory. + +```bash +mkdir {app_name} +cd {app_name} +``` + +### Initialize uv project + +```bash +uv init +``` + +### Add Reflex to the project + +```bash +uv add reflex +``` + +### Initialize the Reflex project + +```bash +uv run reflex init +``` + +```md alert warning +# Error `Install Failed - You are missing a DLL required to run bun.exe` Windows +Bun requires runtime components of Visual C++ libraries to run on Windows. This issue is fixed by installing [Microsoft Visual C++ 2015 Redistributable](https://www.microsoft.com/en-us/download/details.aspx?id=53840). +``` +-- + +--- + + +Running `uv run reflex init` will return the option to start with a blank Reflex app, premade templates built by the Reflex team, or to try our [AI builder]({constants.REFLEX_BUILD_URL}). + +```bash +Initializing the web directory. + +Get started with a template: +(0) A blank Reflex app. +(1) Premade templates built by the Reflex team. +(2) Try our AI builder. +Which template would you like to use? (0): +``` + +From here select an option. + + +## Run the App + +Run it in development mode: + +```bash +uv run reflex run +``` + +Your app runs at [http://localhost:3000](http://localhost:3000). + +Reflex prints logs to the terminal. To increase log verbosity to help with debugging, use the `--loglevel` flag: + +```bash +uv run reflex run --loglevel debug +``` + +Reflex will *hot reload* any code changes in real time when running in development mode. Your code edits will show up on [http://localhost:3000](http://localhost:3000) automatically. diff --git a/docs/getting_started/introduction.md b/docs/getting_started/introduction.md new file mode 100644 index 00000000000..f4e852cde4e --- /dev/null +++ b/docs/getting_started/introduction.md @@ -0,0 +1,353 @@ +```python exec +import reflex as rx +from pcweb import constants, styles +from pcweb.pages.docs import getting_started +from pcweb.pages.docs import wrapping_react +from pcweb.pages.docs.library import library +from pcweb.pages.docs import pages +from pcweb.pages.docs import vars +from pcweb.styles.colors import c_color +from pcweb.pages.docs import styling +from pcweb.styles.fonts import base +from pcweb.pages.docs import hosting +from pcweb.flexdown import markdown_with_shiki +from pcweb.pages.docs import advanced_onboarding +``` + + + +# Introduction + +**Reflex** is an open-source framework for quickly building beautiful, interactive web applications in **pure Python**. + +## Goals + +```md section +### Pure Python + +Use Python for everything. Don't worry about learning a new language. + +### Easy to Learn + +Build and share your first app in minutes. No web development experience required. + +### Full Flexibility + +Remain as flexible as traditional web frameworks. Reflex is easy to use, yet allows for advanced use cases. + +Build anything from small data science apps to large, multi-page websites. **This entire site was built and deployed with Reflex!** + +### Batteries Included + +No need to reach for a bunch of different tools. Reflex handles the user interface, server-side logic, and deployment of your app. +``` + +## An example: Make it count + +Here, we go over a simple counter app that lets the user count up or down. + +```python exec +class CounterExampleState(rx.State): + count: int = 0 + + @rx.event + def increment(self): + self.count += 1 + + @rx.event + def decrement(self): + self.count -= 1 + +class IntroTabsState(rx.State): + """The app state.""" + + value: str = "tab1" + tab_selected: str = "" + + @rx.event + def change_value(self, val: str): + self.tab_selected = f"{val} clicked!" + self.value = val + +def tabs(): + return rx.tabs.root( + rx.tabs.list( + rx.tabs.trigger( + "Frontend", value="tab1", + class_name="tab-style" + ), + rx.tabs.trigger( + "Backend", value="tab2", + class_name="tab-style" + ), + rx.tabs.trigger( + "Page", value="tab3", + class_name="tab-style" + ), + ), + rx.tabs.content( + markdown_with_shiki( + """The frontend is built declaratively using Reflex components. Components are compiled down to JS and served to the users browser, therefore: + +- Only use Reflex components, vars, and var operations when building your UI. Any other logic should be put in your `State` (backend). + +- Use `rx.cond` and `rx.foreach` (replaces if statements and for loops), for creating dynamic UIs. + """, + ), + value="tab1", + class_name="pt-4" + ), + rx.tabs.content( + markdown_with_shiki( + """Write your backend in the `State` class. Here you can define functions and variables that can be referenced in the frontend. This code runs directly on the server and is not compiled, so there are no special caveats. Here you can use any Python external library and call any method/function. + """, + ), + value="tab2", + class_name="pt-4" + ), + rx.tabs.content( + markdown_with_shiki( + f"""Each page is a Python function that returns a Reflex component. You can define multiple pages and navigate between them, see the [Routing]({pages.overview.path}) section for more information. + +- Start with a single page and scale to 100s of pages. + """, + ), + value="tab3", + class_name="pt-4" + ), + class_name="text-slate-12 font-normal", + default_value="tab1", + value=IntroTabsState.value, + on_change=lambda x: IntroTabsState.change_value( + x + ), + ) +``` + +```python demo box id=counter +rx.hstack( + rx.button( + "Decrement", + color_scheme="ruby", + on_click=CounterExampleState.decrement, + ), + rx.heading(CounterExampleState.count, font_size="2em"), + rx.button( + "Increment", + color_scheme="grass", + on_click=CounterExampleState.increment, + ), + spacing="4", +) +``` + +Here is the full code for this example: + +```python eval +tabs() +``` + +```python demo box +rx.box( + rx._x.code_block( + """import reflex as rx """, + class_name="code-block !bg-transparent !border-none", + ), + rx._x.code_block( + """class State(rx.State): + count: int = 0 + + @rx.event + def increment(self): + self.count += 1 + + @rx.event + def decrement(self): + self.count -= 1""", + background=rx.cond( + IntroTabsState.value == "tab2", + "var(--c-violet-3) !important", + "transparent", + ), + border=rx.cond( + IntroTabsState.value == "tab2", + "1px solid var(--c-violet-5)", + "none !important" + ), + class_name="code-block", + ), + rx._x.code_block( + """def index(): + return rx.hstack( + rx.button( + "Decrement", + color_scheme="ruby", + on_click=State.decrement, + ), + rx.heading(State.count, font_size="2em"), + rx.button( + "Increment", + color_scheme="grass", + on_click=State.increment, + ), + spacing="4", + )""", + border=rx.cond( + IntroTabsState.value == "tab1", + "1px solid var(--c-violet-5)", + "none !important", + ), + background=rx.cond( + IntroTabsState.value == "tab1", + "var(--c-violet-3) !important", + "transparent", + ), + class_name="code-block", + ), + rx._x.code_block( + """app = rx.App() +app.add_page(index)""", + background=rx.cond( + IntroTabsState.value == "tab3", + "var(--c-violet-3) !important", + "transparent", + ), + border=rx.cond( + IntroTabsState.value == "tab3", + "1px solid var(--c-violet-5)", + "none !important", + ), + class_name="code-block", + ), + class_name="w-full flex flex-col", +) +``` + +## The Structure of a Reflex App + +Let's break this example down. + +### Import + +```python +import reflex as rx +``` + +We begin by importing the `reflex` package (aliased to `rx`). We reference Reflex objects as `rx.*` by convention. + +### State + +```python +class State(rx.State): + count: int = 0 +``` + +The state defines all the variables (called **[vars]({vars.base_vars.path})**) in an app that can change, as well as the functions (called **[event_handlers](#event-handlers)**) that change them. + +Here our state has a single var, `count`, which holds the current value of the counter. We initialize it to `0`. + +### Event Handlers + +```python +@rx.event +def increment(self): + self.count += 1 + +@rx.event +def decrement(self): + self.count -= 1 +``` + +Within the state, we define functions, called **event handlers**, that change the state vars. + +Event handlers are the only way that we can modify the state in Reflex. +They can be called in response to user actions, such as clicking a button or typing in a text box. +These actions are called **events**. + +Our counter app has two event handlers, `increment` and `decrement`. + +### User Interface (UI) + +```python +def index(): + return rx.hstack( + rx.button( + "Decrement", + color_scheme="ruby", + on_click=State.decrement, + ), + rx.heading(State.count, font_size="2em"), + rx.button( + "Increment", + color_scheme="grass", + on_click=State.increment, + ), + spacing="4", + ) +``` + +This function defines the app's user interface. + +We use different components such as `rx.hstack`, `rx.button`, and `rx.heading` to build the frontend. Components can be nested to create complex layouts, and can be styled using the full power of CSS or [Tailwind CSS]({styling.tailwind.path}). + +Reflex comes with [50+ built-in components]({library.path}) to help you get started. +We are actively adding more components. Also, it's easy to [wrap your own React components]({wrapping_react.overview.path}). + +```python +rx.heading(State.count, font_size="2em"), +``` + +Components can reference the app's state vars. +The `rx.heading` component displays the current value of the counter by referencing `State.count`. +All components that reference state will reactively update whenever the state changes. + +```python +rx.button( + "Decrement", + color_scheme="ruby", + on_click=State.decrement, +), +``` + +Components interact with the state by binding events triggers to event handlers. +For example, `on_click` is an event that is triggered when a user clicks a component. + +The first button in our app binds its `on_click` event to the `State.decrement` event handler. Similarly the second button binds `on_click` to `State.increment`. + +In other words, the sequence goes like this: + +- User clicks "increment" on the UI. +- `on_click` event is triggered. +- Event handler `State.increment` is called. +- `State.count` is incremented. +- UI updates to reflect the new value of `State.count`. + +### Add pages + +Next we define our app and add the counter component to the base route. + +```python +app = rx.App() +app.add_page(index) +``` + +## Next Steps + +🎉 And that's it! + +We've created a simple, yet fully interactive web app in pure Python. + +By continuing with our documentation, you will learn how to build awesome apps with Reflex. Use the sidebar to navigate through the sections, or search (`Ctrl+K` or `Cmd+K`) to quickly find a page. + +For a glimpse of the possibilities, check out these resources: + +* For a more real-world example, check out either the [dashboard tutorial]({getting_started.dashboard_tutorial.path}) or the [chatapp tutorial]({getting_started.chatapp_tutorial.path}). +* Check out our open-source [templates]({getting_started.open_source_templates.path})! +* We have an AI Builder that can generate full Reflex apps or help with your existing app! Check it out at [Reflex Build]({constants.REFLEX_BUILD_URL})! +* Deploy your app with a single command using [Reflex Cloud]({hosting.deploy_quick_start.path})! + +If you want to learn more about how Reflex works, check out the [How Reflex Works]({advanced_onboarding.how_reflex_works.path}) section. + +## Join our Community + +If you have questions about anything related to Reflex, you're always welcome to ask our community on [GitHub Discussions]({constants.GITHUB_DISCUSSIONS_URL}), [Discord]({constants.DISCORD_URL}), [Forum]({constants.FORUM_URL}), and [X]({constants.TWITTER_URL}). diff --git a/docs/getting_started/open_source_templates.md b/docs/getting_started/open_source_templates.md new file mode 100644 index 00000000000..aa28a5e23e7 --- /dev/null +++ b/docs/getting_started/open_source_templates.md @@ -0,0 +1,67 @@ +# Open Source Templates + +Check out what the community is building with Reflex. See 2000+ more public projects on [Github](https://github.com/reflex-dev/reflex/network/dependents). Want to get your app featured? Submit it [here](https://github.com/reflex-dev/templates). Copy the template command and use it during `reflex init` + +```python exec + +import reflex as rx + +from pcweb.components.code_card import gallery_app_card +from pcweb.components.webpage.comps import h1_title +from pcweb.pages.gallery.sidebar import TemplatesState, pagination, sidebar +from pcweb.templates.webpage import webpage + + +@rx.memo +def skeleton_card() -> rx.Component: + return rx.skeleton( + class_name="box-border shadow-large border rounded-xl w-full h-[280px] overflow-hidden", + loading=True, + ) + + +def component_grid() -> rx.Component: + from pcweb.pages.gallery.apps import gallery_apps_data + + posts = [] + for path, document in list(gallery_apps_data.items()): + posts.append( + rx.cond( + TemplatesState.filtered_templates.contains(document.metadata["title"]), + gallery_app_card(app=document.metadata), + None, + ) + ) + return rx.box( + *posts, + rx.box( + rx.el.h4( + "No templates found", + class_name="text-base font-semibold text-slate-12 text-nowrap", + ), + class_name="flex-col gap-2 flex absolute left-1 top-0 z-[-1] w-full", + ), + class_name="gap-6 grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 w-full relative", + ) + + +def gallery() -> rx.Component: + return rx.el.section( + rx.box( + sidebar(), + rx.box( + component_grid(), + pagination(), + class_name="flex flex-col", + ), + class_name="flex flex-col gap-6 lg:gap-10 w-full", + ), + id="gallery", + class_name="mx-auto", + ) + +``` + +```python eval +gallery() +``` diff --git a/docs/getting_started/project-structure.md b/docs/getting_started/project-structure.md new file mode 100644 index 00000000000..f2ec8da416c --- /dev/null +++ b/docs/getting_started/project-structure.md @@ -0,0 +1,71 @@ +# Project Structure + +```python exec +from pcweb.pages.docs import advanced_onboarding +from pcweb.constants import REFLEX_ASSETS_CDN +``` + +## Directory Structure + +```python exec +app_name = "hello" +``` + +Let's create a new app called `{app_name}` + +```bash +mkdir {app_name} +cd {app_name} +reflex init +``` + +This will create a directory structure like this: + +```bash +{app_name} +├── .web +├── assets +├── {app_name} +│ ├── __init__.py +│ └── {app_name}.py +└── rxconfig.py +``` + +Let's go over each of these directories and files. + +## .web + +This is where the compiled Javascript files will be stored. You will never need to touch this directory, but it can be useful for debugging. + +Each Reflex page will compile to a corresponding `.js` file in the `.web/pages` directory. + +## Assets + +The `assets` directory is where you can store any static assets you want to be publicly available. This includes images, fonts, and other files. + +For example, if you save an image to `assets/image.png` you can display it from your app like this: + +```python +rx.image(src=f"{REFLEX_ASSETS_CDN}other/image.png") +``` +j +## Main Project + +Initializing your project creates a directory with the same name as your app. This is where you will write your app's logic. + +Reflex generates a default app within the `{app_name}/{app_name}.py` file. You can modify this file to customize your app. + +## Configuration + +The `rxconfig.py` file can be used to configure your app. By default it looks something like this: + +```python +import reflex as rx + + +config = rx.Config( + app_name="{app_name}", +) +``` + +We will discuss project structure and configuration in more detail in the [advanced project structure]({advanced_onboarding.code_structure.path}) documentation. \ No newline at end of file diff --git a/docs/hosting/adding-members.md b/docs/hosting/adding-members.md new file mode 100644 index 00000000000..9e27e8dd5c4 --- /dev/null +++ b/docs/hosting/adding-members.md @@ -0,0 +1,39 @@ +```python exec +import reflex as rx +from pcweb.constants import REFLEX_ASSETS_CDN +from reflex_image_zoom import image_zoom +``` + +# Project + +A project is a collection of applications (apps / websites). + +Every project has its own billing page that are accessible to Admins. + + + +## Adding Team Members + +To see the team members of a project click on the `Members` tab in the Cloud UI on the project page. + +If you are a User you have the ability to create, deploy and delete apps, but you do not have the power to add or delete users from that project. You must be an Admin for that. + +As an Admin you will see the an `Add user` button in the top right of the screen, as shown in the image below. Clicking on this will allow you to add a user to the project. You will need to enter the email address of the user you wish to add. + +```python eval +image_zoom(rx.image(src=f"{REFLEX_ASSETS_CDN}other/hosting_adding_team_members.webp", alt="Adding team members to Reflex Cloud")) +``` + +```python eval +rx.box(height="20px") +``` + +```md alert warning +# Currently a User must already have logged in once before they can be added to a project. +At this time a User must be logged in to be added to a project. In future there will be automatic email invites sent to add new users who have never logged in before. +``` + + +## Other project settings + +Clicking on the `Settings` tab in the Cloud UI on the project page allows a user to change the `project name`, check the `project id` and, if the project is not your default project, delete the project. \ No newline at end of file diff --git a/docs/hosting/app-management.md b/docs/hosting/app-management.md new file mode 100644 index 00000000000..c05fbbf33f8 --- /dev/null +++ b/docs/hosting/app-management.md @@ -0,0 +1,58 @@ +```python exec +import reflex as rx +from pcweb.constants import REFLEX_ASSETS_CDN +from reflex_image_zoom import image_zoom +from pcweb.pages.docs import hosting +from pcweb.pages import docs +from pcweb.styles.styles import get_code_style, cell_style +``` + +# App + +In Reflex Cloud an "app" (or "application" or "website") refers to a web application built using the Reflex framework, which can be deployed and managed within the Cloud platform. + +You can deploy an app using the `reflex deploy` command. + +There are many actions you can take in the Cloud UI to manage your app. Below are some of the most common actions you may want to take. + + +## Stopping an App + +To stop an app follow the arrow in the image below and press on the `Stop app` button. Pausing an app will stop it from running and will not be accessible to users until you resume it. In addition, this will stop you being billed for your app. + +```python eval +image_zoom(rx.image(src=f"{REFLEX_ASSETS_CDN}other/stopping_app.webp", padding_bottom="20px")) +``` + +```md alert info +# CLI Command to stop an app +`reflex cloud apps stop [OPTIONS] [APP_ID]` +``` + +## Deleting an App + +To delete an app click on the `Settings` tab in the Cloud UI on the app page. + +```python eval +image_zoom(rx.image(src=f"{REFLEX_ASSETS_CDN}other/environment_variables.webp")) +``` + +Then click on the `Danger` tab as shown below. + +```python eval +image_zoom(rx.image(src=f"{REFLEX_ASSETS_CDN}other/deleting_app.webp")) +``` + +Here there is a `Delete app` button. Pressing this button will delete the app and all of its data. This action is irreversible. + +```md alert info +# CLI Command to delete an app +`reflex cloud apps delete [OPTIONS] [APP_ID]` +``` + + +## Other app settings + +Clicking on the `Settings` tab in the Cloud UI on the app page also allows a user to change the `app name`, change the `app description` and check the `app id`. + +The other app settings also allows users to edit and add secrets (environment variables) to the app. For more information on secrets, see the [Secrets (Environment Variables)]({hosting.secrets_environment_vars.path}) page. diff --git a/docs/hosting/billing.md b/docs/hosting/billing.md new file mode 100644 index 00000000000..2cbb6d9e77f --- /dev/null +++ b/docs/hosting/billing.md @@ -0,0 +1,32 @@ +```python exec +import reflex as rx +from reflex_image_zoom import image_zoom +from pcweb.pages.pricing.calculator import compute_table_base +from pcweb.pages.docs import hosting +``` + +## Overview + +Billing for Reflex Cloud is monthly per project. Project owners and admins are able to view and manage the billing page. + +The billing for a project is comprised of two parts - number of `seats` and `compute`. + +## Seats + +Projects on a paid plan can invite collaborators to join their project. + +Each additional collaborator is considered a `seat` and is charged on a flat monthly rate. Project owners and admins can manage permissions and roles for each seat in the settings tab on the project page. + +## Compute + +Reflex Cloud is billed on a per second basis so you only pay for when your app is being used by your end users. When your app is idle, you are not charged. + +For more information on compute pricing, please see the [compute]({hosting.compute.path}) page. + +## Manage Billing + +To manage your billing, you can go to the `Billing` tab in the Cloud UI on the project page. + +## Setting Billing Limits + +If you want to set a billing limit for your project, you can do so by going to the `Billing` tab in the Cloud UI on the project page. diff --git a/docs/hosting/compute.md b/docs/hosting/compute.md new file mode 100644 index 00000000000..cae2fee5399 --- /dev/null +++ b/docs/hosting/compute.md @@ -0,0 +1,230 @@ +```python exec +import reflex as rx +from reflex_image_zoom import image_zoom +from pcweb.pages.pricing.calculator import compute_table_base +``` + +## Compute Usage + +Reflex Cloud is billed on a per second basis so you only pay for when your app is being used by your end users. When your app is idle, you are not charged. + +This allows you to deploy your app on larger sizes and multiple regions without worrying about paying for idle compute. We bill on a per second basis so you only pay for the compute you use. + +By default your app stays alive for 5 minutes after the no users are connected. After this time your app will be considered idle and you will not be charged. Start up times usually take less than 1 second for you apps to come back online. + +#### Warm vs Cold Start +- Apps below `c2m2` are considered warm starts and are usually less than 1 second. +- If your app is larger than `c2m2` it will be a cold start which takes around 15 seconds. If you want to avoid this you can reserve a machine. + +## Compute Pricing Table + +```python eval +compute_table_base() +``` + +## Reserved Machines (Coming Soon) + +If you expect your apps to be continuously receiving users, you may want to reserve a machine instead of having us manage your compute. + +This will be a flat monthly rate for the machine. + +## Monitoring Usage + +To monitor your projects usage, you can go to the billing tab in the Reflex Cloud UI on the project page. + +Here you can see the current billing and usage for your project. + + +## Real Life Examples of compute charges on the paid tiers + + +```md alert +# Single Application - Single Region + +Anna, a hobbyist game developer, built a pixel art generator and hosted it on Reflex Cloud so fellow artists could use it anytime. She deployed her app in the San Francisco region, where she lives. If her users use the site for an hour a day, how much would Anna pay? + +**Facts:** + +- **Machine size:** `c1m1` (1 CPU, 1 GB Memory) - `$0.083` per hour +- **Regions:** `1` (SJC) +- **Avg usage per day per region:** `1 Hour` + +**Maths:** + +`1 region * 1 hour * 30 days = 30 compute hours` + +`30 * 0.083 = 2.49` + +(assuming a 30 day month) + +Anna's total cost for compute would be `$2.49` for the month. However, since paid users receive a `$10` credit, her compute cost is fully covered. + +**Charge for compute:** + +`$0.00 dollars` +``` + + + +```md alert +# Single Application - Multi Region + +Bob created a social media application and decided to host it on Reflex Cloud. Bob has users in Paris, London, San Jose and Sydney. Bob decided to deploy his application to all those region as well as additional one in Paris since that where Bob lives. If users use the site in each region for 30 minutes a day how much would Bob pay? + +**Facts:** + +- **Machine size:** `c1m1` (1 CPU, 1 GB Memory) - `$0.083` Cost per hour +- **Regions:** `5` (CDG x 2, LHR x 1, SJC x 1, SYD x 1) +- **Avg usage per day per region:** `0.5 Hours` + +**Maths:** + +`5 regions * 0.5 hours * 30 days = 75 compute hours` + +`75 * 0.083 = 6.23` + +(assuming a 30 day month) + +Bob would owe `$6.23` for this month. However since Bob is a paid user they receive a `$10` credit which brings Bob's bill down to `$0`. + +**Charge for compute:** + +`$0.00 dollars` +``` + + + + +```md alert +# Single Growing Application - Multi Region + +Charlie, a small startup founder, built a finance tracking app that allows users to create and share finance insights in real time. As his user base expanded across different regions, he needed a multi-region setup to reduce latency and improve performance. To ensure a smooth experience, he deployed his app on Reflex Cloud using a `c1m2` machine in four regions. + +If users access the app on average for **16 hours per week** in each region, how much would Charlie pay? + +**Facts:** +- **Machine size:** `c1m2` (1 CPU, 2 GB Memory) - `$0.157` per hour +- **Regions:** `4` +- **Avg usage per week per region:** `16 Hours` + +**Maths:** + +`4 regions * 16 hours * 4 weeks = 256 compute hours` + +`256 * 0.157 = 40.19` + +(assuming 4 weeks in a month) + +Charlie would owe `$40.19` for this month. However since Charlie is a paid user they receive a `$10` credit which brings Bob's bill down to `$30.19`. + +**Charge for compute:** + +`$30.19 dollars` + +``` + + + +```md alert +# Single Application High-Performance App - Single Region + +David, an **AI enthusiast**, developed a **real-time image enhancement tool** that allows photographers to upscale and enhance their images using machine learning. Since his app requires more processing power, he deployed it on a **`c2m2` machine**, which offers increased CPU and memory to handle the intensive AI workloads. + +With users accessing the app **2 hours per day** over a **30-day month**, how much would David pay? + +**Facts:** +- **Machine size:** `c2m2` (2 CPU, 2 GB Memory) - `$0.166` per hour +- **Regions:** `1` +- **Avg usage per day:** `2 Hours` + + +**Maths:** + +`1 region * 2 hours * 30 days = 60 compute hours` + +`60 * 0.166 = 9.96` + +(assuming a 30 day month) + +David would owe `$9.96` for this month. However since David is a paid user they receive a `$10` credit, he will not be charged for compute for this month. + +**Charge for compute:** + +`$0.00 dollars` + +``` + + +```md alert +# Single Fast Scaling App - Multiple Regions + +Emily, a **productivity app developer**, built a **real-time team collaboration tool** that helps remote teams manage tasks and communicate efficiently. With users spread across multiple locations, she needed **low-latency performance** to ensure a seamless experience. To achieve this, Emily deployed her app using a `c1m1` machine in **three regions**. + +With users actively using the app **6 hours per day in each region** over a **30-day month**, how much would Emily pay? + + +**Facts:** +- **Machine size:** `c1m1` (1 CPU, 1 GB Memory) - `$0.083` per hour +- **Regions:** `3` +- **Avg usage per day per region:** `6 Hours` + + +**Maths:** + +`3 regions * 6 hours * 30 days = 540 compute hours` + +`540 * 0.083 = 44.82` + +(assuming a 30 day month) + +Emily would owe `$44.82` for this month. However since Emily is a paid user they receive a `$10` credit which brings Emily's bill down to `$34.82`. + +**Charge for compute:** + +`$34.82 dollars` + +``` + + +```md alert +# Multiple Apps - Multiple Regions + +Fred, a **freelance developer**, built a **portfolio of web applications** that cater to different clients across the globe. He has built **5 apps** where **4 apps** have a small amount of traffic with an average of **0.5 hours a day** and **1 app** that has a high amount of traffic with an average of **6 hours** a day. He has deployed the 4 small traffic apps on a `c1m1` machine in **1 region** each and the high traffic app on a `c1m1` machine in **2 regions**. How much would Fred pay? + + +**Facts for 4 small traffic apps:** +- **Machine size:** `c1m1` (1 CPU, 1 GB Memory) - `$0.083` per hour +- **Regions:** `1` +- **Avg usage per day per region:** `0.5 Hours` + + +**Facts for 1 large traffic app:** +- **Machine size:** `c1m1` (1 CPU, 1 GB Memory) - `$0.083` per hour +- **Regions:** `2` +- **Avg usage per day per region:** `6 Hours` + + +**Maths:** + +4 small traffic apps: + +`4 apps * 1 region * 0.5 hours * 30 days = 60 compute hours` + +1 large traffic apps: + +`2 regions * 6 hours * 30 days = 360 compute hours` + +Total compute hours = `60 + 360 = 420 compute hours` + +`420 * 0.083 = 34.86` + +(assuming a 30 day month) + +Fred would owe `$34.86` for this month. However since Fred is a paid user they receive a `$10` credit which brings Fred's bill down to `$24.86`. + +**Charge for compute:** + +`$24.82 dollars` +``` + +One thing that is important to note is that in the hypothetical example where you have `50 people` using your app `continuously for 24 hours` or if you have `1 person` using your app `continuously for 24 hours`, you `will be charged the same amount` as the charge is based on the amount of time your app up and not the number of users using your app. In `both these examples` your `app is up for 24 hours` and therefore you will be `charged for 24 hours of compute`. \ No newline at end of file diff --git a/docs/hosting/config_file.md b/docs/hosting/config_file.md new file mode 100644 index 00000000000..b56c90da526 --- /dev/null +++ b/docs/hosting/config_file.md @@ -0,0 +1,181 @@ +```python exec +import reflex as rx +from reflex_image_zoom import image_zoom +from pcweb import constants +from pcweb.pages.docs import hosting +from pcweb.pages import docs +from pcweb.styles.styles import get_code_style, cell_style +``` + +## What is reflex cloud config? + +The following command: + +```bash +reflex cloud config +``` + +generates a `cloud.yml` configuration file used to deploy your Reflex app to the Reflex cloud platform. This file tells Reflex how and where to run your app in the cloud. + +## Configuration File Structure + +The `cloud.yml` file uses YAML format and supports the following structure. **All fields are optional** and will use sensible defaults if not specified: + +```yaml +# Basic deployment settings +name: my-app-prod # Optional: defaults to project folder name +description: 'Production deployment' # Optional: empty by default +projectname: my-client-project # Optional: defaults to personal project + +# Infrastructure settings +regions: # Optional: defaults to sjc: 1 + sjc: 1 # San Jose (# of machines) + lhr: 2 # London (# of machines) +vmtype: c2m2 # Optional: defaults to c1m1 + +# Custom domain and environment +hostname: myapp # Optional: myapp.reflex.dev +envfile: .env.production # Optional: defaults to .env + +# Additional dependencies +packages: # Optional: empty by default + - procps +``` + +## Configuration Options Reference + +```python demo-only +rx.table.root( + rx.table.header( + rx.table.row( + rx.table.column_header_cell(rx.text("Option", size="1", weight="bold", color=rx.color("slate", 11))), + rx.table.column_header_cell(rx.text("Type", size="1", weight="bold", color=rx.color("slate", 11))), + rx.table.column_header_cell(rx.text("Default", size="1", weight="bold", color=rx.color("slate", 11))), + rx.table.column_header_cell(rx.text("Description", size="1", weight="bold", color=rx.color("slate", 11))), + align="center" + ) + ), + rx.table.body(*[ + rx.table.row( + rx.table.cell(rx.text(option, class_name="text-sm")), + rx.table.cell(rx.text(type_, class_name="text-sm")), + rx.table.cell(rx.text(default, class_name="text-sm")), + rx.table.cell(rx.link(description, href=link, class_name="text-sm") if link else rx.text(description, size="1", weight="regular")), + align="center" + ) for option, type_, default, description, link in [ + ("name", "string", "folder name", "Deployment identifier in dashboard", None), + ("description", "string", "empty", "Description of deployment", None), + ("regions", "object", "sjc: 1", "Region deployment mapping", "/docs/hosting/regions"), + ("vmtype", "string", "c1m1", "Virtual machine specifications", "/docs/hosting/machine-types"), + ("hostname", "string", "null", "Custom subdomain", None), + ("envfile", "string", ".env", "Environment variables file path", "/docs/hosting/secrets-environment-vars"), + ("project", "uuid", "null", "Project uuid", None), + ("projectname", "string", "null", "Project name", None), + ("packages", "array", "empty", "Additional system packages", None), + ("include_db", "boolean", "false", "Include local sqlite", None), + ("strategy", "string", "auto", "Deployment strategy", None) + ] + ]), + variant="ghost", + size="2", + width="100%", + max_width="800px", +) +``` + +## Configuration Options + +For details of specific sections click the links in the table. + +### Projects + +Organize deployments using projects: + +```yaml +projectname: client-alpha # Groups related deployments +``` + +You can also specify a project uuid instead of name: +```yaml +project: 12345678-1234-1234-1234-1234567890ab +``` + +You can go to the homepage of the project in the reflex cloud dashboard to find your project uuid in the url `{constants.REFLEX_CLOUD_URL.rstrip("/")}/project/uuid` + +### Apt Packages + +Install additional system packages your application requires. Package names are based on the apt package manager: + +```yaml +packages: + - procps=2.0.32-1 # Version pinning is optional + - imagemagick + - ffmpeg +``` + +### Include SQLite + +Include local sqlite database: + +```yaml +include_db: true +``` + +This is not persistent and will be lost on restart. It is recommended to use a database service instead. + +### Strategy + +Deployment strategy: +Available strategies: +- `immediate`: [Default] Deploy immediately +- `rolling`: Deploy in a rolling manner +- `bluegreen`: Deploy in a blue-green manner +- `canary`: Deploy in a canary manner, boot as single machine verify its health and then restart the rest. + +```yaml +strategy: immediate +``` + +## Multi-Environment Setup + +**Development (`cloud-dev.yml`):** +```yaml +name: myapp-dev +description: 'Development environment' +vmtype: c1m1 +envfile: .env.development +``` + +**Staging (`cloud-staging.yml`):** +```yaml +name: myapp-staging +description: 'Staging environment' +regions: + sjc: 1 +vmtype: c2m2 +envfile: .env.staging +``` + +**Production (`cloud-prod.yml`):** +```yaml +name: myapp-production +description: 'Production environment' +regions: + sjc: 2 + lhr: 1 +vmtype: c4m4 +hostname: myapp +envfile: .env.production +``` + +Deploy with specific configuration files: + +```bash +# Use default cloud.yml +reflex deploy + +# Use specific configuration file +reflex deploy --config cloud-prod.yml +reflex deploy --config cloud-staging.yml +``` + diff --git a/docs/hosting/custom-domains.md b/docs/hosting/custom-domains.md new file mode 100644 index 00000000000..73c8961d62b --- /dev/null +++ b/docs/hosting/custom-domains.md @@ -0,0 +1,75 @@ +```python exec +import reflex as rx +from pcweb.constants import REFLEX_ASSETS_CDN +from reflex_image_zoom import image_zoom +``` + +# Custom Domains + + +With the Enterprise tier of Reflex Cloud you can use your own custom domain to host your app. + +## Prerequisites + +You must purchase a domain from a domain registrar such as GoDaddy, Cloudflare, Namecheap, or AWS. + +For this tutorial we will use GoDaddy and the example domain `tomgotsman.us`. + + +## Steps + +Once you have purchased your domain, you can add it to your Reflex Cloud app by following these steps: + +1 - Ensure you have deployed your app to Reflex Cloud. + +2 - Once your app is deployed click the `Custom Domain` tab and add your custom domain to the input field and press the Add domain button. You should now see a page like below: + +```python eval +image_zoom(rx.image(src=f"{REFLEX_ASSETS_CDN}other/custom-domains-DNS-inputs.webp")) +``` + +```python eval +rx.box(height="20px") +``` + +3 - On the domain registrar's website, navigate to the DNS settings for your domain. It should look something like the image below: + +```python eval +image_zoom(rx.image(src=f"{REFLEX_ASSETS_CDN}other/custom-domains-DNS-before.webp")) +``` + +```python eval +rx.box(height="20px") +``` + +4 - Add all four of the DNS records provided by Reflex Cloud to your domain registrar's DNS settings. If there is already an A name record, delete it and replace it with the one provided by Reflex Cloud. Your DNS settings should look like the image below: + +```python eval +image_zoom(rx.image(src=f"{REFLEX_ASSETS_CDN}other/custom-domains-DNS-after.webp")) +``` + +```md alert warning +# It may alert you that this record will resolve on ######.tomgotsman.us.tomgotsman.us. +If this happens ensure that you select to only have the record resolve on ######.tomgotsman.us. +``` + +```md alert warning +# Your domain provider may not support an Apex CNAME record, in this case just use an A record. +![Image showing failed CNAME record](/custom-domains-CNAME-fail.png) +``` + +```python eval +rx.box(height="20px") +``` + +5 - Once you have added the DNS records, refresh the page on the Reflex Cloud page (it may take a few minutes to a few hours to update successfully). If the records are correct, you should see a success message like the one below: + +```python eval +image_zoom(rx.image(src=f"{REFLEX_ASSETS_CDN}other/custom-domains-success.webp")) +``` + +```python eval +rx.box(height="20px") +``` + +6 - Now redeploy your app using the `reflex deploy` command and your app should now be live on your custom domain! \ No newline at end of file diff --git a/docs/hosting/databricks.md b/docs/hosting/databricks.md new file mode 100644 index 00000000000..67a4967d18a --- /dev/null +++ b/docs/hosting/databricks.md @@ -0,0 +1,209 @@ +```python exec +import reflex as rx +from pcweb import constants +from pcweb.styles.styles import get_code_style, cell_style + +``` + +# Deploy Reflex to Databricks + +This guide walks you through deploying a Reflex web application on Databricks using the Apps platform. + +## Prerequisites + +- Databricks workspace with Unity Catalog enabled +- GitHub repository containing your Reflex application +- Reflex Enterprise license (for single-port deployment) + +## Step 1: Connect Your Repository + +1. **Link GitHub Repository** + - Navigate to your Databricks workspace + - Go to your user directory + - Click **Create** → **Git folder** + - Paste the URL of your GitHub repository containing the Reflex application + +## Step 2: Configure Application Settings + +### Create Configuration File + +Create a new file called `app.yaml` directly in Databricks (not in GitHub): + +```yaml +command: [ + "reflex", + "run", + "--env", + "prod", + "--backend-port", + "$DATABRICKS_APP_PORT" +] + +env: + - name: "HOME" + value: "/tmp/reflex" + - name: "REFLEX_ACCESS_TOKEN" + value: "your-token-here" + - name: "DATABRICKS_WAREHOUSE_ID" + value: "your-sql-warehouse-id" + - name: "DATABRICKS_CATALOG" + value: "your-catalog-name" + - name: "DATABRICKS_SCHEMA" + value: "your-schema-name" + - name: "REFLEX_SHOW_BUILT_WITH_REFLEX" + value: 0 +``` + +### Obtain Required Tokens + +1. **Reflex Access Token** + - Visit [Reflex Cloud Tokens]({constants.REFLEX_CLOUD_URL.rstrip("/")}/tokens/) + - Navigate to Account Settings → Tokens + - Create a new token and copy the value + - Replace `your-token-here` in the configuration +2. **Databricks Resources** + - Update `DATABRICKS_WAREHOUSE_ID` with your SQL warehouse ID + - Update `DATABRICKS_CATALOG` with your target catalog name + - Update `DATABRICKS_SCHEMA` with your target schema name + +## Step 3: Enable Single-Port Deployment + +Update your Reflex application for Databricks compatibility: + +### Update rxconfig.py + +```python +import reflex as rx +import reflex_enterprise as rxe + +rxe.Config(app_name="app", use_single_port=True) +``` + +### Update Application Entry Point + +Modify your main application file where you define `rx.App`: + +```python +import reflex_enterprise as rxe + +app = rxe.App( + # your app configuration +) +``` + +```md alert info +# Also add `reflex-enterprise` and `asgiproxy` to your `requirements.txt` file. +``` + +## Step 4: Create Databricks App + +1. **Navigate to Apps** + - Go to **Compute** → **Apps** + - Click **Create App** +2. **Configure Application** + - Select **Custom App** + - Configure SQL warehouse for your application + +## Step 5: Set Permissions + +If you are using the `samples` Catalog then you can skip the permissions section. + +### Catalog Permissions + +1. Navigate to **Catalog** → Select your target catalog +2. Go to **Permissions** +3. Add the app's service principal user +4. Grant the following permissions: + - **USE CATALOG** + - **USE SCHEMA** + +### Schema Permissions + +1. Navigate to the specific schema +2. Go to **Permissions** +3. Grant the following permissions: + - **USE SCHEMA** + - **EXECUTE** + - **SELECT** + - **READ VOLUME** (if required) + +## Step 6: Deploy Application + +1. **Initiate Deployment** + - Click **Deploy** in the Apps interface + - When prompted for the code path, provide your Git folder path or select your repository folder +2. **Monitor Deployment** + - The deployment process will begin automatically + - Monitor logs for any configuration issues + +## Updating Your Application + +To deploy updates from your GitHub repository: + +1. **Pull Latest Changes** + - In the deployment interface, click **Deployment Source** + - Select **main** branch + - Click **Pull** to fetch the latest changes from GitHub +2. **Redeploy** + - Click **Deploy** again to apply the updates + +## Configuration Reference + +```python eval +rx.table.root( + rx.table.header( + rx.table.row( + rx.table.column_header_cell("Environment Variable"), + rx.table.column_header_cell("Description"), + rx.table.column_header_cell("Example"), + ), + ), + rx.table.body( + rx.table.row( + rx.table.cell(rx.code("HOME")), + rx.table.cell("Application home directory"), + rx.table.cell(rx.code("/tmp/reflex")), + ), + rx.table.row( + rx.table.cell(rx.code("REFLEX_ACCESS_TOKEN")), + rx.table.cell("Authentication for Reflex Cloud"), + rx.table.cell(rx.code("rx_token_...")), + ), + rx.table.row( + rx.table.cell(rx.code("DATABRICKS_WAREHOUSE_ID")), + rx.table.cell("SQL warehouse identifier"), + rx.table.cell("Auto-assigned"), + ), + rx.table.row( + rx.table.cell(rx.code("DATABRICKS_CATALOG")), + rx.table.cell("Target catalog name"), + rx.table.cell(rx.code("main")), + ), + rx.table.row( + rx.table.cell(rx.code("DATABRICKS_SCHEMA")), + rx.table.cell("Target schema name"), + rx.table.cell(rx.code("default")), + ), + rx.table.row( + rx.table.cell(rx.code("REFLEX_SHOW_BUILT_WITH_REFLEX")), + rx.table.cell("Show Reflex branding (Enterprise only)"), + rx.table.cell([rx.code("0"), " or ", rx.code("1")]), + ), + ), + variant="surface", + margin_y="1em", +) +``` + +## Troubleshooting + +- **Permission Errors**: Verify that all catalog and schema permissions are correctly set +- **Port Issues**: Ensure you're using `$DATABRICKS_APP_PORT` and single-port configuration +- **Token Issues**: Verify your Reflex access token is valid and properly configured +- **Deployment Failures**: Check the deployment logs for specific error messages + +## Notes + +- Single-port deployment requires Reflex Enterprise +- Configuration must be created directly in Databricks, not pushed from GitHub +- Updates require manual pulling from the deployment interface diff --git a/docs/hosting/deploy-quick-start.md b/docs/hosting/deploy-quick-start.md new file mode 100644 index 00000000000..ade0fc99a6f --- /dev/null +++ b/docs/hosting/deploy-quick-start.md @@ -0,0 +1,90 @@ +# Reflex Cloud - Quick Start + +```python exec +import reflex as rx +from pcweb.constants import REFLEX_ASSETS_CDN +from reflex_image_zoom import image_zoom +from pcweb.pages import docs +``` + +So far, we have been running our apps locally on our own machines. +But what if we want to share our apps with the world? This is where +the hosting service comes in. + +## Quick Start + +Reflex’s hosting service makes it easy to deploy your apps without worrying about configuring the infrastructure. + +### Prerequisites + +1. Hosting service requires `reflex>=0.6.6`. +2. This tutorial assumes you have successfully `reflex init` and `reflex run` your app. +3. Also make sure you have a `requirements.txt` file at the top level app directory that contains all your python dependencies! (To create a `requirements.txt` file, run `pip freeze > requirements.txt`.) + + +### Authentication + +First run the command below to login / signup to your Reflex Cloud account: (command line) + +```bash +reflex login +``` + +You will be redirected to your browser where you can authenticate through Github or Gmail. + +### Web UI + +Once you are at this URL and you have successfully authenticated, click on the one project you have in your workspace. You should get a screen like this: + +```python eval +image_zoom(rx.image(src=f"{REFLEX_ASSETS_CDN}other/cloud_project_page.webp", alt="Reflex Cloud Dashboard")) +``` + +This screen shows the login command and the deploy command. As we are already logged in, we can skip the login command. + +### Deployment + +Now you can start deploying your app. + +In your cloud UI copy the `reflex deploy` command similar to the one shown below. + +```bash +reflex deploy --project 2a432b8f-2605-4753-####-####0cd1#### +``` + +In your project directory (where you would normally run `reflex run`) paste this command. + +The command is by default interactive. It asks you a few questions for information required for the deployment. + + +1. The first question will compare your `requirements.txt` to your python environment and if they are different then it will ask you if you want to update your `requirements.txt` or to continue with the current one. If they are identical this question will not appear. To create a `requirements.txt` file, run `pip freeze > requirements.txt`. +2. The second question will search for a deployed app with the name of your current app, if it does not find one then it will ask if you wish to proceed in deploying your new app. +3. The third question is optional and will ask you for an app description. + + +That’s it! You should receive some feedback on the progress of your deployment and in a few minutes your app should be up. 🎉 + +For detailed information about the deploy command and its options, see the [Deploy API Reference]({docs.cloud.deploy.path}) and the [CLI Reference](https://reflex.dev/docs/api-reference/cli/). + + +```md alert info +# Once your code is uploaded, the hosting service will start the deployment. After a complete upload, exiting from the command **does not** affect the deployment process. The command prints a message when you can safely close it without affecting the deployment. +``` + +If you go back to the Cloud UI you should be able to see your deployed app and other useful app information. + + +```md alert info +# Setup a Cloud Config File +To create a `config.yml` file for your app to set your app configuration check out the [Cloud Config Docs]({docs.hosting.config_file.path}). +``` + +```md alert info +# Moving around the Cloud UI +To go back, i.e. from an app to a project or from a project to your list of projects you just click the `REFLEX logo` in the top left corner of the page. +``` + +```md alert info +# All flag values are saved between runs +All your flag values, i.e. environment variables or regions or tokens, are saved between runs. This means that if you run a command and you pass a flag value, the next time you run the same command the flag value will be the same as the last time you ran it. This means you should only set the flag values again if you want to change them. +``` diff --git a/docs/hosting/deploy-with-github-actions.md b/docs/hosting/deploy-with-github-actions.md new file mode 100644 index 00000000000..55ff68ad7a1 --- /dev/null +++ b/docs/hosting/deploy-with-github-actions.md @@ -0,0 +1,118 @@ +```python exec +from pcweb.pages import docs +import reflex as rx +from pcweb.styles.styles import get_code_style, cell_style + +github_actions_configs = [ + { + "name": "auth_token", + "description": "Reflex authentication token stored in GitHub Secrets.", + "required": True, + "default": "N/A" + }, + { + "name": "project_id", + "description": "The ID of the project you want to deploy to.", + "required": True, + "default": "N/A" + }, + { + "name": "app_directory", + "description": "The directory containing your Reflex app.", + "required": False, + "default": ". (root)" + }, + { + "name": "extra_args", + "description": "Additional arguments to pass to the `reflex deploy` command.", + "required": False, + "default": "N/A" + }, + { + "name": "python_version", + "description": "The Python version to use for the deployment environment.", + "required": False, + "default": "3.12" + } +] +``` + +# Deploy with Github Actions + +This GitHub Action simplifies the deployment of Reflex applications to Reflex Cloud. It handles setting up the environment, installing the Reflex CLI, and deploying your app with minimal configuration. + +```md alert info +# This action requires `reflex>=0.6.6` +``` + +**Features:** +- Deploy Reflex apps directly from your GitHub repository to Reflex Cloud. +- Supports subdirectory-based app structures. +- Securely uses authentication tokens via GitHub Secrets. + +## Usage +### Add the Action to Your Workflow +Create a `.github/workflows/deploy.yml` file in your repository and add the following: + +```yaml +name: Deploy Reflex App + +on: + push: + branches: + - main + +jobs: + deploy: + runs-on: ubuntu-latest + steps: + - name: Deploy to Reflex Cloud + uses: reflex-dev/reflex-deploy-action@v1 + with: + auth_token: ${\{ secrets.REFLEX_PROJECT_ID }} + project_id: ${\{ secrets.REFLEX_PROJECT_ID }} + app_directory: "my-app-folder" # Optional, defaults to root + extra_args: "--env THIRD_PARTY_APIKEY=***" # Optional + python_version: "3.12" # Optional +``` + +### Set Up Your Secrets +Store your Reflex authentication token securely in your repository's secrets: + + +1. Go to your GitHub repository. +2. Navigate to Settings > Secrets and variables > Actions > New repository secret. +3. Create new secrets for `REFLEX_AUTH_TOKEN` and `REFLEX_PROJECT_ID`. + +(Create a `REFLEX_AUTH_TOKEN` in the tokens tab of your UI, check out these [docs]({docs.hosting.tokens.path}#tokens). + +The `REFLEX_PROJECT_ID` can be found in the UI when you click on the How to deploy button on the top right when inside a project and copy the ID after the `--project` flag.) + + + +### Inputs + +```python eval +rx.table.root( + rx.table.header( + rx.table.row( + rx.table.column_header_cell("Name"), + rx.table.column_header_cell("Description"), + rx.table.column_header_cell("required"), + rx.table.column_header_cell("Default"), + ), + ), + rx.table.body( + *[ + rx.table.row( + rx.table.cell(rx.code(github_config["name"], style=get_code_style("violet"))), + rx.table.cell(github_config["description"], style=cell_style), + rx.table.cell(rx.code(github_config["required"], style=get_code_style("violet"))), + rx.table.cell(rx.code(github_config["default"], style=get_code_style("violet")), min_width="100px"), + ) + for github_config in github_actions_configs + ] + ), + variant="surface", +) +``` diff --git a/docs/hosting/logs.md b/docs/hosting/logs.md new file mode 100644 index 00000000000..1ab4144959e --- /dev/null +++ b/docs/hosting/logs.md @@ -0,0 +1,46 @@ +```python exec +import reflex as rx +from pcweb.constants import REFLEX_ASSETS_CDN +from reflex_image_zoom import image_zoom +from pcweb.pages.docs import hosting +from pcweb.pages import docs +from pcweb.styles.styles import get_code_style, cell_style +``` + +## View Logs + +To view the app logs follow the arrow in the image below and press on the `Logs` dropdown. + +```python eval +image_zoom(rx.image(src=f"{REFLEX_ASSETS_CDN}other/view_logs.webp", padding_bottom="20px")) +``` + +```md alert info +# CLI Command to view logs +`reflex cloud apps logs [OPTIONS] [APP_ID]` +``` + +## View Deployment Logs and Deployment History + +To view the deployment history follow the arrow in the image below and press on the `Deployments`. + +```python eval +image_zoom(rx.image(src=f"{REFLEX_ASSETS_CDN}other/view_deployment_logs.webp")) +``` + +This brings you to the page below where you can see the deployment history of your app. Click on deployment you wish to explore further. + +```python eval +image_zoom(rx.image(src=f"{REFLEX_ASSETS_CDN}other/view_deployment_logs_2.webp", padding_bottom="20px")) +``` + +```md alert info +# CLI Command to view deployment history +`reflex cloud apps history [OPTIONS] [APP_ID]` +``` + +This brings you to the page below where you can view the deployment logs of your app by clicking the `Build logs` dropdown. + +```python eval +image_zoom(rx.image(src=f"{REFLEX_ASSETS_CDN}other/view_deployment_logs_3.webp")) +``` \ No newline at end of file diff --git a/docs/hosting/machine-types.md b/docs/hosting/machine-types.md new file mode 100644 index 00000000000..e4056f5eb78 --- /dev/null +++ b/docs/hosting/machine-types.md @@ -0,0 +1,35 @@ +```python exec +import reflex as rx +from pcweb.constants import REFLEX_ASSETS_CDN +from reflex_image_zoom import image_zoom +from pcweb.pages.docs import hosting +from pcweb.pages import docs +from pcweb.styles.styles import get_code_style, cell_style +``` + +## Machine Types + + +To scale your app you can choose different VMTypes. VMTypes are different configurations of CPU and RAM. + +To scale your VM in the Cloud UI, click on the `Settings` tab in the Cloud UI on the app page, and then click on the `Scale` tab as shown below. Clicking on the `Change VM` button will allow you to scale your app. + +```python eval +image_zoom(rx.image(src=f"{REFLEX_ASSETS_CDN}other/scaling_vms.webp", padding_bottom="20px")) +``` + +### VMTypes in the CLI + +To get all the possible VMTypes you can run the following command: + +```bash +reflex cloud vmtypes +``` + +To set which VMType to use when deploying your app you can pass the `--vmtype` flag with the id of the VMType. For example: + +```bash +reflex deploy --project f88b1574-f101-####-####-5f########## --vmtype c2m4 +``` + +This will deploy your app with the `c2m4` VMType, giving your app 2 CPU cores and 4 GB of RAM. \ No newline at end of file diff --git a/docs/hosting/regions.md b/docs/hosting/regions.md new file mode 100644 index 00000000000..e4283214147 --- /dev/null +++ b/docs/hosting/regions.md @@ -0,0 +1,151 @@ +```python exec +import reflex as rx +from reflex_image_zoom import image_zoom +from pcweb.constants import REFLEX_ASSETS_CDN, REFLEX_CLOUD_URL +from pcweb.pages.docs import hosting +from pcweb.pages import docs +from pcweb.styles.styles import get_code_style, cell_style + + +REGIONS_DICT = { + "ams": "Amsterdam, Netherlands", + "arn": "Stockholm, Sweden", + "bom": "Mumbai, India", + "cdg": "Paris, France", + "dfw": "Dallas, Texas (US)", + "ewr": "Secaucus, NJ (US)", + "fra": "Frankfurt, Germany", + "gru": "Sao Paulo, Brazil", + "iad": "Ashburn, Virginia (US)", + "jnb": "Johannesburg, South Africa", + "lax": "Los Angeles, California (US)", + "lhr": "London, United Kingdom", + "nrt": "Tokyo, Japan", + "ord": "Chicago, Illinois (US)", + "sjc": "San Jose, California (US)", + "sin": "Singapore, Singapore", + "syd": "Sydney, Australia", + "yyz": "Toronto, Canada", +} + +COUNTRIES_CODES = { + "ams": "NL", + "arn": "SE", + "bom": "IN", + "cdg": "FR", + "dfw": "US", + "ewr": "US", + "fra": "DE", + "gru": "BR", + "iad": "US", + "jnb": "ZA", + "lax": "US", + "lhr": "GB", + "nrt": "JP", + "ord": "US", + "sjc": "US", + "sin": "SG", + "syd": "AU", + "yyz": "CA", +} + + +``` + +## Regions + +To scale your app you can choose different regions. Regions are different locations around the world where your app can be deployed. + +To scale your app to multiple regions in the Cloud UI, click on the `Settings` tab in the Cloud UI on the app page, and then click on the `Regions` tab as shown below. Clicking on the `Add new region` button will allow you to scale your app to multiple regions. + +```python eval +image_zoom(rx.image(src=f"{REFLEX_ASSETS_CDN}other/scaling_regions.webp", padding_bottom="20px")) +``` + +The table below show all the regions that can be deployed in. + +```python eval +rx.el.table( + rx.el.thead( + rx.el.tr( + rx.el.th( + rx.el.div( + "Region", + ), + class_name="px-6 py-3 text-left text-sm font-semibold text-secondary-12 text-nowrap", + ), + rx.el.th( + rx.el.div( + "Country", + ), + class_name="px-6 py-3 text-left text-sm font-semibold text-secondary-12 text-nowrap", + ), + ), + class_name="bg-slate-2", + ), + rx.el.tbody( + *[ + rx.el.tr( + rx.el.td( + rx.el.div( + region, + class_name="h-5 rounded-md border justify-start items-center inline-flex bg-slate-1 text-xs font-medium shrink-0 px-1.5 w-fit text-slate-12 border-slate-6" + ), + class_name="px-6 py-3", + ), + rx.el.td( + rx.el.div( + rx.image( + src=f"{REFLEX_CLOUD_URL.rstrip('/')}/flags/{COUNTRIES_CODES[region]}.svg", + class_name="rounded-[2px] mr-2 w-5 h-4", + ), + REGIONS_DICT[region], + class_name="flex flex-row items-center gap-2", + ), + class_name="px-6 py-3 text-sm font-medium text-slate-9" + ), + class_name="even:bg-slate-2 odd:bg-slate-1 hover:bg-secondary-3", + ) + for region in REGIONS_DICT.keys() + ], + class_name="divide-y divide-slate-4", + ), + class_name="w-full table-fixed rounded-xl overflow-hidden divide-y divide-slate-4", +) +``` + +### Selecting Regions to Deploy in the CLI + +Below is an example of how to deploy your app in several regions: + +```bash +reflex deploy --project f88b1574-f101-####-####-5f########## --region sjc --region iad +``` + +By default all apps are deloyed in `sjc` if no other regions are given. If you wish to deploy in another region or several regions you can pass the `--region` flag (`-r` also works) with the region code. Check out all the regions that we can deploy to below: + + +## Config File + +To create a `config.yml` file for your app run the command below: + +```bash +reflex cloud config +``` + +This will create a yaml file similar to the one below where you can edit the app configuration: + +```yaml +name: medo +description: '' +regions: + sjc: 1 + lhr: 2 +vmtype: c1m1 +hostname: null +envfile: .env +project: null +packages: +- procps +``` + diff --git a/docs/hosting/secrets-environment-vars.md b/docs/hosting/secrets-environment-vars.md new file mode 100644 index 00000000000..14b7af5b023 --- /dev/null +++ b/docs/hosting/secrets-environment-vars.md @@ -0,0 +1,55 @@ +```python exec +import reflex as rx +from pcweb.constants import REFLEX_ASSETS_CDN +from reflex_image_zoom import image_zoom +``` + +# Secrets (Environment Variables) + + +## Adding Secrets through the CLI + +Below is an example of how to use an environment variable file. You can pass the `--envfile` flag with the path to the env file. For example: + +```bash +reflex deploy --project f88b1574-f101-####-####-5f########## --envfile .env +``` + +In this example the path to the file is `.env`. + + +If you prefer to pass the environment variables manually below is deployment command example: + +```bash +reflex deploy --project f88b1574-f101-####-####-5f########## --env OPENAI_API_KEY=sk-proj-vD4i9t6U############################ +``` + +They are passed after the `--env` flag as key value pairs. + +To pass multiple environment variables, you can repeat the `--env` tag. i.e. `reflex deploy --project f88b1574-f101-####-####-5f########## --env KEY1=VALUE1 --env KEY2=VALUE`. The `--envfile` flag will override any envs set manually. + + +```md alert info +# More information on Environment Variables +Environment variables are encrypted and safely stored. We recommend that backend API keys or secrets are entered as `envs`. Make sure to enter the `envs` without any quotation marks. We do not show the values of them in any CLI commands, only their names (or keys). + +You access the values of `envs` by referencing `os.environ` with their names as keys in your app's backend. For example, if you set an env `ASYNC_DB_URL`, you are able to access it by `os.environ["ASYNC_DB_URL"]`. Some Python libraries automatically look for certain environment variables. For example, `OPENAI_API_KEY` for the `openai` python client. The `boto3` client credentials can be configured by setting `AWS_ACCESS_KEY_ID`,`AWS_SECRET_ACCESS_KEY`. This information is typically available in the documentation of the Python packages you use. +``` + +## Adding Secrets through the Cloud UI + +To find the secrets tab, click on the `Settings` tab in the Cloud UI on the app page. + +```python eval +image_zoom(rx.image(src=f"{REFLEX_ASSETS_CDN}other/environment_variables.webp")) +``` + +Then click on the `Secrets` tab as shown below. + +```python eval +image_zoom(rx.image(src=f"{REFLEX_ASSETS_CDN}other/environment_variables_2.webp")) +``` + +From here you can add or edit your environment variables. You will need to restart your app for these changes to take effect. + +This functionality in the UI can be disabled by an admin of the project. \ No newline at end of file diff --git a/docs/hosting/self-hosting.md b/docs/hosting/self-hosting.md new file mode 100644 index 00000000000..2e687a11970 --- /dev/null +++ b/docs/hosting/self-hosting.md @@ -0,0 +1,121 @@ +```python exec +import reflex as rx + +from pcweb.pages.docs import getting_started +from pcweb.pages.pricing.pricing import pricing_path +``` + +# Self Hosting + +We recommend using `reflex deploy`, but if this does not fit your use case then you can also host your apps yourself. + +Clone your code to a server and install the [requirements]({getting_started.installation.path}). + +## API URL + +Edit your `rxconfig.py` file and set `api_url` to the publicly accessible IP +address or hostname of your server, with the port `:8000` at the end. Setting +this correctly is essential for the frontend to interact with the backend state. + +For example if your server is at `app.example.com`, your config would look like this: + +```python +config = rx.Config( + app_name="your_app_name", + api_url="http://app.example.com:8000", +) +``` + +It is also possible to set the environment variable `API_URL` at run time or +export time to retain the default for local development. + +## Production Mode + +Then run your app in production mode: + +```bash +reflex run --env prod +``` + +Production mode creates an optimized build of your app. By default, the static +frontend of the app (HTML, Javascript, CSS) will be exposed on port `3000` and +the backend (event handlers) will be listening on port `8000`. + +```md alert warning +# Reverse Proxy and Websockets +Because the backend uses websockets, some reverse proxy servers, like [nginx](https://nginx.org/en/docs/http/websocket.html) or [apache](https://httpd.apache.org/docs/2.4/mod/mod_proxy.html#protoupgrade), must be configured to pass the `Upgrade` header to allow backend connectivity. +``` + +## Exporting a Static Build + +Exporting a static build of the frontend allows the app to be served using a +static hosting provider, like Netlify or Github Pages. Be sure `api_url` is set +to an accessible backend URL when the frontend is exported. + +```bash +API_URL=http://app.example.com:8000 reflex export +``` + +This will create a `frontend.zip` file with your app's minified HTML, +Javascript, and CSS build that can be uploaded to your static hosting service. + +It also creates a `backend.zip` file with your app's backend python code to +upload to your server and run. + +You can export only the frontend or backend by passing in the `--frontend-only` +or `--backend-only` flags. + +It is also possible to export the components without zipping. To do +this, use the `--no-zip` parameter. This provides the frontend in the +`.web/build/client/` directory and the backend can be found in the root directory of +the project. + +## Reflex Container Service + +Another option is to run your Reflex service in a container. For this +purpose, a `Dockerfile` and additional documentation is available in the Reflex +project in the directory `docker-example`. + +For the build of the container image it is necessary to edit the `rxconfig.py` +and the add the `requirements.txt` +to your project folder. The following changes are necessary in `rxconfig.py`: + +```python +config = rx.Config( + app_name="app", + api_url="http://app.example.com:8000", +) +``` + +Notice that the `api_url` should be set to the externally accessible hostname or +IP, as the client browser must be able to connect to it directly to establish +interactivity. + +You can find the `requirements.txt` in the `docker-example` folder of the +project too. + +The project structure should looks like this: + +```bash +hello +├── .web +├── assets +├── hello +│ ├── __init__.py +│ └── hello.py +├── rxconfig.py +├── Dockerfile +└── requirements.txt +``` + +After all changes have been made, the container image can now be created as follows. + +```bash +docker build -t reflex-project:latest . +``` + +Finally, you can start your Reflex container service as follows. + +```bash +docker run -d -p 3000:3000 -p 8000:8000 --name app reflex-project:latest +``` diff --git a/docs/hosting/tokens.md b/docs/hosting/tokens.md new file mode 100644 index 00000000000..b7a6442f785 --- /dev/null +++ b/docs/hosting/tokens.md @@ -0,0 +1,22 @@ +```python exec +import reflex as rx +from pcweb.constants import REFLEX_ASSETS_CDN +from reflex_image_zoom import image_zoom +``` + +# Tokens + +A token gives someone else all the permissions you have as a User or Admin. They can run any Reflex Cloud command from the CLI as if they were you, using the `--token` flag. A common use case is for GitHub Actions (you store this token in your secrets). + +To access or create tokens, first click the avatar in the top-right corner to open the drop-down menu, then click `Account Settings`. + +```python eval +image_zoom(rx.image(src=f"{REFLEX_ASSETS_CDN}other/hosting_tokens_1.webp", alt="Adding tokens to Reflex Cloud", padding="1em 0em")) +``` + +Clicking `Account Settings` will redirect you to both the `Settings` and `Tokens` dashboards. Click the `Tokens` tab at the top to access your tokens or create new ones. + + +```python eval +image_zoom(rx.image(src=f"{REFLEX_ASSETS_CDN}other/hosting_tokens_2.webp", alt="Adding tokens to Reflex Cloud", padding="1em 0em")) +``` diff --git a/docs/images/dalle.gif b/docs/images/dalle.gif deleted file mode 100644 index 74d487849e92c951c9f2550c7d5e69a744ec6ac8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 918253 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 z zJNnCa!0BR-*7ima1rtIb{}!JWr9gjJXceV58<6Z69$gq7$p&!W2uJ6JrG-aK2!^Mi zDC5E-qhBAiB+}r21T|VkwH8KozK)Wyik1tHeo`3SpWopi6j2DuK(S33)zBN8hpg&a z|B)2~Ez8*cpmhjG^o5$Pn!As_7QOrt=_Vh>hIZHd5!vd3(5vSri-_%?cB}jj&kL5> zDHKLA3M>M2JvB7WVeqRT!cFMJZx(oCXPtFe?Cz+59wK%wm6?d$^)^*f>UxP|FZu^V zJKfjBv3!vn*}>gozY}hq5?z#X{|YzrBQ8lPB6dABfj#ZsI(K1_JtP}nsZ^<5M2EQ& z{VtQRnkd^D5&x$SAEYG9@dGY?HaQZnb6Q15-7jiujHl-rBIhudxi6bQn%2oV%VUOP^X zK1)#eNU1}}JLxA2`45_)`mB#vwho1H`p)U9KZP3}e6|PL41xblfXc>79~#k@W@_l?EQoQs|JXyyorALREzArO`&t@`ln7 zV=e#tEDm&OeWZwb3Cy7crc_ebRQ7rI{miupbYfol3_mDVrA=&CsT@2E<> z``KsD)$7JTU|(u~Nfx<-?j)-j;1KSj0yQ#$dMGsewe!n3eu%?m zXe`>U;{fI4*n+;xLT5uDw3TR5|x(ss$-=aBU~|}-`@_;B;h>XG?3Vh zHRD={Z^mxKh`=^Q-woa*FP+dCPQS!h6NgpD1;^=4H;CBBnZ;3>c)QsTk<&cGT@#TSzbKy`2~2Fi8CCcL`@%8Ew)gnq+xuhePt2SDzR(W@*p7eKUYYCOg8|po~mo!oEZadUK*)xtB>eeos zyiFSe&gnERlwDLy>F`1+_q+sRVRv%ofhy`;?bX+$WdgS{ZkaAL#_dg%c=H>Xp5M=R zCttF;R`c7xB6hv{3@i)A%zI4C9SE#^=7unrt_as4IWEQ@)~?DRdcTf0qq}NWZOY-I z>*4h$<1ualv7%As7OqDC22|M8A@HFUkg<%_lNNva7{Ck1h}$?SF5o$DRQdH3HecjV9dV_HCrbnXtP+ zx3d_e+HF*5==0tQqw`$k(`E%;fniidLd+Tm2%`2+F=4ETt0qSVx4RXf=}ookz$@19 zgic*foNh!^Hf|vtRHYnu^g58JX?(-;NZ3gIpchne4uw4JIVpd4R6ITvz`NNVcLa6# zK!Tz+ztd5Q1pAC5zvtVcaS! zicV3;nCqzu)4j!=-KiO+=q?jDv05Y^1$m);~p= zd4ZnwzgxE?Sk!+UP#aN~eO#UEEU&Cf+rDCsGdt`SHCO;|-7+cJ{`dFjEq3n**96$` z+_ohpzjGWc2ue1SqM<^*;2ZzhKHFJ-ANH^Ea<{h6inTZvNh9^oHZEE?k8dBRwyCVr z_~d<`PB-7rXc%_!ZztmK@~s14XX~L$929g8*ZiqL|FhLLZm@PhxAZ^VFX!Qcf2Asl z*zq4f4%V0R0eO7N@5-!jY73;pp?)JvB%1U95dMi)EtOEEkrWP48Bog9Or|l5*$$`5 z)J|t|t494f!lu*6otgzBw3j zg3t`1G}(F@18>O?>Fu19jVIxk7gy@uo65KeB3)zOhhwwFbpCRgbw3v}1Oorj4#`c9 z7U-9%$6l;Hw`?~KC_7Ub(CzNw57tHw4Sd=P)xQ{tqWu1Ow^mn&oaf?j?3H!~g>@ZYmA;hNP%e>gJOyo|2-u$XIm(oSJtw#+5zBb}B^vuvp!Igz zO0BLxk%OgO=W$8180TeiI5>HHO~Ko(G49D$0#P}7p;dTLZ<$B?q?!Wzx}sU4bN}4~ zDNHJRX@KS^IM-O7u*jlYbE+NErAxK8VWJUE*=Jn0{&Ih5;EB^4sWNh^$)HKD>ekcH z|Nds5WBw$&vgids#j?2#1oOnEfIr}5Hbv5fWxHv&OEJrv9%^jQ*enzvvnb&e1G#FI zv+jHjk>kn*%BjsfOLGR--YK{2>BB__zWss~CX3=8?QnR=_t$lb2l6_rmis-=ToNp_ zImNv)S6P@qUA=GO0+{^*o&E7?X$Y62ymhp_{WnK9HKY~O)nCKGxA*((Bi$0sn04R4 zh$0nC?~QC|r*9WmW^r!tMrIA^wlUOMrCTSc=RT|Pn8kcx_4*aQBj|Ja`O(d236KeGBoM57y z7Y?eDR9fklmBLrdc850%{d%^`YPpE~E;U6wQESrEs=IndJSSPaAt8==_qH8-DwJ4Y z{@lUj#IXtEk~HG9uWGXBWWI0QQ0RrX4*zA}{N^>aJ9a`OJhFVp^#<#6Cjm*$4G5cX z&QwB7P&9|r9EW8v3r(oIhie@-*F*9lRZD>g%1HPfJ(=OBXHX*?{_SKSM&w~Ul)qWc zNk^bj`+*&!Cs^$r?Gd8tsq}joDVILLMLJVwj8Cx1Gt;r;nAX!gP($p?NbE_mJ zM1;5I08>zaHJN7iav;#Aj5?2fn zF`ANiw}O?q(+qW8yH^E+!gV;m7&aT5iB>c{$RuCtvQ|2_(xq;u*!peuJ6exZBMFGQvFV zBDuU^7J73v=TA-_y|)yon~MjY*;E zIL@`}?|i)-e@;s)!9Z$rSj;j<*1Y+Awdua0@Cf5isIu*T{cTfL8#}lO-jx+diJf<~ z#6#urW@rEDjR$zZ52&I7(n~YV2>v!CA7r~4;)25AGF=pzV>?$a4Fo% zl=-?PfeCyrV5KHBMS5?_%&ch7`NE68;>BFw8SMGfIza?i0g=v;%6oTEO5O9)y&o?a)Vd|7(bmwhZ+kbJ|l%5mSsuIa6Y6hQv9w zpM~wbXivw!!T{rCFc93Fp)J#Rk2{pOB!5%bwh zh!?5a4ze48S$cDL+_gBYtOrd2_~u$WWoHQdn1ZIxq(=|=a|&WEhdo?aaZL`p{SHo zj8#R`$-g6;QAbPlR!Vv=i8Nh332MtMDDa8Z@T)Gwfe>(PP*0*JMYroDN!m zO3&@5EA%(2;jN>%)jzcDe|iCx^r8Q07mGi=rTed&kr;PxQFtR%I$8Se1-Wo-h!5zV zv!O}&15;>6VU%cXSWJ-RcCyOmBLl|8`$A4P?#soM)Ug%C2U?BAxtiR0Aug>P)*=SP5de_toAdkqU+33 zc~r$aaEb{$L?+34npMBvHdmW<^@iLZMP@#-sBV))xn#&U9agJZwkuMWf|Mx6UB^3= zP;KiV`SFCt>co)61nFe^D4D4HNOpA{$F5}cKoc~(+xcP9eUY57XN zO6DJ;Zr77wgdaB8YjHBU$Awb@28d@MNyOJoZa@Ca+-yR(?;rjeYDWP3Bi+SkNe zG0Pe?rZQ8=F6@a2^n~8KZq+@$hGA2cWF9nJBn}s*7}q>-8$)<}Rh^t=#UCqpBfT=% zlhbx%aS3U`GWhM_snV&@xD%%D_;)bB%?BN;P`6uc71H4?Hpt0UZy&7=#1x=_IvN}5(nl*rAS5C|Ao=h{lh+Y?Bd+#Co z@W+FYYEAJ;TK{$CKVM@oHk|hxb0K5-p~{fM>+i68;qTY(Mek!cvuPA1X6~Y|81rJ_T5r~w&#YOOs%V)GT+HCEr-;JVby&)6w$)}=UFqcB2OQW8#JI+1Ccf-bxk=3Ua+ND`QqEu|7+tHt~H zD;q`>LI!Qvo^XifnknmCrx>akJp#%9D`p=bl*C5FV{065CuF(?Is2utr0qGb45ru3 zX+#V%sqMOEL%jucD_STkKOl+hpHiGdx`ofD?R$F##F_Gkn|a&!b5Sm36ps;w#yGP@ z<9pB$5^59U>f01DRcn@2R@Jb8>Z(_}`9h044QW!__Y{k$gxR73pHZpu3%D4W1n_&; z$_G)FCsG>BhAYnJNDgu_>#%0cl}Z(4w$CJ^~(7z&VNM#o1 zAud$_sIetAl_BNa^#axZ;u5U_0i!LF3oTuzAYtiNQj=E6z*>`p)~3^zUX@l+RBIn6 z^hp%-aSKQU#v<$33cyGw-?Y`gX#=WSgiT20q)AtYNF}>SU#pOA=(TK%w(r=I?ooGS zl8_loLvEduat3w$zx9p(O>m(9zxu{lA|+Kvac)=jL<;2pR*L!Nq;>oBB7HYA%qW1r%f^7Vd ze(yT|yV7dywbqi~-}<`KaUw3?m1R`Y7;sIqe>U(DtEry!CPe>H$D~@sS9h||fipvw zCuh{WzTuL)6Db!_tD%=1dozS<_pX8RS?{wfF`swO2YZiRQAj278V>cHEHf{v&}Izv z`z)C|n?)M_hHa;0kbQEtfAZ+_m_^HCuw}h$4LrAj1Y+s4K|+mip)ZS z%Zl_rqW^4`z*EZ$L}BVaw;ZY54)Q<30Qi?WFwb0ll`=HX_2EcZ04>|dBkD!QH3LP;;1s7T=cjK z&+3iL$8km+*JLQ39@oCy#nIH|S$dz;7rD2bG?ay%Hgi^VD4n1tP%5rXqFLT2`Aq|y z`#IrZ#7Kj<4tFo%5|gzkL~E7limQgtpOz0@9qmpg;eL@5c4o%irF`Vg6f?=7;PQ&2Nk|2br1 zhPS(Y#q&*p+?CcZ&*kr`QVb~%a;H2~s4XhRL!|oUhE#7fQV-v}JKr17h|iO+*CLJo z8layz%h_~geX0&^Ge4!BsMYpze4w0N+Gmgqq{7s|i zED*m=CS`HILIS?dQNn@Y5tVz@=E3P7^L?FFCZ4Lj)5m81z)|n_7WVzFXqotAEC}m& z_;VU7T{DDEM{4Q{mLdDEME<)KAdP)@t5XfLoD%Tw=W*j_Bwr9mUH>mls8RTKP5>?O zZ&J^HHi9z&+B7+@VA{Yv*-;~?bBy2aUE+CT^LLrR(0TL@sqhuciOD&R{m-4B>gWgb z?&qRhe|9{AvuRXpjPbP^^ufln<9vdjVT_Y2L|g0xr>Sd#;`3_y@L4qAnCvF9NajNf z>+O2+jeww3DdOXfm@K&pYDw!;{U(HNb@O zl!0`L63|lG3;DglKz0K=Jrp>e{(#zm@d>-sT59q`ID?(*HgGqwgaufwIEHurwREv%Zuh19O&G4nT8~V z)cspT>M0*k-U9B`OS8=1bV--6Ut;FBsLMmDkrdk$f$m$XkzE+E(HQ;-K{OD+NBEL@ zxnLcplA8XWWp&1b0?uQP|#JpWf{WdUC=}M)jLUWTU znb_!Ii`D`OfG?j!{DDy=tCKPOcrO+`{fkE>Vy!o~3hhNk@-~B5#mb*xeCij8r*(Do z>^-Id^9FOJ@@SyZqhx@H5;ezvOJodz@&2jj)bYD|h)1hW{XPL@1U*GLW;_W|KhPemj8`$tDOk9(%(cQ;VfExP)Yslp2sPwtv z27=B@eBJiB<8)&A>JHWK)PB~E)Bv^vstORQo^mYkASXNHT)DrFV&gNHMoha6mZg#> zOGa4jY}Y4^79)^#T{Mq@r1dCGP>EcWO}}wUU><~Xx=9kKK$-79oj@N#u>9YB2z*k- zW_lw%1&^K|NJjsBhUgP15_4NvBsfpkEi~rOJa6I*rzUphpg3KzWiDm%Hn7l+)#7h+ zX{K!)rhx_bT)$*6-(`Qp=38JOSEb9TLZd7!77~2ue16T+@Q^>STe)o8~i|Z{g?b~_8oaT z?MLp9!meo|X5Xd|PJ!~T;z`2lPa7GoQM)>-#DUe?44km;wnv;`$;6Aa+h+oA`b+=- za!&BVxNrcJVj4=B1os6lg~9=S3f1EW`Z*Ymr$m%;NnY7uLE{~faz`UNp?{D_8a$W| z8mPf}76gPi!sICEm?^*hSHrgd9>4b^GYW_&MPWusv8gYq7>rrJEgm%rAt&=KtMoLQ2cTDpX*%Ld3Vn1nRzvH&CK(C?$75|KlQ$A z`=avstsM1J;J3r24ApFb@@B%MPY+Sa_sF)6>1(xtE}8ox4)jS-SG?d zY}Hp`iWD{fVR8MmJGXl9nS9hHJ{ke_d|+pH?=7Hw545-Pmiq;0loNyx1Q~DHN zp21osVUcsMK?R}r*utphLS#)a=6Adw9F%bQ7+?8_euiLYmNPMAQ>$B!bIc$N)OWzDi;Fo|W94K=_B9ZSUpa=K#YxQ( zUyrf41VvGKx}2>#`lC4^63A)*Qc@B7TMZyIexW`X2a`y|mk(v<3Xu^aF-XUyRz%U6 zCUN*BNi4zz+Z?SyoZTjoJoX7JnbmN{ZCa1e)sq{ca(M5092l4(=m zBE`X^wkce27R9v`^?6@%JirXyP(fWJ zpbDI+0$3N|F-FEsHzWL-4v7(s@i(5yiKKfSF-eP>`P-pkV{Vo@F(R(*}Za!mc5K7^s~MWLt>ZsUaJJ zm(%Y;KA)NE3Ce+c=D={WseGyqg z9n8R!URsM`2!!LvxHll*=ali=%OX-Uq^vtqTi zVuj9Bxgh+v3Iw1A!e8PgTulTl6D7KIrRzmS`evm@uS-p`N{faFGjY*g`h{k6WjV|Q zmS;(J2p0Ku_C-08CkS*f2oyYP*(q9n?3iRXRyf6sF8~7h^N@>ff`xyVi=y#^H7bH! zD?)fGf_Q+STSSF$f5n|;QA!v8!JTf=BgU@182tkO0UDX}rxJ8lko}ov7 zSU|M(>vL6P1GSe`j258!>j~z`)q0viLV0)tfq8?-5+S}@1EPzO|C$iq)o{E;2uL-4 zHGsHm5>mx9Qkz4-o{i3Djm{n@dio{`bGmzfld1F|JY7xf{&bE(O?Rtw8axR9Boogq z5yYmTN%ZeM@vi24WV4(U$`NRJXx^d{BQN-|(WRk9Rf<7U3gGz&ahYwI!8RM&5Xz}H z8}c&BblnBL-r3N%+DWxKn72Clw>oFHx^}hhth{soNcHG%YX!3H#Wz~3O+sT2La{|R zb+0x_3iSZ=Zf=gKQ;=9CXhSQ9Zi7?k1eG zO=9cbCZbX;(_GDd*!EZYTlPb3n}?kay=U}&qQmZAzfnzBQ-lS<7FW53-7rh^RNG-a z-#-HVK%vG!N>R0@oe^~e0GF7ccx3~%f`{I(N+Or08z|_PI!^$f7t`lZSY4++zUs+m zwz6Iw&_NE!n!$o*DX5N{p%wt&21TN7+=_4Ue^P^N^RfRCfg(V6rD~m1t$jLruPj_UPF3f^qfb9hgcUJtWadfq9$S!B>u44WuqSmprUVRR)AoxijBDy8r{}QW(sRjsOvuY|u-~LAxubCX{Uof-N45mk zgX8scZlY5Z_*1mFDT?qZ#=K)Xlp7MA{T{$JffGl$<|e^{@u|l^CIc3>7B?bw@H-9cg7zpaYw+MxYdL-EOiOMJ3%D~Do_ zf%5AV!}sc*XbZaH?v6aM=_f=m4r#g{lQRnPBQ(el##^Z`&Zb1dX3OOJ80{uQ<=b-X z=kf&~75C3d7RD)y_k8_`D7Kl!>!G0e$NqClVk(wm&K>D_yD`GhG$KYqx5J?I&jPs> zIYxIel0P?nUq(H%`@NGppx(o+{7Jdi0wvQkuRAv+$G`MpXimqLP%CJ}Sr;Plk>ajN z%|sKhjTqI{=S2)POnPr44Sc-VF4dN!N+ zljnV+@~{LnPgsj&Xu<;dHMz*IT9g0Hk&X~P$cHvBd2J>LH=<>D%3bq(OZ!w&1AxSC8PQhd|6(JMl!m3-eDB%BPg1wtX zk&UX4{hwKslDJcma=7cFg+=o|%aDAu9O;GHJS&$8SRQY+6xH9Dgt4E^5yK|B#RQ(v8PsK%^J(2w_yIFBCHuMJvh_$MwQ>I+@$r?9;^m%%TXMe**vYc2;e2 z=G0G=*sC0lb=jG5hXmw>#XS#(G8&KZN63Xv2nJB((Eq=at>0I>h$TyGbD-uI}P;(jjjmP%_ zLX#_t1S_#yCd;;Jpk##!omJE#I+YbW!9x0q@Aq6@&)ZvQOhCZ1-3d`mXo0i-t0V87 z>@Qlet2ck~Wyu-DW__mqTp+q3DY$N;?7k!1O8}>FL^j;E~ir2 z8wwJdG335mL9ZpkKE8zXKoQZ-hjxW>Luywn+O3Apsc%7J;ydw7f~B^K1_5J_T_^dU z02ZFuT`PH#JIMd8JfkSd{gr9zT8!X@+5*3XcUa8ZZoD8?b<(=fNL__}<$gV{a{ZV^y-R2YI7>zk$awjh5?A^q*(_#|Q$&d-XGV0}XrMCE-q-0Qv=B(hMj#=3_+qKxf9y!^tntH(00Q8<9Be5_R zhA>>}Az12oJP9`C(5xVt+^v-FR65xXu>Oyjidp=-`@7EQjQ5)InnWq5zUz!5*S>(OSnx4|vvk}c>ByM?h7r_z;@g?E!&O8lL-}ro5XO>B_-hn~` zaeRK7yT8};@Bg>3HE?m$y5Br6Q@AUDwM-K9r4<%m$6I%9!VHEIOD3pLX)QA#&vVio zs?G$GYjEjIH^=@*Qn_MS?3)EbL%Zy;J|8$__w zDDm)He811uqf3;Jlr^w4Q)uje(4Ze%5kaDYkP;EKK~#LX9|4>iB<_6IvQNr4Qjw|Z z&{jT}c@{40!bIefTK;6C^hK6SI_B0xO^M~`)T~R65s0Blyk8p^rF$IpHQu+DTID>N zQj?5|H?ot+iY?r^u&^NKV3;4F5d)JirhW3FBcq#`&+a(p$$tu?oOl1Q8(l?o3npxP zSDq3N&Fzh&k?-?cG;xu9>KmwWMi{n{H$sG}>ebNLHQ#g3te`Ls7O=wRux_|`i!J*H z#Y9bo8jBMH>j^`7`GVOGydZxz2JGW8J=A-yw=ItH@#hnfCVJ_wUDElm>PZjjk~2hU zw@~D!lX*(0Oibn2y?-$6tj*Dw;sASN3&VT5M$Md4>JIn$Bc?S-^#nfMaI`X%m%n&D zoO`sP@!z%!VDOMFr(0C*5gwUbhzCSN{S>>DpbQe>xY*a)wGc@fA0>t7bG))1^SQ>w)KSn=45V+mV8Q0v~Kr}QJbNB!zf^4&qt=Y)-I zx%k=enDE#zX|5|=m#h(&%k6H4pqp3z)g*y#x&uY66a?_py1vorW!60j_9 z+NGEFh!g6*1rweBDT!!#{7eh1m7FcO?PTImZ~R#^M>~k62RySZ46`67u-5Bjb>j{_ zkzDfwECo`M&fGFcja;_9LM2F+)4$5o)A7%hp}K0FMN8R@9&s zJ7?y2L%#$L8|7w;j(Z-P)~QkQp1|{5DQ#4Nltf=Q(9{uSvy7;rq7MYCk2skPqx6r`{&e%|Lgb1pD$X{XQyD?+kM%T7;O z3<9CwQYQvLkp58l7gudTR|Bv8ZG+gEjflmu5G7COtGD|qSh@_kLJ*c+-cwc3k6t`j zN+GBK>9?dB+_@W6QHm%-2ge8plztUdd_qZH7EC1w;}~OY=?5AM4AfHfNXk8MqeWd)5DnnR1rLVKUxEet1 z916MJg1uHCYQaRTdP&G<(95?=4j7ZahDq`{^FjCJzZ$@8+G%S*lX;`F7VC@J)%=eh<4kNT9M6E4%g4CU8L> zr+Yyq$o)2<;~9y6ytJ1H?8GXmi)Vp;oE(|D`Ma-*QkAmZK`B?zs4v(c#GghZJG0Y>ttOqKdR!g$-$*G>@2~I7J1Mh8IFDZGAX|QT#vOOs%H=1B3X-DGOJU zSM`&@1MpCF!H-Y);yp3~{F3jBq!K$PDs9EYRlY%IQd_nVy*15bWb-%^XAO5j=4`^p zE;W5UGmU=OH}61EY}97W=>^4!Ictiz%se4 zQ8ue0ysd-B1`djpqZo!G>-s6@Og*iJi4wt?+o^dX19^DDX5S;?@hb!h;S?LD!0$uK zy4eC#=*#0@wnp6Ka!<0^JH*jkIbXSHxS$ldPT8!Yc`TxZzt7VMJqy;$T1N z40wWIWS%x1hp=;X26uJO65R@drlcIf2&X-V7tB_#ssxmdfWV0&{3m6F>p%gb3cLl4 z?XPz6tR|KB{OLzgiMxw#)R-fQDzfao#u6R$o~r*5e#=u}PLPA|Tt-${?T;4~>XK0w z4SZRr+;ibe8KX`xQ?K!eh7i;Mg|#n`RiEkVAm^}zGZ&RCwWra*wW(~JIP+&ox=0OR zPohp<1J$fSF^K{C3W)m}h)E|AeYm=t`TDgZ=xHbG#UJW=U0{2yk&P!0dl2e0V)Vrk zhU^2u~;bU&Tx(<;!9~y5LT{5ZZ#3$mCoTB z-g`Am7nQ*V>Z}^m=4ju|A2v_ZwJ4aQCi;oVcpixR-_!ZaSSMG+B-ns&-r%GOSn~4B zndUn9x85{)%#9bi*uodYnPXglUt<|let?~uLRmo#{#*!GxZN{Ns#oJJ9OU^pwJd01 z?SgYg-X^>-vlWDH8_>%wnuSx_=8`qmG}-~O6*Ye=5Y1=7d4IJRCV;n+Oh-J(SK~_5 zZW%AL8^OqqEKMNSqYH)XNQ?oxC)-IH;|H^;f{4iPU$uo~+f8V;htVTz|7zb2XyPzE zaUf8?6mmtA%0aV11Q}7`0snH_m6U}c+L_Fhe$`w37vQFUzCZ+uIm@O>tj3%p=(B;P zL*S!Z-a=*S;6cqxIOu=~WUPrjiD@O1NBC%>IVc5=;#U$o-&+dJrrB-5eStH^M5-q%4h$nbm+cs;lvF zOWnMb)q=tV1uTvM2Vzh*8{8@u`s8K=(bfB~@!fl`kuFzCSuNnDE>upM`Q=%!%Q5iW zVibgX=uy)vOgUzGD-Vr6elLoJ;%|*6S&T&x)A4rS_p{LU1&yZ?)1^(`kJ*G~5`#R@ zY7PLboETIY3w;&@#cobmcy@gy(+Dl{JEVulchFi=N5=noF=@=A6fzoEIr2g`p?A^%HzTsgBW{I@YXAh2 zk(&Q+fLM{r3iy;dZv#+SjwOh$ddC5!x(E^G5>Eu(8vqtcrx_Q{*6%iVu)6aW#EAJH zA#aIVeh7Zp-zs}1$-T3-6vNuV&q-3B3wwrJe@2WnCIR&`A|F-6`8-)>Yg|AvzpuOf z_o?-tU8Kxsf6LFifuH^3MiWLi+ANpw=RS}N%^GaY3dwIgJ(>q?u3_C&qCb7{KlxG` zz4m01IFA2)4L@=z5NUcsdGQn}5Es|1?a&^$Y3VudGGN~WBI);yTPd%Z;E9TLST&mK zvP$e>J57iq5oA7@oz-Fh>NsuPkR`YZKyNMeA7$rc`cywL0rq2ipebqg#xi{ep4INZ ze{fOH9rjD2>vz5LxNX?oCLV_Q(L><=>1VgN4RSpMIKD0K46bI?vDUTvyO97quv08V zGL0cwKm+d2xaZODImqX(a_QwssT#G>zoodnfw8;bv@QrVv$_A2iR%Ru3Bj!ZFo*tX z#zNv;ClM>4S0KY{fdrt`b%NfRlB4r}v!ni+z3D-zS>-TQI8PfvH(7M^HZPaFO+ zl(_t=SE8OO;JChcXi%Y7N@vKWyH{UirGM$;5N-L=f@0<0pt%P;BXUZ2(WWXI-Y2}{8 zrQ=14S**W&Ut9jT-ipWcykGO_7RQ@Nqq^G<-8J6d_djJnZ+4o?C}N4H3F6YDo6~r< zgs}TDs=;sm%ktA7@8h+5R1ao6CH5k!ja)dbKi~LW;+NlPiXb2KAJIqkg-0@!?I!yy zeE<68%k6+uP^Jo+YwYT~909qZhkSgHlyl6t0%5pp$_x9mI}>cIs|<+Y*5(Nn>KB?qqbBDxlDzxm%bZ#wzJWye}B#n7E1RS0jf9~@=0swsoGZ4RR}MN_w9TB6gP2IvRyr>>7OokGBm)%%0^(AC;G)MbS@?Ev@BS(Osk6IzwP;Td~3aQ0|-eiJi(YLE{e(~ zC^?+=Ko`$=!{YfKLc-fX-I3dS?rGH~c2$&yndwbdIIZhOtgp3eNh-y1eY?Xe+aDU- z%A0JmhYq_s6vwHeJQAQSEUY52VDB?G!cmtH@GW%NRyUMYOAoB>s}M;r3)i(FNuHPcJu({v z#?K@r*-^mW)v;(*nj>8fhU!cjq`_WcyoQm(^0dh4t(~p@vQ=tm9$Sp@uJ)0kG5dca zjZqehGgRXyx%{EFvX7E!J-R&-ccK z>Xr!=W9u-x#u)@KPR3ooxY+1>VQdwG`boT6>p2|sYYd@&FL>k@b_|Zr2thC9Q}7Qz zE2nu%z`{II3x$}UAKO4d7ToF0cW=Kjv@C&XY&x5IzG>cr@z{+Q=^cjbAcaUZt|Ly? z`IJ43jNg>~tywb%0YRBq`Mdto94&MZZb6cj*88p|!ocrSx z?kvK2M+{MB=*8*4XcxooDo1rXlO!*Wmt=S&Y9LzIT6S9nR-E`SYnq{M(ylIgJAlg| z4HU-T-(0-C!=-synJY>>41F#)#-O%6pE$e)wM53Mc{GKw-&`;=F}OUcS43Q9;DL@8 zVHBUa^@ab4$kQlqAq4G|(?e}QptFHz^N~o|jhUhd-z0tSNbUbhyiwgYFxAy^L}^W^+kEV)$(0DJIUG0K6@34VL*3XOwCVA$!?LuVR@yNvf3klEdV1+y&E1M3?rR@&d2I2k; zRqOn$Y(Ci&fCkM!zuYe`CfKrCd%Xs=`&BD7^394eV?mwe;!nKfF}JIK;kdKix3R!e zTL%YC_y0Qe*REK+g{KQjFHU>zIWDsz#o_S20*mItOfCYjW<$)xu>VM;mg=M3WSOb) zC%t@Zs<^L*uG-?ER+kQ20WPy=uU9A6;;O!@pB(i=dh*q56s>NiHE*BMtq%3Z?^($h zWRzBHYZ8)LoBVfy*MB1rfc?dpICN4g`-@1%M8zmOvmv=YKfW8NkO4#)8>A}w;;fp0x!Y~~r00eEa zT=G9~%?P%XfAlGe7z&Z7P!>dy@C8kRURl7K11%aWQZ_V)a}?1j+;Vp&^fHPtAJ~K zaF?j%zB{Ya+I^t_MiB{=D4!e%*DRC6;zUd9bXfS=i(cRK9uM1a2u^tIV=?3=Lw1Tm zL@!DH7)G~PD8LY@_)_Q*7ol;67$`_;n3-$NF8F=}xS3`It@Hqk>uXI^OcbYO-DTyP ziWF`iAY_%(ULUay9<)kPLfR=iSg2f}E0Sr2onlqKp;cUJMzc2Q6eEYfsR@3=sMueE z?{n2I``2QQoAZN4^VBLfzL9vizxSZ1^`xj}^%H#B67Tg|Q1=eW zOA%?e5_qui`~9Lp9m6=UiPqqhsE}BUhaIyB7s>P3!P|X=e7k3VnFm+qR!r1J7w4tQ z)U{GGMzPYl^qxXWEjKy720m79V9-M$ivS-KIp`C>&s?Hh>5;ksQx~`x)eVTYXA^yd zCX9a>&De1->@0uqpd=O;ZMzg| z(6}JZlWTIdMYt=e)j5xOQ=qsu-pf=q4B|8><{C>26fBMr68)+*HSm{w#4s&nTR?Pw zO0-NndsR1SVT<~Tg65!_Q139%mlG8v0=wrvRRM_f)WpYhPfTcWEvQZP*0h$I3{?7R zVQHp6%gI-ZOwTuI_32I%`fEu=P7N-SD&n|q@#m*wW}6mp)1QLIzl5Tc4pLte-`gIJ zY+#7_tfaGCF_vusB;~MZ;U>4EW0LMM+(B(o8u!cn!%@Fx_a(E)Karxy@{S4rex>- z@WXeuxL?OG3Z`b{)?P#leLL1#f@3BYa|Yu^xjk##wSR1LiFhM%y43(7i7jDJG3m z!3PaqBJL{5;>QK3p@2w1 z^F`SStrA}*j>g6Ge)h}DXtn_6W;WuUjd!~I{2DR^vNXcqD5A(kV@#If*t_GkAIDk7 zOdE+B**hl5Tb7!CiZYyDl3HDgZx6BJkI6cYvw24pW4(1gP4(v(X zLqbf;H!xmP(F%2Zf6K^4m{{RpGA6T}RjGDg!p z9m5($cpU9lGN|S|ui>aSrAe#Qb+L$KsbaP8qor7&W0B`5*u;7O-@Dwvi|f zX4r%MR9dav)V*jozr8fEa5G)Ys<5Do`!ppJV(o~_wItz6nMb`a&wrhl!*8V#`pF^$ zE@tsrrc?8a<;PgL#dHsmY_4VX?#~gpktAouQ)L|ewB$r;j}{(e?o{_z>$@@yz@{7}Y*m`Ax#| zUgUEb7|ETz>R>Z#b|XYCL|Dy+0TRTL@|oz`_7=7s^e%)XUL7cdH}i$81=^uBZMh#N zqXOv+WDvY6T(^9rn__a?gp-R0${W-pJI5 z;(=|3#}>(mPN%-3)t@~gbtia;QyI)jyU7VMux}v-dsfEw{68n@yeNFjQO{>3FPJO` z_aZgbo+QdW++t(a5nB9*-sd8;f1$DQ&hl&08JOGK4!>y6gNi(e1CIR#4i-R%h$VPD zT@5*nybWcF!Y&s^pgIw#`v=T7W@FCJVt^z~35lTu(p_|NXct zb1**j%9g{}^6TIUy z*)8j*r_51Lk8v*a{6}a|P^Lh_7p6k=baxNsvx~8(U#!gHJsr84PA`ILb;zV7)0*8}>F=iCx5H!3Xu z{rCKHfrAj@EDdnGQ??S^N1f<+Fq)lxYjA^paDnH0ByzYBeefXp;z9b|gY5PWQTIfy zur)KCTN${Jx1FK-NNcn`KW{#xev_=Pw&LJCW%i;Y;g%ay*wp1q_rDb>vHB&Uhge+#43#Ut1C z4)MUFJh(LW!Rn-Yn??LI?fdzt{HJcZcPRq)|DzIT{x6ki;&?|T+K*b~G@scGN(WGo z=vYxXNjxBdy zsS;Iomym6CosK42F{*vI6u{^8+CsCle^W|)pPA`Eff?vKhH+g!BAQ)?nVB| z)B+pR+73~+oKJOpKJ#tdXutbXk!8UjWtqwmA<~;=buSlPHO$VR>k>2 zKDXvsO{84={{OymGO|@-PoUNJ5(`~`DDkyJgVH%y=nk_65L(yqs z!2fPFY$*tg$F&nr&iFH3(JY9I(sqm)272Ov9e)3UpwbzKUd9G9x@BpV<|-!9!dMeT z)?UQK>3<5|!B}lM&!^GQaoggcqNT>K-3nnrXdSR_d5-uAcp;Ur@Fj72P@3UN+%4f9 zx+;reW0QZzB#DgaE_H}~+%4{UsAywVb!al|{cE%g)xl0=4}C|VnO0CH3B)NJLW1I} zByS~^%SjWdzFA-n?T(Z_;9h^L7ay1?&D!PHNUn{SRJDEsJA?-brh<7-T2PgEUSWLyfj7@a(QZ6ZBRhy)w<@}bjgRkd zI5-+6Xa-_&uc*igVmbhqDvStXyq=L4TCO@(Ig$AvOgjqG!OE;~gBn%}eia9Ot@hO9R5(sI2O z%eIG%Lw;&K#cznwH2n(pDe!zhq4&u*Uuq1k?&Bmj+Lhz3w?)9t-b|{Ig-w z6CI;Hf}j7Ta#UFuO3N3MmUPQ~k*Z%Q5O~w0r@AftdWAkxn_&KX3bsi7>^}0*TZN(L zA9ODcIn%fuWxsu?UFSp|J?3ENZk*V34HmCd?2)yCy~Lu$eHq6!p2=KS2UH zjASh0i?O|6nqiJ`4A0jlGgPiPQqVIl+N1@pPJuVVb@zK{v$0B3LOq(Tb{@kqw`HQg zugGiF7;8bts!{-bA5);g;mUB=${R-8(|#UjA!I|ow~7;_OX7}Xb71Vdh?TKZwvLG{ zQKe%aKV9WWcxcbkFs4MTZItxjH7_HV7IqMuGLJ4VdBn;HS9VJ9|vzBo2y>ket5UqocprI zi0kK|a$^677Yq)O7e-^PUSfjJE*U0e@1sP1{1g^gJp8>#p(A(f z5eG)yQQpv#c-il=lr?tiQR@{$llL6k@_4icUO}|4nBW7sFz&I%V6^Gz-b+YfrM#92 zZ8}S6spCzVuBalNgNzdQ3#Ag(&v5-(h7>ftHehIdSXKFJv7G zaF&CCnXxzED8(@tNnJAx7nE!@4<)AHWb~_ju9L{3t~J=qE?BGMp*g~Mgy|5seda}C z$Fx+p)kfC1spOOaY#oiXmc!iTITB_5BmEV0v#zp`(^ev=%Y<|LpQpbI--E|fMbwR$3uO_`-I#;c#;VllmtG3S zSwOqcATLY^hX*nRwU66~!4qXJ%zlw-@fa^hCvgt$IsBrz?SDwx?5IbKBpPKVt!0Ge z{U8PjmnTp^O&hO>FEqv-YRyD+3KOq=ManKfhsv*`((!w&#nT%q`i}SnnFv`gw^UVDAoR&Y7i)M$bzs^Dk3X7m#^-6W^Vp)){FO?3(3(|lf!|y{ zXFi~~yfdi2M#AxJ$jetlYx1*b#NS`*|J|xaHP=4N|e(?HlaklZIIlSg%>HLBA8=`EN*hId`Ofs*iiGdp}qvhRxuW zcj|MNeOU?f!tn0<&JW;QKb|FX7Pm?;eSzP5m{7P%txNcU+WDTB519peeI4RLJPBg0BbF z0!xSPdV9pn)M(#V1PN@}N-)z_afxwPU_w2D!CMT`9#~--@|K2h=4;ddD5G6QqRKGFmu3gvKVxNz83 z9N9ThUq3LQ68pPC=pPK^b0Aed6!~@p+P&&;jh6@(i9)&n*#}%doRRtMp`bg^t{|>p zp6FL(=%2_3mhX54ePxxP?2o7!Wkctwy@Zo6rj85EwR5WG*B@ zoLm;3qHh?b)Im7|i+h}@d>%^M)_FGsDc3D=I)9mLEB!X!(JFy>xfHe_XQbHvwqi~ru#_CO_G)RWd zE;JsEBgAGten3lfrLF~0Ra$(|*MvyC)<6H1PHy`qU(sFvJW7fc2d;#@DM(4F6cS%d z{~DZ>SCLK6YVKp2{Yj9vMxDwa%A@)!+e9$9-xP@PqyOMRT-lyOwgp`@O^@G(2_M2H zxN~>sGD?%7SrXLCx3qcS;RxdKTm@_vS+U*gfxIBQkUDO^IykZxodLDY=a|F&`;pc! z@nY;ysqn5TSq{NDo=vTn1#H3dg7T6mo+@+ke>6oUa3gEb1+rK~$v9BdkRuR5UR;#- zTOp|7ig$~)@PRy{1VD066u>-nGGPyyhJUx4ztpmaJCz=DN*lGIwS$x}EU=)KmiLmQfEFr6C#=IP|4 zv`Rf0WtN@NS8K+$RTxKvQm=2Rkb+waRe77B^Q+fo;IgupubIjr{AdkY?4L4)^UR%} z`7eGT+-_-z>T$qCgZZ3L4o72oz#k{##?lxEJ-oHbA6JzS!BAfHY%|*;_e$J~vJ`WQYeyABQdTP#XOKxQ}4B2&STj!hOl;AYW?xby@SQhNMeTYsK8c)mksl z5M)*EKo&qtuT)$?eV(OE%OmH0Sj_5AZgd1gR27gGHd$j4Fk1EHUyUp!#EGu~%G-5W z8n>#i*v4G>tYQp8z`TL*Z#lP~@q z-fSWDzbN~wwzRQDShw<6yXk+INt|3E$egY-b2M} zZB7~rbUtjg@Iwx2Ma|#Ip1_Agn*+DV5|-#v^jXV|N@!JcDq;&YmRbOny7yD>Z4X*t zrbRSQKUhve>ARt2aDa2ZRj9Q|HLV(Q@vy-W_DR3hLA>BqtO%7y9b^*nYrhpJER_A* z(&qs#95C6owxdq!k=I4~tn3{`U3d4M>VLZLyuWCSd|6KFhb2M1f9M;Y1m~O52W92o z6S22X%Y$7^D7#pY;1J9N0215IyG5|=1JvGq7`NXhiqTllsh17``BaV89Y**pr2fv>)9Ov0(IHKh>u-tAdsJ6RlL@jpFQ$YWJx` zh70cwe-J|e2e+e`mOdi7S{au?{%1WT?A=VuP$f*4=W80Bzu|hGeNmLn5Z|X>)|Cdf zpwUR5s{)mUAQkshs((-WMQ-$SE%tjc3&t9OEnLt?LHgg`L0+Sw-pfq1f2e#gtPyPr zWfR`gVRp~2h1g5~hKC@4L{Bgf0Q}LY#KTW#oM4LBV5}!NT#Ha<|JZ!DBJvQGZVNs# z8uUT4r>uWU#!x}9EP2adp^(|5N*bTqA(7J|3Dn@r%VGR@V8fyD?I44&&f%V*Tk*A! z?I%Mn)DUN26hM!Lh5i>c&IZw7fA@)we9&oz*}XDbh6j!19PLJz<2BFvOPqTL+Zq&x z!BT~hd(Uj}fg552)@7t+*l@cMJ@U}^>5m|OF@*^c!l8m?P?C9)a(ndsR2>CDf5X7L9U{>`3B_0?- zmriq9%DOzWvN0_Y)@Fi~w1}s=8O&OBTnbGt=8Gw|7iPZYMM)mi#%&ixd&qX9tPp+K zOKms#gzU4VuYS1Q+>7oRyI0eN65)Jr^FANUJqYIG4Ib|-{=Bz2pXlXJVj%qIE!D4~ zkxXIdLb$+^KCBc?&7b=?rSMax%6w?~WaWK+Sqvz#B!F&#KD}E96<~NbYvoq09m^|h?^^2K-(@pX2t+J?N% z5|v89{GJSh>|qcI_^h8J=V`mv$elL3Q7bk6B{To1Zd}n+zS^>yj29_ik3E0KgIc1R zoHYq+F+t=qhKRrtRvXvh8iou~9h_Cii=gTf$>e9bORA5^TtP+98@*O$-n>V-=%7@X zYXlP*+Z0mTpzWGKl^_mATBdzAq9~CPBH-#S=~9)14y)GT^$J*C*mQ+~;cAO9N}!7f zy9OE*uHyf^g2=AggLLhQ_-#0$*M6)>mp^6ltfr~<*{jhYYe4{a007U=upogcG%~7q ze|mjys`iU5C9(+vs){i0V`$sRms>ET=|_mZBR;N;9nwj{u;QetN(n@E-#Nkfuj0+} zdSdShM6%66bP<4W6Xgjsn^=)(az5q|rd=;eY3pyFS@}*vL{shCDOopqJ-+@E{{nO( zCMUiI8xw7bfUHIYa+8SQ)vxpR?0^Gk%6j+x1c=Z1-=l*SFi)l|~*-)Hhphzrl`SD9Mvbnw(kWYDKzKf@4~5{2m+$O1`?;*qtogi(Y`wcmxDu& zf0W<-&aL4mQ#pc&14Oj|mQ^A&b^Zi8$%Oa2Y1G7;pMEdW|K$^pv2|Pr< zOm2L#x3wX8kIrE=NF%H&i!KhiE<3vW?74569^=K6%ilk~TMoo!OSQNjlu(5)M!2OJ z+qi$}2;YrJREBV-`E;Mkpx{29>$7uAIo3~VOtqfp>_mqv)ib*H`+(1uhD=(TpPs zRKP7tgKINMmobkxRprr$?Id-G99N1CUmt?e;q}RYDd3&v$7n>g&pq=Io8~quE&aD| zU~nwX-y%lfi|M!$TMRLPF(pc8w0ZT%dEV)s+&Q<6{a%^GQ1~MY9?UCgXCV{6;emeF z(8-b+G1N0jE#o`E0n1qd?Pp_SI$lvZYywH7^u|4LH^qqQMu%0c2Wj3?2NSFlBwFs| zdE~q-+hZs6sQmU==o#IZC=JduSv<9Dz=u?&X7{!xHw3Q_IfcFPFRgRZ=hsxN*D#)U z%0V0IY(_>79Aq*Ysbpn5c3Rr+a%RIb@GzMPg-sn)COvmdt#x_+h4SB2gkhW|YjtHSyagq(6Bp3IO&UTY?aMXF#o6~g7E#ErsDe)rMFy}wok zqcnrrw-JRL+cC+>$I`7PMR6U|-PinHmg&V=R)DWMbN7T8n-m4ixU6R+&c~9I3%+_x zs?kLpqs)iU5(~j;a54C@PeyDuQ)BA|B%%WFsJ5AZre?NVsbKFCh07hX{?GoXc&>9N z*xZGdjJ@z3-*XrV%2#{Ioy*hTbA#&lZaSt+lu9~i-~*zlUwJ3;HvW=ynUaBC{igss zaZrg>xcF<{mdcbTAER0MX+yFYG3PFYHIU}*r zBR0DYrr~O{hHZScV(5Pp5=rZT+FnzS>WO*S`1@o8ZcKBp?UhRP`D=GwFN?814&Pxr z@*%q)?hJLxa*=to-4;Ed8ydFfDmA)?>g%n~`EF*?$l)}Qxo*n2w#_TE_j_C?xY!VN zQlfM_ygJ;Cs?aH{n>Hiqb)>yvk?B)))uh^}$X->>;E4fdKS%m|LJ}M}_g&fqYY?Gj zR{D1ERS5B-joOg;wF)(5zd;lQmTpAfXz~QM`u3dQcdW-3GM=pvG zC~@JSNCF@`p^-T_)m*hZg?}?*PzIESrfih_s{JZd_Z&-LacGE^=qr`gx{bFC?zf*T zThsgeF=bz4&Xkk@LirqtN$z(F+})A2Oe}%aNw^c(5u>btZKvzHRAh#hY=m7L6HH5f=O=RwY>(X2aMCUcKMewdgc7)uU+qMXUK|Eo?*nZw+2O3(I+} z(sG`6I=#$l%w)bs6Q^Q;Hl8*w|rO+Ww-SMkl7`MR# zy_53{^swTCXVzz|C$)3!XQdCA<8_w)>^yeTVtyO)ua(VHjyV0DKa~98g#&RcZzs+J z!YWwSoYB$S41Im&@YZq)5Q$V5fb)81Z&T@CQ3 z^Yz2=k4Iv^kVhX*Ba5Eo{WW?~GLtskx9RCRy`?y>Iw*JjV?ZzkI-v{@wp4BV!qXL; z;yW>=&nF_19$LH51k7=k2A(Rn{&uM%SRdfZ^^R=!)HA5g9;MDd?;vyxfT5 z9i;BeUQDt({9U}h82Nhe=ZRYAYGSdD@oq?1!#LGKPd;yzo6Qh%>Qib2vlx;?+Ym?f zl4q_|bZYOGX<72al8O6v5o;8}1Q1|XtwKF(=d#mAO*6E#vPyUQhJn^ZoJIWG@w1Ve z`na%$44zAlCXYM%$A&%M;@=1AtH_v2C&u5uM9i znr}!GdCXGA6!I|mdsZ$J@W*0+a)=?>Sx|RECGGw7i|M?h?88tpWh)tAUNKoN&|J;$ z-6LObF(Saxc8lTbMgN;9B0&aOS(-PvCUGLa@Iq!lfb7H2*`{BpNaiNn=hJ&j7(a$+ z=JsQfzw$kF>+JCH4>QSxeH6!EhLMDT+XqjjUnog2P8jv28glD-mZ=v9Q%ixr&g1VF z$gdb6du@7HgdWdSZ|?h2DFA~_&Z`W&k)9yn4 zQEW?;c>j-h9_G$1DsR$UWzY4FHK#j|dP(covriU2PaXb&niohf{4)C0nz$#wnskw= z`drno;L4&Y>L0bA@~<9ZQn5*~a1~}1dcnF>^c~}h8I^Z(M11chPGTqnmiicw<;cnrY82>mLDM=p)A&>%y;^Y^pPSkHjpcOGG%(8ujS&q)sc4X^cJqSA z44$ZI+e;qD_u-|W>>yQLbMGrqQ;I9WM4>NIukU4wID;gGu|lFMSFepi-QZB)!CfTV zR)b+&^vUgVS%&`47d&?r`ZEhPXma0@5V1QfBkHHS2k)d;i08H?(fr#$Jg97zP$5!k zRnKtm91-j4gnzQK!Zvvr_X)Vs9L` z3ApRZ5E7ToAYw;i2aP()YX?P{V_#Fl(}MIaCv-%CA%{lIVMitP8B_^ZQ)Y9A?(d=6 z^@cPAMMT?heOrG6_0VXu9=*GM>G=?+4I+&T*CIuJEI-g@E0Bdje$=SkPa$d#??06x z&aTY;w4FL<)Uq`(tkbIr0DWD9BjHK-)}l^Zx7TZ>E-MWBp{09PqEqn zJQbV#$+^fV9Q418;qpZ62SH_sIT}6V6wXrRptnx8Kh^3)FqfqLz`XgA7X1^OYL{Fx zHZV$?c55g%`mb%=KU+vu8tOXkmY?jcs8DrC6X8knM?nb&OyZ0xdBnHtTfU8)M1C zu?iDWR@L%!BiLC?!kb$(Z6R+32e8MJbJiw;lOilGCXuycP-h51Ued%=o`w`ch{VKZ zaH_oaR>!&MHE+A2=)b91qx!xS;}Rp&F=qMCmuzij$kNvO8xQ-iof4)TLv2Q;(vi`7 z-2=lut&kMM@#bkRqM69z=~uO;zzXkJp~kA407F#e(a%t&L z^vhT>5dx1qamHyg;uw}1-^7*sm7^Yg+7%*d{k8%rurb2uju`ofEQ8ViQ^ zC0>L6mN^raI4dhb-CN<1xviGDuFgqp^I$E#XiHYwrACZFTP{v9+=s?;p0S7FaOj0q z(~Sz~XEkdSrIsEH9{-Oa0J)$%(AigO5^e+r8btaj!lJ3;ZuQ0%vRIz&z9tmTGo;Tp zr>d)T60w+0+(=uzod+xMk3MNb_@-NPCM|RW*4sqlu%^iMyt_7#1pbV))U)a^!_=uV%!cSA57nNTN$T#}Cx?aAeZ! zAIZ*futjsolJxCmniT+uB_n zPQ}(E7ZZVsB?&^(5&ZQ?K7`C00?Ci=ZC{hvHLLK#k!RQ&y;WuS;1pde#ZtYNRj4V} z+=7n6vH!$@N0|bwaIORhw*`)QwffVKWcq%q%TA76BqD|jE>049EwpooM+BiwQgkx} zO6wW@n&*)wRQs^+Uuxdx%B?PMyyNDu>PWwCKNZy<58Nvcu^amHa^NUUnh1 zfCek}>2^PGPqq3UPRFjIJ;Iy@eo-mr>m3bQE?muX5qEdHPW3UtWf(<^#NF+O$B;t# zI@~DAMcR(d=^1zGc6SqN-JmhMkaCfq3?^UGqU}hbpNU-{zgPPm-65#Gx@Pv2D0UCh zXs(7?aMLn3ibH4iUJM_$P)q2{#G`I^gCV&`XmkEw*+!RfOV(;3dUqdf1$pJYd|Kv+ z19x$Jw;g^$yl#-weX;(xyVHj=E1!S>0-B!l49=fu7%5cotvKtY=mc|bbt zNv-c=9CIje@_owx0WuM~w(3Jc@%`(S59&GOpq@~P6rQ)bg3@jUwJFbUaJSf>uo(OD z8+XsnyD|I%8R@v0Z{Y}xkC@-bvCt*XCV&Wv(|`1Sy=@L$pG3HyAMzyn@fY|B)c6U0 z^b;EQ6JGNZSu1=I?91khzlz}y`u zLf_ncehU(;Ob~nvLL+k#IsPl?!9~R53v#N^!05GiF>iiRU%Uwg{(mKs|9``c#lk?7 z$NutQ!_Lc_iY^||{bSuO0dqZ9208IpTE!?$wW=o)Uxv%^iR8*q`g+xEyAOol=-(Y` z2uQpUOV66DT*?+E=*@FGtkS*{@c#2qHr>WthvIWWRzzpC-5WB&JMlgAd>b!XjR-?Y z^fHIuk|%z9iXRbKsd=vWiCPH1fz|Wi6CZ1H!2PXQVP1wb;xE?uDp}JQG{8-A^&ioW$mhd#5E~f$J^7>)^y3Q7?#jt z-KDDGyhQu;hI_W6xvN{axn$)_hlB|CNRXUKBuKL zx3w;FFYTi@#~9OUJO7X#2;d#E$WuKDtG^RPZ2{TBymOS94gDY7NXtEohZ}KOvmT?; z_l$0PhY>rdQvTm?Bc<2F6P+0`IUC(6;djT8G$KTQNPz?{C+OX1F_W+iRh!0)In#hD z$c74KN73 zA!RBFgBzrMs?OKFkyMdUhhOJ=zCcVLzWcHKN znb$&q4x23$$HP4RmSk_UQRFggP|^}R6L&T#Gh{MWcnEIY^V&cz+{je?WT|LgJOd_1c!YT~ibOAjrPzm*o%5d69ej6`e}_`cE!P6GSJO|HR%9(G%WdGN_FFSnFI93Q3E;OX}B3 z0>eHh@?CZ8FQqX+iQ(Z!$0vWtfU_Ukq4MJW;v!{TlsUDMBb++TK&NWH*S^0Uf7FNq zWh)EK5mrVBdS-(YxWA#zfw?~)a*a*y;qVZ9+`ZVh$9Jz67s~Ht5Lb+md!gafWfWsI zGxxL}mp*M*$0Z@8kE}ZH<@HOp7+Tt7_;-U!56pit~Kpy_IDp5T0 zjJM+Nr+?HfqQW@E@j!mt$B!UX%%e1^i_-i7wKeM(VMD6g2@O>hh}zbGPZVz(JGZ83 zE7GaT_E5iI&`h-}-%2zpp19P4^a-y`LcNsw#+ESIX=`b+Po|SoCTG}Q@_%rnB*R8u zsQYlX3S~A|IfU&O#2|ZjC-a3rr)A-5HfxKAJYxjgf}+J5cPLMUS1w&^neTCX{s%W= zhyHEKg8EuqxvgCcV~w=exfNC==Eo&?xUrn-K|j{@aV3xSl?=T+<#`dv4OKszdbeDc z%9BICs4;}!lJC5ei$az!TIi&Web81j7m60RQJ3DqqVBGF8IRHYQ~kzDAeq{rOr;{j zB<$E!=CM3ZyFo`g=VF|DYi(g z|B^WCe{iFY4sX*rrlO@uX_j5k_z&%D4FOQ~;9GRrsZBQ?ZhYnP|Aia(iSck_S8Ego z4>wl5_OuennZ(15m%pLz|AQO3?vc;p;l|`@HpbK3tREw;NQoEJV;ThpNLozYokgK* zj|Sdb~}-YMrGhCJQj1AtR`Hl1p<@gyE@TyJ1k`TZZ7r>w z1JtPZX3bge=iq0jYkYc^cZC#>=SY`UaEQ4!g~9Jm?J=rWWgN5>yPA<~cdQVpT=>;4 zFC%6dn*A{YsW@SXR3QOiPyM-9h87u1rZ$z+e9`AreqX~4lSFxo*u8zEE``ZY{%Uli z;(?Es>eC>_{}GL)-&5Ylfmg}9U63LPTYW!RaohQjxN45e9-)Z{Rob-%Gr>cvN5wG` z%3Qqx#tlM42Rll4je6VBIDr(@ZqsM3VYig7>jIn}ajky=)G zq8jJrmrca8Wa}7%cnrnO@wD$C*SHQE)G>-ap&NTJzv`x;Pz)G zt!nJvx^09Z>3i)A5}~^9x4jaTnF#rB{mv6J=C#=h?CmF0&9{2Yfn9Ia4B5}*HC5$5HbPi-W)7CV$}v zfxv5QNR9U;f!I~-F6%Z`bl{&~-&w|pcCPS19`n6A!|@dAu+43k%b<7-@cuJH6X zqGeR#A_Hd3^K?PSA&&_otdbFGI>8HmYyuD}vzweP$7;)#Dj)B$f3c)oo#SsrN7Vk3 zx>0r$;UvpZg6TQKem4oZA_*&#z^cnga1My+mGCdkVTlGHy4nyWD}h1B$Og-2vDg=; zby08Lho3(VrQLv7;E6`ZsQ!mf0F{VV)rbntXL8q*QP~XAngcF`R#OEL1`>)cqmAK zBf=Yvp$VO8ZVf7-<&Xr#bTrpw{NK43!N0t}@5FMdC!&d>`<<}b7Y_oYlFS=oh@6rX zCWE2~v0`0`=q=3_-x#c~CVr+|cAkb`g>4j6`Sr1%3+Ao}*wK!A7 zzBg57CPZHstkR z6g<#a|Nj7u#P{$H!o-+7 z)L?sslHqQ+chUkj#mSFySYwL66x}0+X@tvCN#&%uW~jq*9-a{)RDQ!%qVHO66w^)? zYxv%Up7=+VI4J!ao(!VZf$PKY^%Awl1?4Q6;f_R*fyC|eZ>%-3&QWRO}F?f}plW={U z*ME}Us0;IpVXZPMA_~hbb&7K3cJ^ksXUGweUIaa!YLP)6nC+J#C(GjT9CIh6%7eCS zA0)V$uVD-;1%UTMRDHT&D;*yd zXZ&5ddA5I>zK1{PS}1gN%BJ0fJdI9xI{1>TsENTDR_FLa?81r7^_^W&h8mOj8b8ic zzENK{E@cm2*cLbmf*$hBGyJeTAJpt`kcA)R$Qki?DWO6P&Sv#t&8!m9_4-Ru6=hLI zsBe=+cYTUGGy`2t9M+tS_Zq=g%CKgfAk|VjQU9Bg1N`h20aa}S$-V7BKQ@zof?=L3U4rG%R!gxGo$BTqg|#A&I#(`ETo1;gv*LRh3{a$w8jh z2*@!4h9PtU-oYfSTvPg^r3iXSPr4!EZE0y?T^lfML6D1yzaSU9XnkH8ghC1VsQ&@$ znRBg$_~YfWHyMlVnYu871Nly^M7E&x$Vl>!8UzXCz zDr<;s7JQkDM;O0dmwgcU#tU1Km=ogvM^l~tdt^^%DwlAKB1o7GDJYGBigM8Lr)Z*Qrs2Omw#v`1?50S__`?=L#eKSiLVkwET0P@_36wscapYmRoKKQ?bE zHQou+KGU(X)cJ3TT>O(BEvaNYudD%e<2NwBy%dQA-hHdHHp@0=GAY;^j~zy<%*WW$(%aFh&%-Csu>gR5%ml}}>4dOQzi*|Pc)1obFiSkS z(oH#Wgc$BmjpoLuh@qN!bwn4)mXes_C?F=VB{hzeP)`mxYw zQxNH$2Xf`JPlCuVieNNCVv|L)SQu4axQ;Z=E|}Q(3vZ8qiPlGhBc1%y6Nu^BP4!j2 zQKQu8QL+4A%dM&#b{q3hlQ&T&WUd%ee4h-gBM6PRA9HL>b|Sb#{3)-D@LFzdHwk_L zP4Nn*9Kv5;H%oFt@VtJLFl@krT>L{GnlxY7gk0!9e(`wAc9re;Hn^LD^Q4NSJ{gv+zVzAx-ABdqLDt{Xf8+8kkRJoddCamh%$R{y~@|ML(BgN zMU(`_h=8$q!aMT4#RL%&W#dywlo&3qft7k&fhKEJq6*xy^#_OnV5;yr(Z(U_?x!3d zXr0QpQdL|>=fNYZ7agHND~9m3)o;Hs@Co2((D7(p1pLZ+^i>J$8mqHtqp`C&^dJ2n z?g#)gM!R{a?d+b`AwJYO%i4tS@z<*(toVW#`GT0+_o1V@nj=sr)IFhNJg~TvR28T4 zN8|%6Q7uGE)SG#v_LxHTJ8BC`L9TOA{r#W#{=Mzx6XCs{YxXmlpC7LU`6)!@c@5!M z7E%lEFVq@j&10OC!i=1^4q}mqwytNuAs@sCSf$!|T&cskHqo)Zi%8zwQlyauPx2(T zr@>k0r0sZe;A7}9gW6eAI@GfOx?qB|7NME}gt%CLDsW8dJ?G~fN2uEzfPFv9BzDhC ze;Fe=-PZW*h7gzg+hkp{zrJ-DJxBGB17t=-{C zM{Run4^^BX2;}%do9 zS%Bw2o+2vb!QTM!9TKP0K(%?RE%@m1rAE=x#5IM^OA&BnT+>E*?>gt}c4Eqy%4xFZ z{2CiG7WadQ;MiP-=AH0A>B+i9QB-82p8^1S=ZX^<;o(N?|2vT^v6|!n_()EYx`@7R zs+qAdOcghYSqLQ~^?A-U59Wp)_-V9a13>!P@F57HLtHj9yZ>*vk*S00 z$uXr~nw}*G*WU2lw8K!Z9{26fht`emv(mDZzT_l!r8P?fZ@7hDeSJercePgOZT zyK=rgf9%v7O(QCzC-};`z;|$%V6f_0u^7Wh0-ddOw&mtSVM8p5LFZ za~XdvT*q)->rVQ9W@lyT*Ms%0=dGH}w`D>ZZ$sz#g~{-3c(OvVL< z5)k=MRB9tO6Pw2DyZ9R}S!a@pFcM)UXdN{~vZPI65l`vzjiB9*2sORrawz{cg2q_u zp`bd=!;!m*{cQCMyR~kiMXn?{t!|bV~#*`Z_>Kal0ZMK9y>uO4Dsu`W@~CK(7>yW`8cqE* zpL6{d&HVNJkV^Z>%cva1Hw00P)EtF8Vl=<72K_o!K`v(eY}>y4tjB?U6K{;1v~tMw z3VFE9E}ePlN7P8rW4%X=(&=UsjOLc3QPO2*cLX<`s$8tVZ4HlNP1SJg$1!Q9eSGKJ zjm`u@rPPk_%MvO-c_{u52?ZPnmYgdvl!TTmi#91^!OM*>7lCHwiwz0!9yBE!kT63%Xz@Ptt$@U$$H(Brsp!8Nb)%M*`^w>Pf& zhF8W8Z-4uJA707@ZuAs3!=Gu_p@0Uq;q$#I|jdt&rE&kAzj zLJ5cHwTV#b~I@RmR|+Xg&g;mESNI77XOZ9q*ut& znm~%b8_ax0!x87V=qe``15*=_~U?B}tB!`k*_uWxQT7;3;yE;%QV;XTb~TbXM?2VXPELr%c=4jq`E zzpkGHv=AVw*`8puxP6_(C=5}T3@T2}i)ovT>W6{10Q z!Y!+*fZz(6DT>`rh@SBsp^dp&pk**AwnsImeu$M7?&k@hCg`U<+F|S6n4obALWToH zXsmxi{L2k8d{uDqrwq&B>opp{1Unq9j4aEwv0(2ji-5vh&-u z2lJg#;ei^-scE{0)w=~ke)W89TbQrgyGiu^?-i(1Af0qn#!I>pmPnBk6|>EbEp zWU7t>aI9;WgM8+uYN-malbIf=W_P9Gg!n0=xF=Gw6F6Mdl}@5Pu3wX6zR7*95h%+5 z9Fk_OEL&$BnhnOfedic*{>nO_i<)bn_8{x-RY&{cw3#7!w7QO}G5oiBg$Q7`lXz6v zF2;4Ly3k62>S{ZV$uBIp$d!#Sl1B8&S)NW%XL?>>@t#rA)C=k@aeQ&M2l8o{<|rWC z6MnjPx5+w3YC`dR_Bzp8Rvmi?Xsbj~SC~S(rcZd>DKtsqW0V zX;~Wj4*g`qSy_1^CNS^P5aFFrHRU&h-qa#W$~uK(d+}CC@9V4Skp%$^7L_f2`MqgF9F}Ud?I8m z>UEq6?G~s9*^xVrh_5=TD$+O-@so{hSF7H8>?HimR(CcO>evNbzv*k|W^tO+HU#;i z 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

FY}>2l?AP2`(Z0^8*T&_Ed&h!~|{RvJ;$-W4b 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

F%}knqBvBD2}Y#zn3_gcmIrS$G$0 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 diff --git a/docs/images/dalle_colored_code_example.png b/docs/images/dalle_colored_code_example.png deleted file mode 100644 index 18c8307c545ca31dbbaee3a572754430f596da6b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 378844 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

    S3feuZ+}*~?0C4w&CT7u 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

    @S()le(?40 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

    ;3L>c-urP%gY4!&}Y;O!*<$}J726$^uqKE4aFoS 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�?$dU6!o1)>sHYV&w^{ZNxY|wo|J{rm?$i2h@8lZJ(W&F1C%A*9KwPse zKp;hs+B9wc&5f>TWO@e>QLJTxL;`^ld*D2L!VTuK&0JAjRC3q0&$DIjXrK% z4l(;ssr5s@{?liFblY9GtY2OGqc3~~r;>4+=aa)H4}a$u-(5Vubo*Vm730bISI_)+ z-~6}hSFSG(mZq-cMf>Al|Ey$Z(&XZs7e4!uPaaNJ$TKPhtt&|{0)#=N@d`jB6_IgK zZVGH5eCLfBxTg-0}uf}HC3Py{lv(!1pp8NimwyQ5I@w; z3UuZW++G;FroynN6?AJ>xSx1mC(!%6D1AWUcVI*OW(t7!erJ7d4G+|L-&^svfZZtA zxk5__F$X0kRh4<2U z0EnpSTa1m)KpA^oYThzOqJ^)tE5k$)<|Ha20h!ycm>Y|u#JBCZSR#U`-;bcgg5ZMB zr_-`5oeKao7%Zk~50S04L`+-{E~?6bDj-5LplH55m?$Sob-JGlkH92z8zVuzgY6SH8tXgOVaB%p8c@!&^loB_Pb}-@{x3teI2k zoXlDX5Nb0p6;&^BAW(m#cBoYq#(<`P>5^Fs)#a9)INCst01V zw~!HI&BJl7pJjQGd+*~$a6$8;0-!YMIVVd?OZl|Or*6<+3Wad)a5Hyj&VvG^$>8qW zmTy^HU4Q*we(e*-`;e$71rZSu6z!n-iE*YGk4@7QP(UJkqOMe7TMaOOf^3_Rchq12 zL^Y6tF;rT+njWzxc~HV6%eCLl5DWrR>BY^rQm6xVX5s zzJB<~5o^t4I_V{;K|-d=yP#TB)p&Ebw6IteWtrzuU4Vg$jhv=odTG79d?jwzTOPT8 zBx?^pe%m7-Ix5v#YDF~=2mu;q01)*=QOZUTXP8Fj#wY-(X8Ojnp&q|TWcNm@MvLQW zD>rIhsoB7YZErr-05idaE&EsZ?AXt=|DirFn!_q~+~<~aqbpf$ANRJMfNwVT7+@a6 z??4<(01o1JITQktMuKUVq{~FX`>Lv{s&d{jGZ7nW4Ri25x;;dcbDMk=p$Tj8z08 zs$v8JMFO#e;3d@hMFL*c3pmw62?zaOzOX88AnUa8>J_*)1n2tq-MMhfk+CTQiAYG; zS~5jZgdn}F7j=7Md7mVSbKZF`5;iwCZIU?`%F+cdSn@!UjW;gArFB38qemXO%cs?3 za^cUv^)dF4V(3{D4S?%h5TdF~qy;x>k9GP;(U)ouhv1uHj*7N&SRt?SbRqr4^FI@G z8st#A@TrGC?k5!sjLIQo_`RpT_p#4@EL85o={LXl#AkaU(cGtLnv=gChCh7%XS9$o z+pA}PLqUFOEl>&R1EMj7B2>$dXi4Zg5~h6T0xh3l}o2|*xDmTx_h)8r4n{^ZhOQ5{>Oq{2uD(GnO8 zq2;<7tLfS>^8`@!buT{_30l)RpdkY^(!ceMpzYk(d%UyPW;=0y4)6}f9&noXk&fg+ zyszUoQvhsN^xyfbcYJh~@SB(;KWBuP{QGX?M4Bf+2{-Fn?u z^+{qv2--lUMF4=LY1mV(?-!)DRD#%w+WJEpq5{<*`&ytm9+(kSiqu+wMdgsKwZRr_ zhDz5Khue+W{YDWXG*lzY=Y|^qK*$iOjZNc98eEOcBXA-yFQ(ZoQV3etf_A4XpeV&= z95DbyR&kN%)9EyL?PUXzprVn(7kmKKx`o_$wm@An}H(q+g^PuU&*`1*mXz;f}{2*!0(ymi?DL zel&5RXIrIPG|WuYSZ&lsR4pbA5e;&!2!u84q&*H>`f1djCsYEi)#P>lNNBF$q#Q{& zsEkN?l~?3`_^TftI=*uC!qvwg{K#RmYy&Z31?BVSetmi4;;B1N=HuxLPd)qRU;9_K zfSwvrg@xYpmrtJ`t(u-4uW$B4_OW|D>ZX-3i6R3TfthBjA_$ObsH%by0%%D*G4wY- zdqEDLST8D03;<{d878>y;aoZWE(RTh!soqymZmmB`!vAX+FC!& zHrI!VNxcuuCSL29t@u!SnHC;`k&&cR-FWQ+jB`LVM~=Xv@x9Tv|Kjt^F2$;6O(gyr zB+DFiKMmEU;5AMSHA=M_;y6?Rkmguq#IA3H#4XOyI0&Eu3e`?wwO&wk8>o*{X|VD% zW5~q9Lp8)3OVk**&9Zui99 zv#VXMKS(?Bd>Urd-0-knpAU{Fzz%cQ-wBVlg9*Svyfe|XU?Ya)?H*S*>!yK?sS;$Lu^w z%;M5ORYM4Qo_inS2A|_OZ0mL-a6O7gtnIEk@2Pjwo;`Bx@bGT9U*7((dpF?LZ;MMu z4Vk37om(ehXc0k8Jt&DHfGNj8HUf`v^TjtHfe*Ro??3R%WYHVf`pO<}2)2Ug1{;HndMC?nR z^Q}i0?l?5DIb>3~sjAo{F=)MW{eHhJ%a!G2S5@9Q?_61ys)(ZAi#0|9gi6?RSj)2# zhi5LrxB#0i+8p|0bfV^!3|bmxFls;s-hTuIRN~o{wfqHAcXm?O?f1U} zKe^*@tZi8rLH{bT2H0`^+rzv2r=Y6RIF!X#f~czCOd7wjI1sNOpkAO6sf0?XWb@oB z8%3T>ua|4DoQ1UwP!B8F;rmX80XQuBgDh08pZ1-1!MQBUL^aE@2-onX{Ftf80v8&_;Qs2n ze|yiP_it=oJAeB0BKK}tIzGUZl!{Pln*8k#zkB?yTPjz)dHS`leCErPd&@Qi@H4Ct zH1jT0q*}0*v_j33?4{M=FJ3?Adn=ohDFAur%DggDTC92(pL!k6jEMVE6$>A}+pmyI zTz|P2DsPq3H0|~J&U;lgma;5~k!5`V@&XGB3svPbgiyJVy9APQZ5md`;p!%o3Y;9e z_15)j^RZ9d`Pj#fIk#ch69#}_2&2US0Wwjv2cCKTf-ou*#xA}=YA-BZrT;B^M57sb zwceV2%!-Hs#B|Yw-=E(G^oAq8vt~}(+&HnUAH4Twy$Ia|ES156T1kAmms*0G+MTy z*xP(XwFu*(?TA(2eK#LBVVRzqRZ25UzUc_(W&iC75)a~FYw#{O%OH7{~+n^zY*$`mP ze64W~1^@s-h}!=%KVXFDFV>taC;%a185$G{07S!D0YbI`Y3#yCC`bIx4_-NX`0nBP zjmg=|P?lie(8Kps3lLHbRGHKi&ShE87(1O#0AQL=5iv=U$#fDz$ohlHWGtXbl5CDA z-ihHv9LhA9v0QuQEQ~5Zg&yDakq1f`id_Hf6Gy2mt$0=;KvJpEBLJwfT~3|>u|94k zO3HNKK)ZC5yVtbckB6Y-JUVZ}_n-d$^3lchtE+b&zV)G79+0Vo;0G&q;Go=q3^_kAVDG% zz)KK-pyEYJksN4h>_0t!_DTiXU!Hi+HU&{X4JK!^p%;JlDr{E77NEHMGmlq2nPQO* ztaGIys9a%<0RZp34_;J*gnXJ8d7-Mq;W#k~5gW1qFy732l+nZw&tLJYLkI|qOUoyZ zjl<@bzV^u@#}bfnYQ-|4cv2zI=*$@Tz6`*PApk&a%#6(*;TqdBvvu3DN47u5ZRoo$ zmt*hOcdG;6`s5Zt zaSrtaKoqt$I8{TmaBBUA4Vw^xF(yq@BBKI?rrwoZaSXz!$0E|!mOE>ws^9f^t1FpK zQ-8)7p&8(HyT;#^HsKuNsON9kj)WCz*i>l_y7FNvE|!0-dr7@z6jGC`sqC%e^eJxGbttS z_0yK84gl6zRdvo0lC^d`9uEc!Hc5t~iM6)Z8{|c0O$rs*+-4J+zIFjh0n2c7aOAFI zBe(j+Pu+KX02v3q<0g;CR zsB;h?s;3Ik>6rk)OAreqoc{RPpWJfm@%77BKlShvhj}4YGp(j(CHu+CKff@#`p8E= zbm{zsi>KfC%YX5$d^9|=bhrqfmikYfe(qX1ra>PZRo9CzKk~V9ePWp%>g)g$RYgG( zAYmf_g5We1$ZpX8tDpR;$`*67Y7790mtrID<;Jdsji0{;Q;^C*2Dd$SZ_y9f2iwES z6-erQWi9*Q%d#Xy5$W}NX_~TOYpi#k$OwRQ!P?Xpng&X88NYcRHm3kKId%${`%}#S z#kao{&{-n}10gUfQ8a8uqY(&%43VX+gf$bOB>>f;K!7b{n-OX$P--+o6>7a#ii*z? z-LV;>4lE!6BM}0s#ED7E@M+bbqL%*_o8k5m-O9SJH&^4i9#Gve*-79ZY3Iky#|@Ad z2mSsJRJ?x*fP?s*i*CnpkGJ7X@&VeA`Q;m80;GoS8E?AQ8YCpd5F`XIB1~kMRg{Tq zk}h?I{!OEriMv`lQYV_S1I%}?^^fTfb;T3As?slTjH`OXZQibA;}1|X2b`-F zkccP*F~;1mzUo#cvo|BQH3pkHxo3(>tR7ITv9Tm;yvC5wAab3XM!zSS38ccTiX$Rw zsReCG&>Df=+!3zqR{G$sH55Wzy)x`0J=7|Zkr^Y`dqpx8_Ke*-yc+ESyfeiQYW@3ishB%vMeX# zaaoow_`+3bnifSBDj;t#r}4S#)ti@r09Y0tywe^|udSW_zy60W+7cGYC)mi#5HV29 z5g7p+OIobWn_|TqH9Y}l)WEIya;(*@_VnxMobQMPh6^cakuQucymId4Uf+(du6^@M zf26s~D4i5}w%q^QAAMH`yf(hhpf5lD{J;C7e>1wey0Ua=(-*ir_~8q`^ci`;a-83~ zbmES|NtuR(IH)Sta3F08MIS945Wt`?P5?KR{_D@4qow0{Ag`R*jK#oE`jf<;pH81W zL(qc|=;+|k-N(i_EvYa)27ok8eej5A*aQg+gN5;Uf`nyR1_@b~<#{d=yjR1Cc=5SQ zJ@KTNp1%OO1I49NCtWIL(SQ42eQG)#rdACB0T}`!sA~WurZ@qpiMcViH<8Z@2tkk< zr6B5<9%^&|5JYSd{Ow-DR^OtzAau`_y@%2_%U*jg#NNl`!A#(HIDUHt!26`5e-Q8A zV5&I)%q&tvRzeT}h-q#F_Ec2`07S@alEfmS4?(p~_d{@`bz24iB#0sIw8`JD80eNj znajZ5@r!Srv+jf!QlccWDjF4gNI}ER|GL;PZW`nO(57Wk_7o9`$a%*Gqxvc_Hz2D3 zL4X*TK$MswF=GypLs7_o>%66F1h(%r-QjA}?a1zDw@kOx`Xg38w08s>0RK-{-cmfYtc}FHG zf)nCE;DQG-9$*;6CGfLn*B1M?uAg4L`pj!kdMJwYk$e5&K4jQm%BID{81~*7Ypu0; zo*P51DwD)ar(-l^(xlfR6ytM$ z{G~h0G^FV3ZoLFH2mm37s7DQ=iW-HG>g@&qNF+PmzX$~9_bJsHHM?@&qQ*cRGm@pe z%DG2RpM5&VNmb^Yhi`uFYcj49PR6d39{wNS|GUq8^$ROURz|DWcwBz;t`C)KY<_`?@|*+0IVq`kFs*M9HGFQyJn1x!Yu5xL(9wk{AV3{{dmzdHTt>lbM8a6l6R zNa4|g4xQP6jhC*%`E|%WBxdPDcfnyU(vmV`Qxk$ungD>wbh5OxR8>`)T4QK)b915J zSJgC4^YMffjM`#zk~qvSuT@vp01TM)$lZ5LWaEwpj(+$fM~h;^TJBga3jhF0hndkF z#tOBt4-&Ic4Xv+YE6jyaM5tjgwa0&#kWG!MM9Ux8BeUP`0X!SPHe$H%{8{}@$NOR> z58@q+g9*Sv{5IolCb)a+Hngzvn0L02mAZRpj4|9F^bH#T2qB30wpYzOgR(>%jMTkQ zVlt{Vs-(`>rG@|z()O;f?u3LPXNk09!@@ zuq|0{pX~VCyL(|f7oxj;0;tT8?K{0kY6iqxAmGkT06LGGUCwVJbUxyXb5N*xgJf0Q zvgWE96r;W*00^ik2Gszls-UD1n}nb>Edhh7B02FuCRP*z>NTjbQ|~J~*zoY3e}49k z!}sJDhvggR!IuElJ)i&hn9EU^EUqk$Mnh&38Ndq*3!-8ZD=OAlYivBJ7Z!R!-14DA zKscS|MNzUQ`Sl5p!s^TCK^fG;0FT{sV(6|v_RxukZUf^(qJbn3Q-qH*Dg)Jx|L7^J zz;U)QAGNx??y!a1?RUf7LBzFp8h}$DtbXt5?;k#K=<>x2$NMYy9lirfLBSII^Sw);%hx~s;l~%zN?F+~9fe}mO`d(@RqZ8Uc)UJ(;=YfDX;7zz6Kj$H>ZRN2 zj)#F)!T>rE3H|IRFQ30SDW_;AE|??{Cq556fyE-b^wjGxVsHc+Zu!)Q!Xgg+FhH=D ztEwu?(pUokNs{FGG|v6J_q~2^b8`~_M#Ev6rVK0wwx?TkP@+EkG#BO@w z`_U2qO~elRxZAXWyUpzGT5Z9p*B@^+c5Za(iBsCn-M8+jQPZ4@8kY=M4|Y*il@t_0 z>jW8mAf{&aj-sLI=P3Yy2q~c9h>nP0^hR8w#_iO}G_#KYhYkd&yB)e)slJN{)&uu0 z<)D%O?~bI~&$U~uUa}HKd3%fs@7?^KL}v(o)CjY#iL=n$=X7p`W8Ov(wRW_jcehOv_doX>4Wj9Q z!FG74Kzsnq#QRkNJ4As-U!1A2GH4d4A`$4XGZ(OUjH$%!qd+nHk8^B~|6?)d(ePZE`L#ykr{hL4e>>|PdWsoLuwVTedGLUITWbO5*?kpgY3WH+p z{UHR3DvU^^0JYwHT}=?~!4pGeQfl_Sl%dSA8d3gFPk;Z&t%t6>dHyS(`SKEGJxm0t z1bO!LmkR@TKXAWzfBE#8Z+_wTWpmsIPLd=?|LWDV>sW*ytqnI;vXuvp-^T@0G--xT zL%m4|T2PQf$r+~!{ip9gqbtXx*LMtJ8JT<@WK$M~+4>Jp!vvuc$oS4DA1u?77XVR7 z1yQ}P5W#zA$;umZKZx zr9b<(e?a66yIzWjffNj}AwUpDG)RWXAR8oV3E5cFFc^p;Y6~O+f$F>;iLiDPYx%*d zjh_V|&N~Az5)yDT;6!9nivL1$lyni8EiDA0bp@^;-0>uCHIxYvK%(_Vw1}=f4OI4X zPoH@R?0%iz=D2hNVPb5`p*>XpxBs=hV7@P00^ZGh>Ae>3`-J!)4g#8_e+$~ay`nK9 zHrLR04xm~)f@N7Iq7jV}rvY=M;rY6;%_h6;`RzAy+VfhuQ|zvRTUWyKr)t9)Xn12g zX!*~!%*|Za;t#Y*(r$s-=Gw=807VG`h!R4S{uBvJCb6kCDUoTnSdF4@P)3m&3OHla zA{wdr!E^HR^@@pNbMM?Ek(y!I->Na+R#Z<%*7LU&-M;&lDa~7!2YqX;zL_n+jgU*R z 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 z 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 b_Oti!{{LUo_Zv$Eu7POZHGo&B+eQ2zRZCiM diff --git a/docs/images/reflex.svg b/docs/images/reflex.svg deleted file mode 100644 index f837234f8b5..00000000000 --- a/docs/images/reflex.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/docs/images/reflex_dark.svg b/docs/images/reflex_dark.svg deleted file mode 100644 index 0aeffe59105..00000000000 --- a/docs/images/reflex_dark.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/docs/images/reflex_light.svg b/docs/images/reflex_light.svg deleted file mode 100644 index 63876bdd13e..00000000000 --- a/docs/images/reflex_light.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/docs/in/README.md b/docs/in/README.md deleted file mode 100644 index d290a585d8d..00000000000 --- a/docs/in/README.md +++ /dev/null @@ -1,249 +0,0 @@ -

    - ---- - -[English](https://github.com/reflex-dev/reflex/blob/main/README.md) | [简体中文](https://github.com/reflex-dev/reflex/blob/main/docs/zh/zh_cn/README.md) | [繁體中文](https://github.com/reflex-dev/reflex/blob/main/docs/zh/zh_tw/README.md) | [Türkçe](https://github.com/reflex-dev/reflex/blob/main/docs/tr/README.md) | [हिंदी](https://github.com/reflex-dev/reflex/blob/main/docs/in/README.md) | [Português (Brasil)](https://github.com/reflex-dev/reflex/blob/main/docs/pt/pt_br/README.md) | [Italiano](https://github.com/reflex-dev/reflex/blob/main/docs/it/README.md) | [Español](https://github.com/reflex-dev/reflex/blob/main/docs/es/README.md) | [한국어](https://github.com/reflex-dev/reflex/blob/main/docs/kr/README.md) | [日本語](https://github.com/reflex-dev/reflex/blob/main/docs/ja/README.md) | [Deutsch](https://github.com/reflex-dev/reflex/blob/main/docs/de/README.md) | [Persian (پارسی)](https://github.com/reflex-dev/reflex/blob/main/docs/pe/README.md) | [Tiếng Việt](https://github.com/reflex-dev/reflex/blob/main/docs/vi/README.md) - -# Reflex - -Reflex शुद्ध पायथन में पूर्ण-स्टैक वेब ऐप्स बनाने के लिए एक लाइब्रेरी है। - -मुख्य विशेषताएँ: - -- **शुद्ध पायथन** - अपने ऐप के फ्रंटएंड और बैकएंड को पायथन में लिखें, जावास्क्रिप्ट सीखने की जरूरत नहीं है। -- **पूर्ण लचीलापन** - Reflex के साथ शुरुआत करना आसान है, लेकिन यह जटिल ऐप्स के लिए भी स्केल कर सकता है। -- **तुरंत तैनाती** - बिल्डिंग के बाद, अपने ऐप को [एकल कमांड](https://reflex.dev/docs/hosting/deploy-quick-start/) के साथ तैनात करें या इसे अपने सर्वर पर होस्ट करें। - -Reflex के अंदर के कामकाज को जानने के लिए हमारे [आर्किटेक्चर पेज](https://reflex.dev/blog/2024-03-21-reflex-architecture/#the-reflex-architecture) को देखें। - -## ⚙️ इंस्टॉलेशन (Installation) - -एक टर्मिनल खोलें और चलाएं (Python 3.10+ की आवश्यकता है): - -```bash -pip install reflex -``` - -## 🥳 अपना पहला ऐप बनाएं (Create your first App) - -reflex को इंस्टॉल करने से ही reflex कमांड लाइन टूल भी इंस्टॉल हो जाता है। - -सुनिश्चित करें कि इंस्टॉलेशन सफल थी, एक नया प्रोजेक्ट बनाकर इसे टेस्ट करें। ('my_app_name' की जगह अपने प्रोजेक्ट का नाम रखें): - -```bash -mkdir my_app_name -cd my_app_name -reflex init -``` - -यह कमांड आपकी नयी डायरेक्टरी में एक टेम्पलेट ऐप को प्रारंभ करता है। - -आप इस ऐप को development मोड में चला सकते हैं: - -```bash -reflex run -``` - -आपको http://localhost:3000 पर अपने ऐप को चलते हुए देखना चाहिए। - -अब आप my_app_name/my_app_name.py में source कोड को संशोधित कर सकते हैं। Reflex में तेज रिफ्रेश की सुविधा है, इसलिए जब आप अपनी कोड को सहेजते हैं, तो आप अपने बदलावों को तुरंत देख सकते हैं। - -## 🫧 उदाहरण ऐप (Example App) - -एक उदाहरण पर चलते हैं: [DALL·E](https://platform.openai.com/docs/guides/images/image-generation?context=node) से एक इमेज उत्पन्न करने के लिए UI। सरलता के लिए, हम सिर्फ [OpenAI API](https://platform.openai.com/docs/api-reference/authentication) को बुलाते हैं, लेकिन आप इसे ML मॉडल से बदल सकते हैं locally। - -  - -
    -DALL·E के लिए एक फ्रंटएंड रैपर, छवि उत्पन्न करने की प्रक्रिया में दिखाया गया। -
    - -  - -यहाँ पर इसका पूरा कोड है जिससे यह बनाया जा सकता है। यह सब एक ही Python फ़ाइल में किया गया है! - -```python -import reflex as rx -import openai - -openai_client = openai.OpenAI() - - -class State(rx.State): - """The app state.""" - - prompt = "" - image_url = "" - processing = False - complete = False - - def get_image(self): - """Get the image from the prompt.""" - if self.prompt == "": - return rx.window_alert("Prompt Empty") - - self.processing, self.complete = True, False - yield - response = openai_client.images.generate( - prompt=self.prompt, n=1, size="1024x1024" - ) - self.image_url = response.data[0].url - self.processing, self.complete = False, True - - -def index(): - return rx.center( - rx.vstack( - rx.heading("DALL-E", font_size="1.5em"), - rx.input( - placeholder="Enter a prompt..", - on_blur=State.set_prompt, - width="25em", - ), - rx.button( - "Generate Image", - on_click=State.get_image, - width="25em", - loading=State.processing - ), - rx.cond( - State.complete, - rx.image(src=State.image_url, width="20em"), - ), - align="center", - ), - width="100%", - height="100vh", - ) - -# Add state and page to the app. -app = rx.App() -app.add_page(index, title="Reflex:DALL-E") -``` - -## इसे समझते हैं। - -
    -DALL-E ऐप के बैकएंड और फ्रंटएंड भागों के बीच के अंतर की व्याख्या करता है। -
    - -### **Reflex UI** - -हम UI के साथ शुरू करेंगे। - -```python -def index(): - return rx.center( - ... - ) -``` - -यह `index` फ़ंक्शन एप्लिकेशन की फ़्रंटएंड को परिभाषित करता है। - -हम फ़्रंटएंड बनाने के लिए `center`, `vstack`, `input`, और `button` जैसे विभिन्न components का उपयोग करते हैं। Components को एक-दूसरे के भीतर डाल सकते हैं विस्तारित लेआउट बनाने के लिए। और आप CSS की पूरी ताक़त के साथ इन्हें स्टाइल करने के लिए कीवर्ड आर्ग्यूमेंट (keyword args) का उपयोग कर सकते हैं। - -रिफ़्लेक्स के पास [60+ built-in components](https://reflex.dev/docs/library) हैं जो आपको शुरुआती मदद के लिए हैं। हम बहुत से components जोड़ रहे हैं, और अपने खुद के components बनाना भी आसान है। [create your own components](https://reflex.dev/docs/wrapping-react/overview/) - -### **स्टेट (State)** - -Reflex आपके UI को आपकी स्टेट (state) के एक फ़ंक्शन के रूप में प्रस्तुत करता है। - -```python -class State(rx.State): - """The app state.""" - prompt = "" - image_url = "" - processing = False - complete = False -``` - -स्टेट (state) ऐप में उन सभी वेरिएबल्स (vars) को परिभाषित करती है जो बदल सकती हैं और उन फ़ंक्शनों को जो उन्हें बदलते हैं। - -यहां स्टेट (state) में `prompt` और `image_url` शामिल हैं। प्रगति और छवि दिखाने के लिए `processing` और `complete` बूलियन भी हैं। - -### **इवेंट हैंडलर (Event Handlers)** - -```python -def get_image(self): - """Get the image from the prompt.""" - if self.prompt == "": - return rx.window_alert("Prompt Empty") - - self.processing, self.complete = True, False - yield - response = openai_client.images.generate( - prompt=self.prompt, n=1, size="1024x1024" - ) - self.image_url = response.data[0].url - self.processing, self.complete = False, True -``` - -स्टेट (state) के अंदर, हम इवेंट हैंडलर्स (event handlers) को परिभाषित करते हैं जो स्टेट वेरिएबल्स को बदलते हैं। इवेंट हैंडलर्स (event handlers) से reflex में स्टेट (state) को मॉडिफ़ाय किया जा सकता हैं। इन्हें उपयोगकर्ता क्रियाओं (user actions) के प्रति प्रतिक्रिया (response) के रूप में बुलाया जा सकता है, जैसे कि बटन को क्लिक करना या टेक्स्ट बॉक्स में टाइप करना। इन क्रियाओं को इवेंट्स (events) कहा जाता है। - -हमारे DALL·E. ऐप में एक इवेंट हैंडलर `get_image` है जिससे यह OpenAI API से इमेज प्राप्त करता है। इवेंट हैंडलर में `yield` का उपयोग करने कि वजह से UI अपडेट हो जाएगा। अन्यथा UI इवेंट हैंडलर के अंत में अपडेट होगा। - -### **रूटिंग (Routing)** - -आखिरकार, हम अपने एप्लिकेशन को परिभाषित करते हैं। - -```python -app = rx.App() -``` - -हम अपने एप्लिकेशन के रूट से इंडेक्स कॉम्पोनेंट तक एक पेज को जोड़ते हैं। हम एक शीर्षक भी जोड़ते हैं जो पेज प्रीव्यू/ब्राउज़र टैब में दिखाई देगा। - -```python -app.add_page(index, title="DALL-E") -``` - -आप और पेज जोड़कर एक मल्टी-पेज एप्लिकेशन बना सकते हैं। - -## 📑 संसाधन (Resources) - -
    - -📑 [दस्तावेज़](https://reflex.dev/docs/getting-started/introduction)   |   🗞️ [ब्लॉग](https://reflex.dev/blog)   |   📱 [कॉम्पोनेंट लाइब्रेरी](https://reflex.dev/docs/library)   |   🖼️ [टेम्पलेट्स](https://reflex.dev/templates/)   |   🛸 [तैनाती](https://reflex.dev/docs/hosting/deploy-quick-start)   - -
    - -## ✅ स्टेटस (Status) - -Reflex दिसंबर 2022 में Pynecone नाम से शुरू हुआ। - -2025 की शुरुआत से, [Reflex Cloud](https://cloud.reflex.dev) लॉन्च किया गया है जो Reflex ऐप्स के लिए सर्वोत्तम होस्टिंग अनुभव प्रदान करता है। हम इसे विकसित करना और अधिक सुविधाएँ लागू करना जारी रखेंगे। - -Reflex में हर सप्ताह नए रिलीज़ और फीचर्स आ रहे हैं! सुनिश्चित करें कि :star: स्टार और :eyes: वॉच इस रेपोजिटरी को अपडेट रहने के लिए। - -## (योगदान) Contributing - -हम हर तरह के योगदान का स्वागत करते हैं! रिफ्लेक्स कम्यूनिटी में शुरुआत करने के कुछ अच्छे तरीके नीचे दिए गए हैं। - -- **Join Our Discord** (डिस्कॉर्ड सर्वर से जुड़ें): हमारा [Discord](https://discord.gg/T5WSbC2YtQ) रिफ्लेक्स प्रोजेक्ट पर सहायता प्राप्त करने और आप कैसे योगदान दे सकते हैं, इस पर चर्चा करने के लिए सबसे अच्छी जगह है। -- **GitHub Discussions** (गिटहब चर्चाएँ): उन सुविधाओं के बारे में बात करने का एक शानदार तरीका जिन्हें आप जोड़ना चाहते हैं या ऐसी चीज़ें जो भ्रमित करने वाली हैं/स्पष्टीकरण की आवश्यकता है। -- **GitHub Issues** (गिटहब समस्याएं): [Issues](https://github.com/reflex-dev/reflex/issues) बग की रिपोर्ट करने का एक शानदार तरीका है। इसके अतिरिक्त, आप किसी मौजूदा समस्या को हल करने का प्रयास कर सकते हैं और एक पीआर सबमिट कर सकते हैं। - -हम सक्रिय रूप से योगदानकर्ताओं की तलाश कर रहे हैं, चाहे आपका कौशल स्तर या अनुभव कुछ भी हो। योगदान करने के लिए [CONTRIBUTING.md](https://github.com/reflex-dev/reflex/blob/main/CONTRIBUTING.md) देखें। - -## हमारे सभी योगदानकर्ताओं का धन्यवाद: - -
    - - - -## लाइसेंस (License) - -रिफ्लेक्स ओपन-सोर्स है और [अपाचे लाइसेंस 2.0](/LICENSE) के तहत लाइसेंस प्राप्त है। diff --git a/docs/it/README.md b/docs/it/README.md deleted file mode 100644 index 441c079b0ab..00000000000 --- a/docs/it/README.md +++ /dev/null @@ -1,250 +0,0 @@ -
    -Reflex Logo -
    - -### **✨ App web performanti e personalizzabili in puro Python. Distribuisci in pochi secondi. ✨** - -[![PyPI version](https://badge.fury.io/py/reflex.svg)](https://badge.fury.io/py/reflex) -![versions](https://img.shields.io/pypi/pyversions/reflex.svg) -[![Documentaiton](https://img.shields.io/badge/Documentation%20-Introduction%20-%20%23007ec6)](https://reflex.dev/docs/getting-started/introduction) -[![PyPI Downloads](https://static.pepy.tech/badge/reflex)](https://pepy.tech/projects/reflex) -[![Discord](https://img.shields.io/discord/1029853095527727165?color=%237289da&label=Discord)](https://discord.gg/T5WSbC2YtQ) - -
    - ---- - -[English](https://github.com/reflex-dev/reflex/blob/main/README.md) | [简体中文](https://github.com/reflex-dev/reflex/blob/main/docs/zh/zh_cn/README.md) | [繁體中文](https://github.com/reflex-dev/reflex/blob/main/docs/zh/zh_tw/README.md) | [Türkçe](https://github.com/reflex-dev/reflex/blob/main/docs/tr/README.md) | [हिंदी](https://github.com/reflex-dev/reflex/blob/main/docs/in/README.md) | [Português (Brasil)](https://github.com/reflex-dev/reflex/blob/main/docs/pt/pt_br/README.md) | [Italiano](https://github.com/reflex-dev/reflex/blob/main/docs/it/README.md) | [Español](https://github.com/reflex-dev/reflex/blob/main/docs/es/README.md) | [한국어](https://github.com/reflex-dev/reflex/blob/main/docs/kr/README.md) | [日本語](https://github.com/reflex-dev/reflex/blob/main/docs/ja/README.md) | [Deutsch](https://github.com/reflex-dev/reflex/blob/main/docs/de/README.md) | [Persian (پارسی)](https://github.com/reflex-dev/reflex/blob/main/docs/pe/README.md) | [Tiếng Việt](https://github.com/reflex-dev/reflex/blob/main/docs/vi/README.md) - ---- - -# Reflex - -Reflex è una libreria per sviluppare applicazioni web full-stack in Python puro. - -Caratteristiche principali: - -- **Python Puro** - Scrivi il frontend e il backend della tua app interamente in Python, senza bisogno di imparare Javascript. -- **Flessibilità Totale** - Reflex è facile da iniziare, ma può anche gestire app complesse. -- **Distribuzione Istantanea** - Dopo lo sviluppo, distribuisci la tua app con un [singolo comando](https://reflex.dev/docs/hosting/deploy-quick-start/) o ospitala sul tuo server. - -Consulta la nostra [pagina di architettura](https://reflex.dev/blog/2024-03-21-reflex-architecture/#the-reflex-architecture) per scoprire come funziona Reflex sotto il cofano. - -## ⚙️ Installazione - -Apri un terminale ed esegui (Richiede Python 3.10+): - -```bash -pip install reflex -``` - -## 🥳 Crea la tua prima app - -Installando `reflex` si installa anche lo strumento da riga di comando `reflex`. - -Verifica che l'installazione sia stata eseguita correttamente creando un nuovo progetto. (Sostituisci `nome_app` con il nome del tuo progetto): - -```bash -mkdir nome_app -cd nome_app -reflex init -``` - -Questo comando inizializza un'app template nella tua nuova directory. - -Puoi eseguire questa app in modalità sviluppo con: - -```bash -reflex run -``` - -Dovresti vedere la tua app in esecuzione su http://localhost:3000. - -Ora puoi modificare il codice sorgente in `nome_app/nome_app.py`. Reflex offre aggiornamenti rapidi, così puoi vedere le tue modifiche istantaneamente quando salvi il tuo codice. - -## 🫧 Esempio App - -Esaminiamo un esempio: creare un'interfaccia utente per la generazione di immagini attorno a [DALL·E](https://platform.openai.com/docs/guides/images/image-generation?context=node). Per semplicità, chiamiamo semplicemente l'[API OpenAI](https://platform.openai.com/docs/api-reference/authentication), ma potresti sostituirla con un modello ML eseguito localmente. - -  - -
    -Un wrapper frontend per DALL·E, mostrato nel processo di generazione di un'immagine. -
    - -  - -Ecco il codice completo per crearlo. Tutto fatto in un unico file Python! - -```python -import reflex as rx -import openai - -openai_client = openai.OpenAI() - - -class State(rx.State): - """Lo stato dell'app.""" - - prompt = "" - image_url = "" - processing = False - complete = False - - def get_image(self): - """Ottieni l'immagine dal prompt.""" - if self.prompt == "": - return rx.window_alert("Prompt Vuoto") - - self.processing, self.complete = True, False - yield - response = openai_client.images.generate( - prompt=self.prompt, n=1, size="1024x1024" - ) - self.image_url = response.data[0].url - self.processing, self.complete = False, True - - -def index(): - return rx.center( - rx.vstack( - rx.heading("DALL-E", font_size="1.5em"), - rx.input( - placeholder="Inserisci un prompt..", - on_blur=State.set_prompt, - width="25em", - ), - rx.button( - "Genera Immagine", - on_click=State.get_image, - width="25em", - loading=State.processing - ), - rx.cond( - State.complete, - rx.image(src=State.image_url, width="20em"), - ), - align="center", - ), - width="100%", - height="100vh", - ) - -# Aggiungi stato e pagina all'app. -app = rx.App() -app.add_page(index, title="Reflex:DALL-E") -``` - -## Analizziamolo. - -
    -Spiegazione delle differenze tra le parti backend e frontend dell'app DALL-E. -
    - -### **Reflex UI** - -Cominciamo con l'UI. - -```python -def index(): - return rx.center( - ... - ) -``` - -Questa funzione `index` definisce il frontend dell'app. - -Utilizziamo diversi componenti come `center`, `vstack`, `input`, e `button` per costruire il frontend. I componenti possono essere annidati gli uni negli altri per creare layout complessi. E puoi utilizzare argomenti chiave per stilizzarli con tutta la potenza di CSS. - -Reflex offre [più di 60 componenti integrati](https://reflex.dev/docs/library) per aiutarti a iniziare. Stiamo attivamente aggiungendo più componenti ed è facile [creare i tuoi componenti](https://reflex.dev/docs/wrapping-react/overview/). - -### **Stato (State)** - -Reflex rappresenta la tua UI come una funzione del tuo stato. - -```python -class State(rx.State): - """Lo stato dell'app.""" - prompt = "" - image_url = "" - processing = False - complete = False - -``` - -Lo stato definisce tutte le variabili (chiamate vars) in un'app che possono cambiare e le funzioni che le cambiano. - -Qui lo stato è composto da un `prompt` e `image_url`. Ci sono anche i booleani `processing` e `complete` per indicare quando disabilitare il pulsante (durante la generazione dell'immagine) e quando mostrare l'immagine risultante. - -### **Gestori di Eventi (Event Handlers)** - -```python -def get_image(self): - """Ottieni l'immagine dal prompt.""" - if self.prompt == "": - return rx.window_alert("Prompt Vuoto") - - self.processing, self.complete = True, False - yield - response = openai_client.images.generate( - prompt=self.prompt, n=1, size="1024x1024" - ) - self.image_url = response.data[0].url - self.processing, self.complete = False, True -``` - -Dentro lo stato, definiamo funzioni chiamate gestori di eventi che cambiano le vars dello stato. I gestori di eventi sono il modo in cui possiamo modificare lo stato in Reflex. Possono essere chiamati in risposta alle azioni dell'utente, come fare clic su un pulsante o digitare in una casella di testo. Queste azioni vengono chiamate eventi. - -La nostra app DALL·E ha un gestore di eventi, `get_image` con cui ottiene questa immagine dall'API OpenAI. Utilizzando `yield` nel mezzo di un gestore di eventi farà sì che l'UI venga aggiornata. Altrimenti, l'UI verrà aggiornata alla fine del gestore di eventi. - -### **Instradamento (Routing)** - -Infine, definiamo la nostra app. - -```python -app = rx.App() -``` - -Aggiungiamo una pagina dalla radice dell'app al componente dell'indice. Aggiungiamo anche un titolo che apparirà nell'anteprima della pagina/scheda del browser. - -```python -app.add_page(index, title="DALL-E") -``` - -Puoi creare un'app multi-pagina aggiungendo altre pagine. - -## 📑 Risorse - -
    - -📑 [Documentazione](https://reflex.dev/docs/getting-started/introduction)   |   🗞️ [Blog](https://reflex.dev/blog)   |   📱 [Libreria Componenti](https://reflex.dev/docs/library)   |   🖼️ [Templates](https://reflex.dev/templates/)   |   🛸 [Distribuzione](https://reflex.dev/docs/hosting/deploy-quick-start)   - -
    - -## ✅ Stato - -Reflex è stato lanciato nel dicembre 2022 con il nome Pynecone. - -A partire dal 2025, [Reflex Cloud](https://cloud.reflex.dev) è stato lanciato per fornire la migliore esperienza di hosting per le app Reflex. Continueremo a svilupparlo e implementare più funzionalità. - -Reflex ha nuove versioni e funzionalità in arrivo ogni settimana! Assicurati di :star: mettere una stella e :eyes: osservare questa repository per rimanere aggiornato. - -## Contribuire - -Diamo il benvenuto a contributi di qualsiasi dimensione! Di seguito sono alcuni modi per iniziare nella comunità Reflex. - -- **Unisciti al nostro Discord**: Il nostro [Discord](https://discord.gg/T5WSbC2YtQ) è posto migliore per ottenere aiuto sul tuo progetto Reflex e per discutere come puoi contribuire. -- **Discussioni su GitHub**: Un ottimo modo per parlare delle funzionalità che desideri aggiungere o di cose che creano confusione o necessitano chiarimenti. -- **GitHub Issues**: Le [Issues](https://github.com/reflex-dev/reflex/issues) sono un ottimo modo per segnalare bug. Inoltre, puoi provare a risolvere un problema esistente e inviare un PR. - -Stiamo attivamente cercando collaboratori, indipendentemente dal tuo livello di abilità o esperienza. Per contribuire, consulta [CONTRIBUTING.md](https://github.com/reflex-dev/reflex/blob/main/CONTRIBUTING.md) - -## Un Grazie a Tutti i Nostri Contributori: - - - - - -## Licenza - -Reflex è open-source e rilasciato sotto la [Licenza Apache 2.0](/LICENSE). diff --git a/docs/ja/README.md b/docs/ja/README.md deleted file mode 100644 index 0c6be480512..00000000000 --- a/docs/ja/README.md +++ /dev/null @@ -1,250 +0,0 @@ -
    -Reflex Logo -
    - -### **✨ 即時デプロイが可能な、Pure Python で作ったパフォーマンスと汎用性が高い Web アプリケーション ✨** - -[![PyPI version](https://badge.fury.io/py/reflex.svg)](https://badge.fury.io/py/reflex) -![versions](https://img.shields.io/pypi/pyversions/reflex.svg) -[![Documentation](https://img.shields.io/badge/Documentation%20-Introduction%20-%20%23007ec6)](https://reflex.dev/docs/getting-started/introduction) -[![PyPI Downloads](https://static.pepy.tech/badge/reflex)](https://pepy.tech/projects/reflex) -[![Discord](https://img.shields.io/discord/1029853095527727165?color=%237289da&label=Discord)](https://discord.gg/T5WSbC2YtQ) - -
    - ---- - -[English](https://github.com/reflex-dev/reflex/blob/main/README.md) | [简体中文](https://github.com/reflex-dev/reflex/blob/main/docs/zh/zh_cn/README.md) | [繁體中文](https://github.com/reflex-dev/reflex/blob/main/docs/zh/zh_tw/README.md) | [Türkçe](https://github.com/reflex-dev/reflex/blob/main/docs/tr/README.md) | [हिंदी](https://github.com/reflex-dev/reflex/blob/main/docs/in/README.md) | [Português (Brasil)](https://github.com/reflex-dev/reflex/blob/main/docs/pt/pt_br/README.md) | [Italiano](https://github.com/reflex-dev/reflex/blob/main/docs/it/README.md) | [Español](https://github.com/reflex-dev/reflex/blob/main/docs/es/README.md) | [한국어](https://github.com/reflex-dev/reflex/blob/main/docs/kr/README.md) | [日本語](https://github.com/reflex-dev/reflex/blob/main/docs/ja/README.md) | [Deutsch](https://github.com/reflex-dev/reflex/blob/main/docs/de/README.md) | [Persian (پارسی)](https://github.com/reflex-dev/reflex/blob/main/docs/pe/README.md) | [Tiếng Việt](https://github.com/reflex-dev/reflex/blob/main/docs/vi/README.md) - ---- - -# Reflex - -Reflex は Python のみでフルスタック Web アプリケーションを作成できるライブラリです。 - -主な特徴: - -- **Pure Python** - Web アプリケーションのフロントエンドとバックエンドを Python のみで実装できるため、Javascript を学ぶ必要がありません。 -- **高い柔軟性** - Reflex は簡単に始められて、複雑なアプリケーションまで作成できます。 -- **即時デプロイ** - ビルド後、すぐにデプロイが可能です。[単純な CLI コマンド](https://reflex.dev/docs/hosting/deploy-quick-start/)を使ったアプリケーションのデプロイや、自身のサーバーへのホストができます。 - -Reflex がどのように動作しているかを知るには、[アーキテクチャページ](https://reflex.dev/blog/2024-03-21-reflex-architecture/#the-reflex-architecture)をご覧ください。 - -## ⚙️ インストール - -ターミナルを開いて以下のコマンドを実行してください。(Python 3.10 以上が必要です。): - -```bash -pip install reflex -``` - -## 🥳 最初のアプリケーションを作ろう - -`reflex`をインストールすると、`reflex`の CLI ツールが自動でインストールされます。 - -新しいプロジェクトを作成して、インストールが成功しているかを確認しましょう。(`my_app_name`を自身のプロジェクト名に書き換えて実行ください。): - -```bash -mkdir my_app_name -cd my_app_name -reflex init -``` - -上記のコマンドを実行すると、新しいフォルダにテンプレートアプリを作成します。 - -下記のコマンドを実行すると、開発モードでアプリを開始します。 - -```bash -reflex run -``` - -http://localhost:3000 にアクセスしてアプリの動作を見ることができます。 - -`my_app_name/my_app_name.py`のソースコードを編集してみましょう!Reflex は fast refresh なので、ソースを保存した直後に変更が Web ページに反映されます。 - -## 🫧 実装例 - -実装例を見てみましょう: [DALL·E](https://platform.openai.com/docs/guides/images/image-generation?context=node)を中心とした画像生成 UI を作成しました。説明を簡単にするためにここでは[OpenAI API](https://platform.openai.com/docs/api-reference/authentication)を呼んでいますが、ローカルで動作している機械学習モデルに置き換えることも可能です。 - -  - -
    -DALL·Eのフロントエンドラッパーです。画像を生成している過程を表示しています。 -
    - -  - -画像生成 UI のソースコードの全貌を見てみましょう。下記のように、単一の Python ファイルで作れます! - -```python -import reflex as rx -import openai - -openai_client = openai.OpenAI() - - -class State(rx.State): - """アプリのステート""" - - prompt = "" - image_url = "" - processing = False - complete = False - - def get_image(self): - """プロンプトからイメージを取得する""" - if self.prompt == "": - return rx.window_alert("Prompt Empty") - - self.processing, self.complete = True, False - yield - response = openai_client.images.generate( - prompt=self.prompt, n=1, size="1024x1024" - ) - self.image_url = response.data[0].url - self.processing, self.complete = False, True - - -def index(): - return rx.center( - rx.vstack( - rx.heading("DALL-E", font_size="1.5em"), - rx.input( - placeholder="Enter a prompt..", - on_blur=State.set_prompt, - width="25em", - ), - rx.button( - "Generate Image", - on_click=State.get_image, - width="25em", - loading=State.processing - ), - rx.cond( - State.complete, - rx.image(src=State.image_url, width="20em"), - ), - align="center", - ), - width="100%", - height="100vh", - ) - -# ステートとページをアプリに追加 -app = rx.App() -app.add_page(index, title="Reflex:DALL-E") -``` - -## それぞれの実装を見てみましょう - -
    -DALL-E appのフロントエンドとバックエンドのパーツの違いを説明しています。 -
    - -### **Reflex UI** - -UI から見てみましょう。 - -```python -def index(): - return rx.center( - ... - ) -``` - -`index`関数において、アプリのフロントエンドを定義しています。 - -フロントエンドを実装するにあたり、`center`、`vstack`、`input`、`button`など異なるコンポーネントを使用しています。コンポーネントはお互いにネストが可能であり、複雑なレイアウトを作成できます。また、keyword args を使うことで、CSS の機能をすべて使ったスタイルが可能です。 - -Reflex は[60 を超える内臓コンポーネント](https://reflex.dev/docs/library)があるため、すぐに始められます。私たちは、積極的にコンポーネントを追加していますが、簡単に[自身のコンポーネントを追加](https://reflex.dev/docs/wrapping-react/overview/)することも可能です。 - -### **ステート** - -Reflex はステートの関数を用いて UI を表示します。 - -```python -class State(rx.State): - """アプリのステート""" - prompt = "" - image_url = "" - processing = False - complete = False - -``` - -ステートでは、アプリで変更が可能な全ての変数(vars と呼びます)と、vars の変更が可能な関数を定義します。 - -この例では、ステートを`prompt`と`image_url`で構成しています。そして、ブール型の`processing`と`complete`を用いて、ボタンを無効にするタイミング(画像生成中)や生成された画像を表示するタイミングを示しています。 - -### **イベントハンドラ** - -```python -def get_image(self): - """プロンプトからイメージを取得する""" - if self.prompt == "": - return rx.window_alert("Prompt Empty") - - self.processing, self.complete = True, False - yield - response = openai_client.images.generate( - prompt=self.prompt, n=1, size="1024x1024" - ) - self.image_url = response.data[0].url - self.processing, self.complete = False, True -``` - -ステートにおいて、ステートの vars を変更できるイベントハンドラ関数を定義しています。イベントハンドラは Reflex において、ステートの vars を変更する方法です。ボタンクリックやテキストボックスの入力など、ユーザのアクションに応じてイベントハンドラが呼ばれます。 - -DALL·E.アプリには、OpenAI API からイメージを取得する`get_image`関数があります。イベントハンドラの最後で UI の更新がかかるため、関数の途中に`yield`を入れることで先に UI を更新しています。 - -### **ルーティング** - -最後に、アプリを定義します。 - -```python -app = rx.App() -``` - -アプリにページを追加し、ドキュメントルートを index コンポーネントにルーティングしています。更に、ページのプレビューやブラウザタブに表示されるタイトルを記載しています。 - -```python -app.add_page(index, title="DALL-E") -``` - -ページを追加することで、マルチページアプリケーションを作成できます。 - -## 📑 リソース - -
    - -📑 [Docs](https://reflex.dev/docs/getting-started/introduction)   |   🗞️ [Blog](https://reflex.dev/blog)   |   📱 [Component Library](https://reflex.dev/docs/library)   |   🖼️ [Templates](https://reflex.dev/templates/)   |   🛸 [Deployment](https://reflex.dev/docs/hosting/deploy-quick-start)   - -
    - -## ✅ ステータス - -2022 年 12 月に、Reflex は Pynecone という名前でローンチしました。 - -2025 年から [Reflex Cloud](https://cloud.reflex.dev) がローンチされ、Reflex アプリケーションの最高のホスティング体験を提供しています。私たちは引き続き開発を続け、より多くの機能を実装していきます。 - -Reflex は毎週、新しいリリースや機能追加を行っています!最新情報を逃さないために、 :star: Star や :eyes: Watch をお願いします。 - -## コントリビュート - -様々なサイズのコントリビュートを歓迎しています!Reflex コミュニティに入るための方法を、いくつかリストアップします。 - -- **Discord に参加**: [Discord](https://discord.gg/T5WSbC2YtQ)は、Reflex プロジェクトの相談や、コントリビュートについての話し合いをするための、最適な場所です。 -- **GitHub Discussions**: GitHub Discussions では、追加したい機能や、複雑で解明が必要な事柄についての議論に適している場所です。 -- **GitHub Issues**: [Issues](https://github.com/reflex-dev/reflex/issues)はバグの報告に適している場所です。また、課題を解決した PR のサブミットにチャレンジしていただくことも、可能です。 - -スキルや経験に関わらず、私たちはコントリビュータを積極的に探しています。コントリビュートするために、[CONTRIBUTING.md](https://github.com/reflex-dev/reflex/blob/main/CONTRIBUTING.md)をご覧ください。 - -## 私たちのコントリビュータに感謝!: - - - - - -## ライセンス - -Reflex はオープンソースであり、[Apache License 2.0](/LICENSE)に基づいてライセンス供与されます。 diff --git a/docs/kr/README.md b/docs/kr/README.md deleted file mode 100644 index 6234a075724..00000000000 --- a/docs/kr/README.md +++ /dev/null @@ -1,251 +0,0 @@ -
    -Reflex Logo -
    - -### **✨ 순수 Python으로 고성능 사용자 정의 웹앱을 만들어 보세요. 몇 초만에 배포 가능합니다. ✨** - -[![PyPI version](https://badge.fury.io/py/reflex.svg)](https://badge.fury.io/py/reflex) -![versions](https://img.shields.io/pypi/pyversions/reflex.svg) -[![Documentation](https://img.shields.io/badge/Documentation%20-Introduction%20-%20%23007ec6)](https://reflex.dev/docs/getting-started/introduction) -[![PyPI Downloads](https://static.pepy.tech/badge/reflex)](https://pepy.tech/projects/reflex) -[![Discord](https://img.shields.io/discord/1029853095527727165?color=%237289da&label=Discord)](https://discord.gg/T5WSbC2YtQ) - -
    - ---- - -[English](https://github.com/reflex-dev/reflex/blob/main/README.md) | [简体中文](https://github.com/reflex-dev/reflex/blob/main/docs/zh/zh_cn/README.md) | [繁體中文](https://github.com/reflex-dev/reflex/blob/main/docs/zh/zh_tw/README.md) | [Türkçe](https://github.com/reflex-dev/reflex/blob/main/docs/tr/README.md) | [हिंदी](https://github.com/reflex-dev/reflex/blob/main/docs/in/README.md) | [Português (Brasil)](https://github.com/reflex-dev/reflex/blob/main/docs/pt/pt_br/README.md) | [Italiano](https://github.com/reflex-dev/reflex/blob/main/docs/it/README.md) | [Español](https://github.com/reflex-dev/reflex/blob/main/docs/es/README.md) | [한국어](https://github.com/reflex-dev/reflex/blob/main/docs/kr/README.md) | [日本語](https://github.com/reflex-dev/reflex/blob/main/docs/ja/README.md) | [Deutsch](https://github.com/reflex-dev/reflex/blob/main/docs/de/README.md) | [Persian (پارسی)](https://github.com/reflex-dev/reflex/blob/main/docs/pe/README.md) | [Tiếng Việt](https://github.com/reflex-dev/reflex/blob/main/docs/vi/README.md) - ---- - -# Reflex - -Reflex는 순수 Python으로 풀스택 웹 앱을 구축하기 위한 라이브러리입니다. - -주요 기능: - -- **순수 Python** - 앱의 프론트엔드와 백엔드를 모두 Python으로 작성하며, Javascript를 배울 필요가 없습니다. -- **완전한 유연성** - Reflex는 시작하기 쉽지만, 복잡한 앱으로도 확장할 수 있습니다. -- **즉시 배포** - 앱을 빌드한 후 [단일 명령어](https://reflex.dev/docs/hosting/deploy-quick-start/)로 배포하거나 자체 서버에서 호스팅할 수 있습니다. - -Reflex가 내부적으로 어떻게 작동하는지 알아보려면 [아키텍처 페이지](https://reflex.dev/blog/2024-03-21-reflex-architecture/#the-reflex-architecture)를 참조하세요. - -## ⚙️ 설치 - -터미널을 열고 실행하세요. (Python 3.10+ 필요): - -```bash -pip install reflex -``` - -## 🥳 첫 앱 만들기 - -`reflex`를 설치하면, `reflex` 명령어 라인 도구도 설치됩니다. - -새 프로젝트를 생성하여 설치가 성공적인지 확인합니다. (`my_app_name`을 프로젝트 이름으로 변경합니다.): - -```bash -mkdir my_app_name -cd my_app_name -reflex init -``` - -이 명령어는 새 디렉토리에 템플릿 앱을 초기화합니다. - -개발 모드에서 이 앱을 실행할 수 있습니다: - -```bash -reflex run -``` - -http://localhost:3000 에서 앱이 실행 됩니다. - -이제 `my_app_name/my_app_name.py`에서 소스코드를 수정할 수 있습니다. Reflex는 빠른 새로고침을 지원하므로 코드를 저장할 때마다 즉시 변경 사항을 볼 수 있습니다. - -## 🫧 예시 앱 - -예시를 살펴보겠습니다: [DALL·E](https://platform.openai.com/docs/guides/images/image-generation?context=node)를 중심으로 이미지 생성 UI를 만들어 보겠습니다. 간단하게 하기 위해 [OpenAI API](https://platform.openai.com/docs/api-reference/authentication)를 호출했지만, 이를 로컬에서 실행되는 ML 모델로 대체할 수 있습니다. - -  - -
    -A frontend wrapper for DALL·E, shown in the process of generating an image. -
    - -  - -이것이 완성된 코드입니다. 이 모든 것은 하나의 Python 파일에서 이루어집니다! - -```python -import reflex as rx -import openai - -openai_client = openai.OpenAI() - - -class State(rx.State): - """The app state.""" - - prompt = "" - image_url = "" - processing = False - complete = False - - def get_image(self): - """Get the image from the prompt.""" - if self.prompt == "": - return rx.window_alert("Prompt Empty") - - self.processing, self.complete = True, False - yield - response = openai_client.images.generate( - prompt=self.prompt, n=1, size="1024x1024" - ) - self.image_url = response.data[0].url - self.processing, self.complete = False, True - - -def index(): - return rx.center( - rx.vstack( - rx.heading("DALL-E", font_size="1.5em"), - rx.input( - placeholder="Enter a prompt..", - on_blur=State.set_prompt, - width="25em", - ), - rx.button( - "Generate Image", - on_click=State.get_image, - width="25em", - loading=State.processing - ), - rx.cond( - State.complete, - rx.image(src=State.image_url, width="20em"), - ), - align="center", - ), - width="100%", - height="100vh", - ) - -# Add state and page to the app. -app = rx.App() -app.add_page(index, title="Reflex:DALL-E") -``` - -## 하나씩 살펴보겠습니다. - -
    -Explaining the differences between backend and frontend parts of the DALL-E app. -
    - -### **Reflex UI** - -UI부터 시작해봅시다. - -```python -def index(): - return rx.center( - ... - ) -``` - -`index` 함수는 앱의 프론트엔드를 정의합니다. - -`center`, `vstack`, `input`, `button`과 같은 다양한 컴포넌트를 사용하여 프론트엔드를 구축합니다. -컴포넌트들은 복잡한 레이아웃을 만들기 위해 서로 중첩될 수 있습니다. -그리고 키워드 인자를 사용하여 CSS의 모든 기능을 사용하여 스타일을 지정할 수 있습니다. - -Reflex는 시작하기 위한 [60개 이상의 기본 컴포넌트](https://reflex.dev/docs/library)를 제공하고 있습니다. 더 많은 컴포넌트를 추가하고 있으며, [자신만의 컴포넌트를 생성하는 것](https://reflex.dev/docs/wrapping-react/overview/)도 쉽습니다. - -### **State** - -Reflex는 UI를 State 함수로 표현합니다. - -```python -class State(rx.State): - """The app state.""" - prompt = "" - image_url = "" - processing = False - complete = False -``` - -state는 앱에서 변경될 수 있는 모든 변수(vars로 불림)와 이러한 변수를 변경하는 함수를 정의합니다. - -여기서 state는 `prompt`와 `image_url`로 구성됩니다. 또한 `processing`과 `complete`라는 불리언 값이 있습니다. 이 값들은 이미지 생성 중 버튼을 비활성화할 때와, 결과 이미지를 표시할 때를 나타냅니다. - -### **Event Handlers** - -```python -def get_image(self): - """Get the image from the prompt.""" - if self.prompt == "": - return rx.window_alert("Prompt Empty") - - self.processing, self.complete = True, False - yield - response = openai_client.images.generate( - prompt=self.prompt, n=1, size="1024x1024" - ) - self.image_url = response.data[0].url - self.processing, self.complete = False, True -``` - -State 내에서, state vars를 변경하는 이벤트 핸들러라고 불리는 함수를 정의합니다. 이벤트 핸들러는 Reflex에서 state를 변경하는 방법입니다. 버튼을 클릭하거나 텍스트 상자에 입력하는 것과 같이 사용자 동작에 응답하여 호출될 수 있습니다. 이러한 동작을 이벤트라고 합니다. - -DALL·E. 앱에는 OpenAI API에서 이미지를 가져오는 `get_image` 이벤트 핸들러가 있습니다. 이벤트 핸들러의 중간에 `yield`를 사용하면 UI가 업데이트됩니다. 그렇지 않으면 UI는 이벤트 핸들러의 끝에서 업데이트됩니다. - -### **Routing** - -마지막으로, 앱을 정의합니다. - -```python -app = rx.App() -``` - -앱의 루트에서 index 컴포넌트로 페이지를 추가합니다. 또한 페이지 미리보기/브라우저 탭에 표시될 제목도 추가합니다. - -```python -app.add_page(index, title="DALL-E") -``` - -여러 페이지를 추가하여 멀티 페이지 앱을 만들 수 있습니다. - -## 📑 자료 - -
    - -📑 [문서](https://reflex.dev/docs/getting-started/introduction)   |   🗞️ [블로그](https://reflex.dev/blog)   |   📱 [컴포넌트 라이브러리](https://reflex.dev/docs/library)   |   🖼️ [템플릿](https://reflex.dev/templates/)   |   🛸 [배포](https://reflex.dev/docs/hosting/deploy-quick-start)   - -
    - -## ✅ 상태 - -Reflex는 2022년 12월 Pynecone이라는 이름으로 출시되었습니다. - -2025년부터 [Reflex Cloud](https://cloud.reflex.dev)가 출시되어 Reflex 앱을 위한 최상의 호스팅 경험을 제공합니다. 우리는 계속해서 개발하고 더 많은 기능을 구현할 예정입니다. - -Reflex는 매주 새로운 릴리즈와 기능을 제공합니다! 최신 정보를 확인하려면 :star: Star와 :eyes: Watch를 눌러 이 저장소를 확인하세요. - -## 기여 - -우리는 모든 기여를 환영합니다! 아래는 Reflex 커뮤니티에 참여하는 좋은 방법입니다. - -- **Discord 참여**: 우리의 [Discord](https://discord.gg/T5WSbC2YtQ)는 Reflex 프로젝트에 대한 도움을 받고 기여하는 방법을 논의하는 최고의 장소입니다. -- **GitHub Discussions**: 추가하고 싶은 기능이나 혼란스럽거나 해결이 필요한 것들에 대해 이야기하는 좋은 방법입니다. -- **GitHub Issues**: [Issues](https://github.com/reflex-dev/reflex/issues)는 버그를 보고하는 훌륭한 방법입니다. 또한, 기존의 이슈를 해결하고 PR을 제출할 수 있습니다. - -우리는 능력이나 경험에 상관없이 적극적으로 기여자를 찾고 있습니다. 기여하려면 [CONTRIBUTING.md](https://github.com/reflex-dev/reflex/blob/main/CONTRIBUTING.md)를 확인하세요. - -## 모든 기여자들에게 감사드립니다: - - - - - -## License - -Reflex는 오픈소스이며 [Apache License 2.0](/LICENSE)로 라이선스가 부여됩니다. diff --git a/docs/library/data-display/avatar.md b/docs/library/data-display/avatar.md new file mode 100644 index 00000000000..9e7f71b8525 --- /dev/null +++ b/docs/library/data-display/avatar.md @@ -0,0 +1,146 @@ +--- +components: + - rx.avatar +Avatar: | + lambda **props: rx.hstack(rx.avatar(src=f"{REFLEX_ASSETS_CDN}other/logo.jpg", **props), rx.avatar(fallback="RX", **props), spacing="3") +--- +# Avatar + +```python exec +import reflex as rx +from pcweb.constants import REFLEX_ASSETS_CDN +from pcweb.templates.docpage import style_grid +``` + +The Avatar component is used to represent a user, and display their profile pictures or fallback texts such as initials. + +## Basic Example + +To create an avatar component with an image, pass the image URL as the `src` prop. + +```python demo +rx.avatar(src=f"{REFLEX_ASSETS_CDN}other/logo.jpg") +``` + +To display a text such as initials, set the `fallback` prop without passing the `src` prop. + +```python demo +rx.avatar(fallback="RX") +``` + +## Styling + +```python eval +style_grid(component_used=rx.avatar, component_used_str="rx.avatar", variants=["solid", "soft"], fallback="RX") +``` + +### Size + +The `size` prop controls the size and spacing of the avatar. The acceptable size is from `"1"` to `"9"`, with `"3"` being the default. + +```python demo +rx.flex( + rx.avatar(src=f"{REFLEX_ASSETS_CDN}other/logo.jpg", fallback="RX", size="1"), + rx.avatar(src=f"{REFLEX_ASSETS_CDN}other/logo.jpg", fallback="RX", size="2"), + rx.avatar(src=f"{REFLEX_ASSETS_CDN}other/logo.jpg", fallback="RX", size="3"), + rx.avatar(src=f"{REFLEX_ASSETS_CDN}other/logo.jpg", fallback="RX"), + rx.avatar(src=f"{REFLEX_ASSETS_CDN}other/logo.jpg", fallback="RX", size="4"), + rx.avatar(src=f"{REFLEX_ASSETS_CDN}other/logo.jpg", fallback="RX", size="5"), + rx.avatar(src=f"{REFLEX_ASSETS_CDN}other/logo.jpg", fallback="RX", size="6"), + rx.avatar(src=f"{REFLEX_ASSETS_CDN}other/logo.jpg", fallback="RX", size="7"), + rx.avatar(src=f"{REFLEX_ASSETS_CDN}other/logo.jpg", fallback="RX", size="8"), + spacing="1", +) +``` + +### Variant + +The `variant` prop controls the visual style of the avatar fallback text. The variant can be `"solid"` or `"soft"`. The default is `"soft"`. + +```python demo +rx.flex( + rx.avatar(fallback="RX", variant="solid"), + rx.avatar(fallback="RX", variant="soft"), + rx.avatar(fallback="RX"), + spacing="2", +) +``` + +### Color Scheme + +The `color_scheme` prop sets a specific color to the fallback text, ignoring the global theme. + +```python demo +rx.flex( + rx.avatar(fallback="RX", color_scheme="indigo"), + rx.avatar(fallback="RX", color_scheme="cyan"), + rx.avatar(fallback="RX", color_scheme="orange"), + rx.avatar(fallback="RX", color_scheme="crimson"), + spacing="2", +) +``` + +### High Contrast + +The `high_contrast` prop increases color contrast of the fallback text with the background. + +```python demo +rx.grid( + rx.avatar(fallback="RX", variant="solid"), + rx.avatar(fallback="RX", variant="solid", high_contrast=True), + rx.avatar(fallback="RX", variant="soft"), + rx.avatar(fallback="RX", variant="soft", high_contrast=True), + rows="2", + spacing="2", + flow="column", +) +``` + +### Radius + +The `radius` prop sets specific radius value, ignoring the global theme. It can take values `"none" | "small" | "medium" | "large" | "full"`. + +```python demo +rx.grid( + rx.avatar(src=f"{REFLEX_ASSETS_CDN}other/logo.jpg", fallback="RX", radius="none"), + rx.avatar(fallback="RX", radius="none"), + rx.avatar(src=f"{REFLEX_ASSETS_CDN}other/logo.jpg", fallback="RX", radius="small"), + rx.avatar(fallback="RX", radius="small"), + rx.avatar(src=f"{REFLEX_ASSETS_CDN}other/logo.jpg", fallback="RX", radius="medium"), + rx.avatar(fallback="RX", radius="medium"), + rx.avatar(src=f"{REFLEX_ASSETS_CDN}other/logo.jpg", fallback="RX", radius="large"), + rx.avatar(fallback="RX", radius="large"), + rx.avatar(src=f"{REFLEX_ASSETS_CDN}other/logo.jpg", fallback="RX", radius="full"), + rx.avatar(fallback="RX", radius="full"), + rows="2", + spacing="2", + flow="column", +) +``` + +### Fallback + +The `fallback` prop indicates the rendered text when the `src` cannot be loaded. + +```python demo +rx.flex( + rx.avatar(fallback="RX"), + rx.avatar(fallback="PC"), + spacing="2", +) +``` + +## Final Example + +As part of a user profile page, the Avatar component is used to display the user's profile picture, with the fallback text showing the user's initials. Text components displays the user's full name and username handle and a Button component shows the edit profile button. + +```python demo +rx.flex( + rx.avatar(src=f"{REFLEX_ASSETS_CDN}other/logo.jpg", fallback="RU", size="9"), + rx.text("Reflex User", weight="bold", size="4"), + rx.text("@reflexuser", color_scheme="gray"), + rx.button("Edit Profile", color_scheme="indigo", variant="solid"), + direction="column", + spacing="1", +) +``` diff --git a/docs/library/data-display/badge.md b/docs/library/data-display/badge.md new file mode 100644 index 00000000000..9ee9225e77f --- /dev/null +++ b/docs/library/data-display/badge.md @@ -0,0 +1,128 @@ +--- +components: + - rx.badge + +Badge: | + lambda **props: rx.badge("Basic Badge", **props) +--- +# Badge + +```python exec +import reflex as rx +from pcweb.templates.docpage import style_grid +``` + +Badges are used to highlight an item's status for quick recognition. + +## Basic Example + +To create a badge component with only text inside, pass the text as an argument. + +```python demo +rx.badge("New") +``` + +## Styling + +```python eval +style_grid(component_used=rx.badge, component_used_str="rx.badge", variants=["solid", "soft", "surface", "outline"], components_passed="England!",) +``` + +### Size + +The `size` prop controls the size and padding of a badge. It can take values of `"1" | "2"`, with default being `"1"`. + +```python demo +rx.flex( + rx.badge("New"), + rx.badge("New", size="1"), + rx.badge("New", size="2"), + align="center", + spacing="2", +) +``` + +### Variant + +The `variant` prop controls the visual style of the badge. The supported variant types are `"solid" | "soft" | "surface" | "outline"`. The variant default is `"soft"`. + +```python demo +rx.flex( + rx.badge("New", variant="solid"), + rx.badge("New", variant="soft"), + rx.badge("New"), + rx.badge("New", variant="surface"), + rx.badge("New", variant="outline"), + spacing="2", +) +``` + +### Color Scheme + +The `color_scheme` prop sets a specific color, ignoring the global theme. + +```python demo +rx.flex( + rx.badge("New", color_scheme="indigo"), + rx.badge("New", color_scheme="cyan"), + rx.badge("New", color_scheme="orange"), + rx.badge("New", color_scheme="crimson"), + spacing="2", +) +``` + +### High Contrast + +The `high_contrast` prop increases color contrast of the fallback text with the background. + +```python demo +rx.flex( + rx.flex( + rx.badge("New", variant="solid"), + rx.badge("New", variant="soft"), + rx.badge("New", variant="surface"), + rx.badge("New", variant="outline"), + spacing="2", + ), + rx.flex( + rx.badge("New", variant="solid", high_contrast=True), + rx.badge("New", variant="soft", high_contrast=True), + rx.badge("New", variant="surface", high_contrast=True), + rx.badge("New", variant="outline", high_contrast=True), + spacing="2", + ), + direction="column", + spacing="2", +) +``` + +### Radius + +The `radius` prop sets specific radius value, ignoring the global theme. It can take values `"none" | "small" | "medium" | "large" | "full"`. + +```python demo +rx.flex( + rx.badge("New", radius="none"), + rx.badge("New", radius="small"), + rx.badge("New", radius="medium"), + rx.badge("New", radius="large"), + rx.badge("New", radius="full"), + spacing="3", +) +``` + +## Final Example + +A badge may contain more complex elements within it. This example uses a `flex` component to align an icon and the text correctly, using the `gap` prop to +ensure a comfortable spacing between the two. + +```python demo +rx.badge( + rx.flex( + rx.icon(tag="arrow_up"), + rx.text("8.8%"), + spacing="1", + ), + color_scheme="grass", +) +``` diff --git a/docs/library/data-display/callout-ll.md b/docs/library/data-display/callout-ll.md new file mode 100644 index 00000000000..c4b9ef22285 --- /dev/null +++ b/docs/library/data-display/callout-ll.md @@ -0,0 +1,140 @@ +--- +components: + - rx.callout.root + - rx.callout.icon + - rx.callout.text +--- + + +```python exec +import reflex as rx +``` + +# Callout + +A `callout` is a short message to attract user's attention. + +```python demo +rx.callout.root( + rx.callout.icon(rx.icon(tag="info")), + rx.callout.text("You will need admin privileges to install and access this application."), +) +``` + +The `callout` component is made up of a `callout.root`, which groups `callout.icon` and `callout.text` parts. This component is based on the `div` element and supports common margin props. + +The `callout.icon` provides width and height for the `icon` associated with the `callout`. This component is based on the `div` element. See the [**icon** component for all icons that are available.](/docs/library/data-display/icon/) + +The `callout.text` renders the callout text. This component is based on the `p` element. + +## As alert + +```python demo +rx.callout.root( + rx.callout.icon(rx.icon(tag="triangle_alert")), + rx.callout.text("Access denied. Please contact the network administrator to view this page."), + color_scheme="red", + role="alert", +) +``` + +## Style + +### Size + +Use the `size` prop to control the size. + +```python demo +rx.flex( + rx.callout.root( + rx.callout.icon(rx.icon(tag="info")), + rx.callout.text("You will need admin privileges to install and access this application."), + size="3", + ), + rx.callout.root( + rx.callout.icon(rx.icon(tag="info")), + rx.callout.text("You will need admin privileges to install and access this application."), + size="2", + ), + rx.callout.root( + rx.callout.icon(rx.icon(tag="info")), + rx.callout.text("You will need admin privileges to install and access this application."), + size="1", + ), + direction="column", + spacing="3", + align="start", +) +``` + +### Variant + +Use the `variant` prop to control the visual style. It is set to `soft` by default. + +```python demo +rx.flex( + rx.callout.root( + rx.callout.icon(rx.icon(tag="info")), + rx.callout.text("You will need admin privileges to install and access this application."), + variant="soft", + ), + rx.callout.root( + rx.callout.icon(rx.icon(tag="info")), + rx.callout.text("You will need admin privileges to install and access this application."), + variant="surface", + ), + rx.callout.root( + rx.callout.icon(rx.icon(tag="info")), + rx.callout.text("You will need admin privileges to install and access this application."), + variant="outline", + ), + direction="column", + spacing="3", +) +``` + +### Color + +Use the `color_scheme` prop to assign a specific color, ignoring the global theme. + +```python demo +rx.flex( + rx.callout.root( + rx.callout.icon(rx.icon(tag="info")), + rx.callout.text("You will need admin privileges to install and access this application."), + color_scheme="blue", + ), + rx.callout.root( + rx.callout.icon(rx.icon(tag="info")), + rx.callout.text("You will need admin privileges to install and access this application."), + color_scheme="green", + ), + rx.callout.root( + rx.callout.icon(rx.icon(tag="info")), + rx.callout.text("You will need admin privileges to install and access this application."), + color_scheme="red", + ), + direction="column", + spacing="3", +) +``` + +### High Contrast + +Use the `high_contrast` prop to add additional contrast. + +```python demo +rx.flex( + rx.callout.root( + rx.callout.icon(rx.icon(tag="info")), + rx.callout.text("You will need admin privileges to install and access this application."), + ), + rx.callout.root( + rx.callout.icon(rx.icon(tag="info")), + rx.callout.text("You will need admin privileges to install and access this application."), + high_contrast=True, + ), + direction="column", + spacing="3", +) +``` diff --git a/docs/library/data-display/callout.md b/docs/library/data-display/callout.md new file mode 100644 index 00000000000..81f84904632 --- /dev/null +++ b/docs/library/data-display/callout.md @@ -0,0 +1,97 @@ +--- +components: + - rx.callout + - rx.callout.root + - rx.callout.icon + - rx.callout.text + +Callout: | + lambda **props: rx.callout("Basic Callout", icon="search", **props) + +CalloutRoot: | + lambda **props: rx.callout.root( + rx.callout.icon(rx.icon(tag="info")), + rx.callout.text("You will need admin privileges to install and access this application."), + **props + ) +--- + + +```python exec +import reflex as rx +from pcweb.pages import docs +``` + +# Callout + +A `callout` is a short message to attract user's attention. + +```python demo +rx.callout("You will need admin privileges to install and access this application.", icon="info") +``` + +The `icon` prop allows an icon to be passed to the `callout` component. See the [**icon** component for all icons that are available.](/docs/library/data-display/icon) + +## As alert + +```python demo +rx.callout("Access denied. Please contact the network administrator to view this page.", icon="triangle_alert", color_scheme="red", role="alert") +``` + +## Style + +### Size + +Use the `size` prop to control the size. + +```python demo +rx.flex( + rx.callout("You will need admin privileges to install and access this application.", icon="info", size="3",), + rx.callout("You will need admin privileges to install and access this application.", icon="info", size="2",), + rx.callout("You will need admin privileges to install and access this application.", icon="info", size="1",), + direction="column", + spacing="3", + align="start", +) +``` + +### Variant + +Use the `variant` prop to control the visual style. It is set to `soft` by default. + +```python demo +rx.flex( + rx.callout("You will need admin privileges to install and access this application.", icon="info", variant="soft",), + rx.callout("You will need admin privileges to install and access this application.", icon="info", variant="surface",), + rx.callout("You will need admin privileges to install and access this application.", icon="info", variant="outline",), + direction="column", + spacing="3", +) +``` + +### Color + +Use the `color_scheme` prop to assign a specific color, ignoring the global theme. + +```python demo +rx.flex( + rx.callout("You will need admin privileges to install and access this application.", icon="info", color_scheme="blue",), + rx.callout("You will need admin privileges to install and access this application.", icon="info", color_scheme="green",), + rx.callout("You will need admin privileges to install and access this application.", icon="info", color_scheme="red",), + direction="column", + spacing="3", +) +``` + +### High Contrast + +Use the `high_contrast` prop to add additional contrast. + +```python demo +rx.flex( + rx.callout("You will need admin privileges to install and access this application.", icon="info",), + rx.callout("You will need admin privileges to install and access this application.", icon="info", high_contrast=True,), + direction="column", + spacing="3", +) +``` diff --git a/docs/library/data-display/code_block.md b/docs/library/data-display/code_block.md new file mode 100644 index 00000000000..e19f551f7aa --- /dev/null +++ b/docs/library/data-display/code_block.md @@ -0,0 +1,25 @@ +--- +components: + - rx.code_block +--- + +```python exec +import reflex as rx +``` + +# Code Block + +The Code Block component can be used to display code easily within a website. +Put in a multiline string with the correct spacing and specify and language to show the desired code. + +```python demo +rx.code_block( + """def fib(n): + if n <= 1: + return n + else: + return(fib(n-1) + fib(n-2))""", + language="python", + show_line_numbers=True, +) +``` diff --git a/docs/library/data-display/data_list.md b/docs/library/data-display/data_list.md new file mode 100644 index 00000000000..ebb45f5098b --- /dev/null +++ b/docs/library/data-display/data_list.md @@ -0,0 +1,91 @@ +--- +components: + - rx.data_list.root + - rx.data_list.item + - rx.data_list.label + - rx.data_list.value +DataListRoot: | + lambda **props: rx.data_list.root( + rx.foreach( + [["Status", "Authorized"], ["ID", "U-474747"], ["Name", "Developer Success"], ["Email", "foo@reflex.dev"]], + lambda item: rx.data_list.item(rx.data_list.label(item[0]), rx.data_list.value(item[1])), + ), + **props, + ) +DataListItem: | + lambda **props: rx.data_list.root( + rx.foreach( + [["Status", "Authorized"], ["ID", "U-474747"], ["Name", "Developer Success"], ["Email", "foo@reflex.dev"]], + lambda item: rx.data_list.item(rx.data_list.label(item[0]), rx.data_list.value(item[1]), **props), + ), + ) +DataListLabel: | + lambda **props: rx.data_list.root( + rx.foreach( + [["Status", "Authorized"], ["ID", "U-474747"], ["Name", "Developer Success"], ["Email", "foo@reflex.dev"]], + lambda item: rx.data_list.item(rx.data_list.label(item[0], **props), rx.data_list.value(item[1])), + ), + ) +DataListValue: | + lambda **props: rx.data_list.root( + rx.foreach( + [["Status", "Authorized"], ["ID", "U-474747"], ["Name", "Developer Success"], ["Email", "foo@reflex.dev"]], + lambda item: rx.data_list.item(rx.data_list.label(item[0]), rx.data_list.value(item[1], **props)), + ), + ) +--- + +```python exec +import reflex as rx +``` + +# Data List + +The `DataList` component displays key-value pairs and is particularly helpful for showing metadata. + +A `DataList` needs to be initialized using `rx.data_list.root()` and currently takes in data list items: `rx.data_list.item` + +```python demo +rx.card( + rx.data_list.root( + rx.data_list.item( + rx.data_list.label("Status"), + rx.data_list.value( + rx.badge( + "Authorized", + variant="soft", + radius="full", + ) + ), + align="center", + ), + rx.data_list.item( + rx.data_list.label("ID"), + rx.data_list.value(rx.code("U-474747")), + ), + rx.data_list.item( + rx.data_list.label("Name"), + rx.data_list.value("Developer Success"), + align="center", + ), + rx.data_list.item( + rx.data_list.label("Email"), + rx.data_list.value( + rx.link( + "success@reflex.dev", + href="mailto:success@reflex.dev", + ), + ), + ), + rx.data_list.item( + rx.data_list.label("Company"), + rx.data_list.value( + rx.link( + "Reflex", + href="https://reflex.dev", + ), + ), + ), + ), + ), +``` \ No newline at end of file diff --git a/docs/library/data-display/icon.md b/docs/library/data-display/icon.md new file mode 100644 index 00000000000..f2f31a908dd --- /dev/null +++ b/docs/library/data-display/icon.md @@ -0,0 +1,197 @@ +--- +components: + - rx.lucide.Icon +--- + +```python exec +import reflex as rx +from pcweb.components.icons.lucide.lucide import lucide_icons +``` + +# Icon + +The Icon component is used to display an icon from a library of icons. This implementation is based on the [Lucide Icons](https://lucide.dev/icons) where you can find a list of all available icons. + + +## Icons List + +```python eval +lucide_icons() +``` + +## Basic Example + +To display an icon, specify the `tag` prop from the list of available icons. +Passing the tag as the first children is also supported and will be assigned to the `tag` prop. + +The `tag` is expected to be in `snake_case` format, but `kebab-case` is also supported to allow copy-paste from [https://lucide.dev/icons](https://lucide.dev/icons). + +```python demo +rx.flex( + rx.icon("calendar"), + rx.icon(tag="calendar"), + gap="2", +) +``` + +## Dynamic Icons + +There are two ways to use dynamic icons in Reflex: + +### Using rx.match + +If you have a specific subset of icons you want to use dynamically, you can define an `rx.match` with them: + +```python +def dynamic_icon_with_match(icon_name): + return rx.match( + icon_name, + ("plus", rx.icon("plus")), + ("minus", rx.icon("minus")), + ("equal", rx.icon("equal")), + ) +``` + +```python exec +def dynamic_icon_with_match(icon_name): + return rx.match( + icon_name, + ("plus", rx.icon("plus")), + ("minus", rx.icon("minus")), + ("equal", rx.icon("equal")), + ) +``` + +### Using Dynamic Icon Tags + +Reflex also supports using dynamic values directly as the `tag` prop in `rx.icon()`. This allows you to use any icon from the Lucide library dynamically at runtime. + +```python exec +class DynamicIconState(rx.State): + current_icon: str = "heart" + + def change_icon(self): + icons = ["heart", "star", "bell", "calendar", "settings"] + import random + self.current_icon = random.choice(icons) +``` + +```python demo +rx.vstack( + rx.heading("Dynamic Icon Example"), + rx.icon(DynamicIconState.current_icon, size=30, color="red"), + rx.button("Change Icon", on_click=DynamicIconState.change_icon), + spacing="4", + align="center", +) +``` + +Under the hood, when a dynamic value is passed as the `tag` prop to `rx.icon()`, Reflex automatically uses a special `DynamicIcon` component that can load icons at runtime. + +```md alert +When using dynamic icons, make sure the icon names are valid. Invalid icon names will cause runtime errors. +``` + +## Styling + +Icon from Lucide can be customized with the following props `stroke_width`, `size` and `color`. + +### Stroke Width + +```python demo +rx.flex( + rx.icon("moon", stroke_width=1), + rx.icon("moon", stroke_width=1.5), + rx.icon("moon", stroke_width=2), + rx.icon("moon", stroke_width=2.5), + gap="2" +) +``` + + +### Size + +```python demo +rx.flex( + rx.icon("zoom_in", size=15), + rx.icon("zoom_in", size=20), + rx.icon("zoom_in", size=25), + rx.icon("zoom_in", size=30), + align="center", + gap="2", +) +``` + +### Color + +Here is an example using basic colors in icons. + +```python demo +rx.flex( + rx.icon("zoom_in", size=18, color="indigo"), + rx.icon("zoom_in", size=18, color="cyan"), + rx.icon("zoom_in", size=18, color="orange"), + rx.icon("zoom_in", size=18, color="crimson"), + gap="2", +) +``` + +A radix color with a scale may also be specified using `rx.color()` as seen below. + +```python demo +rx.flex( + rx.icon("zoom_in", size=18, color=rx.color("purple", 1)), + rx.icon("zoom_in", size=18, color=rx.color("purple", 2)), + rx.icon("zoom_in", size=18, color=rx.color("purple", 3)), + rx.icon("zoom_in", size=18, color=rx.color("purple", 4)), + rx.icon("zoom_in", size=18, color=rx.color("purple", 5)), + rx.icon("zoom_in", size=18, color=rx.color("purple", 6)), + rx.icon("zoom_in", size=18, color=rx.color("purple", 7)), + rx.icon("zoom_in", size=18, color=rx.color("purple", 8)), + rx.icon("zoom_in", size=18, color=rx.color("purple", 9)), + rx.icon("zoom_in", size=18, color=rx.color("purple", 10)), + rx.icon("zoom_in", size=18, color=rx.color("purple", 11)), + rx.icon("zoom_in", size=18, color=rx.color("purple", 12)), + gap="2", +) +``` + +Here is another example using the `accent` color with scales. The `accent` is the most dominant color in your theme. + +```python demo +rx.flex( + rx.icon("zoom_in", size=18, color=rx.color("accent", 1)), + rx.icon("zoom_in", size=18, color=rx.color("accent", 2)), + rx.icon("zoom_in", size=18, color=rx.color("accent", 3)), + rx.icon("zoom_in", size=18, color=rx.color("accent", 4)), + rx.icon("zoom_in", size=18, color=rx.color("accent", 5)), + rx.icon("zoom_in", size=18, color=rx.color("accent", 6)), + rx.icon("zoom_in", size=18, color=rx.color("accent", 7)), + rx.icon("zoom_in", size=18, color=rx.color("accent", 8)), + rx.icon("zoom_in", size=18, color=rx.color("accent", 9)), + rx.icon("zoom_in", size=18, color=rx.color("accent", 10)), + rx.icon("zoom_in", size=18, color=rx.color("accent", 11)), + rx.icon("zoom_in", size=18, color=rx.color("accent", 12)), + gap="2", +) +``` + + +## Final Example + +Icons can be used as child components of many other components. For example, adding a magnifying glass icon to a search bar. + +```python demo +rx.badge( + rx.flex( + rx.icon("search", size=18), + rx.text("Search documentation...", size="3", weight="medium"), + direction="row", + gap="1", + align="center", + ), + size="2", + radius="full", + color_scheme="gray", +) +``` diff --git a/docs/library/data-display/list.md b/docs/library/data-display/list.md new file mode 100644 index 00000000000..941abf9f4ef --- /dev/null +++ b/docs/library/data-display/list.md @@ -0,0 +1,70 @@ +--- +components: + - rx.list.item + - rx.list.ordered + - rx.list.unordered +--- + +```python exec +import reflex as rx +``` + +# List + +A `list` is a component that is used to display a list of items, stacked vertically by default. A `list` can be either `ordered` or `unordered`. It is based on the `flex` component and therefore inherits all of its props. + +`list.unordered` has bullet points to display the list items. + +```python demo +rx.list.unordered( + rx.list.item("Example 1"), + rx.list.item("Example 2"), + rx.list.item("Example 3"), +) +``` + + `list.ordered` has numbers to display the list items. + +```python demo +rx.list.ordered( + rx.list.item("Example 1"), + rx.list.item("Example 2"), + rx.list.item("Example 3"), +) +``` + +`list.unordered()` and `list.ordered()` can have no bullet points or numbers by setting the `list_style_type` prop to `none`. +This is effectively the same as using the `list()` component. + +```python demo +rx.hstack( + rx.list( + rx.list.item("Example 1"), + rx.list.item("Example 2"), + rx.list.item("Example 3"), + ), + rx.list.unordered( + rx.list.item("Example 1"), + rx.list.item("Example 2"), + rx.list.item("Example 3"), + list_style_type="none", + ) +) +``` + +Lists can also be used with icons. + +```python demo +rx.list( + rx.list.item( + rx.icon("circle_check_big", color="green"), " Allowed", + ), + rx.list.item( + rx.icon("octagon_x", color="red"), " Not", + ), + rx.list.item( + rx.icon("settings", color="grey"), " Settings" + ), + list_style_type="none", +) +``` diff --git a/docs/library/data-display/moment.md b/docs/library/data-display/moment.md new file mode 100644 index 00000000000..10ac603b5d8 --- /dev/null +++ b/docs/library/data-display/moment.md @@ -0,0 +1,170 @@ +--- +components: + - rx.moment + +--- + +# Moment + +Displaying date and relative time to now sometimes can be more complicated than necessary. + +To make it easy, Reflex is wrapping [react-moment](https://www.npmjs.com/package/react-moment) under `rx.moment`. + + +```python exec +import reflex as rx +from reflex.utils.serializers import serialize_datetime +from pcweb.templates.docpage import docdemo, docdemobox, doccode, docgraphing +``` + +## Examples + +Using a date from a state var as a value, we will display it in a few different +way using `rx.moment`. + +The `date_now` state var is initialized when the site was deployed. The +button below can be used to update the var to the current datetime, which will +be reflected in the subsequent examples. + +```python demo exec +from datetime import datetime, timezone + +class MomentState(rx.State): + date_now: datetime = datetime.now(timezone.utc) + + @rx.event + def update(self): + self.date_now = datetime.now(timezone.utc) + + +def moment_update_example(): + return rx.button("Update", rx.moment(MomentState.date_now), on_click=MomentState.update) +``` + +### Display the date as-is: + +```python demo +rx.moment(MomentState.date_now) +``` + +### Humanized interval + +Sometimes we don't want to display just a raw date, but we want something more instinctive to read. That's when we can use `from_now` and `to_now`. + +```python demo +rx.moment(MomentState.date_now, from_now=True) +``` + +```python demo +rx.moment(MomentState.date_now, to_now=True) +``` +You can also set a duration (in milliseconds) with `from_now_during` where the date will display as relative, then after that, it will be displayed as defined in `format`. + +```python demo +rx.moment(MomentState.date_now, from_now_during=100000) # after 100 seconds, date will display normally +``` + +### Formatting dates + +```python demo +rx.moment(MomentState.date_now, format="YYYY-MM-DD") +``` + +```python demo +rx.moment(MomentState.date_now, format="HH:mm:ss") +``` + +### Offset Date + +With the props `add` and `subtract`, you can pass an `rx.MomentDelta` object to modify the displayed date without affecting the stored date in your state. + +```python exec +add_example = """rx.vstack( + rx.moment(MomentState.date_now, add=rx.MomentDelta(years=2), format="YYYY-MM-DD - HH:mm:ss"), + rx.moment(MomentState.date_now, add=rx.MomentDelta(quarters=2), format="YYYY-MM-DD - HH:mm:ss"), + rx.moment(MomentState.date_now, add=rx.MomentDelta(months=2), format="YYYY-MM-DD - HH:mm:ss"), + rx.moment(MomentState.date_now, add=rx.MomentDelta(months=2), format="YYYY-MM-DD - HH:mm:ss"), + rx.moment(MomentState.date_now, add=rx.MomentDelta(months=2), format="YYYY-MM-DD - HH:mm:ss"), + rx.moment(MomentState.date_now, add=rx.MomentDelta(weeks=2), format="YYYY-MM-DD - HH:mm:ss"), + rx.moment(MomentState.date_now, add=rx.MomentDelta(days=2), format="YYYY-MM-DD - HH:mm:ss"), + rx.moment(MomentState.date_now, add=rx.MomentDelta(hours=2), format="YYYY-MM-DD - HH:mm:ss"), + rx.moment(MomentState.date_now, add=rx.MomentDelta(minutes=2), format="YYYY-MM-DD - HH:mm:ss"), + rx.moment(MomentState.date_now, add=rx.MomentDelta(seconds=2), format="YYYY-MM-DD - HH:mm:ss"), +) +""" +subtract_example = """rx.vstack( + rx.moment(MomentState.date_now, subtract=rx.MomentDelta(years=2), format="YYYY-MM-DD - HH:mm:ss"), + rx.moment(MomentState.date_now, subtract=rx.MomentDelta(quarters=2), format="YYYY-MM-DD - HH:mm:ss"), + rx.moment(MomentState.date_now, subtract=rx.MomentDelta(months=2), format="YYYY-MM-DD - HH:mm:ss"), + rx.moment(MomentState.date_now, subtract=rx.MomentDelta(months=2), format="YYYY-MM-DD - HH:mm:ss"), + rx.moment(MomentState.date_now, subtract=rx.MomentDelta(months=2), format="YYYY-MM-DD - HH:mm:ss"), + rx.moment(MomentState.date_now, subtract=rx.MomentDelta(weeks=2), format="YYYY-MM-DD - HH:mm:ss"), + rx.moment(MomentState.date_now, subtract=rx.MomentDelta(days=2), format="YYYY-MM-DD - HH:mm:ss"), + rx.moment(MomentState.date_now, subtract=rx.MomentDelta(hours=2), format="YYYY-MM-DD - HH:mm:ss"), + rx.moment(MomentState.date_now, subtract=rx.MomentDelta(minutes=2), format="YYYY-MM-DD - HH:mm:ss"), + rx.moment(MomentState.date_now, subtract=rx.MomentDelta(seconds=2), format="YYYY-MM-DD - HH:mm:ss"), +) +""" +``` + +```python eval +rx.tabs( + rx.tabs.list( + rx.tabs.trigger("Add", value="add"), + rx.tabs.trigger("Subtract", value="subtract") + ), + rx.tabs.content(docdemo(add_example, comp=eval(add_example)), value="add"), + rx.tabs.content(docdemo(subtract_example, comp=eval(subtract_example)), value="subtract"), + default_value="add", +) +``` + +### Timezones + +You can also set dates to display in a specific timezone: + +```python demo +rx.vstack( + rx.moment(MomentState.date_now, tz="America/Los_Angeles"), + rx.moment(MomentState.date_now, tz="Europe/Paris"), + rx.moment(MomentState.date_now, tz="Asia/Tokyo"), +) +``` + +### Client-side periodic update + +If a date is not passed to `rx.moment`, it will use the client's current time. + +If you want to update the date every second, you can use the `interval` prop. + +```python demo +rx.moment(interval=1000, format="HH:mm:ss") +``` + +Even better, you can actually link an event handler to the `on_change` prop that will be called every time the date is updated: + +```python demo exec +class MomentLiveState(rx.State): + updating: bool = False + + @rx.event + def on_update(self, date): + return rx.toast(f"Date updated: {date}") + + @rx.event + def set_updating(self, value: bool): + self.updating = value + +def moment_live_example(): + return rx.hstack( + rx.moment( + format="HH:mm:ss", + interval=rx.cond(MomentLiveState.updating, 5000, 0), + on_change=MomentLiveState.on_update, + ), + rx.switch( + is_checked=MomentLiveState.updating, + on_change=MomentLiveState.set_updating, + ), + ) +``` diff --git a/docs/library/data-display/progress.md b/docs/library/data-display/progress.md new file mode 100644 index 00000000000..a5df0dd478d --- /dev/null +++ b/docs/library/data-display/progress.md @@ -0,0 +1,54 @@ +--- +components: + - rx.progress + +Progress: | + lambda **props: rx.progress(value=50, **props) +--- + +# Progress + +Progress is used to display the progress status for a task that takes a long time or consists of several steps. + +```python exec +import reflex as rx +``` +## Basic Example + +`rx.progress` expects the `value` prop to set the progress value. +`width` is default to 100%, the width of its parent component. + +```python demo +rx.vstack( + rx.progress(value=0), + rx.progress(value=50), + rx.progress(value=100), + width="50%", +) +``` + +For a dynamic progress, you can assign a state variable to the `value` prop instead of a constant value. + +```python demo exec +import asyncio + +class ProgressState(rx.State): + value: int = 0 + + @rx.event(background=True) + async def start_progress(self): + async with self: + self.value = 0 + while self.value < 100: + await asyncio.sleep(0.1) + async with self: + self.value += 1 + + +def live_progress(): + return rx.hstack( + rx.progress(value=ProgressState.value), + rx.button("Start", on_click=ProgressState.start_progress), + width="50%" + ) +``` diff --git a/docs/library/data-display/scroll_area.md b/docs/library/data-display/scroll_area.md new file mode 100644 index 00000000000..17327eae412 --- /dev/null +++ b/docs/library/data-display/scroll_area.md @@ -0,0 +1,239 @@ +--- +components: + - rx.scroll_area + +ScrollArea: | + lambda **props: rx.scroll_area( + rx.flex( + rx.text( + """Three fundamental aspects of typography are legibility, readability, and aesthetics. Although in a non-technical sense "legible" and "readable"are often used synonymously, typographically they are separate but related concepts.""", + size="5", + ), + rx.text( + """Legibility describes how easily individual characters can be distinguished from one another. It is described by Walter Tracy as "the quality of being decipherable and recognisable". For instance, if a "b" and an "h", or a "3" and an "8", are difficult to distinguish at small sizes, this is a problem of legibility.""", + size="5", + ), + direction="column", + spacing="4", + height="100px", + width="50%", + ), + **props + ) + +--- + + +```python exec +import random +import reflex as rx +from pcweb.templates.docpage import style_grid +``` + +# Scroll Area + +Custom styled, cross-browser scrollable area using native functionality. + +## Basic Example + +```python demo +rx.scroll_area( + rx.flex( + rx.text( + """Three fundamental aspects of typography are legibility, readability, and + aesthetics. Although in a non-technical sense “legible” and “readable” + are often used synonymously, typographically they are separate but + related concepts.""", + ), + rx.text( + """Legibility describes how easily individual characters can be + distinguished from one another. It is described by Walter Tracy as “the + quality of being decipherable and recognisable”. For instance, if a “b” + and an “h”, or a “3” and an “8”, are difficult to distinguish at small + sizes, this is a problem of legibility.""", + ), + rx.text( + """Typographers are concerned with legibility insofar as it is their job to + select the correct font to use. Brush Script is an example of a font + containing many characters that might be difficult to distinguish. The + selection of cases influences the legibility of typography because using + only uppercase letters (all-caps) reduces legibility.""", + ), + direction="column", + spacing="4", + ), + type="always", + scrollbars="vertical", + style={"height": 180}, + +) + +``` + +## Control the scrollable axes + +Use the `scrollbars` prop to limit scrollable axes. This prop can take values `"vertical" | "horizontal" | "both"`. + +```python demo +rx.grid( + rx.scroll_area( + rx.flex( + rx.text( + """Three fundamental aspects of typography are legibility, readability, and + aesthetics. Although in a non-technical sense "legible" and "readable" + are often used synonymously, typographically they are separate but + related concepts.""", + size="2", trim="both", + ), + rx.text( + """Legibility describes how easily individual characters can be + distinguished from one another. It is described by Walter Tracy as "the + quality of being decipherable and recognisable". For instance, if a "b" + and an "h", or a "3" and an "8", are difficult to distinguish at small + sizes, this is a problem of legibility.""", + size="2", trim="both", + ), + padding="8px", padding_right="48px", direction="column", spacing="4", + ), + type="always", + scrollbars="vertical", + style={"height": 150}, + ), + rx.scroll_area( + rx.flex( + rx.text( + """Three fundamental aspects of typography are legibility, readability, and + aesthetics. Although in a non-technical sense "legible" and "readable" + are often used synonymously, typographically they are separate but + related concepts.""", + size="2", trim="both", + ), + rx.text( + """Legibility describes how easily individual characters can be + distinguished from one another. It is described by Walter Tracy as "the + quality of being decipherable and recognisable". For instance, if a "b" + and an "h", or a "3" and an "8", are difficult to distinguish at small + sizes, this is a problem of legibility.""", + size="2", trim="both", + ), + padding="8px", spacing="4", style={"width": 700}, + ), + type="always", + scrollbars="horizontal", + style={"height": 150}, + ), + rx.scroll_area( + rx.flex( + rx.text( + """Three fundamental aspects of typography are legibility, readability, and + aesthetics. Although in a non-technical sense "legible" and "readable" + are often used synonymously, typographically they are separate but + related concepts.""", + size="2", trim="both", + ), + rx.text( + """Legibility describes how easily individual characters can be + distinguished from one another. It is described by Walter Tracy as "the + quality of being decipherable and recognisable". For instance, if a "b" + and an "h", or a "3" and an "8", are difficult to distinguish at small + sizes, this is a problem of legibility.""", + size="2", trim="both", + ), + padding="8px", spacing="4", style={"width": 400}, + ), + type="always", + scrollbars="both", + style={"height": 150}, + ), + columns="3", + spacing="2", +) +``` + +## Setting the type of the Scrollbars + +The `type` prop describes the nature of scrollbar visibility. + +`auto` means that scrollbars are visible when content is overflowing on the corresponding orientation. + +`always` means that scrollbars are always visible regardless of whether the content is overflowing. + +`scroll` means that scrollbars are visible when the user is scrolling along its corresponding orientation. + +`hover` when the user is scrolling along its corresponding orientation and when the user is hovering over the scroll area. + +```python demo +rx.grid( + rx.scroll_area( + rx.flex( + rx.text("type = 'auto'", weight="bold"), + rx.text( + """Legibility describes how easily individual characters can be + distinguished from one another. It is described by Walter Tracy as "the + quality of being decipherable and recognisable". For instance, if a "b" + and an "h", or a "3" and an "8", are difficult to distinguish at small + sizes, this is a problem of legibility.""", + size="2", trim="both", + ), + padding="8px", direction="column", spacing="4", + ), + type="auto", + scrollbars="vertical", + style={"height": 150}, + ), + rx.scroll_area( + rx.flex( + rx.text("type = 'always'", weight="bold"), + rx.text( + """Legibility describes how easily individual characters can be + distinguished from one another. It is described by Walter Tracy as "the + quality of being decipherable and recognisable". For instance, if a "b" + and an "h", or a "3" and an "8", are difficult to distinguish at small + sizes, this is a problem of legibility.""", + size="2", trim="both", + ), + padding="8px", direction="column", spacing="4", + ), + type="always", + scrollbars="vertical", + style={"height": 150}, + ), + rx.scroll_area( + rx.flex( + rx.text("type = 'scroll'", weight="bold"), + rx.text( + """Legibility describes how easily individual characters can be + distinguished from one another. It is described by Walter Tracy as "the + quality of being decipherable and recognisable". For instance, if a "b" + and an "h", or a "3" and an "8", are difficult to distinguish at small + sizes, this is a problem of legibility.""", + size="2", trim="both", + ), + padding="8px", direction="column", spacing="4", + ), + type="scroll", + scrollbars="vertical", + style={"height": 150}, + ), + rx.scroll_area( + rx.flex( + rx.text("type = 'hover'", weight="bold"), + rx.text( + """Legibility describes how easily individual characters can be + distinguished from one another. It is described by Walter Tracy as "the + quality of being decipherable and recognisable". For instance, if a "b" + and an "h", or a "3" and an "8", are difficult to distinguish at small + sizes, this is a problem of legibility.""", + size="2", trim="both", + ), + padding="8px", direction="column", spacing="4", + ), + type="hover", + scrollbars="vertical", + style={"height": 150}, + ), + columns="4", + spacing="2", +) + +``` diff --git a/docs/library/data-display/spinner.md b/docs/library/data-display/spinner.md new file mode 100644 index 00000000000..90850f3f45c --- /dev/null +++ b/docs/library/data-display/spinner.md @@ -0,0 +1,56 @@ +--- +components: + - rx.spinner +--- + +# Spinner + +Spinner is used to display an animated loading indicator when a task is in progress. + +```python exec +import reflex as rx +``` + +```python demo +rx.spinner() +``` + +## Basic Examples + +Spinner can have different sizes. + +```python demo +rx.vstack( + rx.hstack( + rx.spinner(size="1"), + rx.spinner(size="2"), + rx.spinner(size="3"), + align="center", + gap="1em" + ) +) +``` + +## Demo with buttons + +Buttons have their own loading prop that automatically composes a spinner. + +```python demo +rx.button("Bookmark", loading=True) +``` + +## Spinner inside a button + +If you have an icon inside the button, you can use the button's disabled state and wrap the icon in a standalone rx.spinner to achieve a more sophisticated design. + +```python demo +rx.button( + rx.spinner( + loading=True + ), + "Bookmark", + disabled=True +) +``` + + diff --git a/docs/library/disclosure/accordion.md b/docs/library/disclosure/accordion.md new file mode 100644 index 00000000000..4a2a9eda9bb --- /dev/null +++ b/docs/library/disclosure/accordion.md @@ -0,0 +1,359 @@ +--- +components: + - rx.accordion.root + - rx.accordion.item + +AccordionRoot: | + lambda **props: rx.accordion.root( + rx.accordion.item(header="First Item", content="The first accordion item's content"), + rx.accordion.item( + header="Second Item", content="The second accordion item's content", + ), + rx.accordion.item(header="Third item", content="The third accordion item's content"), + width="300px", + **props, + ) + +AccordionItem: | + lambda **props: rx.accordion.root( + rx.accordion.item(header="First Item", content="The first accordion item's content", **props), + rx.accordion.item( + header="Second Item", content="The second accordion item's content", **props, + ), + rx.accordion.item(header="Third item", content="The third accordion item's content", **props), + width="300px", + ) +--- + +```python exec +import reflex as rx +``` + +# Accordion + +An accordion is a vertically stacked set of interactive headings that each reveal an associated section of content. +The accordion component is made up of `accordion`, which is the root of the component and takes in an `accordion.item`, +which contains all the contents of the collapsible section. + +## Basic Example + +```python demo +rx.accordion.root( + rx.accordion.item(header="First Item", content="The first accordion item's content"), + rx.accordion.item( + header="Second Item", content="The second accordion item's content", + ), + rx.accordion.item(header="Third item", content="The third accordion item's content"), + width="300px", +) +``` + +## Styling + +### Type + +We use the `type` prop to determine whether multiple items can be opened at once. The allowed values for this prop are +`single` and `multiple` where `single` will only open one item at a time. The default value for this prop is `single`. + +```python demo +rx.accordion.root( + rx.accordion.item(header="First Item", content="The first accordion item's content"), + rx.accordion.item( + header="Second Item", content="The second accordion item's content", + ), + rx.accordion.item(header="Third item", content="The third accordion item's content"), + collapsible=True, + width="300px", + type="multiple", +) +``` + +### Default Value + +We use the `default_value` prop to specify which item should open by default. The value for this prop should be any of the +unique values set by an `accordion.item`. + +```python demo +rx.flex( + rx.accordion.root( + rx.accordion.item( + header="First Item", + content="The first accordion item's content", + value="item_1", + ), + rx.accordion.item( + header="Second Item", + content="The second accordion item's content", + value="item_2", + ), + rx.accordion.item( + header="Third item", + content="The third accordion item's content", + value="item_3", + ), + width="300px", + default_value="item_2", + ), + direction="row", + spacing="2" +) +``` + +### Collapsible + +We use the `collapsible` prop to allow all items to close. If set to `False`, an opened item cannot be closed. + +```python demo +rx.flex( + rx.accordion.root( + rx.accordion.item(header="First Item", content="The first accordion item's content"), + rx.accordion.item(header="Second Item", content="The second accordion item's content"), + rx.accordion.item(header="Third item", content="The third accordion item's content"), + collapsible=True, + width="300px", + ), + rx.accordion.root( + rx.accordion.item(header="First Item", content="The first accordion item's content"), + rx.accordion.item(header="Second Item", content="The second accordion item's content"), + rx.accordion.item(header="Third item", content="The third accordion item's content"), + collapsible=False, + width="300px", + ), + direction="row", + spacing="2" +) +``` + +### Disable + +We use the `disabled` prop to prevent interaction with the accordion and all its items. + +```python demo +rx.accordion.root( + rx.accordion.item(header="First Item", content="The first accordion item's content"), + rx.accordion.item(header="Second Item", content="The second accordion item's content"), + rx.accordion.item(header="Third item", content="The third accordion item's content"), + collapsible=True, + width="300px", + disabled=True, +) +``` + +### Orientation + +We use `orientation` prop to set the orientation of the accordion to `vertical` or `horizontal`. By default, the orientation +will be set to `vertical`. Note that, the orientation prop won't change the visual orientation but the +functional orientation of the accordion. This means that for vertical orientation, the up and down arrow keys moves focus between the next or previous item, +while for horizontal orientation, the left or right arrow keys moves focus between items. + +```python demo +rx.accordion.root( + rx.accordion.item(header="First Item", content="The first accordion item's content"), + rx.accordion.item( + header="Second Item", content="The second accordion item's content", + ), + rx.accordion.item(header="Third item", content="The third accordion item's content"), + collapsible=True, + width="300px", + orientation="vertical", +) +``` + +```python demo +rx.accordion.root( + rx.accordion.item(header="First Item", content="The first accordion item's content"), + rx.accordion.item( + header="Second Item", content="The second accordion item's content", + ), + rx.accordion.item(header="Third item", content="The third accordion item's content"), + collapsible=True, + width="300px", + orientation="horizontal", +) +``` + +### Variant + +```python demo +rx.flex( + rx.accordion.root( + rx.accordion.item(header="First Item", content="The first accordion item's content"), + rx.accordion.item( + header="Second Item", content="The second accordion item's content", + ), + rx.accordion.item(header="Third item", content="The third accordion item's content"), + collapsible=True, + variant="classic", + ), + rx.accordion.root( + rx.accordion.item(header="First Item", content="The first accordion item's content"), + rx.accordion.item( + header="Second Item", content="The second accordion item's content", + ), + rx.accordion.item(header="Third item", content="The third accordion item's content"), + collapsible=True, + variant="soft", + ), + rx.accordion.root( + rx.accordion.item(header="First Item", content="The first accordion item's content"), + rx.accordion.item( + header="Second Item", content="The second accordion item's content", + ), + rx.accordion.item(header="Third item", content="The third accordion item's content"), + collapsible=True, + variant="outline", + ), + rx.accordion.root( + rx.accordion.item(header="First Item", content="The first accordion item's content"), + rx.accordion.item( + header="Second Item", content="The second accordion item's content", + ), + rx.accordion.item(header="Third item", content="The third accordion item's content"), + collapsible=True, + variant="surface", + ), + rx.accordion.root( + rx.accordion.item(header="First Item", content="The first accordion item's content"), + rx.accordion.item( + header="Second Item", content="The second accordion item's content", + ), + rx.accordion.item(header="Third item", content="The third accordion item's content"), + collapsible=True, + variant="ghost", + ), + direction="row", + spacing="2" +) +``` + +### Color Scheme + +We use the `color_scheme` prop to assign a specific color to the accordion background, ignoring the global theme. + +```python demo +rx.flex( + rx.accordion.root( + rx.accordion.item(header="First Item", content="The first accordion item's content"), + rx.accordion.item( + header="Second Item", content="The second accordion item's content", + ), + rx.accordion.item(header="Third item", content="The third accordion item's content"), + collapsible=True, + width="300px", + color_scheme="grass", + ), + rx.accordion.root( + rx.accordion.item(header="First Item", content="The first accordion item's content"), + rx.accordion.item( + header="Second Item", content="The second accordion item's content", + ), + rx.accordion.item(header="Third item", content="The third accordion item's content"), + collapsible=True, + width="300px", + color_scheme="green", + ), + direction="row", + spacing="2" +) +``` + +### Value + +We use the `value` prop to specify the controlled value of the accordion item that we want to activate. +This property should be used in conjunction with the `on_value_change` event argument. + +```python demo exec +class AccordionState(rx.State): + """The app state.""" + value: str = "item_1" + item_selected: str + + @rx.event + def change_value(self, value): + self.value = value + self.item_selected = f"{value} selected" + + +def index() -> rx.Component: + return rx.theme( + rx.container( + rx.text(AccordionState.item_selected), + rx.flex( + rx.accordion.root( + rx.accordion.item( + header="Is it accessible?", + content=rx.button("Test button"), + value="item_1", + ), + rx.accordion.item( + header="Is it unstyled?", + content="Yes. It's unstyled by default, giving you freedom over the look and feel.", + value="item_2", + ), + rx.accordion.item( + header="Is it finished?", + content="It's still in beta, but it's ready to use in production.", + value="item_3", + ), + collapsible=True, + width="300px", + value=AccordionState.value, + on_value_change=lambda value: AccordionState.change_value(value), + ), + direction="column", + spacing="2", + ), + padding="2em", + text_align="center", + ) + ) +``` + +## AccordionItem + +The accordion item contains all the parts of a collapsible section. + +## Styling + +### Value + +```python demo +rx.accordion.root( + rx.accordion.item( + header="First Item", + content="The first accordion item's content", + value="item_1", + ), + rx.accordion.item( + header="Second Item", + content="The second accordion item's content", + value="item_2", + ), + rx.accordion.item( + header="Third item", + content="The third accordion item's content", + value="item_3", + ), + collapsible=True, + width="300px", +) +``` + +### Disable + +```python demo +rx.accordion.root( + rx.accordion.item( + header="First Item", + content="The first accordion item's content", + disabled=True, + ), + rx.accordion.item( + header="Second Item", content="The second accordion item's content", + ), + rx.accordion.item(header="Third item", content="The third accordion item's content"), + collapsible=True, + width="300px", + color_scheme="blue", +) +``` diff --git a/docs/library/disclosure/segmented_control.md b/docs/library/disclosure/segmented_control.md new file mode 100644 index 00000000000..f67748abdd6 --- /dev/null +++ b/docs/library/disclosure/segmented_control.md @@ -0,0 +1,56 @@ +--- +components: + - rx.segmented_control.root + - rx.segmented_control.item +--- + +```python exec +import reflex as rx + +class SegmentedState(rx.State): + """The app state.""" + + control: str = "test" + + @rx.event + def set_control(self, value: str | list[str]): + self.control = value + + +``` + +# Segmented Control + +Segmented Control offers a clear and accessible way to switch between predefined values and views, e.g., "Inbox," "Drafts," and "Sent." + +With Segmented Control, you can make mutually exclusive choices, where only one option can be active at a time, clear and accessible. Without Segmented Control, end users might have to deal with controls like dropdowns or multiple buttons that don't clearly convey state or group options together visually. + +## Basic Example + +The `Segmented Control` component is made up of a `rx.segmented_control.root` which groups `rx.segmented_control.item`. + +The `rx.segmented_control.item` components define the individual segments of the control, each with a label and a unique value. + +```python demo +rx.vstack( + rx.segmented_control.root( + rx.segmented_control.item("Home", value="home"), + rx.segmented_control.item("About", value="about"), + rx.segmented_control.item("Test", value="test"), + on_change=SegmentedState.set_control, + value=SegmentedState.control, + ), + rx.card( + rx.text(SegmentedState.control, align="left"), + rx.text(SegmentedState.control, align="center"), + rx.text(SegmentedState.control, align="right"), + width="100%", + ), +) +``` + +**In the example above:** + +`on_change` is used to specify a callback function that will be called when the user selects a different segment. In this case, the `SegmentedState.setvar("control")` function is used to update the `control` state variable when the user changes the selected segment. + +`value` prop is used to specify the currently selected segment, which is bound to the `SegmentedState.control` state variable. diff --git a/docs/library/disclosure/tabs.md b/docs/library/disclosure/tabs.md new file mode 100644 index 00000000000..05fdb8a67be --- /dev/null +++ b/docs/library/disclosure/tabs.md @@ -0,0 +1,330 @@ +--- +components: + - rx.tabs.root + - rx.tabs.list + - rx.tabs.trigger + - rx.tabs.content + +only_low_level: + - True + +TabsRoot: | + lambda **props: rx.tabs.root( + rx.tabs.list( + rx.tabs.trigger("Account", value="account"), + rx.tabs.trigger("Documents", value="documents"), + rx.tabs.trigger("Settings", value="settings"), + ), + rx.box( + rx.tabs.content( + rx.text("Make changes to your account"), + value="account", + ), + rx.tabs.content( + rx.text("Update your documents"), + value="documents", + ), + rx.tabs.content( + rx.text("Edit your personal profile"), + value="settings", + ), + ), + **props, + ) + +TabsList: | + lambda **props: rx.tabs.root( + rx.tabs.list( + rx.tabs.trigger("Account", value="account"), + rx.tabs.trigger("Documents", value="documents"), + rx.tabs.trigger("Settings", value="settings"), + **props, + ), + rx.box( + rx.tabs.content( + rx.text("Make changes to your account"), + value="account", + ), + rx.tabs.content( + rx.text("Update your documents"), + value="documents", + ), + rx.tabs.content( + rx.text("Edit your personal profile"), + value="settings", + ), + ), + ) + +TabsTrigger: | + lambda **props: rx.tabs.root( + rx.tabs.list( + rx.tabs.trigger("Account", value="account", **props,), + rx.tabs.trigger("Documents", value="documents"), + rx.tabs.trigger("Settings", value="settings"), + ), + rx.box( + rx.tabs.content( + rx.text("Make changes to your account"), + value="account", + ), + rx.tabs.content( + rx.text("Update your documents"), + value="documents", + ), + rx.tabs.content( + rx.text("Edit your personal profile"), + value="settings", + ), + ), + ) + +TabsContent: | + lambda **props: rx.tabs.root( + rx.tabs.list( + rx.tabs.trigger("Account", value="account"), + rx.tabs.trigger("Documents", value="documents"), + rx.tabs.trigger("Settings", value="settings"), + ), + rx.box( + rx.tabs.content( + rx.text("Make changes to your account"), + value="account", + **props, + ), + rx.tabs.content( + rx.text("Update your documents"), + value="documents", + **props, + ), + rx.tabs.content( + rx.text("Edit your personal profile"), + value="settings", + **props, + ), + ), + ) +--- + +```python exec +import reflex as rx +``` + +# Tabs + +Tabs are a set of layered sections of content—known as tab panels that are displayed one at a time. +They facilitate the organization and navigation between sets of content that share a connection and exist at a similar level of hierarchy. + +## Basic Example + +```python demo +rx.tabs.root( + rx.tabs.list( + rx.tabs.trigger("Tab 1", value="tab1"), + rx.tabs.trigger("Tab 2", value="tab2") + ), + rx.tabs.content( + rx.text("item on tab 1"), + value="tab1", + ), + rx.tabs.content( + rx.text("item on tab 2"), + value="tab2", + ), +) + +``` + +The `tabs` component is made up of a `rx.tabs.root` which groups `rx.tabs.list` and `rx.tabs.content` parts. + +## Styling + +### Default value + +We use the `default_value` prop to set a default active tab, this will select the specified tab by default. + +```python demo +rx.tabs.root( + rx.tabs.list( + rx.tabs.trigger("Tab 1", value="tab1"), + rx.tabs.trigger("Tab 2", value="tab2") + ), + rx.tabs.content( + rx.text("item on tab 1"), + value="tab1", + ), + rx.tabs.content( + rx.text("item on tab 2"), + value="tab2", + ), + default_value="tab2", +) +``` + +### Orientation + +We use `orientation` prop to set the orientation of the tabs component to `vertical` or `horizontal`. By default, the orientation +will be set to `horizontal`. Setting this value will change both the visual orientation and the functional orientation. + +```md alert info +The functional orientation means for `vertical`, the `up` and `down` arrow keys moves focus between the next or previous tab, +while for `horizontal`, the `left` and `right` arrow keys moves focus between tabs. +``` + +```md alert warning +# When using vertical orientation, make sure to assign a tabs.content for each trigger. + +Defining triggers without content will result in a visual bug where the width of the triggers list isn't constant. +``` + +```python demo +rx.tabs.root( + rx.tabs.list( + rx.tabs.trigger("Tab 1", value="tab1"), + rx.tabs.trigger("Tab 2", value="tab2") + ), + rx.tabs.content( + rx.text("item on tab 1"), + value="tab1", + ), + rx.tabs.content( + rx.text("item on tab 2"), + value="tab2", + ), + default_value="tab1", + orientation="vertical", +) +``` + +```python demo +rx.tabs.root( + rx.tabs.list( + rx.tabs.trigger("Tab 1", value="tab1"), + rx.tabs.trigger("Tab 2", value="tab2") + ), + rx.tabs.content( + rx.text("item on tab 1"), + value="tab1", + ), + rx.tabs.content( + rx.text("item on tab 2"), + value="tab2", + ), + default_value="tab1", + orientation="horizontal", +) +``` + +### Value + +We use the `value` prop to specify the controlled value of the tab that we want to activate. This property should be used in conjunction with the `on_change` event argument. + +```python demo exec +class TabsState(rx.State): + """The app state.""" + + value = "tab1" + tab_selected = "" + + @rx.event + def change_value(self, val): + self.tab_selected = f"{val} clicked!" + self.value = val + + +def index() -> rx.Component: + return rx.container( + rx.flex( + rx.text(f"{TabsState.value} clicked !"), + rx.tabs.root( + rx.tabs.list( + rx.tabs.trigger("Tab 1", value="tab1"), + rx.tabs.trigger("Tab 2", value="tab2"), + ), + rx.tabs.content( + rx.text("items on tab 1"), + value="tab1", + ), + rx.tabs.content( + rx.text("items on tab 2"), + value="tab2", + ), + default_value="tab1", + value=TabsState.value, + on_change=lambda x: TabsState.change_value(x), + ), + direction="column", + spacing="2", + ), + padding="2em", + font_size="2em", + text_align="center", + ) +``` + +## Tablist + +The Tablist is used to list the respective tabs to the tab component + +## Tab Trigger + +This is the button that activates the tab's associated content. It is typically used in the `Tablist` + +## Styling + +### Value + +We use the `value` prop to assign a unique value that associates the trigger with content. + +```python demo +rx.tabs.root( + rx.tabs.list( + rx.tabs.trigger("Tab 1", value="tab1"), + rx.tabs.trigger("Tab 2", value="tab2"), + rx.tabs.trigger("Tab 3", value="tab3") + ), +) +``` + +### Disable + +We use the `disabled` prop to disable the tab. + +```python demo +rx.tabs.root( + rx.tabs.list( + rx.tabs.trigger("Tab 1", value="tab1"), + rx.tabs.trigger("Tab 2", value="tab2"), + rx.tabs.trigger("Tab 3", value="tab3", disabled=True) + ), +) +``` + +## Tabs Content + +Contains the content associated with each trigger. + +## Styling + +### Value + +We use the `value` prop to assign a unique value that associates the content with a trigger. + +```python +rx.tabs.root( + rx.tabs.list( + rx.tabs.trigger("Tab 1", value="tab1"), + rx.tabs.trigger("Tab 2", value="tab2") + ), + rx.tabs.content( + rx.text("item on tab 1"), + value="tab1", + ), + rx.tabs.content( + rx.text("item on tab 2"), + value="tab2", + ), + default_value="tab1", + orientation="vertical", +) +``` diff --git a/docs/library/dynamic-rendering/auto_scroll.md b/docs/library/dynamic-rendering/auto_scroll.md new file mode 100644 index 00000000000..4e6ade214fe --- /dev/null +++ b/docs/library/dynamic-rendering/auto_scroll.md @@ -0,0 +1,70 @@ +```python exec +import reflex as rx +``` + +# Auto Scroll + +The `rx.auto_scroll` component is a div that automatically scrolls to the bottom when new content is added. This is useful for chat interfaces, logs, or any container where new content is dynamically added and you want to ensure the most recent content is visible. + +## Basic Usage + +```python demo exec +import reflex as rx + +class AutoScrollState(rx.State): + messages: list[str] = ["Initial message"] + + def add_message(self): + self.messages.append(f"New message #{len(self.messages) + 1}") + +def auto_scroll_example(): + return rx.vstack( + rx.auto_scroll( + rx.foreach( + AutoScrollState.messages, + lambda message: rx.box( + message, + padding="0.5em", + border_bottom="1px solid #eee", + width="100%" + ) + ), + height="200px", + width="300px", + border="1px solid #ddd", + border_radius="md", + ), + rx.button("Add Message", on_click=AutoScrollState.add_message), + width="300px", + align_items="center", + ) +``` + +The `auto_scroll` component automatically scrolls to show the newest content when it's added. In this example, each time you click "Add Message", a new message is added to the list and the container automatically scrolls to display it. + +## When to Use Auto Scroll + +- **Chat applications**: Keep the chat window scrolled to the most recent messages. +- **Log viewers**: Automatically follow new log entries as they appear. +- **Feed interfaces**: Keep the newest content visible in dynamically updating feeds. + +## Props + +`rx.auto_scroll` is based on the `rx.div` component and inherits all of its props. By default, it sets `overflow="auto"` to enable scrolling. + +Some common props you might use with `auto_scroll`: + +- `height`: Set the height of the scrollable container. +- `width`: Set the width of the scrollable container. +- `padding`: Add padding inside the container. +- `border`: Add a border around the container. +- `border_radius`: Round the corners of the container. + +## How It Works + +The component tracks when new content is added and maintains the scroll position in two scenarios: + +1. When the user is already near the bottom of the content (within 50 pixels), it will scroll to the bottom when new content is added. +2. When the container didn't have a scrollbar before but does now (due to new content), it will automatically scroll to the bottom. + +This behavior ensures that users can scroll up to view older content without being forced back to the bottom, while still automatically following new content in most cases. diff --git a/docs/library/dynamic-rendering/cond.md b/docs/library/dynamic-rendering/cond.md new file mode 100644 index 00000000000..1fbd4dab8e8 --- /dev/null +++ b/docs/library/dynamic-rendering/cond.md @@ -0,0 +1,161 @@ +```python exec +import reflex as rx +``` + +# Cond + +This component is used to conditionally render components. + +The cond component takes a condition and two components. +If the condition is `True`, the first component is rendered, otherwise the second component is rendered. + +```python demo exec +class CondState(rx.State): + show: bool = True + + @rx.event + def change(self): + self.show = not (self.show) + + +def cond_example(): + return rx.vstack( + rx.button("Toggle", on_click=CondState.change), + rx.cond(CondState.show, rx.text("Text 1", color="blue"), rx.text("Text 2", color="red")), + ) +``` + +The second component is optional and can be omitted. +If it is omitted, nothing is rendered if the condition is `False`. + +```python demo exec +class CondOptionalState(rx.State): + show_optional: bool = True + + @rx.event + def toggle_optional(self): + self.show_optional = not (self.show_optional) + + +def cond_optional_example(): + return rx.vstack( + rx.button("Toggle", on_click=CondOptionalState.toggle_optional), + rx.cond(CondOptionalState.show_optional, rx.text("This text appears when condition is True", color="green")), + rx.text("This text is always visible", color="gray"), + ) +``` + +```md video https://youtube.com/embed/ITOZkzjtjUA?start=6040&end=6463 +# Video: Conditional Rendering +``` + +## Negation + +You can use the logical operator `~` to negate a condition. + +```python +rx.vstack( + rx.button("Toggle", on_click=CondState.change), + rx.cond(CondState.show, rx.text("Text 1", color="blue"), rx.text("Text 2", color="red")), + rx.cond(~CondState.show, rx.text("Text 1", color="blue"), rx.text("Text 2", color="red")), +) +``` + +## Multiple Conditions + +It is also possible to make up complex conditions using the `logical or` (|) and `logical and` (&) operators. + +Here we have an example using the var operators `>=`, `<=`, `&`. We define a condition that if a person has an age between 18 and 65, including those ages, they are able to work, otherwise they cannot. + +We could equally use the operator `|` to represent a `logical or` in one of our conditions. + +```python demo exec +import random + +class CondComplexState(rx.State): + age: int = 19 + + @rx.event + def change(self): + self.age = random.randint(0, 100) + + +def cond_complex_example(): + return rx.vstack( + rx.button("Toggle", on_click=CondComplexState.change), + rx.text(f"Age: {CondComplexState.age}"), + rx.cond( + (CondComplexState.age >= 18) & (CondComplexState.age <=65), + rx.text("You can work!", color="green"), + rx.text("You cannot work!", color="red"), + ), + ) + +``` + +## Nested Conditional + +We can also nest `cond` components within each other to create more complex logic. In python we can have an `if` statement that then has several `elif` statements before finishing with an `else`. This is also possible in reflex using nested `cond` components. In this example we check whether a number is positive, negative or zero. + +Here is the python logic using `if` statements: + +```python +number = 0 + +if number > 0: + print("Positive number") + +elif number == 0: + print('Zero') +else: + print('Negative number') +``` + +This reflex code that is logically identical: + +```python demo exec +import random + + +class NestedState(rx.State): + + num: int = 0 + + def change(self): + self.num = random.randint(-10, 10) + + +def cond_nested_example(): + return rx.vstack( + rx.button("Toggle", on_click=NestedState.change), + rx.cond( + NestedState.num > 0, + rx.text(f"{NestedState.num} is Positive!", color="orange"), + rx.cond( + NestedState.num == 0, + rx.text(f"{NestedState.num} is Zero!", color="blue"), + rx.text(f"{NestedState.num} is Negative!", color="red"), + ) + ), + ) + +``` + +Here is a more advanced example where we have three numbers and we are checking which of the three is the largest. If any two of them are equal then we return that `Some of the numbers are equal!`. + +The reflex code that follows is logically identical to doing the following in python: + +```python +a = 8 +b = 10 +c = 2 + +if((a>b and a>c) and (a != b and a != c)): + print(a, " is the largest!") +elif((b>a and b>c) and (b != a and b != c)): + print(b, " is the largest!") +elif((c>a and c>b) and (c != a and c != b)): + print(c, " is the largest!") +else: + print("Some of the numbers are equal!") +``` diff --git a/docs/library/dynamic-rendering/foreach.md b/docs/library/dynamic-rendering/foreach.md new file mode 100644 index 00000000000..7b107e86c3e --- /dev/null +++ b/docs/library/dynamic-rendering/foreach.md @@ -0,0 +1,199 @@ +```python exec +import reflex as rx +``` + +# Foreach + +The `rx.foreach` component takes an iterable(list, tuple or dict) and a function that renders each item in the list. +This is useful for dynamically rendering a list of items defined in a state. + +```md alert warning +# `rx.foreach` is specialized for usecases where the iterable is defined in a state var. + +For an iterable where the content doesn't change at runtime, i.e a constant, using a list/dict comprehension instead of `rx.foreach` is preferred. +``` + +```python demo exec +from typing import List +class ForeachState(rx.State): + color: List[str] = ["red", "green", "blue", "yellow", "orange", "purple"] + +def colored_box(color: str): + return rx.box( + rx.text(color), + bg=color + ) + +def foreach_example(): + return rx.grid( + rx.foreach( + ForeachState.color, + colored_box + ), + columns="2", + ) +``` + +The function can also take an index as a second argument. + +```python demo exec +def colored_box_index(color: str, index: int): + return rx.box( + rx.text(index), + bg=color + ) + +def foreach_example_index(): + return rx.grid( + rx.foreach( + ForeachState.color, + lambda color, index: colored_box_index(color, index) + ), + columns="2", + ) +``` + +Nested foreach components can be used to render nested lists. + +When indexing into a nested list, it's important to declare the list's type as Reflex requires it for type checking. +This ensures that any potential frontend JS errors are caught before the user can encounter them. + +```python demo exec +from typing import List + +class NestedForeachState(rx.State): + numbers: List[List[str]] = [["1", "2", "3"], ["4", "5", "6"], ["7", "8", "9"]] + +def display_row(row): + return rx.hstack( + rx.foreach( + row, + lambda item: rx.box( + item, + border="1px solid black", + padding="0.5em", + ) + ), + ) + +def nested_foreach_example(): + return rx.vstack( + rx.foreach( + NestedForeachState.numbers, + display_row + ) + ) +``` + +Below is a more complex example of foreach within a todo list. + +```python demo exec +from typing import List +class ListState(rx.State): + items: List[str] = ["Write Code", "Sleep", "Have Fun"] + new_item: str + + @rx.event + def set_new_item(self, new_item: str): + self.new_item = new_item + + @rx.event + def add_item(self): + self.items += [self.new_item] + + def finish_item(self, item: str): + self.items = [i for i in self.items if i != item] + +def get_item(item): + return rx.list.item( + rx.hstack( + rx.button( + on_click=lambda: ListState.finish_item(item), + height="1.5em", + background_color="white", + border="1px solid blue", + ), + rx.text(item, font_size="1.25em"), + ), + ) + +def todo_example(): + return rx.vstack( + rx.heading("Todos"), + rx.input(on_blur=ListState.set_new_item, placeholder="Add a todo...", bg = "white"), + rx.button("Add", on_click=ListState.add_item, bg = "white"), + rx.divider(), + rx.list.ordered( + rx.foreach( + ListState.items, + get_item, + ), + ), + bg = "#ededed", + padding = "1em", + border_radius = "0.5em", + shadow = "lg" + ) +``` + +## Dictionaries + +Items in a dictionary can be accessed as list of key-value pairs. +Using the color example, we can slightly modify the code to use dicts as shown below. + +```python demo exec +from typing import List +class SimpleDictForeachState(rx.State): + color_chart: dict[int, str] = { + 1 : "blue", + 2: "red", + 3: "green" + } + +def display_color(color: List): + # color is presented as a list key-value pair([1, "blue"],[2, "red"], [3, "green"]) + return rx.box(rx.text(color[0]), bg=color[1]) + + +def foreach_dict_example(): + return rx.grid( + rx.foreach( + SimpleDictForeachState.color_chart, + display_color + ), + columns = "2" + ) +``` + +Now let's show a more complex example with dicts using the color example. +Assuming we want to display a dictionary of secondary colors as keys and their constituent primary colors as values, we can modify the code as below: + +```python demo exec +from typing import List, Dict +class ComplexDictForeachState(rx.State): + color_chart: Dict[str, List[str]] = { + "purple": ["red", "blue"], + "orange": ["yellow", "red"], + "green": ["blue", "yellow"] + } + +def display_colors(color: List): + return rx.vstack( + rx.text(color[0], color=color[0]), + rx.hstack( + rx.foreach( + color[1], lambda x: rx.box(rx.text(x, color="black"), bg=x) + ) + + ) + ) + +def foreach_complex_dict_example(): + return rx.grid( + rx.foreach( + ComplexDictForeachState.color_chart, + display_colors + ), + columns="2" + ) +``` diff --git a/docs/library/dynamic-rendering/match.md b/docs/library/dynamic-rendering/match.md new file mode 100644 index 00000000000..d258d68d64b --- /dev/null +++ b/docs/library/dynamic-rendering/match.md @@ -0,0 +1,295 @@ +```python exec +import reflex as rx +``` + +# Match + +The `rx.match` feature in Reflex serves as a powerful alternative to `rx.cond` for handling conditional statements. +While `rx.cond` excels at conditionally rendering two components based on a single condition, +`rx.match` extends this functionality by allowing developers to handle multiple conditions and their associated components. +This feature is especially valuable when dealing with intricate conditional logic or nested scenarios, +where the limitations of `rx.cond` might lead to less readable and more complex code. + +With `rx.match`, developers can not only handle multiple conditions but also perform structural pattern matching, +making it a versatile tool for managing various scenarios in Reflex applications. + +## Basic Usage + +The `rx.match` function provides a clear and expressive syntax for handling multiple +conditions and their corresponding components: + +```python +rx.match( + condition, + (case_1, component_1), + (case_2, component_2), + ... + default_component, +) + +``` + +- `condition`: The value to match against. +- `(case_i, component_i)`: A Tuple of matching cases and their corresponding return components. +- `default_component`: A special case for the default component when the condition isn't matched by any of the match cases. + +Example + +```python demo exec +from typing import List + +import reflex as rx + + +class MatchState(rx.State): + cat_breed: str = "" + animal_options: List[str] = ["persian", "siamese", "maine coon", "ragdoll", "pug", "corgi"] + + @rx.event + def set_cat_breed(self, breed: str): + self.cat_breed = breed + +def match_demo(): + return rx.flex( + rx.match( + MatchState.cat_breed, + ("persian", rx.text("Persian cat selected.")), + ("siamese", rx.text("Siamese cat selected.")), + ("maine coon", rx.text("Maine Coon cat selected.")), + ("ragdoll", rx.text("Ragdoll cat selected.")), + rx.text("Unknown cat breed selected.") + ), + rx.select.root( + rx.select.trigger(), + rx.select.content( + rx.select.group( + rx.foreach(MatchState.animal_options, lambda x: rx.select.item(x, value=x)) + ), + ), + value=MatchState.cat_breed, + on_change=MatchState.set_cat_breed, + + ), + direction= "column", + gap= "2" + ) +``` + +## Default Case + +The default case in `rx.match` serves as a universal handler for scenarios where none of +the specified match cases aligns with the given match condition. Here are key considerations +when working with the default case: + +- **Placement in the Match Function**: The default case must be the last non-tuple argument in the `rx.match` component. + All match cases should be enclosed in tuples; any non-tuple value is automatically treated as the default case. For example: + +```python +rx.match( + MatchState.cat_breed, + ("persian", rx.text("persian cat selected")), + rx.text("Unknown cat breed selected."), + ("siamese", rx.text("siamese cat selected")), + ) +``` + +The above code snippet will result in an error due to the misplaced default case. + +- **Single Default Case**: Only one default case is allowed in the `rx.match` component. + Attempting to specify multiple default cases will lead to an error. For instance: + +```python +rx.match( + MatchState.cat_breed, + ("persian", rx.text("persian cat selected")), + ("siamese", rx.text("siamese cat selected")), + rx.text("Unknown cat breed selected."), + rx.text("Another unknown cat breed selected.") + ) +``` + +- **Optional Default Case for Component Return Values**: If the match cases in a `rx.match` component + return components, the default case can be optional. In this scenario, if a default case is + not provided, `rx.fragment` will be implicitly assigned as the default. For example: + +```python +rx.match( + MatchState.cat_breed, + ("persian", rx.text("persian cat selected")), + ("siamese", rx.text("siamese cat selected")), + ) +``` + +In this case, `rx.fragment` is the default case. However, not providing a default case for non-component +return values will result in an error: + +```python +rx.match( + MatchState.cat_breed, + ("persian", "persian cat selected"), + ("siamese", "siamese cat selected"), + ) +``` + +The above code snippet will result in an error as a default case must be explicitly +provided in this scenario. + +## Handling Multiple Conditions + +`rx.match` excels in efficiently managing multiple conditions and their corresponding components, +providing a cleaner and more readable alternative compared to nested `rx.cond` structures. + +Consider the following example: + +```python demo exec +from typing import List + +import reflex as rx + + +class MultiMatchState(rx.State): + animal_breed: str = "" + animal_options: List[str] = ["persian", "siamese", "maine coon", "pug", "corgi", "mustang", "rahvan", "football", "golf"] + + @rx.event + def set_animal_breed(self, breed: str): + self.animal_breed = breed + +def multi_match_demo(): + return rx.flex( + rx.match( + MultiMatchState.animal_breed, + ("persian", "siamese", "maine coon", rx.text("Breeds of cats.")), + ("pug", "corgi", rx.text("Breeds of dogs.")), + ("mustang", "rahvan", rx.text("Breeds of horses.")), + rx.text("Unknown animal breed") + ), + rx.select.root( + rx.select.trigger(), + rx.select.content( + rx.select.group( + rx.foreach(MultiMatchState.animal_options, lambda x: rx.select.item(x, value=x)) + ), + ), + value=MultiMatchState.animal_breed, + on_change=MultiMatchState.set_animal_breed, + + ), + direction= "column", + gap= "2" + ) +``` + +In a match case tuple, you can specify multiple conditions. The last value of the match case +tuple is automatically considered as the return value. It's important to note that a match case +tuple should contain a minimum of two elements: a match case and a return value. +The following code snippet will result in an error: + +```python +rx.match( + MatchState.cat_breed, + ("persian",), + ("maine coon", rx.text("Maine Coon cat selected")), + ) +``` + +## Usage as Props + +Similar to `rx.cond`, `rx.match` can be used as prop values, allowing dynamic behavior for UI components: + +```python demo exec +import reflex as rx + + +class MatchPropState(rx.State): + value: int = 0 + + @rx.event + def incr(self): + self.value += 1 + + @rx.event + def decr(self): + self.value -= 1 + + +def match_prop_demo_(): + return rx.flex( + rx.button("decrement", on_click=MatchPropState.decr, background_color="red"), + rx.badge( + MatchPropState.value, + color_scheme= rx.match( + MatchPropState.value, + (1, "red"), + (2, "blue"), + (6, "purple"), + (10, "orange"), + "green" + ), + size="2", + ), + rx.button("increment", on_click=MatchPropState.incr), + align_items="center", + direction= "row", + gap= "3" + ) +``` + +In the example above, the background color property of the box component containing `State.value` changes to red when +`state.value` is 1, blue when its 5, green when its 5, orange when its 15 and black for any other value. + +The example below also shows handling multiple conditions with the match component as props. + +```python demo exec +import reflex as rx + + +class MatchMultiPropState(rx.State): + value: int = 0 + + @rx.event + def incr(self): + self.value += 1 + + @rx.event + def decr(self): + self.value -= 1 + + +def match_multi_prop_demo_(): + return rx.flex( + rx.button("decrement", on_click=MatchMultiPropState.decr, background_color="red"), + rx.badge( + MatchMultiPropState.value, + color_scheme= rx.match( + MatchMultiPropState.value, + (1, 3, 9, "red"), + (2, 4, 5, "blue"), + (6, 8, 12, "purple"), + (10, 15, 20, 25, "orange"), + "green" + ), + size="2", + ), + rx.button("increment", on_click=MatchMultiPropState.incr), + align_items="center", + direction= "row", + gap= "3" + ) +``` + +```md alert warning +# Usage with Structural Pattern Matching + +The `rx.match` component is designed for structural pattern matching. If the value of your match condition evaluates to a boolean (True or False), it is recommended to use `rx.cond` instead. + +Consider the following example, which is more suitable for `rx.cond`:\* +``` + +```python +rx.cond( + MatchPropState.value == 10, + "true value", + "false value" +) +``` diff --git a/docs/library/forms/button.md b/docs/library/forms/button.md new file mode 100644 index 00000000000..7793fe81e99 --- /dev/null +++ b/docs/library/forms/button.md @@ -0,0 +1,63 @@ +--- +components: + - rx.button + +Button: | + lambda **props: rx.button("Basic Button", **props) +--- + +```python exec +import reflex as rx +``` + +# Button + +Buttons are essential elements in your application's user interface that users can click to trigger events. + +## Basic Example + +The `on_click` trigger is called when the button is clicked. + +```python demo exec +class CountState(rx.State): + count: int = 0 + + @rx.event + def increment(self): + self.count += 1 + + @rx.event + def decrement(self): + self.count -= 1 + +def counter(): + return rx.flex( + rx.button( + "Decrement", + color_scheme="red", + on_click=CountState.decrement, + ), + rx.heading(CountState.count), + rx.button( + "Increment", + color_scheme="grass", + on_click=CountState.increment, + ), + spacing="3", + ) +``` + +### Loading and Disabled + +The `loading` prop is used to indicate that the action triggered by the button is currently in progress. When set to `True`, the button displays a loading spinner, providing visual feedback to the user that the action is being processed. This also prevents multiple clicks while the button is in the loading state. By default, `loading` is set to `False`. + +The `disabled` prop also prevents the button from being but does not provide a spinner. + +```python demo +rx.flex( + rx.button("Regular"), + rx.button("Loading", loading=True), + rx.button("Disabled", disabled=True), + spacing="2", +) +``` diff --git a/docs/library/forms/checkbox.md b/docs/library/forms/checkbox.md new file mode 100644 index 00000000000..ef00cff7026 --- /dev/null +++ b/docs/library/forms/checkbox.md @@ -0,0 +1,73 @@ +--- +components: + - rx.checkbox + +HighLevelCheckbox: | + lambda **props: rx.checkbox("Basic Checkbox", **props) +--- + +```python exec +import reflex as rx +``` + +# Checkbox + +## Basic Example + +The `on_change` trigger is called when the `checkbox` is clicked. + +```python demo exec +class CheckboxState(rx.State): + checked: bool = False + + @rx.event + def set_checked(self, value: bool): + self.checked = value + +def checkbox_example(): + return rx.vstack( + rx.heading(CheckboxState.checked), + rx.checkbox(on_change=CheckboxState.set_checked), + ) +``` + +The `input` prop is used to set the `checkbox` as a controlled component. + +```python demo exec +class FormCheckboxState(rx.State): + form_data: dict = {} + + @rx.event + def handle_submit(self, form_data: dict): + """Handle the form submit.""" + print(form_data) + self.form_data = form_data + + +def form_checkbox_example(): + return rx.card( + rx.vstack( + rx.heading("Example Form"), + rx.form.root( + rx.hstack( + rx.checkbox( + name="checkbox", + label="Accept terms and conditions", + ), + rx.button("Submit", type="submit"), + width="100%", + ), + on_submit=FormCheckboxState.handle_submit, + reset_on_submit=True, + ), + rx.divider(), + rx.hstack( + rx.heading("Results:"), + rx.badge(FormCheckboxState.form_data.to_string()), + ), + align_items="left", + width="100%", + ), + width="50%", + ) +``` diff --git a/docs/library/forms/form-ll.md b/docs/library/forms/form-ll.md new file mode 100644 index 00000000000..8e5dfdd6f4b --- /dev/null +++ b/docs/library/forms/form-ll.md @@ -0,0 +1,464 @@ +--- +components: + - rx.form.root + - rx.form.field + - rx.form.control + - rx.form.label + - rx.form.message + - rx.form.submit + +FormRoot: | + lambda **props: rx.form.root( + rx.form.field( + rx.flex( + rx.form.label("Email"), + rx.form.control( + rx.input( + placeholder="Email Address", + # type attribute is required for "typeMismatch" validation + type="email", + ), + as_child=True, + ), + rx.form.message("Please enter a valid email"), + rx.form.submit( + rx.button("Submit"), + as_child=True, + ), + direction="column", + spacing="2", + align="stretch", + ), + name="email", + ), + **props, + ) + +FormField: | + lambda **props: rx.form.root( + rx.form.field( + rx.flex( + rx.form.label("Email"), + rx.form.control( + rx.input( + placeholder="Email Address", + # type attribute is required for "typeMismatch" validation + type="email", + ), + as_child=True, + ), + rx.form.message("Please enter a valid email", match="typeMismatch"), + rx.form.submit( + rx.button("Submit"), + as_child=True, + ), + direction="column", + spacing="2", + align="stretch", + ), + **props, + ), + reset_on_submit=True, + ) + +FormMessage: | + lambda **props: rx.form.root( + rx.form.field( + rx.flex( + rx.form.label("Email"), + rx.form.control( + rx.input( + placeholder="Email Address", + # type attribute is required for "typeMismatch" validation + type="email", + ), + as_child=True, + ), + rx.form.message("Please enter a valid email", **props,), + rx.form.submit( + rx.button("Submit"), + as_child=True, + ), + direction="column", + spacing="2", + align="stretch", + ), + name="email", + ), + on_submit=lambda form_data: rx.window_alert(form_data.to_string()), + reset_on_submit=True, + ) +--- + +# Form + +```python exec +import reflex as rx +import reflex.components.radix.primitives as rdxp +``` + +```md warning info +# Low Level Form is Experimental + +Please use the High Level Form for now for production. +``` + +Forms are used to collect information from your users. Forms group the inputs and submit them together. + +## Basic Example + +Here is an example of a form collecting an email address, with built-in validation on the email. If email entered is invalid, the form cannot be submitted. Note that the `form.submit` button is not automatically disabled. It is still clickable, but does not submit the form data. After successful submission, an alert window shows up and the form is cleared. There are a few `flex` containers used in the example to control the layout of the form components. + +```python demo +rx.form.root( + rx.form.field( + rx.flex( + rx.form.label("Email"), + rx.form.control( + rx.input( + placeholder="Email Address", + # type attribute is required for "typeMismatch" validation + type="email", + ), + as_child=True, + ), + rx.form.message("Please enter a valid email", match="typeMismatch"), + rx.form.submit( + rx.button("Submit"), + as_child=True, + ), + direction="column", + spacing="2", + align="stretch", + ), + name="email", + ), + on_submit=lambda form_data: rx.window_alert(form_data.to_string()), + reset_on_submit=True, +) +``` + +In this example, the `rx.input` has an attribute `type="email"` and the `form.message` has the attribute `match="typeMismatch"`. Those are required for the form to validate the input by its type. The prop `as_child="True"` is required when using other components to construct a Form component. This example has used `rx.input` to construct the Form Control and `button` the Form Submit. + +## Form Anatomy + +```python eval +rx._x.code_block( + """form.root( + form.field( + form.label(...), + form.control(...), + form.message(...), + ), + form.submit(...), +)""", + language="python", +) +``` + +A Form Root (`form.root`) contains all the parts of a form. The Form Field (`form.field`), Form Submit (`form.submit`), etc should all be inside a Form Root. A Form Field can contain a Form Label (`form.label`), a Form Control (`form.control`), and a Form Message (`form.message`). A Form Label is a label element. A Form Control is where the user enters the input or makes selections. By default, the Form Control is a input. Using other form components to construct the Form Control is supported. To do that, set the prop `as_child=True` on the Form Control. + +```md alert info +The current version of Radix Forms does not support composing **Form Control** with other Radix form primitives such as **Checkbox**, **Select**, etc. +``` + +The Form Message is a validation message which is automatically wired (functionality and accessibility). When the Form Control determines the input is invalid, the Form Message is shown. The `match` prop is to enable [client side validation](#client-side-validation). To perform [server side validation](#server-side-validation), **both** the `force_match` prop of the Form Control and the `server_invalid` prop of the Form Field are set together. + +The Form Submit is by default a button that submits the form. To use another button component as a Form Submit, include that button as a child inside `form.submit` and set the prop `as_child=True`. + +The `on_submit` prop of the Form Root accepts an event handler. It is called with the submitted form data dictionary. To clear the form after submission, set the `reset_on_submit=True` prop. + +## Data Submission + +As previously mentioned, the various pieces of data in the form are submitted together as a dictionary. The form control or the input components must have the `name` attribute. This `name` is the key to get the value from the form data dictionary. If no validation is needed, the form type components such as Checkbox, Radio Groups, TextArea can be included directly under the Form Root instead of inside a Form Control. + +```python demo exec +import reflex as rx +import reflex.components.radix.primitives as rdxp + +class RadixFormSubmissionState(rx.State): + form_data: dict + + @rx.event + def handle_submit(self, form_data: dict): + """Handle the form submit.""" + self.form_data = form_data + + @rx.var + def form_data_keys(self) -> list: + return list(self.form_data.keys()) + + @rx.var + def form_data_values(self) -> list: + return list(self.form_data.values()) + + +def radix_form_submission_example(): + return rx.flex( + rx.form.root( + rx.flex( + rx.flex( + rx.checkbox( + default_checked=True, + name="box1", + ), + rx.text("box1 checkbox"), + direction="row", + spacing="2", + align="center", + ), + rx.radio.root( + rx.flex( + rx.radio.item(value="1"), + "1", + direction="row", + align="center", + spacing="2", + ), + rx.flex( + rx.radio.item(value="2"), + "2", + direction="row", + align="center", + spacing="2", + ), + rx.flex( + rx.radio.item(value="3"), + "3", + direction="row", + align="center", + spacing="2", + ), + default_value="1", + name="box2", + ), + rx.input( + placeholder="box3 textfield input", + name="box3", + ), + rx.select.root( + rx.select.trigger( + placeholder="box4 select", + ), + rx.select.content( + rx.select.group( + rx.select.item( + "Orange", + value="orange" + ), + rx.select.item( + "Apple", + value="apple" + ), + ), + ), + name="box4", + ), + rx.flex( + rx.switch( + default_checked=True, + name="box5", + ), + "box5 switch", + spacing="2", + align="center", + direction="row", + ), + rx.flex( + rx.slider( + default_value=[40], + width="100%", + name="box6", + ), + "box6 slider", + direction="row", + spacing="2", + align="center", + ), + rx.text_area( + placeholder="Enter for box7 textarea", + name="box7", + ), + rx.form.submit( + rx.button("Submit"), + as_child=True, + ), + direction="column", + spacing="4", + ), + on_submit=RadixFormSubmissionState.handle_submit, + ), + rx.divider(size="4"), + rx.text( + "Results", + weight="bold", + ), + rx.foreach(RadixFormSubmissionState.form_data_keys, + lambda key, idx: rx.text(key, " : ", RadixFormSubmissionState.form_data_values[idx]) + ), + direction="column", + spacing="4", + ) +``` + +## Validation + +Server side validation is done through **Computed Vars** on the State. The **Var** should return a boolean flag indicating when input is invalid. Set that **Var** on both the `server_invalid` prop of `form.field` and the `force_match` prop of `form.message`. There is an example how to do that in the [Final Example](#final-example). + +## Final Example + +The final example shows a form that collects username and email during sign-up and validates them using server side validation. When server side validation fails, messages are displayed in red to show what is not accepted in the form, and the submit button is disabled. After submission, the collected form data is displayed in texts below the form and the form is cleared. + +```python demo exec +import re +import reflex as rx +import reflex.components.radix.primitives as rdxp + +class RadixFormState(rx.State): + # These track the user input real time for validation + user_entered_username: str + user_entered_email: str + + # These are the submitted data + username: str + email: str + + mock_username_db: list[str] = ["reflex", "admin"] + + # Add explicit setters + def set_user_entered_username(self, value: str): + self.user_entered_username = value + + def set_user_entered_email(self, value: str): + self.user_entered_email = value + + def set_username(self, value: str): + self.username = value + + def set_email(self, value: str): + self.email = value + + @rx.var + def invalid_email(self) -> bool: + return not re.match(r"[^@]+@[^@]+\.[^@]+", self.user_entered_email) + + @rx.var + def username_empty(self) -> bool: + return not self.user_entered_username.strip() + + @rx.var + def username_is_taken(self) -> bool: + return self.user_entered_username in self.mock_username_db + + @rx.var + def input_invalid(self) -> bool: + return self.invalid_email or self.username_is_taken or self.username_empty + + @rx.event + def handle_submit(self, form_data: dict): + """Handle the form submit.""" + self.username = form_data.get("username") + self.email = form_data.get("email") + +def radix_form_example(): + return rx.flex( + rx.form.root( + rx.flex( + rx.form.field( + rx.flex( + rx.form.label("Username"), + rx.form.control( + rx.input( + placeholder="Username", + # workaround: `name` seems to be required when on_change is set + on_change=RadixFormState.set_user_entered_username, + name="username", + ), + as_child=True, + ), + # server side validation message can be displayed inside a rx.cond + rx.cond( + RadixFormState.username_empty, + rx.form.message( + "Username cannot be empty", + color="var(--red-11)", + ), + ), + # server side validation message can be displayed by `force_match` prop + rx.form.message( + "Username already taken", + # this is a workaround: + # `force_match` does not work without `match` + # This case does not want client side validation + # and intentionally not set `required` on the input + # so "valueMissing" is always false + match="valueMissing", + force_match=RadixFormState.username_is_taken, + color="var(--red-11)", + ), + direction="column", + spacing="2", + align="stretch", + ), + name="username", + server_invalid=RadixFormState.username_is_taken, + ), + rx.form.field( + rx.flex( + rx.form.label("Email"), + rx.form.control( + rx.input( + placeholder="Email Address", + on_change=RadixFormState.set_user_entered_email, + name="email", + ), + as_child=True, + ), + rx.form.message( + "A valid Email is required", + match="valueMissing", + force_match=RadixFormState.invalid_email, + color="var(--red-11)", + ), + direction="column", + spacing="2", + align="stretch", + ), + name="email", + server_invalid=RadixFormState.invalid_email, + ), + rx.form.submit( + rx.button( + "Submit", + disabled=RadixFormState.input_invalid, + ), + as_child=True, + ), + direction="column", + spacing="4", + width="25em", + ), + on_submit=RadixFormState.handle_submit, + reset_on_submit=True, + ), + rx.divider(size="4"), + rx.text( + "Username submitted: ", + rx.text( + RadixFormState.username, + weight="bold", + color="var(--accent-11)", + ), + ), + rx.text( + "Email submitted: ", + rx.text( + RadixFormState.email, + weight="bold", + color="var(--accent-11)", + ), + ), + direction="column", + spacing="4", + ) +``` diff --git a/docs/library/forms/form.md b/docs/library/forms/form.md new file mode 100644 index 00000000000..bc771b7559f --- /dev/null +++ b/docs/library/forms/form.md @@ -0,0 +1,239 @@ +--- +components: + - rx.form + - rx.form.root + - rx.form.field + - rx.form.control + - rx.form.label + - rx.form.message + - rx.form.submit + +FormRoot: | + lambda **props: rx.form.root( + rx.form.field( + rx.flex( + rx.form.label("Email"), + rx.form.control( + rx.input( + placeholder="Email Address", + # type attribute is required for "typeMismatch" validation + type="email", + ), + as_child=True, + ), + rx.form.message("Please enter a valid email"), + rx.form.submit( + rx.button("Submit"), + as_child=True, + ), + direction="column", + spacing="2", + align="stretch", + ), + name="email", + ), + **props, + ) + +FormField: | + lambda **props: rx.form.root( + rx.form.field( + rx.flex( + rx.form.label("Email"), + rx.form.control( + rx.input( + placeholder="Email Address", + # type attribute is required for "typeMismatch" validation + type="email", + ), + as_child=True, + ), + rx.form.message("Please enter a valid email", match="typeMismatch"), + rx.form.submit( + rx.button("Submit"), + as_child=True, + ), + direction="column", + spacing="2", + align="stretch", + ), + **props, + ), + reset_on_submit=True, + ) + +FormLabel: | + lambda **props: rx.form.root( + rx.form.field( + rx.flex( + rx.form.label("Email", **props,), + rx.form.control( + rx.input( + placeholder="Email Address", + # type attribute is required for "typeMismatch" validation + type="email", + ), + as_child=True, + ), + rx.form.message("Please enter a valid email", match="typeMismatch"), + rx.form.submit( + rx.button("Submit"), + as_child=True, + ), + direction="column", + spacing="2", + align="stretch", + ), + ), + reset_on_submit=True, + ) + +FormMessage: | + lambda **props: rx.form.root( + rx.form.field( + rx.flex( + rx.form.label("Email"), + rx.form.control( + rx.input( + placeholder="Email Address", + # type attribute is required for "typeMismatch" validation + type="email", + ), + as_child=True, + ), + rx.form.message("Please enter a valid email", **props,), + rx.form.submit( + rx.button("Submit"), + as_child=True, + ), + direction="column", + spacing="2", + align="stretch", + ), + name="email", + ), + on_submit=lambda form_data: rx.window_alert(form_data.to_string()), + reset_on_submit=True, + ) +--- + +```python exec +import reflex as rx +``` + +# Form + +Forms are used to collect user input. The `rx.form` component is used to group inputs and submit them together. + +The form component's children can be form controls such as `rx.input`, `rx.checkbox`, `rx.slider`, `rx.textarea`, `rx.radio_group`, `rx.select` or `rx.switch`. The controls should have a `name` attribute that is used to identify the control in the form data. The `on_submit` event trigger submits the form data as a dictionary to the `handle_submit` event handler. + +The form is submitted when the user clicks the submit button or presses enter on the form controls. + +```python demo exec +class FormState(rx.State): + form_data: dict = {} + + @rx.event + def handle_submit(self, form_data: dict): + """Handle the form submit.""" + self.form_data = form_data + + +def form_example(): + return rx.vstack( + rx.form( + rx.vstack( + rx.input(placeholder="First Name", name="first_name"), + rx.input(placeholder="Last Name", name="last_name"), + rx.hstack( + rx.checkbox("Checked", name="check"), + rx.switch("Switched", name="switch"), + ), + rx.button("Submit", type="submit"), + ), + on_submit=FormState.handle_submit, + reset_on_submit=True, + ), + rx.divider(), + rx.heading("Results"), + rx.text(FormState.form_data.to_string()), + ) +``` + +```md alert warning +# When using the form you must include a button or input with `type='submit'`. +``` + +```md alert info +# Using `name` vs `id`. + +When using the `name` attribute in form controls like `rx.switch`, `rx.radio_group`, and `rx.checkbox`, these controls will only be included in the form data if their values are set (e.g., if the checkbox is checked, the switch is toggled, or a radio option is selected). + +If you need these controls to be passed in the form data even when their values are not set, you can use the `id` attribute instead of name. The id attribute ensures that the control is always included in the submitted form data, regardless of whether its value is set or not. +``` + +```md video https://youtube.com/embed/ITOZkzjtjUA?start=5287&end=6040 +# Video: Forms +``` + +## Dynamic Forms + +Forms can be dynamically created by iterating through state vars using `rx.foreach`. + +This example allows the user to add new fields to the form prior to submit, and all +fields will be included in the form data passed to the `handle_submit` function. + +```python demo exec +class DynamicFormState(rx.State): + form_data: dict = {} + form_fields: list[str] = ["first_name", "last_name", "email"] + + @rx.var(cache=True) + def form_field_placeholders(self) -> list[str]: + return [ + " ".join(w.capitalize() for w in field.split("_")) + for field in self.form_fields + ] + + @rx.event + def add_field(self, form_data: dict): + new_field = form_data.get("new_field") + if not new_field: + return + field_name = new_field.strip().lower().replace(" ", "_") + self.form_fields.append(field_name) + + @rx.event + def handle_submit(self, form_data: dict): + self.form_data = form_data + + +def dynamic_form(): + return rx.vstack( + rx.form( + rx.vstack( + rx.foreach( + DynamicFormState.form_fields, + lambda field, idx: rx.input( + placeholder=DynamicFormState.form_field_placeholders[idx], + name=field, + ), + ), + rx.button("Submit", type="submit"), + ), + on_submit=DynamicFormState.handle_submit, + reset_on_submit=True, + ), + rx.form( + rx.hstack( + rx.input(placeholder="New Field", name="new_field"), + rx.button("+", type="submit"), + ), + on_submit=DynamicFormState.add_field, + reset_on_submit=True, + ), + rx.divider(), + rx.heading("Results"), + rx.text(DynamicFormState.form_data.to_string()), + ) +``` diff --git a/docs/library/forms/input-ll.md b/docs/library/forms/input-ll.md new file mode 100644 index 00000000000..c62c5486020 --- /dev/null +++ b/docs/library/forms/input-ll.md @@ -0,0 +1,128 @@ +--- +components: + - rx.input + - rx.input.slot +--- + +```python exec +import reflex as rx +``` + +# Input + +A text field is an input field that users can type into. This component uses Radix's [text field](https://www.radix-ui.com/themes/docs/components/text-field) component. + + +## Overview + +The TextField component is used to capture user input and can include an optional slot for buttons and icons. It is based on the
    element and supports common margin props. + +## Basic Example + +```python demo +rx.input( + rx.input.slot( + rx.icon(tag="search"), + + ), + placeholder="Search here...", +) +``` + +## Stateful Example with Blur Event + +```python demo exec +class TextfieldBlur1(rx.State): + text: str = "Hello World!" + + @rx.event + def set_text(self, text: str): + self.text = text + +def blur_example1(): + return rx.vstack( + rx.heading(TextfieldBlur1.text), + rx.input( + rx.input.slot( + rx.icon(tag="search"), + ), + placeholder="Search here...", + on_blur=TextfieldBlur1.set_text, + ) + ) +``` + +## Controlled Example + +```python demo exec +class TextfieldControlled1(rx.State): + text: str = "Hello World!" + + @rx.event + def set_text(self, text: str): + self.text = text + +def controlled_example1(): + return rx.vstack( + rx.heading(TextfieldControlled1.text), + rx.input( + rx.input.slot( + rx.icon(tag="search"), + ), + placeholder="Search here...", + value=TextfieldControlled1.text, + on_change=TextfieldControlled1.set_text, + ), + ) +``` + +# Real World Example + +```python demo exec + +def song(title, initials: str, genre: str): + return rx.card(rx.flex( + rx.flex( + rx.avatar(fallback=initials), + rx.flex( + rx.text(title, size="2", weight="bold"), + rx.text(genre, size="1", color_scheme="gray"), + direction="column", + spacing="1", + ), + direction="row", + align_items="left", + spacing="1", + ), + rx.flex( + rx.icon(tag="chevron_right"), + align_items="center", + ), + justify="between", + )) + +def search(): + return rx.card( + rx.flex( + rx.input( + rx.input.slot( + rx.icon(tag="search"), + ), + placeholder="Search songs...", + ), + rx.flex( + song("The Less I Know", "T", "Rock"), + song("Breathe Deeper", "ZB", "Rock"), + song("Let It Happen", "TF", "Rock"), + song("Borderline", "ZB", "Pop"), + song("Lost In Yesterday", "TO", "Rock"), + song("Is It True", "TO", "Rock"), + direction="column", + spacing="1", + ), + direction="column", + spacing="3", + ), + style={"maxWidth": 500}, +) +``` diff --git a/docs/library/forms/input.md b/docs/library/forms/input.md new file mode 100644 index 00000000000..bd41b0a6688 --- /dev/null +++ b/docs/library/forms/input.md @@ -0,0 +1,164 @@ +--- +components: + - rx.input + - rx.input.slot + +Input: | + lambda **props: rx.input(placeholder="Search the docs", **props) + +TextFieldSlot: | + lambda **props: rx.input( + rx.input.slot( + rx.icon(tag="search", height="16", width="16"), + **props, + ), + placeholder="Search the docs", + ) +--- + +```python exec +import reflex as rx +from pcweb.pages.docs import library +``` + +# Input + +The `input` component is an input field that users can type into. + +```md video https://youtube.com/embed/ITOZkzjtjUA?start=1517&end=1869 +# Video: Input +``` + +## Basic Example + +The `on_blur` event handler is called when focus has left the `input` for example, it’s called when the user clicks outside of a focused text input. + +```python demo exec +class TextfieldBlur(rx.State): + text: str = "Hello World!" + + @rx.event + def set_text(self, value: str): + self.text = value + +def blur_example(): + return rx.vstack( + rx.heading(TextfieldBlur.text), + rx.input( + placeholder="Search here...", + on_blur=TextfieldBlur.set_text, + ), + ) +``` + +The `on_change` event handler is called when the `value` of `input` has changed. + +```python demo exec +class TextfieldControlled(rx.State): + text: str = "Hello World!" + + @rx.event + def set_text(self, value: str): + self.text = value + +def controlled_example(): + return rx.vstack( + rx.heading(TextfieldControlled.text), + rx.input( + placeholder="Search here...", + value=TextfieldControlled.text, + on_change=TextfieldControlled.set_text, + ), + ) +``` + +Behind the scenes, the input component is implemented as a debounced input to avoid sending individual state updates per character to the backend while the user is still typing. This allows a state variable to directly control the `value` prop from the backend without the user experiencing input lag. + +## Input Types + +The `type` prop controls how the input is rendered (e.g. plain text, password, file picker). + +It accepts the same values as the native HTML `` attribute, such as: + +- `"text"` (default) +- `"password"` +- `"email"` +- `"number"` +- `"file"` +- `"checkbox"` +- `"radio"` +- `"date"` +- `"time"` +- `"url"` +- `"color"` + +and several others. See the [MDN reference](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#input_types) for the full list. + +```python demo +rx.vstack( + rx.input(placeholder="Username", type="text"), + rx.input(placeholder="Password", type="password"), + rx.input(type="date"), +) +``` + +## Submitting a form using input + +The `name` prop is needed to submit with its owning form as part of a name/value pair. + +When the `required` prop is `True`, it indicates that the user must input text before the owning form can be submitted. + +The `type` is set here to `password`. The element is presented as a one-line plain text editor control in which the text is obscured so that it cannot be read. The `type` prop can take any value of `email`, `file`, `password`, `text` and several others. Learn more [here](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input). + +```python demo exec +class FormInputState(rx.State): + form_data: dict = {} + + @rx.event + def handle_submit(self, form_data: dict): + """Handle the form submit.""" + self.form_data = form_data + + +def form_input1(): + return rx.card( + rx.vstack( + rx.heading("Example Form"), + rx.form.root( + rx.hstack( + rx.input( + name="input", + placeholder="Enter text...", + type="text", + required=True, + ), + rx.button("Submit", type="submit"), + width="100%", + ), + on_submit=FormInputState.handle_submit, + reset_on_submit=True, + ), + rx.divider(), + rx.hstack( + rx.heading("Results:"), + rx.badge(FormInputState.form_data.to_string()), + ), + align_items="left", + width="100%", + ), + width="50%", + ) +``` + +To learn more about how to use forms in the [Form]({library.forms.form.path}) docs. + +## Setting a value without using a State var + +Set the value of the specified reference element, without needing to link it up to a State var. This is an alternate way to modify the value of the `input`. + +```python demo +rx.hstack( + rx.input(id="input1"), + rx.button("Erase", on_click=rx.set_value("input1", "")), +) +``` diff --git a/docs/library/forms/radio_group.md b/docs/library/forms/radio_group.md new file mode 100644 index 00000000000..e0c62187d9a --- /dev/null +++ b/docs/library/forms/radio_group.md @@ -0,0 +1,101 @@ +--- +components: + - rx.radio_group + - rx.radio_group.root + - rx.radio_group.item + +HighLevelRadioGroup: | + lambda **props: rx.radio_group(["1", "2", "3", "4", "5"], **props) + +RadioGroupRoot: | + lambda **props: rx.radio_group.root( + rx.radio_group.item(value="1"), + rx.radio_group.item(value="2"), + rx.radio_group.item(value="3"), + rx.radio_group.item(value="4"), + rx.radio_group.item(value="5"), + **props + ) + +RadioGroupItem: | + lambda **props: rx.radio_group.root( + rx.radio_group.item(value="1", **props), + rx.radio_group.item(value="2", **props), + rx.radio_group.item(value="3",), + rx.radio_group.item(value="4",), + rx.radio_group.item(value="5",), + ) +--- + +```python exec +import reflex as rx +from pcweb.templates.docpage import style_grid +``` + +# Radio Group + +A set of interactive radio buttons where only one can be selected at a time. + +## Basic example + +```python demo exec +class RadioGroupState(rx.State): + item: str = "No Selection" + + @rx.event + def set_item(self, item: str): + self.item = item + +def radio_group_state_example(): + return rx.vstack( + rx.badge(RadioGroupState.item, color_scheme="green"), + rx.radio(["1", "2", "3"], on_change=RadioGroupState.set_item, direction="row"), + ) +``` + +## Submitting a form using Radio Group + +The `name` prop is used to name the group. It is submitted with its owning form as part of a name/value pair. + +When the `required` prop is `True`, it indicates that the user must check a radio item before the owning form can be submitted. + +```python demo exec +class FormRadioState(rx.State): + form_data: dict = {} + + @rx.event + def handle_submit(self, form_data: dict): + """Handle the form submit.""" + self.form_data = form_data + + +def radio_form_example(): + return rx.card( + rx.vstack( + rx.heading("Example Form"), + rx.form.root( + rx.vstack( + rx.radio_group( + ["Option 1", "Option 2", "Option 3"], + name="radio_choice", + direction="row", + ), + rx.button("Submit", type="submit"), + width="100%", + spacing="4", + ), + on_submit=FormRadioState.handle_submit, + reset_on_submit=True, + ), + rx.divider(), + rx.hstack( + rx.heading("Results:"), + rx.badge(FormRadioState.form_data.to_string()), + ), + align_items="left", + width="100%", + spacing="4", + ), + width="50%", + ) +``` diff --git a/docs/library/forms/select-ll.md b/docs/library/forms/select-ll.md new file mode 100644 index 00000000000..50b7ad26ef3 --- /dev/null +++ b/docs/library/forms/select-ll.md @@ -0,0 +1,305 @@ +--- +components: + - rx.select + - rx.select.root + - rx.select.trigger + - rx.select.content + - rx.select.group + - rx.select.item + - rx.select.label + - rx.select.separator +--- + +```python exec +import random +import reflex as rx +from pcweb.constants import REFLEX_ASSETS_CDN +import reflex.components.radix.primitives as rdxp +from pcweb.templates.docpage import style_grid +``` + +# Select + +Displays a list of options for the user to pick from, triggered by a button. + +## Basic Example + +```python demo +rx.select.root( + rx.select.trigger(), + rx.select.content( + rx.select.group( + rx.select.label("Fruits"), + rx.select.item("Orange", value="orange"), + rx.select.item("Apple", value="apple"), + rx.select.item("Grape", value="grape", disabled=True), + ), + rx.select.separator(), + rx.select.group( + rx.select.label("Vegetables"), + rx.select.item("Carrot", value="carrot"), + rx.select.item("Potato", value="potato"), + ), + ), + default_value="apple", +) +``` + +## Usage + +## Disabling + +It is possible to disable individual items in a `select` using the `disabled` prop associated with the `rx.select.item`. + +```python demo +rx.select.root( + rx.select.trigger(placeholder="Select a Fruit"), + rx.select.content( + rx.select.group( + rx.select.item("Apple", value="apple"), + rx.select.item("Grape", value="grape", disabled=True), + rx.select.item("Pineapple", value="pineapple"), + ), + ), +) +``` + +To prevent the user from interacting with select entirely, set the `disabled` prop to `True` on the `rx.select.root` component. + +```python demo +rx.select.root( + rx.select.trigger(placeholder="This is Disabled"), + rx.select.content( + rx.select.group( + rx.select.item("Apple", value="apple"), + rx.select.item("Grape", value="grape"), + ), + ), + disabled=True, +) +``` + +## Setting Defaults + +It is possible to set several default values when constructing a `select`. + +The `placeholder` prop in the `rx.select.trigger` specifies the content that will be rendered when `value` or `default_value` is empty or not set. + +```python demo +rx.select.root( + rx.select.trigger(placeholder="pick a fruit"), + rx.select.content( + rx.select.group( + rx.select.item("Apple", value="apple"), + rx.select.item("Grape", value="grape"), + ), + ), +) +``` + +The `default_value` in the `rx.select.root` specifies the value of the `select` when initially rendered. +The `default_value` should correspond to the `value` of a child `rx.select.item`. + +```python demo +rx.select.root( + rx.select.trigger(), + rx.select.content( + rx.select.group( + rx.select.item("Apple", value="apple"), + rx.select.item("Grape", value="grape"), + ), + ), + default_value="apple", +) +``` + +## Fully controlled + +The `on_change` event trigger is fired when the value of the select changes. +In this example the `rx.select_root` `value` prop specifies which item is selected, and this +can also be controlled using state and a button without direct interaction with the select component. + +```python demo exec +class SelectState2(rx.State): + + values: list[str] = ["apple", "grape", "pear"] + + value: str = "" + + def set_value(self, value: str): + self.value = value + + @rx.event + def choose_randomly(self): + """Change the select value var.""" + original_value = self.value + while self.value == original_value: + self.value = random.choice(self.values) + + +def select_example2(): + return rx.vstack( + rx.select.root( + rx.select.trigger(placeholder="No Selection"), + rx.select.content( + rx.select.group( + rx.foreach(SelectState2.values, lambda x: rx.select.item(x, value=x)) + ), + ), + value=SelectState2.value, + on_change=SelectState2.set_value, + + ), + rx.button("Choose Randomly", on_click=SelectState2.choose_randomly), + rx.button("Reset", on_click=SelectState2.set_value("")), + ) +``` + +The `open` prop and `on_open_change` event trigger work similarly to `value` and `on_change` to control the open state of the select. +If `on_open_change` handler does not alter the `open` prop, the select will not be able to be opened or closed by clicking on the +`select_trigger`. + +```python demo exec +class SelectState8(rx.State): + is_open: bool = False + + @rx.event + def set_is_open(self, value: bool): + self.is_open = value + +def select_example8(): + return rx.flex( + rx.select.root( + rx.select.trigger(placeholder="No Selection"), + rx.select.content( + rx.select.group( + rx.select.item("Apple", value="apple"), + rx.select.item("Grape", value="grape"), + ), + ), + open=SelectState8.is_open, + on_open_change=SelectState8.set_is_open, + ), + rx.button("Toggle", on_click=SelectState8.set_is_open(~SelectState8.is_open)), + spacing="2", + ) +``` + +### Submitting a Form with Select + +When a select is part of a form, the `name` prop of the `rx.select.root` sets the key that will be submitted with the form data. + +The `value` prop of `rx.select.item` provides the value to be associated with the `name` key when the form is submitted with that item selected. + +When the `required` prop of the `rx.select.root` is `True`, it indicates that the user must select a value before the form may be submitted. + +```python demo exec +class FormSelectState(rx.State): + form_data: dict = {} + + @rx.event + def handle_submit(self, form_data: dict): + """Handle the form submit.""" + self.form_data = form_data + + +def form_select(): + return rx.flex( + rx.form.root( + rx.flex( + rx.select.root( + rx.select.trigger(), + rx.select.content( + rx.select.group( + rx.select.label("Fruits"), + rx.select.item("Orange", value="orange"), + rx.select.item("Apple", value="apple"), + rx.select.item("Grape", value="grape"), + ), + rx.select.separator(), + rx.select.group( + rx.select.label("Vegetables"), + rx.select.item("Carrot", value="carrot"), + rx.select.item("Potato", value="potato"), + ), + ), + default_value="apple", + name="select", + ), + rx.button("Submit"), + width="100%", + direction="column", + spacing="2", + ), + on_submit=FormSelectState.handle_submit, + reset_on_submit=True, + ), + rx.divider(size="4"), + rx.heading("Results"), + rx.text(FormSelectState.form_data.to_string()), + width="100%", + direction="column", + spacing="2", + ) +``` + +## Real World Example + +```python demo +rx.card( + rx.flex( + rx.image(src=f"{REFLEX_ASSETS_CDN}other/reflex_banner.png", width="100%", height="auto"), + rx.flex( + rx.heading("Reflex Swag", size="4", margin_bottom="4px"), + rx.heading("$99", size="6", margin_bottom="4px"), + direction="row", justify="between", + width="100%", + ), + rx.text("Reflex swag with a sense of nostalgia, as if they carry whispered tales of past adventures", size="2", margin_bottom="4px"), + rx.divider(size="4"), + rx.flex( + rx.flex( + rx.text("Color", size="2", margin_bottom="4px", color_scheme="gray"), + rx.select.root( + rx.select.trigger(), + rx.select.content( + rx.select.group( + rx.select.item("Light", value="light"), + rx.select.item("Dark", value="dark"), + ), + ), + default_value="light", + ), + direction="column", + ), + rx.flex( + rx.text("Size", size="2", margin_bottom="4px", color_scheme="gray"), + rx.select.root( + rx.select.trigger(), + rx.select.content( + rx.select.group( + rx.select.item("24", value="24"), + rx.select.item("26", value="26"), + rx.select.item("28", value="28", disabled=True), + rx.select.item("30", value="30"), + rx.select.item("32", value="32"), + rx.select.item("34", value="34"), + rx.select.item("36", value="36"), + ), + ), + default_value="30", + ), + direction="column", + ), + rx.button(rx.icon(tag="plus"), "Add"), + align="end", + justify="between", + spacing="2", + width="100%", + ), + width="15em", + direction="column", + spacing="2", + ), +) +``` diff --git a/docs/library/forms/select.md b/docs/library/forms/select.md new file mode 100644 index 00000000000..78e5206bf99 --- /dev/null +++ b/docs/library/forms/select.md @@ -0,0 +1,203 @@ +--- +components: + - rx.select + - rx.select.root + - rx.select.trigger + - rx.select.content + - rx.select.group + - rx.select.item + - rx.select.label + - rx.select.separator + +HighLevelSelect: | + lambda **props: rx.select(["apple", "grape", "pear"], default_value="pear", **props) + +SelectRoot: | + lambda **props: rx.select.root( + rx.select.trigger(), + rx.select.content( + rx.select.group( + rx.select.item("apple", value="apple"), + rx.select.item("grape", value="grape"), + rx.select.item("pear", value="pear"), + ), + ), + default_value="pear", + **props + ) + +SelectTrigger: | + lambda **props: rx.select.root( + rx.select.trigger(**props), + rx.select.content( + rx.select.group( + rx.select.item("apple", value="apple"), + rx.select.item("grape", value="grape"), + rx.select.item("pear", value="pear"), + ), + ), + default_value="pear", + ) + +SelectContent: | + lambda **props: rx.select.root( + rx.select.trigger(), + rx.select.content( + rx.select.group( + rx.select.item("apple", value="apple"), + rx.select.item("grape", value="grape"), + rx.select.item("pear", value="pear"), + ), + **props, + ), + default_value="pear", + ) + +SelectItem: | + lambda **props: rx.select.root( + rx.select.trigger(), + rx.select.content( + rx.select.group( + rx.select.item("apple", value="apple", **props), + rx.select.item("grape", value="grape"), + rx.select.item("pear", value="pear"), + ), + ), + default_value="pear", + ) +--- + +```python exec +import random +import reflex as rx +from pcweb.templates.docpage import style_grid +``` + +# Select + +Displays a list of options for the user to pick from—triggered by a button. + +```python demo exec +class SelectState(rx.State): + value: str = "apple" + + @rx.event + def change_value(self, value: str): + """Change the select value var.""" + self.value = value + + +def select_intro(): + return rx.center( + rx.select( + ["apple", "grape", "pear"], + value=SelectState.value, + on_change=SelectState.change_value, + ), + rx.badge(SelectState.value), + ) +``` + +```python demo exec +class SelectState3(rx.State): + + values: list[str] = ["apple", "grape", "pear"] + + value: str = "apple" + + def set_value(self, value: str): + self.value = value + + @rx.event + def change_value(self): + """Change the select value var.""" + self.value = random.choice(self.values) + + +def select_example3(): + return rx.vstack( + rx.select( + SelectState3.values, + value=SelectState3.value, + on_change=SelectState3.set_value, + ), + rx.button("Change Value", on_click=SelectState3.change_value), + + ) +``` + +The `on_open_change` event handler acts in a similar way to the `on_change` and is called when the open state of the select changes. + +```python demo +rx.select( + ["apple", "grape", "pear"], + on_change=rx.window_alert("on_change event handler called"), +) + +``` + +### Submitting a form using select + +The `name` prop is needed to submit with its owning form as part of a name/value pair. + +When the `required` prop is `True`, it indicates that the user must select a value before the owning form can be submitted. + +```python demo exec +class FormSelectState(rx.State): + form_data: dict = {} + + @rx.event + def handle_submit(self, form_data: dict): + """Handle the form submit.""" + self.form_data = form_data + + +def select_form_example(): + return rx.card( + rx.vstack( + rx.heading("Example Form"), + rx.form.root( + rx.flex( + rx.select(["apple", "grape", "pear"], default_value="apple", name="select", required=True), + rx.button("Submit", flex="1", type="submit"), + width="100%", + spacing="3", + ), + on_submit=FormSelectState.handle_submit, + reset_on_submit=True, + ), + rx.divider(), + rx.hstack( + rx.heading("Results:"), + rx.badge(FormSelectState.form_data.to_string()), + ), + align_items="left", + width="100%", + ), + width="50%", + ) +``` + + +### Using Select within a Drawer component + +If using within a [Drawer](/docs/library/overlay/drawer) component, set the `position` prop to `"popper"` to ensure the select menu is displayed correctly. + +```python demo +rx.drawer.root( + rx.drawer.trigger(rx.button("Open Drawer")), + rx.drawer.overlay(z_index="5"), + rx.drawer.portal( + rx.drawer.content( + rx.vstack( + rx.drawer.close(rx.box(rx.button("Close"))), + rx.select(["apple", "grape", "pear"], position="popper"), + ), + width="20em", + padding="2em", + background_color=rx.color("gray", 1), + ), + ), + direction="left", +) +``` diff --git a/docs/library/forms/slider.md b/docs/library/forms/slider.md new file mode 100644 index 00000000000..cc537b224b6 --- /dev/null +++ b/docs/library/forms/slider.md @@ -0,0 +1,133 @@ +--- +components: + - rx.slider + +Slider: | + lambda **props: rx.center(rx.slider(default_value=40, height="100%", **props), height="4em", width="100%") +--- + +```python exec +import reflex as rx +from pcweb.templates.docpage import style_grid +``` + +# Slider + +Provides user selection from a range of values. The + +## Basic Example + +The slider can be controlled by a single value or a range of values. Slider can be hooked to state to control its value. Passing a list of two values creates a range slider. + +```python demo exec +class SliderState(rx.State): + value: int = 50 + + @rx.event + def set_end(self, value: list[int | float]): + self.value = value[0] + +def slider_intro(): + return rx.vstack( + rx.heading(SliderState.value), + rx.slider(on_value_commit=SliderState.set_end), + width="100%", + ) +``` + +## Range Slider + +Range slider is created by passing a list of two values to the `default_value` prop. The list should contain two values that are in the range of the slider. + +```python demo exec +class RangeSliderState(rx.State): + value_start: int = 25 + value_end: int = 75 + + @rx.event + def set_end(self, value: list[int | float]): + self.value_start = value[0] + self.value_end = value[1] + +def range_slider_intro(): + return rx.vstack( + rx.hstack( + rx.heading(RangeSliderState.value_start), + rx.heading(RangeSliderState.value_end), + ), + rx.slider( + default_value=[25, 75], + min_=0, + max=100, + size="1", + on_value_commit=RangeSliderState.set_end, + ), + width="100%", + ) +``` + +## Live Updating Slider + +You can use the `on_change` prop to update the slider value as you interact with it. The `on_change` prop takes a function that will be called with the new value of the slider. + +Here we use the `throttle` method to limit the rate at which the function is called, which is useful to prevent excessive updates. In this example, the slider value is updated every 100ms. + +```python demo exec +class LiveSliderState(rx.State): + value: int = 50 + + @rx.event + def set_end(self, value: list[int | float]): + self.value = value[0] + +def live_slider_intro(): + return rx.vstack( + rx.heading(LiveSliderState.value), + rx.slider( + default_value=50, + min_=0, + max=100, + on_change=LiveSliderState.set_end.throttle(100), + ), + width="100%", + ) +``` + +## Slider in forms + +Here we show how to use a slider in a form. We use the `name` prop to identify the slider in the form data. The form data is then passed to the `handle_submit` method to be processed. + +```python demo exec +class FormSliderState(rx.State): + form_data: dict = {} + + @rx.event + def handle_submit(self, form_data: dict): + """Handle the form submit.""" + self.form_data = form_data + + +def slider_form_example(): + return rx.card( + rx.vstack( + rx.heading("Example Form"), + rx.form.root( + rx.hstack( + rx.slider(default_value=40, name="slider"), + rx.button("Submit", type="submit"), + width="100%", + ), + on_submit=FormSliderState.handle_submit, + reset_on_submit=True, + ), + rx.divider(), + rx.hstack( + rx.heading("Results:"), + rx.badge(FormSliderState.form_data.to_string()), + ), + align_items="left", + width="100%", + ), + width="50%", + ) +``` diff --git a/docs/library/forms/switch.md b/docs/library/forms/switch.md new file mode 100644 index 00000000000..8efc3e687ca --- /dev/null +++ b/docs/library/forms/switch.md @@ -0,0 +1,108 @@ +--- +components: + - rx.switch + +Switch: | + lambda **props: rx.switch(**props) +--- + +```python exec +import reflex as rx +from pcweb.templates.docpage import style_grid +from pcweb.pages.docs import vars +``` + +# Switch + +A toggle switch alternative to the checkbox. + +## Basic Example + +Here is a basic example of a switch. We use the `on_change` trigger to toggle the value in the state. + +```python demo exec +class SwitchState(rx.State): + value: bool = False + + @rx.event + def set_end(self, value: bool): + self.value = value + +def switch_intro(): + return rx.center( + rx.switch(on_change=SwitchState.set_end), + rx.badge(SwitchState.value), + ) +``` + +## Control the value + +The `checked` prop is used to control the state of the switch. The event `on_change` is called when the state of the switch changes, when the `change_checked` event handler is called. + +The `disabled` prop when `True`, prevents the user from interacting with the switch. In our example below, even though the second switch is `disabled` we are still able to change whether it is checked or not using the `checked` prop. + +```python demo exec +class ControlSwitchState(rx.State): + + checked = True + + @rx.event + def change_checked(self, checked: bool): + """Change the switch checked var.""" + self.checked = checked + + +def control_switch_example(): + return rx.hstack( + rx.switch( + checked=ControlSwitchState.checked, + on_change=ControlSwitchState.change_checked, + ), + rx.switch( + checked=ControlSwitchState.checked, + on_change=ControlSwitchState.change_checked, + disabled=True, + ), + ) +``` + +## Switch in forms + +The `name` of the switch is needed to submit with its owning form as part of a name/value pair. When the `required` prop is `True`, it indicates that the user must check the switch before the owning form can be submitted. + +The `value` prop is only used for form submission, use the `checked` prop to control state of the `switch`. + +```python demo exec +class FormSwitchState(rx.State): + form_data: dict = {} + + @rx.event + def handle_submit(self, form_data: dict): + """Handle the form submit.""" + self.form_data = form_data + + +def switch_form_example(): + return rx.card( + rx.vstack( + rx.heading("Example Form"), + rx.form.root( + rx.hstack( + rx.switch(name="switch"), + rx.button("Submit", type="submit"), + width="100%", + ), + on_submit=FormSwitchState.handle_submit, + reset_on_submit=True, + ), + rx.divider(), + rx.hstack( + rx.heading("Results:"), + rx.badge(FormSwitchState.form_data.to_string()), + ), + align_items="left", + width="100%", + ), + width="50%", + ) +``` diff --git a/docs/library/forms/text_area.md b/docs/library/forms/text_area.md new file mode 100644 index 00000000000..de4f1b6a198 --- /dev/null +++ b/docs/library/forms/text_area.md @@ -0,0 +1,88 @@ +--- +components: + - rx.text_area + +TextArea: | + lambda **props: rx.text_area(**props) +--- + +```python exec +import reflex as rx +``` + +# Text Area + +A text area is a multi-line text input field. + +## Basic Example + +The text area component can be controlled by a single value. The `on_blur` prop can be used to update the value when the text area loses focus. + +```python demo exec +class TextAreaBlur(rx.State): + text: str = "Hello World!" + + @rx.event + def set_text(self, text: str): + self.text = text + +def blur_example(): + return rx.vstack( + rx.heading(TextAreaBlur.text), + rx.text_area( + placeholder="Type here...", + on_blur=TextAreaBlur.set_text, + ), + ) +``` + +## Text Area in forms + +Here we show how to use a text area in a form. We use the `name` prop to identify the text area in the form data. The form data is then passed to the `submit_feedback` method to be processed. + +```python demo exec +class TextAreaFeedbackState(rx.State): + feedback: str = "" + submitted: bool = False + + @rx.event + def set_feedback(self, value: str): + self.feedback = value + + @rx.event + def submit_feedback(self, form_data: dict): + self.submitted = True + + @rx.event + def reset_form(self): + self.feedback = "" + self.submitted = False + +def feedback_form(): + return rx.cond( + TextAreaFeedbackState.submitted, + rx.card( + rx.vstack( + rx.text("Thank you for your feedback!"), + rx.button("Submit another response", on_click=TextAreaFeedbackState.reset_form), + ), + ), + rx.card( + rx.form( + rx.flex( + rx.text("Are you enjoying Reflex?"), + rx.text_area( + placeholder="Write your feedback…", + value=TextAreaFeedbackState.feedback, + on_change=TextAreaFeedbackState.set_feedback, + resize="vertical", + ), + rx.button("Send", type="submit"), + direction="column", + spacing="3", + ), + on_submit=TextAreaFeedbackState.submit_feedback, + ), + ), + ) +``` diff --git a/docs/library/forms/upload.md b/docs/library/forms/upload.md new file mode 100644 index 00000000000..b87391253fa --- /dev/null +++ b/docs/library/forms/upload.md @@ -0,0 +1,523 @@ +--- +components: + - rx.upload + - rx.upload.root + +Upload: | + lambda **props: rx.center(rx.upload(id="my_upload", **props)) +--- + +```python exec +import reflex as rx +from pcweb.constants import REFLEX_ASSETS_CDN +``` + +# File Upload + +Reflex makes it simple to add file upload functionality to your app. You can let users select files, store them on your server, and display or process them as needed. Below is a minimal example that demonstrates how to upload files, save them to disk, and display uploaded images using application state. + + +## Basic File Upload Example + +You can let users upload files and keep track of them in your app’s state. The example below allows users to upload files, saves them using the backend, and then displays the uploaded files as images. + +```python +import reflex as rx +from pcweb.constants import REFLEX_ASSETS_CDN +class State(rx.State): + uploaded_files: list[str] = [] + + @rx.event + async def handle_upload(self, files: list[rx.UploadFile]): + for file in files: + data = await file.read() + path = rx.get_upload_dir() / file.name + with path.open("wb") as f: + f.write(data) + self.uploaded_files.append(file.name) + +def upload_component(): + return rx.vstack( + rx.upload(id="upload"), + rx.button("Upload", on_click=State.handle_upload(rx.upload_files("upload"))), + rx.foreach(State.uploaded_files, lambda f: rx.image(src=rx.get_upload_url(f))), + ) +``` + +## How File Upload Works + +Selecting a file will add it to the browser file list, which can be rendered +on the frontend using the `rx.selected_files(id)` special Var. To clear the +selected files, you can use another special Var `rx.clear_selected_files(id)` as +an event handler. + +To upload the file(s), you need to bind an event handler and pass the special +`rx.upload_files(upload_id=id)` event arg to it. + +## File Storage Functions + +Reflex provides two key functions for handling uploaded files: + +### rx.get_upload_dir() +- **Purpose**: Returns a `Path` object pointing to the server-side directory where uploaded files should be saved +- **Usage**: Used in backend event handlers to determine where to save uploaded files +- **Default Location**: `./uploaded_files` (can be customized via `REFLEX_UPLOADED_FILES_DIR` environment variable) +- **Type**: Returns `pathlib.Path` + +### rx.get_upload_url(filename) +- **Purpose**: Returns the URL string that can be used in frontend components to access uploaded files +- **Usage**: Used in frontend components (like `rx.image`, `rx.video`) to display uploaded files +- **URL Format**: `/_upload/filename` +- **Type**: Returns `str` + +### Key Differences +- **rx.get_upload_dir()** -> Backend file path for saving files +- **rx.get_upload_url()** -> Frontend URL for displaying files + +### Basic Upload Pattern + +Here is the standard pattern for handling file uploads: + +```python +import reflex as rx +from pcweb.constants import REFLEX_ASSETS_CDN + +def create_unique_filename(file_name: str): + import random + import string + + filename = "".join(random.choices(string.ascii_letters + string.digits, k=10)) + return filename + "_" + file_name + +class State(rx.State): + uploaded_files: list[str] = [] + + @rx.event + async def handle_upload(self, files: list[rx.UploadFile]): + """Handle file upload with proper directory management.""" + for file in files: + # Read the file data + upload_data = await file.read() + + # Get the upload directory (backend path) + upload_dir = rx.get_upload_dir() + + # Ensure the directory exists + upload_dir.mkdir(parents=True, exist_ok=True) + + # Create unique filename to prevent conflicts + unique_filename = create_unique_filename(file.name) + + # Create full file path + file_path = upload_dir / unique_filename + + # Save the file + with file_path.open("wb") as f: + f.write(upload_data) + + # Store filename for frontend display + self.uploaded_files.append(unique_filename) + +def upload_component(): + return rx.vstack( + rx.upload( + rx.text("Drop files here or click to select"), + id="file_upload", + border="2px dashed #ccc", + padding="2em", + ), + rx.button( + "Upload Files", + on_click=State.handle_upload(rx.upload_files(upload_id="file_upload")), + ), + # Display uploaded files using rx.get_upload_url() + rx.foreach( + State.uploaded_files, + lambda filename: rx.image(src=rx.get_upload_url(filename)) + ), + ) + +``` + +### Multiple File Upload + +Below is an example of how to allow multiple file uploads (in this case images). + +```python demo box +rx.image(src=f"{REFLEX_ASSETS_CDN}other/upload.gif") +``` + +```python +class State(rx.State): + """The app state.""" + + # The images to show. + img: list[str] + + @rx.event + async def handle_upload(self, files: list[rx.UploadFile]): + """Handle the upload of file(s). + + Args: + files: The uploaded files. + """ + for file in files: + upload_data = await file.read() + outfile = rx.get_upload_dir() / file.name + + # Save the file. + with outfile.open("wb") as file_object: + file_object.write(upload_data) + + # Update the img var. + self.img.append(file.name) + + +color = "rgb(107,99,246)" + + +def index(): + """The main view.""" + return rx.vstack( + rx.upload( + rx.vstack( + rx.button("Select File", color=color, bg="white", border=f"1px solid {color}"), + rx.text("Drag and drop files here or click to select files"), + ), + id="upload1", + border=f"1px dotted {color}", + padding="5em", + ), + rx.hstack(rx.foreach(rx.selected_files("upload1"), rx.text)), + rx.button( + "Upload", + on_click=State.handle_upload(rx.upload_files(upload_id="upload1")), + ), + rx.button( + "Clear", + on_click=rx.clear_selected_files("upload1"), + ), + rx.foreach(State.img, lambda img: rx.image(src=rx.get_upload_url(img))), + padding="5em", + ) +``` + +### Uploading a Single File (Video) + +Below is an example of how to allow only a single file upload and render (in this case a video). + +```python demo box +rx.el.video(src=f"{REFLEX_ASSETS_CDN}other/upload_single_video.webm", auto_play=True, controls=True, loop=True) +``` + +```python +class State(rx.State): + """The app state.""" + + # The video to show. + video: str + + @rx.event + async def handle_upload( + self, files: list[rx.UploadFile] + ): + """Handle the upload of file(s). + + Args: + files: The uploaded files. + """ + current_file = files[0] + upload_data = await current_file.read() + outfile = rx.get_upload_dir() / current_file.name + + # Save the file. + with outfile.open("wb") as file_object: + file_object.write(upload_data) + + # Update the video var. + self.video = current_file.name + + +color = "rgb(107,99,246)" + + +def index(): + """The main view.""" + return rx.vstack( + rx.upload( + rx.vstack( + rx.button( + "Select File", + color=color, + bg="white", + border=f"1px solid \{color}", + ), + rx.text( + "Drag and drop files here or click to select files" + ), + ), + id="upload1", + max_files=1, + border=f"1px dotted {color}", + padding="5em", + ), + rx.text(rx.selected_files("upload1")), + rx.button( + "Upload", + on_click=State.handle_upload( + rx.upload_files(upload_id="upload1") + ), + ), + rx.button( + "Clear", + on_click=rx.clear_selected_files("upload1"), + ), + rx.cond( + State.video, + rx.video(src=rx.get_upload_url(State.video)), + ), + padding="5em", + ) +``` + + +### Customizing the Upload + +In the example below, the upload component accepts a maximum number of 5 files of specific types. +It also disables the use of the space or enter key in uploading files. + +To use a one-step upload, bind the event handler to the `rx.upload` component's +`on_drop` trigger. + +```python +class State(rx.State): + """The app state.""" + + # The images to show. + img: list[str] + + async def handle_upload(self, files: list[rx.UploadFile]): + """Handle the upload of file(s). + + Args: + files: The uploaded files. + """ + for file in files: + upload_data = await file.read() + outfile = rx.get_upload_dir() / file.name + + # Save the file. + with outfile.open("wb") as file_object: + file_object.write(upload_data) + + # Update the img var. + self.img.append(file.name) + + +color = "rgb(107,99,246)" + + +def index(): + """The main view.""" + return rx.vstack( + rx.upload( + rx.vstack( + rx.button("Select File", color=color, bg="white", border=f"1px solid {color}"), + rx.text("Drag and drop files here or click to select files"), + ), + id="upload2", + multiple=True, + accept = { + "application/pdf": [".pdf"], + "image/png": [".png"], + "image/jpeg": [".jpg", ".jpeg"], + "image/gif": [".gif"], + "image/webp": [".webp"], + "text/html": [".html", ".htm"], + }, + max_files=5, + disabled=False, + no_keyboard=True, + on_drop=State.handle_upload(rx.upload_files(upload_id="upload2")), + border=f"1px dotted {color}", + padding="5em", + ), + rx.grid( + rx.foreach( + State.img, + lambda img: rx.vstack( + rx.image(src=rx.get_upload_url(img)), + rx.text(img), + ), + ), + columns="2", + spacing="1", + ), + padding="5em", + ) +``` + +### Unstyled Upload Component + +To use a completely unstyled upload component and apply your own customization, use `rx.upload.root` instead: + +```python demo +rx.upload.root( + rx.box( + rx.icon( + tag="cloud_upload", + style={"width": "3rem", "height": "3rem", "color": "#2563eb", "marginBottom": "0.75rem"}, + ), + rx.hstack( + rx.text( + "Click to upload", + style={"fontWeight": "bold", "color": "#1d4ed8"}, + ), + " or drag and drop", + style={"fontSize": "0.875rem", "color": "#4b5563"}, + ), + rx.text( + "SVG, PNG, JPG or GIF (MAX. 5MB)", + style={"fontSize": "0.75rem", "color": "#6b7280", "marginTop": "0.25rem"}, + ), + style={ + "display": "flex", + "flexDirection": "column", + "alignItems": "center", + "justifyContent": "center", + "padding": "1.5rem", + "textAlign": "center", + }, + ), + id="my_upload", + style={ + "maxWidth": "24rem", + "height": "16rem", + "borderWidth": "2px", + "borderStyle": "dashed", + "borderColor": "#60a5fa", + "borderRadius": "0.75rem", + "cursor": "pointer", + "transitionProperty": "background-color", + "transitionDuration": "0.2s", + "transitionTimingFunction": "ease-in-out", + "display": "flex", + "alignItems": "center", + "justifyContent": "center", + "boxShadow": "0 1px 2px rgba(0, 0, 0, 0.05)", + }, +) +``` + +## Handling the Upload + +Your event handler should be an async function that accepts a single argument, +`files: list[UploadFile]`, which will contain [FastAPI UploadFile](https://fastapi.tiangolo.com/tutorial/request-files) instances. +You can read the files and save them anywhere as shown in the example. + +In your UI, you can bind the event handler to a trigger, such as a button +`on_click` event or upload `on_drop` event, and pass in the files using +`rx.upload_files()`. + +### Saving the File + +By convention, Reflex provides the function `rx.get_upload_dir()` to get the directory where uploaded files may be saved. The upload dir comes from the environment variable `REFLEX_UPLOADED_FILES_DIR`, or `./uploaded_files` if not specified. + +The backend of your app will mount this uploaded files directory on `/_upload` without restriction. Any files uploaded via this mechanism will automatically be publicly accessible. To get the URL for a file inside the upload dir, use the `rx.get_upload_url(filename)` function in a frontend component. + +```md alert info +# When using the Reflex hosting service, the uploaded files directory is not persistent and will be cleared on every deployment. For persistent storage of uploaded files, it is recommended to use an external service, such as S3. +``` + +### Directory Structure and URLs + +By default, Reflex creates the following structure: + +```text +your_project/ +├── uploaded_files/ # rx.get_upload_dir() points here +│ ├── image1.png +│ ├── document.pdf +│ └── video.mp4 +└── ... +``` + +The files are automatically served at: +- `/_upload/image1.png` ← `rx.get_upload_url("image1.png")` +- `/_upload/document.pdf` ← `rx.get_upload_url("document.pdf")` +- `/_upload/video.mp4` ← `rx.get_upload_url("video.mp4")` + +## Cancellation + +The `id` provided to the `rx.upload` component can be passed to the special event handler `rx.cancel_upload(id)` to stop uploading on demand. Cancellation can be triggered directly by a frontend event trigger, or it can be returned from a backend event handler. + +## Progress + +The `rx.upload_files` special event arg also accepts an `on_upload_progress` event trigger which will be fired about every second during the upload operation to report the progress of the upload. This can be used to update a progress bar or other UI elements to show the user the progress of the upload. + +```python +class UploadExample(rx.State): + uploading: bool = False + progress: int = 0 + total_bytes: int = 0 + + @rx.event + async def handle_upload(self, files: list[rx.UploadFile]): + for file in files: + self.total_bytes += len(await file.read()) + + @rx.event + def handle_upload_progress(self, progress: dict): + self.uploading = True + self.progress = round(progress["progress"] * 100) + if self.progress >= 100: + self.uploading = False + + @rx.event + def cancel_upload(self): + self.uploading = False + return rx.cancel_upload("upload3") + + +def upload_form(): + return rx.vstack( + rx.upload( + rx.text("Drag and drop files here or click to select files"), + id="upload3", + border="1px dotted rgb(107,99,246)", + padding="5em", + ), + rx.vstack(rx.foreach(rx.selected_files("upload3"), rx.text)), + rx.progress(value=UploadExample.progress, max=100), + rx.cond( + ~UploadExample.uploading, + rx.button( + "Upload", + on_click=UploadExample.handle_upload( + rx.upload_files( + upload_id="upload3", + on_upload_progress=UploadExample.handle_upload_progress, + ), + ), + ), + rx.button("Cancel", on_click=UploadExample.cancel_upload), + ), + rx.text("Total bytes uploaded: ", UploadExample.total_bytes), + align="center", + ) +``` + +The `progress` dictionary contains the following keys: + +```javascript +\{ + 'loaded': 36044800, + 'total': 54361908, + 'progress': 0.6630525183185255, + 'bytes': 20447232, + 'rate': None, + 'estimated': None, + 'event': \{'isTrusted': True}, + 'upload': True +} +``` diff --git a/docs/library/graphing/charts/areachart.md b/docs/library/graphing/charts/areachart.md new file mode 100644 index 00000000000..c857dc3a704 --- /dev/null +++ b/docs/library/graphing/charts/areachart.md @@ -0,0 +1,444 @@ +--- +components: + - rx.recharts.AreaChart + - rx.recharts.Area +--- + +# Area Chart + +```python exec +import reflex as rx +import random +``` + +A Recharts area chart displays quantitative data using filled areas between a line connecting data points and the axis. + +## Basic Example + +```python demo graphing +data = [ + { + "name": "Page A", + "uv": 4000, + "pv": 2400, + "amt": 2400 + }, + { + "name": "Page B", + "uv": 3000, + "pv": 1398, + "amt": 2210 + }, + { + "name": "Page C", + "uv": 2000, + "pv": 9800, + "amt": 2290 + }, + { + "name": "Page D", + "uv": 2780, + "pv": 3908, + "amt": 2000 + }, + { + "name": "Page E", + "uv": 1890, + "pv": 4800, + "amt": 2181 + }, + { + "name": "Page F", + "uv": 2390, + "pv": 3800, + "amt": 2500 + }, + { + "name": "Page G", + "uv": 3490, + "pv": 4300, + "amt": 2100 + } +] + +def area_simple(): + return rx.recharts.area_chart( + rx.recharts.area( + data_key="uv", + ), + rx.recharts.x_axis(data_key="name"), + rx.recharts.y_axis(), + data=data, + width = "100%", + height = 250, + ) +``` + +## Syncing Charts + +The `sync_id` prop allows you to sync two graphs. In the example, it is set to "1" for both charts, indicating that they should be synchronized. This means that any interactions (such as brushing) performed on one chart will be reflected in the other chart. + +```python demo graphing +data = [ + { + "name": "Page A", + "uv": 4000, + "pv": 2400, + "amt": 2400 + }, + { + "name": "Page B", + "uv": 3000, + "pv": 1398, + "amt": 2210 + }, + { + "name": "Page C", + "uv": 2000, + "pv": 9800, + "amt": 2290 + }, + { + "name": "Page D", + "uv": 2780, + "pv": 3908, + "amt": 2000 + }, + { + "name": "Page E", + "uv": 1890, + "pv": 4800, + "amt": 2181 + }, + { + "name": "Page F", + "uv": 2390, + "pv": 3800, + "amt": 2500 + }, + { + "name": "Page G", + "uv": 3490, + "pv": 4300, + "amt": 2100 + } +] + +def area_sync(): + return rx.vstack( + rx.recharts.bar_chart( + rx.recharts.graphing_tooltip(), + rx.recharts.bar( + data_key="uv", stroke="#8884d8", fill="#8884d8" + ), + rx.recharts.bar( + data_key="pv", stroke="#82ca9d", fill="#82ca9d", + ), + rx.recharts.x_axis(data_key="name"), + rx.recharts.y_axis(), + data=data, + sync_id="1", + width = "100%", + height = 200, + ), + rx.recharts.composed_chart( + rx.recharts.area( + data_key="uv", stroke="#8884d8", fill="#8884d8" + ), + rx.recharts.line( + data_key="pv", type_="monotone", stroke="#ff7300", + ), + + rx.recharts.x_axis(data_key="name"), + rx.recharts.y_axis(), + rx.recharts.graphing_tooltip(), + rx.recharts.brush( + data_key="name", height=30, stroke="#8884d8" + ), + data=data, + sync_id="1", + width = "100%", + height = 250, + ), + width="100%", + ) +``` + +## Stacking Charts + +The `stack_id` prop allows you to stack multiple graphs on top of each other. In the example, it is set to "1" for both charts, indicating that they should be stacked together. This means that the bars or areas of the charts will be vertically stacked, with the values of each chart contributing to the total height of the stacked areas or bars. + +This is similar to the `sync_id` prop, but instead of synchronizing the interaction between the charts, it just stacks the charts on top of each other. + +```python demo graphing + +data = [ + { + "name": "Page A", + "uv": 4000, + "pv": 2400, + "amt": 2400 + }, + { + "name": "Page B", + "uv": 3000, + "pv": 1398, + "amt": 2210 + }, + { + "name": "Page C", + "uv": 2000, + "pv": 9800, + "amt": 2290 + }, + { + "name": "Page D", + "uv": 2780, + "pv": 3908, + "amt": 2000 + }, + { + "name": "Page E", + "uv": 1890, + "pv": 4800, + "amt": 2181 + }, + { + "name": "Page F", + "uv": 2390, + "pv": 3800, + "amt": 2500 + }, + { + "name": "Page G", + "uv": 3490, + "pv": 4300, + "amt": 2100 + } +] + +def area_stack(): + return rx.recharts.area_chart( + rx.recharts.area( + data_key="uv", + stroke=rx.color("accent", 9), + fill=rx.color("accent", 8), + stack_id="1", + ), + rx.recharts.area( + data_key="pv", + stroke=rx.color("green", 9), + fill=rx.color("green", 8), + stack_id="1", + ), + rx.recharts.x_axis(data_key="name"), + rx.recharts.y_axis(), + data=data, + stack_offset="none", + margin={"top": 5, "right": 5, "bottom": 5, "left": 5}, + width = "100%", + height = 300, + ) +``` + +## Multiple Axis + +Multiple axes can be used for displaying different data series with varying scales or units on the same chart. This allows for a more comprehensive comparison and analysis of the data. + +```python demo graphing + +data = [ + { + "name": "Page A", + "uv": 4000, + "pv": 2400, + "amt": 2400 + }, + { + "name": "Page B", + "uv": 3000, + "pv": 1398, + "amt": 2210 + }, + { + "name": "Page C", + "uv": 2000, + "pv": 9800, + "amt": 2290 + }, + { + "name": "Page D", + "uv": 2780, + "pv": 3908, + "amt": 2000 + }, + { + "name": "Page E", + "uv": 1890, + "pv": 4800, + "amt": 2181 + }, + { + "name": "Page F", + "uv": 2390, + "pv": 3800, + "amt": 2500 + }, + { + "name": "Page G", + "uv": 3490, + "pv": 4300, + "amt": 2100 + } +] + +def area_multi_axis(): + return rx.recharts.area_chart( + rx.recharts.area( + data_key="uv", stroke="#8884d8", fill="#8884d8", x_axis_id="primary", y_axis_id="left", + ), + rx.recharts.area( + data_key="pv", x_axis_id="secondary", y_axis_id="right", type_="monotone", stroke="#82ca9d", fill="#82ca9d" + ), + rx.recharts.x_axis(data_key="name", x_axis_id="primary"), + rx.recharts.x_axis(data_key="name", x_axis_id="secondary", orientation="top"), + rx.recharts.y_axis(data_key="uv", y_axis_id="left"), + rx.recharts.y_axis(data_key="pv", y_axis_id="right", orientation="right"), + rx.recharts.graphing_tooltip(), + rx.recharts.legend(), + data=data, + width = "100%", + height = 300, + ) +``` + +## Layout + +Use the `layout` prop to set the orientation to either `"horizontal"` (default) or `"vertical"`. + +```md alert info +# Include margins around your graph to ensure proper spacing and enhance readability. By default, provide margins on all sides of the chart to create a visually appealing and functional representation of your data. +``` + +```python demo graphing + +data = [ + { + "name": "Page A", + "uv": 4000, + "pv": 2400, + "amt": 2400 + }, + { + "name": "Page B", + "uv": 3000, + "pv": 1398, + "amt": 2210 + }, + { + "name": "Page C", + "uv": 2000, + "pv": 9800, + "amt": 2290 + }, + { + "name": "Page D", + "uv": 2780, + "pv": 3908, + "amt": 2000 + }, + { + "name": "Page E", + "uv": 1890, + "pv": 4800, + "amt": 2181 + }, + { + "name": "Page F", + "uv": 2390, + "pv": 3800, + "amt": 2500 + }, + { + "name": "Page G", + "uv": 3490, + "pv": 4300, + "amt": 2100 + } +] + +def area_vertical(): + return rx.recharts.area_chart( + rx.recharts.area( + data_key="uv", + stroke=rx.color("accent", 8), + fill=rx.color("accent", 3), + ), + rx.recharts.x_axis(type_="number"), + rx.recharts.y_axis(data_key="name", type_="category"), + data=data, + layout="vertical", + height = 300, + width = "100%", + ) +``` + +## Stateful Example + +Here is an example of an area graph with a `State`. Here we have defined a function `randomize_data`, which randomly changes the data for both graphs when the first defined `area` is clicked on using `on_click=AreaState.randomize_data`. + +```python demo exec +class AreaState(rx.State): + data = data + curve_type = "" + + @rx.event + def randomize_data(self): + for i in range(len(self.data)): + self.data[i]["uv"] = random.randint(0, 10000) + self.data[i]["pv"] = random.randint(0, 10000) + self.data[i]["amt"] = random.randint(0, 10000) + + def change_curve_type(self, type_input): + self.curve_type = type_input + +def area_stateful(): + return rx.vstack( + rx.hstack( + rx.text("Curve Type:"), + rx.select( + [ + 'basis', + 'natural', + 'step' + ], + on_change = AreaState.change_curve_type, + default_value = 'basis', + ), + ), + rx.recharts.area_chart( + rx.recharts.area( + data_key="uv", + on_click=AreaState.randomize_data, + type_ = AreaState.curve_type, + ), + rx.recharts.area( + data_key="pv", + stroke="#82ca9d", + fill="#82ca9d", + on_click=AreaState.randomize_data, + type_ = AreaState.curve_type, + ), + rx.recharts.x_axis( + data_key="name", + ), + rx.recharts.y_axis(), + rx.recharts.legend(), + rx.recharts.cartesian_grid(), + data=AreaState.data, + width = "100%", + height=400, + ), + width="100%", + ) +``` diff --git a/docs/library/graphing/charts/barchart.md b/docs/library/graphing/charts/barchart.md new file mode 100644 index 00000000000..dd5de4efa3f --- /dev/null +++ b/docs/library/graphing/charts/barchart.md @@ -0,0 +1,388 @@ +--- +components: + - rx.recharts.BarChart + - rx.recharts.Bar +--- + +# Bar Chart + +```python exec +import reflex as rx +import random +from pcweb.pages.docs import library +``` + +A bar chart presents categorical data with rectangular bars with heights or lengths proportional to the values that they represent. + +For a bar chart we must define an `rx.recharts.bar()` component for each set of values we wish to plot. Each `rx.recharts.bar()` component has a `data_key` which clearly states which variable in our data we are tracking. In this simple example we plot `uv` as a bar against the `name` column which we set as the `data_key` in `rx.recharts.x_axis`. + +## Simple Example + +```python demo graphing +data = [ + { + "name": "Page A", + "uv": 4000, + "pv": 2400, + "amt": 2400 + }, + { + "name": "Page B", + "uv": 3000, + "pv": 1398, + "amt": 2210 + }, + { + "name": "Page C", + "uv": 2000, + "pv": 9800, + "amt": 2290 + }, + { + "name": "Page D", + "uv": 2780, + "pv": 3908, + "amt": 2000 + }, + { + "name": "Page E", + "uv": 1890, + "pv": 4800, + "amt": 2181 + }, + { + "name": "Page F", + "uv": 2390, + "pv": 3800, + "amt": 2500 + }, + { + "name": "Page G", + "uv": 3490, + "pv": 4300, + "amt": 2100 + } +] + +def bar_simple(): + return rx.recharts.bar_chart( + rx.recharts.bar( + data_key="uv", + stroke=rx.color("accent", 9), + fill=rx.color("accent", 8), + ), + rx.recharts.x_axis(data_key="name"), + rx.recharts.y_axis(), + data=data, + width = "100%", + height = 250, + ) +``` + +## Multiple Bars + +Multiple bars can be placed on the same `bar_chart`, using multiple `rx.recharts.bar()` components. + +```python demo graphing +data = [ + { + "name": "Page A", + "uv": 4000, + "pv": 2400, + "amt": 2400 + }, + { + "name": "Page B", + "uv": 3000, + "pv": 1398, + "amt": 2210 + }, + { + "name": "Page C", + "uv": 2000, + "pv": 9800, + "amt": 2290 + }, + { + "name": "Page D", + "uv": 2780, + "pv": 3908, + "amt": 2000 + }, + { + "name": "Page E", + "uv": 1890, + "pv": 4800, + "amt": 2181 + }, + { + "name": "Page F", + "uv": 2390, + "pv": 3800, + "amt": 2500 + }, + { + "name": "Page G", + "uv": 3490, + "pv": 4300, + "amt": 2100 + } +] + +def bar_double(): + return rx.recharts.bar_chart( + rx.recharts.bar( + data_key="uv", + stroke=rx.color("accent", 9), + fill=rx.color("accent", 8), + ), + rx.recharts.bar( + data_key="pv", + stroke=rx.color("green", 9), + fill=rx.color("green", 8), + ), + rx.recharts.x_axis(data_key="name"), + rx.recharts.y_axis(), + data=data, + width = "100%", + height = 250, + ) +``` + +## Ranged Charts + +You can also assign a range in the bar by assigning the data_key in the `rx.recharts.bar` to a list with two elements, i.e. here a range of two temperatures for each date. + +```python demo graphing +range_data = [ + { + "day": "05-01", + "temperature": [ + -1, + 10 + ] + }, + { + "day": "05-02", + "temperature": [ + 2, + 15 + ] + }, + { + "day": "05-03", + "temperature": [ + 3, + 12 + ] + }, + { + "day": "05-04", + "temperature": [ + 4, + 12 + ] + }, + { + "day": "05-05", + "temperature": [ + 12, + 16 + ] + }, + { + "day": "05-06", + "temperature": [ + 5, + 16 + ] + }, + { + "day": "05-07", + "temperature": [ + 3, + 12 + ] + }, + { + "day": "05-08", + "temperature": [ + 0, + 8 + ] + }, + { + "day": "05-09", + "temperature": [ + -3, + 5 + ] + } +] + +def bar_range(): + return rx.recharts.bar_chart( + rx.recharts.bar( + data_key="temperature", + stroke=rx.color("accent", 9), + fill=rx.color("accent", 8), + ), + rx.recharts.x_axis(data_key="day"), + rx.recharts.y_axis(), + data=range_data, + width = "100%", + height = 250, + ) +``` + +## Stateful Charts + +Here is an example of a bar graph with a `State`. Here we have defined a function `randomize_data`, which randomly changes the data for both graphs when the first defined `bar` is clicked on using `on_click=BarState.randomize_data`. + +```python demo exec +class BarState(rx.State): + data = data + + @rx.event + def randomize_data(self): + for i in range(len(self.data)): + self.data[i]["uv"] = random.randint(0, 10000) + self.data[i]["pv"] = random.randint(0, 10000) + self.data[i]["amt"] = random.randint(0, 10000) + +def bar_with_state(): + return rx.recharts.bar_chart( + rx.recharts.cartesian_grid( + stroke_dasharray="3 3", + ), + rx.recharts.bar( + data_key="uv", + stroke=rx.color("accent", 9), + fill=rx.color("accent", 8), + ), + rx.recharts.bar( + data_key="pv", + stroke=rx.color("green", 9), + fill=rx.color("green", 8), + ), + rx.recharts.x_axis(data_key="name"), + rx.recharts.y_axis(), + rx.recharts.legend(), + on_click=BarState.randomize_data, + data=BarState.data, + width = "100%", + height = 300, + ) +``` + +## Example with Props + +Here's an example demonstrates how to customize the appearance and layout of bars using the `bar_category_gap`, `bar_gap`, `bar_size`, and `max_bar_size` props. These props accept values in pixels to control the spacing and size of the bars. + +```python demo graphing + +data = [ + {'name': 'Page A', 'value': 2400}, + {'name': 'Page B', 'value': 1398}, + {'name': 'Page C', 'value': 9800}, + {'name': 'Page D', 'value': 3908}, + {'name': 'Page E', 'value': 4800}, + {'name': 'Page F', 'value': 3800}, +] + +def bar_features(): + return rx.recharts.bar_chart( + rx.recharts.bar( + data_key="value", + fill=rx.color("accent", 8), + ), + rx.recharts.x_axis(data_key="name"), + rx.recharts.y_axis(), + data=data, + bar_category_gap="15%", + bar_gap=6, + bar_size=100, + max_bar_size=40, + width="100%", + height=300, + ) +``` + +## Vertical Example + +The `layout` prop allows you to set the orientation of the graph to be vertical or horizontal, it is set horizontally by default. + +```md alert info +# Include margins around your graph to ensure proper spacing and enhance readability. By default, provide margins on all sides of the chart to create a visually appealing and functional representation of your data. +``` + +```python demo graphing +data = [ + { + "name": "Page A", + "uv": 4000, + "pv": 2400, + "amt": 2400 + }, + { + "name": "Page B", + "uv": 3000, + "pv": 1398, + "amt": 2210 + }, + { + "name": "Page C", + "uv": 2000, + "pv": 9800, + "amt": 2290 + }, + { + "name": "Page D", + "uv": 2780, + "pv": 3908, + "amt": 2000 + }, + { + "name": "Page E", + "uv": 1890, + "pv": 4800, + "amt": 2181 + }, + { + "name": "Page F", + "uv": 2390, + "pv": 3800, + "amt": 2500 + }, + { + "name": "Page G", + "uv": 3490, + "pv": 4300, + "amt": 2100 + } +] + +def bar_vertical(): + return rx.recharts.bar_chart( + rx.recharts.bar( + data_key="uv", + stroke=rx.color("accent", 8), + fill=rx.color("accent", 3), + ), + rx.recharts.x_axis(type_="number"), + rx.recharts.y_axis(data_key="name", type_="category"), + data=data, + layout="vertical", + margin={ + "top": 20, + "right": 20, + "left": 20, + "bottom": 20 + }, + width = "100%", + height = 300, + + ) +``` + +To learn how to use the `sync_id`, `stack_id`,`x_axis_id` and `y_axis_id` props check out the of the area chart [documentation]({library.graphing.charts.areachart.path}), where these props are all described with examples. diff --git a/docs/library/graphing/charts/composedchart.md b/docs/library/graphing/charts/composedchart.md new file mode 100644 index 00000000000..f69ed43145e --- /dev/null +++ b/docs/library/graphing/charts/composedchart.md @@ -0,0 +1,90 @@ +--- +components: + - rx.recharts.ComposedChart +--- + +```python exec +import reflex as rx +from pcweb.pages.docs import library +``` +# Composed Chart + +A `composed_chart` is a higher-level component chart that is composed of multiple charts, where other charts are the children of the `composed_chart`. The charts are placed on top of each other in the order they are provided in the `composed_chart` function. + + +```md alert info +# To learn more about individual charts, checkout: **[area_chart]({library.graphing.charts.areachart.path})**, **[line_chart]({library.graphing.charts.linechart.path})**, or **[bar_chart]({library.graphing.charts.barchart.path})**. +``` + +```python demo graphing +data = [ + { + "name": "Page A", + "uv": 4000, + "pv": 2400, + "amt": 2400 + }, + { + "name": "Page B", + "uv": 3000, + "pv": 1398, + "amt": 2210 + }, + { + "name": "Page C", + "uv": 2000, + "pv": 9800, + "amt": 2290 + }, + { + "name": "Page D", + "uv": 2780, + "pv": 3908, + "amt": 2000 + }, + { + "name": "Page E", + "uv": 1890, + "pv": 4800, + "amt": 2181 + }, + { + "name": "Page F", + "uv": 2390, + "pv": 3800, + "amt": 2500 + }, + { + "name": "Page G", + "uv": 3490, + "pv": 4300, + "amt": 2100 + } +] + +def composed(): + return rx.recharts.composed_chart( + rx.recharts.area( + data_key="uv", + stroke="#8884d8", + fill="#8884d8" + ), + rx.recharts.bar( + data_key="amt", + bar_size=20, + fill="#413ea0" + ), + rx.recharts.line( + data_key="pv", + type_="monotone", + stroke="#ff7300" + ), + rx.recharts.x_axis(data_key="name"), + rx.recharts.y_axis(), + rx.recharts.cartesian_grid(stroke_dasharray="3 3"), + rx.recharts.graphing_tooltip(), + data=data, + height=250, + width="100%", + ) +``` diff --git a/docs/library/graphing/charts/errorbar.md b/docs/library/graphing/charts/errorbar.md new file mode 100644 index 00000000000..2e4b3bdd3db --- /dev/null +++ b/docs/library/graphing/charts/errorbar.md @@ -0,0 +1,101 @@ +--- +components: + - rx.recharts.ErrorBar +--- + +```python exec +import reflex as rx +``` + +# Error Bar + +An error bar is a graphical representation of the uncertainty or variability of a data point in a chart, depicted as a line extending from the data point parallel to one of the axes. The `data_key`, `width`, `stroke_width`, `stroke`, and `direction` props can be used to customize the appearance and behavior of the error bars, specifying the data source, dimensions, color, and orientation of the error bars. + +```python demo graphing +data = [ + { + "x": 45, + "y": 100, + "z": 150, + "errorY": [ + 30, + 20 + ], + "errorX": 5 + }, + { + "x": 100, + "y": 200, + "z": 200, + "errorY": [ + 20, + 30 + ], + "errorX": 3 + }, + { + "x": 120, + "y": 100, + "z": 260, + "errorY": 20, + "errorX": [ + 5, + 3 + ] + }, + { + "x": 170, + "y": 300, + "z": 400, + "errorY": [ + 15, + 18 + ], + "errorX": 4 + }, + { + "x": 140, + "y": 250, + "z": 280, + "errorY": 23, + "errorX": [ + 6, + 7 + ] + }, + { + "x": 150, + "y": 400, + "z": 500, + "errorY": [ + 21, + 10 + ], + "errorX": 4 + }, + { + "x": 110, + "y": 280, + "z": 200, + "errorY": 21, + "errorX": [ + 5, + 6 + ] + } +] + +def error(): + return rx.recharts.scatter_chart( + rx.recharts.scatter( + rx.recharts.error_bar(data_key="errorY", direction="y", width=4, stroke_width=2, stroke="red"), + rx.recharts.error_bar(data_key="errorX", direction="x", width=4, stroke_width=2), + data=data, + fill="#8884d8", + name="A"), + rx.recharts.x_axis(data_key="x", name="x", type_="number"), + rx.recharts.y_axis(data_key="y", name="y", type_="number"), + width = "100%", + height = 300, + ) +``` diff --git a/docs/library/graphing/charts/funnelchart.md b/docs/library/graphing/charts/funnelchart.md new file mode 100644 index 00000000000..69e98f84d26 --- /dev/null +++ b/docs/library/graphing/charts/funnelchart.md @@ -0,0 +1,210 @@ +--- +components: + - rx.recharts.FunnelChart + - rx.recharts.Funnel +--- + +```python exec +import reflex as rx +import random +rx.toast.provider() +``` + +# Funnel Chart + +A funnel chart is a graphical representation used to visualize how data moves through a process. In a funnel chart, the dependent variable’s value diminishes in the subsequent stages of the process. It can be used to demonstrate the flow of users through a business or sales process. + +## Simple Example + +```python demo graphing +data = [ + { + "value": 100, + "name": "Sent", + "fill": "#8884d8" + }, + { + "value": 80, + "name": "Viewed", + "fill": "#83a6ed" + }, + { + "value": 50, + "name": "Clicked", + "fill": "#8dd1e1" + }, + { + "value": 40, + "name": "Add to Cart", + "fill": "#82ca9d" + }, + { + "value": 26, + "name": "Purchased", + "fill": "#a4de6c" + } +] + +def funnel_simple(): + return rx.recharts.funnel_chart( + rx.recharts.funnel( + rx.recharts.label_list( + position="right", + data_key="name", + fill="#000", + stroke="none", + ), + data_key="value", + data=data, + ), + width="100%", + height=250, + ) +``` + +## Event Triggers + +Funnel chart supports `on_click`, `on_mouse_enter`, `on_mouse_leave` and `on_mouse_move` event triggers, allows you to interact with the funnel chart and perform specific actions based on user interactions. + +```python demo graphing +data = [ + { + "value": 100, + "name": "Sent", + "fill": "#8884d8" + }, + { + "value": 80, + "name": "Viewed", + "fill": "#83a6ed" + }, + { + "value": 50, + "name": "Clicked", + "fill": "#8dd1e1" + }, + { + "value": 40, + "name": "Add to Cart", + "fill": "#82ca9d" + }, + { + "value": 26, + "name": "Purchased", + "fill": "#a4de6c" + } +] + +def funnel_events(): + return rx.recharts.funnel_chart( + rx.recharts.funnel( + rx.recharts.label_list( + position="right", + data_key="name", + fill="#000", + stroke="none", + ), + data_key="value", + data=data, + ), + on_click=rx.toast("Clicked on funnel chart"), + on_mouse_enter=rx.toast("Mouse entered"), + on_mouse_leave=rx.toast("Mouse left"), + width="100%", + height=250, + ) +``` + +## Dynamic Data + +Here is an example of a funnel chart with a `State`. Here we have defined a function `randomize_data`, which randomly changes the data when the graph is clicked on using `on_click=FunnelState.randomize_data`. + +```python exec +data = [ + { + "value": 100, + "name": "Sent", + "fill": "#8884d8" + }, + { + "value": 80, + "name": "Viewed", + "fill": "#83a6ed" + }, + { + "value": 50, + "name": "Clicked", + "fill": "#8dd1e1" + }, + { + "value": 40, + "name": "Add to Cart", + "fill": "#82ca9d" + }, + { + "value": 26, + "name": "Purchased", + "fill": "#a4de6c" + } +] +``` + +```python demo exec +class FunnelState(rx.State): + data = data + + @rx.event + def randomize_data(self): + self.data[0]["value"] = 100 + for i in range(len(self.data) - 1): + self.data[i + 1]["value"] = self.data[i][ + "value" + ] - random.randint(0, 20) + + +def funnel_state(): + return rx.recharts.funnel_chart( + rx.recharts.funnel( + rx.recharts.label_list( + position="right", + data_key="name", + fill="#000", + stroke="none", + ), + data_key="value", + data=FunnelState.data, + on_click=FunnelState.randomize_data, + ), + rx.recharts.graphing_tooltip(), + width="100%", + height=250, + ) +``` + +## Changing the Chart Animation + +The `is_animation_active` prop can be used to turn off the animation, but defaults to `True`. `animation_begin` sets the delay before animation starts, `animation_duration` determines how long the animation lasts, and `animation_easing` defines the speed curve of the animation for smooth transitions. + +```python demo graphing +data = [ + {"name": "Visits", "value": 5000, "fill": "#8884d8"}, + {"name": "Cart", "value": 3000, "fill": "#83a6ed"}, + {"name": "Checkout", "value": 2500, "fill": "#8dd1e1"}, + {"name": "Purchase", "value": 1000, "fill": "#82ca9d"}, + ] + +def funnel_animation(): + return rx.recharts.funnel_chart( + rx.recharts.funnel( + data_key="value", + data=data, + animation_begin=300, + animation_duration=9000, + animation_easing="ease-in-out", + ), + rx.recharts.graphing_tooltip(), + rx.recharts.legend(), + width="100%", + height=300, + ) +``` diff --git a/docs/library/graphing/charts/linechart.md b/docs/library/graphing/charts/linechart.md new file mode 100644 index 00000000000..4df06cd466d --- /dev/null +++ b/docs/library/graphing/charts/linechart.md @@ -0,0 +1,309 @@ +--- +components: + - rx.recharts.LineChart + - rx.recharts.Line +--- + +# Line Chart + +```python exec +import random +from typing import Any +from pcweb.pages.docs import library +import reflex as rx +``` + +A line chart is a type of chart used to show information that changes over time. Line charts are created by plotting a series of several points and connecting them with a straight line. + +## Simple Example + +For a line chart we must define an `rx.recharts.line()` component for each set of values we wish to plot. Each `rx.recharts.line()` component has a `data_key` which clearly states which variable in our data we are tracking. In this simple example we plot `pv` and `uv` as separate lines against the `name` column which we set as the `data_key` in `rx.recharts.x_axis`. + +```python demo graphing +data = [ + { + "name": "Page A", + "uv": 4000, + "pv": 2400, + "amt": 2400 + }, + { + "name": "Page B", + "uv": 3000, + "pv": 1398, + "amt": 2210 + }, + { + "name": "Page C", + "uv": 2000, + "pv": 9800, + "amt": 2290 + }, + { + "name": "Page D", + "uv": 2780, + "pv": 3908, + "amt": 2000 + }, + { + "name": "Page E", + "uv": 1890, + "pv": 4800, + "amt": 2181 + }, + { + "name": "Page F", + "uv": 2390, + "pv": 3800, + "amt": 2500 + }, + { + "name": "Page G", + "uv": 3490, + "pv": 4300, + "amt": 2100 + } +] + +def line_simple(): + return rx.recharts.line_chart( + rx.recharts.line( + data_key="pv", + ), + rx.recharts.line( + data_key="uv", + ), + rx.recharts.x_axis(data_key="name"), + rx.recharts.y_axis(), + data=data, + width = "100%", + height = 300, + ) +``` + +## Example with Props + +Our second example uses exactly the same data as our first example, except now we add some extra features to our line graphs. We add a `type_` prop to `rx.recharts.line` to style the lines differently. In addition, we add an `rx.recharts.cartesian_grid` to get a grid in the background, an `rx.recharts.legend` to give us a legend for our graphs and an `rx.recharts.graphing_tooltip` to add a box with text that appears when you pause the mouse pointer on an element in the graph. + +```python demo graphing + +data = [ + { + "name": "Page A", + "uv": 4000, + "pv": 2400, + "amt": 2400 + }, + { + "name": "Page B", + "uv": 3000, + "pv": 1398, + "amt": 2210 + }, + { + "name": "Page C", + "uv": 2000, + "pv": 9800, + "amt": 2290 + }, + { + "name": "Page D", + "uv": 2780, + "pv": 3908, + "amt": 2000 + }, + { + "name": "Page E", + "uv": 1890, + "pv": 4800, + "amt": 2181 + }, + { + "name": "Page F", + "uv": 2390, + "pv": 3800, + "amt": 2500 + }, + { + "name": "Page G", + "uv": 3490, + "pv": 4300, + "amt": 2100 + } +] + +def line_features(): + return rx.recharts.line_chart( + rx.recharts.line( + data_key="pv", + type_="monotone", + stroke="#8884d8",), + rx.recharts.line( + data_key="uv", + type_="monotone", + stroke="#82ca9d",), + rx.recharts.x_axis(data_key="name"), + rx.recharts.y_axis(), + rx.recharts.cartesian_grid(stroke_dasharray="3 3"), + rx.recharts.graphing_tooltip(), + rx.recharts.legend(), + data=data, + width = "100%", + height = 300, + ) +``` + +## Layout + +The `layout` prop allows you to set the orientation of the graph to be vertical or horizontal. The `margin` prop defines the spacing around the graph, + +```md alert info +# Include margins around your graph to ensure proper spacing and enhance readability. By default, provide margins on all sides of the chart to create a visually appealing and functional representation of your data. +``` + +```python demo graphing + +data = [ + { + "name": "Page A", + "uv": 4000, + "pv": 2400, + "amt": 2400 + }, + { + "name": "Page B", + "uv": 3000, + "pv": 1398, + "amt": 2210 + }, + { + "name": "Page C", + "uv": 2000, + "pv": 9800, + "amt": 2290 + }, + { + "name": "Page D", + "uv": 2780, + "pv": 3908, + "amt": 2000 + }, + { + "name": "Page E", + "uv": 1890, + "pv": 4800, + "amt": 2181 + }, + { + "name": "Page F", + "uv": 2390, + "pv": 3800, + "amt": 2500 + }, + { + "name": "Page G", + "uv": 3490, + "pv": 4300, + "amt": 2100 + } +] + +def line_vertical(): + return rx.recharts.line_chart( + rx.recharts.line( + data_key="pv", + stroke=rx.color("accent", 9), + ), + rx.recharts.line( + data_key="uv", + stroke=rx.color("green", 9), + ), + rx.recharts.x_axis(type_="number"), + rx.recharts.y_axis(data_key="name", type_="category"), + layout="vertical", + margin={ + "top": 20, + "right": 20, + "left": 20, + "bottom": 20 + }, + data = data, + height = 300, + width = "100%", + ) +``` + +## Dynamic Data + +Chart data can be modified by tying the `data` prop to a State var. Most other +props, such as `type_`, can be controlled dynamically as well. In the following +example the "Munge Data" button can be used to randomly modify the data, and the +two `select` elements change the line `type_`. Since the data and style is saved +in the per-browser-tab State, the changes will not be visible to other visitors. + +```python demo exec + +initial_data = data + +class LineChartState(rx.State): + data: list[dict[str, Any]] = initial_data + pv_type: str = "monotone" + uv_type: str = "monotone" + + @rx.event + def set_pv_type(self, pv_type: str): + self.pv_type = pv_type + + @rx.event + def set_uv_type(self, uv_type: str): + self.uv_type = uv_type + + @rx.event + def munge_data(self): + for row in self.data: + row["uv"] += random.randint(-500, 500) + row["pv"] += random.randint(-1000, 1000) + +def line_dynamic(): + return rx.vstack( + rx.recharts.line_chart( + rx.recharts.line( + data_key="pv", + type_=LineChartState.pv_type, + stroke="#8884d8", + ), + rx.recharts.line( + data_key="uv", + type_=LineChartState.uv_type, + stroke="#82ca9d", + ), + rx.recharts.x_axis(data_key="name"), + rx.recharts.y_axis(), + data=LineChartState.data, + margin={ + "top": 20, + "right": 20, + "left": 20, + "bottom": 20 + }, + width = "100%", + height = 300, + ), + rx.hstack( + rx.button("Munge Data", on_click=LineChartState.munge_data), + rx.select( + ["monotone", "linear", "step", "stepBefore", "stepAfter"], + value=LineChartState.pv_type, + on_change=LineChartState.set_pv_type + ), + rx.select( + ["monotone", "linear", "step", "stepBefore", "stepAfter"], + value=LineChartState.uv_type, + on_change=LineChartState.set_uv_type + ), + ), + width="100%", + ) +``` + +To learn how to use the `sync_id`, `x_axis_id` and `y_axis_id` props check out the of the area chart [documentation]({library.graphing.charts.areachart.path}), where these props are all described with examples. diff --git a/docs/library/graphing/charts/piechart.md b/docs/library/graphing/charts/piechart.md new file mode 100644 index 00000000000..999780ad72f --- /dev/null +++ b/docs/library/graphing/charts/piechart.md @@ -0,0 +1,216 @@ +--- +components: + - rx.recharts.PieChart + - rx.recharts.Pie +--- + +# Pie Chart + +```python exec +import reflex as rx +``` + +A pie chart is a circular statistical graphic which is divided into slices to illustrate numerical proportion. + +For a pie chart we must define an `rx.recharts.pie()` component for each set of values we wish to plot. Each `rx.recharts.pie()` component has a `data`, a `data_key` and a `name_key` which clearly states which data and which variables in our data we are tracking. In this simple example we plot `value` column as our `data_key` against the `name` column which we set as our `name_key`. +We also use the `fill` prop to set the color of the pie slices. + +```python demo graphing + +data01 = [ + { + "name": "Group A", + "value": 400 + }, + { + "name": "Group B", + "value": 300, + "fill":"#AC0E08FF" + }, + { + "name": "Group C", + "value": 300, + "fill":"rgb(80,40, 190)" + }, + { + "name": "Group D", + "value": 200, + "fill":rx.color("yellow", 10) + }, + { + "name": "Group E", + "value": 278, + "fill":"purple" + }, + { + "name": "Group F", + "value": 189, + "fill":"orange" + } +] + +def pie_simple(): + return rx.recharts.pie_chart( + rx.recharts.pie( + data=data01, + data_key="value", + name_key="name", + fill="#8884d8", + label=True, + ), + width="100%", + height=300, + ) +``` + +We can also add two pies on one chart by using two `rx.recharts.pie` components. + +In this example `inner_radius` and `outer_radius` props are used. They define the doughnut shape of a pie chart: `inner_radius` creates the hollow center (use "0%" for a full pie), while `outer_radius` sets the overall size. The `padding_angle` prop, used on the green pie below, adds space between pie slices, enhancing visibility of individual segments. + +```python demo graphing + +data01 = [ + { + "name": "Group A", + "value": 400 + }, + { + "name": "Group B", + "value": 300 + }, + { + "name": "Group C", + "value": 300 + }, + { + "name": "Group D", + "value": 200 + }, + { + "name": "Group E", + "value": 278 + }, + { + "name": "Group F", + "value": 189 + } +] +data02 = [ + { + "name": "Group A", + "value": 2400 + }, + { + "name": "Group B", + "value": 4567 + }, + { + "name": "Group C", + "value": 1398 + }, + { + "name": "Group D", + "value": 9800 + }, + { + "name": "Group E", + "value": 3908 + }, + { + "name": "Group F", + "value": 4800 + } +] + + +def pie_double(): + return rx.recharts.pie_chart( + rx.recharts.pie( + data=data01, + data_key="value", + name_key="name", + fill="#82ca9d", + inner_radius="60%", + padding_angle=5, + ), + rx.recharts.pie( + data=data02, + data_key="value", + name_key="name", + fill="#8884d8", + outer_radius="50%", + ), + rx.recharts.graphing_tooltip(), + width="100%", + height=300, + ) +``` + +## Dynamic Data + +Chart data tied to a State var causes the chart to automatically update when the +state changes, providing a nice way to visualize data in response to user +interface elements. View the "Data" tab to see the substate driving this +half-pie chart. + +```python demo exec +from typing import Any + + +class PieChartState(rx.State): + resources: list[dict[str, Any]] = [ + dict(type_="🏆", count=1), + dict(type_="🪵", count=1), + dict(type_="🥑", count=1), + dict(type_="🧱", count=1), + ] + + @rx.var(cache=True) + def resource_types(self) -> list[str]: + return [r["type_"] for r in self.resources] + + @rx.event + def increment(self, type_: str): + for resource in self.resources: + if resource["type_"] == type_: + resource["count"] += 1 + break + + @rx.event + def decrement(self, type_: str): + for resource in self.resources: + if resource["type_"] == type_ and resource["count"] > 0: + resource["count"] -= 1 + break + + +def dynamic_pie_example(): + return rx.hstack( + rx.recharts.pie_chart( + rx.recharts.pie( + data=PieChartState.resources, + data_key="count", + name_key="type_", + cx="50%", + cy="50%", + start_angle=180, + end_angle=0, + fill="#8884d8", + label=True, + ), + rx.recharts.graphing_tooltip(), + ), + rx.vstack( + rx.foreach( + PieChartState.resource_types, + lambda type_, i: rx.hstack( + rx.button("-", on_click=PieChartState.decrement(type_)), + rx.text(type_, PieChartState.resources[i]["count"]), + rx.button("+", on_click=PieChartState.increment(type_)), + ), + ), + ), + width="100%", + height="15em", + ) +``` diff --git a/docs/library/graphing/charts/radarchart.md b/docs/library/graphing/charts/radarchart.md new file mode 100644 index 00000000000..699ce1e2e33 --- /dev/null +++ b/docs/library/graphing/charts/radarchart.md @@ -0,0 +1,285 @@ +--- +components: + - rx.recharts.RadarChart + - rx.recharts.Radar +--- + +# Radar Chart + +```python exec +import reflex as rx +from typing import Any +``` + +A radar chart shows multivariate data of three or more quantitative variables mapped onto an axis. + +## Simple Example + +For a radar chart we must define an `rx.recharts.radar()` component for each set of values we wish to plot. Each `rx.recharts.radar()` component has a `data_key` which clearly states which variable in our data we are plotting. In this simple example we plot the `A` column of our data against the `subject` column which we set as the `data_key` in `rx.recharts.polar_angle_axis`. + +```python demo graphing +data = [ + { + "subject": "Math", + "A": 120, + "B": 110, + "fullMark": 150 + }, + { + "subject": "Chinese", + "A": 98, + "B": 130, + "fullMark": 150 + }, + { + "subject": "English", + "A": 86, + "B": 130, + "fullMark": 150 + }, + { + "subject": "Geography", + "A": 99, + "B": 100, + "fullMark": 150 + }, + { + "subject": "Physics", + "A": 85, + "B": 90, + "fullMark": 150 + }, + { + "subject": "History", + "A": 65, + "B": 85, + "fullMark": 150 + } +] + +def radar_simple(): + return rx.recharts.radar_chart( + rx.recharts.radar( + data_key="A", + stroke="#8884d8", + fill="#8884d8", + ), + rx.recharts.polar_grid(), + rx.recharts.polar_angle_axis(data_key="subject"), + rx.recharts.polar_radius_axis(angle=90, domain=[0, 150]), + data=data, + width="100%", + height=300, + ) +``` + +## Multiple Radars + +We can also add two radars on one chart by using two `rx.recharts.radar` components. + +In this plot an `inner_radius` and an `outer_radius` are set which determine the chart's size and shape. The `inner_radius` sets the distance from the center to the innermost part of the chart (creating a hollow center if greater than zero), while the `outer_radius` defines the chart's overall size by setting the distance from the center to the outermost edge of the radar plot. + +```python demo graphing + +data = [ + { + "subject": "Math", + "A": 120, + "B": 110, + "fullMark": 150 + }, + { + "subject": "Chinese", + "A": 98, + "B": 130, + "fullMark": 150 + }, + { + "subject": "English", + "A": 86, + "B": 130, + "fullMark": 150 + }, + { + "subject": "Geography", + "A": 99, + "B": 100, + "fullMark": 150 + }, + { + "subject": "Physics", + "A": 85, + "B": 90, + "fullMark": 150 + }, + { + "subject": "History", + "A": 65, + "B": 85, + "fullMark": 150 + } +] + +def radar_multiple(): + return rx.recharts.radar_chart( + rx.recharts.radar( + data_key="A", + stroke="#8884d8", + fill="#8884d8", + ), + rx.recharts.radar( + data_key="B", + stroke="#82ca9d", + fill="#82ca9d", + fill_opacity=0.6, + ), + rx.recharts.polar_grid(), + rx.recharts.polar_angle_axis(data_key="subject"), + rx.recharts.polar_radius_axis(angle=90, domain=[0, 150]), + rx.recharts.legend(), + data=data, + inner_radius="15%", + outer_radius="80%", + width="100%", + height=300, + ) + +``` + +## Using More Props + +The `dot` prop shows points at each data vertex when true. `legend_type="line"` displays a line in the chart legend. `animation_begin=0` starts the animation immediately, `animation_duration=8000` sets an 8-second animation, and `animation_easing="ease-in"` makes the animation start slowly and speed up. These props control the chart's appearance and animation behavior. + +```python demo graphing + +data = [ + { + "subject": "Math", + "A": 120, + "B": 110, + "fullMark": 150 + }, + { + "subject": "Chinese", + "A": 98, + "B": 130, + "fullMark": 150 + }, + { + "subject": "English", + "A": 86, + "B": 130, + "fullMark": 150 + }, + { + "subject": "Geography", + "A": 99, + "B": 100, + "fullMark": 150 + }, + { + "subject": "Physics", + "A": 85, + "B": 90, + "fullMark": 150 + }, + { + "subject": "History", + "A": 65, + "B": 85, + "fullMark": 150 + } + ] + + +def radar_start_end(): + return rx.recharts.radar_chart( + rx.recharts.radar( + data_key="A", + dot=True, + stroke="#8884d8", + fill="#8884d8", + fill_opacity=0.6, + legend_type="line", + animation_begin=0, + animation_duration=8000, + animation_easing="ease-in", + ), + rx.recharts.polar_grid(), + rx.recharts.polar_angle_axis(data_key="subject"), + rx.recharts.polar_radius_axis(angle=90, domain=[0, 150]), + rx.recharts.legend(), + data=data, + width="100%", + height=300, + ) + +``` + +# Dynamic Data + +Chart data tied to a State var causes the chart to automatically update when the +state changes, providing a nice way to visualize data in response to user +interface elements. View the "Data" tab to see the substate driving this +radar chart of character traits. + +```python demo exec +class RadarChartState(rx.State): + total_points: int = 100 + traits: list[dict[str, Any]] = [ + dict(trait="Strength", value=15), + dict(trait="Dexterity", value=15), + dict(trait="Constitution", value=15), + dict(trait="Intelligence", value=15), + dict(trait="Wisdom", value=15), + dict(trait="Charisma", value=15), + ] + + @rx.var + def remaining_points(self) -> int: + return self.total_points - sum(t["value"] for t in self.traits) + + @rx.var(cache=True) + def trait_names(self) -> list[str]: + return [t["trait"] for t in self.traits] + + @rx.event + def set_trait(self, trait: str, value: int): + for t in self.traits: + if t["trait"] == trait: + available_points = self.remaining_points + t["value"] + value = min(value, available_points) + t["value"] = value + break + +def radar_dynamic(): + return rx.hstack( + rx.recharts.radar_chart( + rx.recharts.radar( + data_key="value", + stroke="#8884d8", + fill="#8884d8", + ), + rx.recharts.polar_grid(), + rx.recharts.polar_angle_axis(data_key="trait"), + data=RadarChartState.traits, + ), + rx.vstack( + rx.foreach( + RadarChartState.trait_names, + lambda trait_name, i: rx.hstack( + rx.text(trait_name, width="7em"), + rx.slider( + default_value=RadarChartState.traits[i]["value"].to(int), + on_change=lambda value: RadarChartState.set_trait(trait_name, value[0]), + width="25vw", + ), + rx.text(RadarChartState.traits[i]['value']), + ), + ), + rx.text("Remaining points: ", RadarChartState.remaining_points), + ), + width="100%", + height="15em", + ) +``` diff --git a/docs/library/graphing/charts/radialbarchart.md b/docs/library/graphing/charts/radialbarchart.md new file mode 100644 index 00000000000..136105967d6 --- /dev/null +++ b/docs/library/graphing/charts/radialbarchart.md @@ -0,0 +1,109 @@ +--- +components: + - rx.recharts.RadialBarChart +--- + +# Radial Bar Chart + +```python exec +import reflex as rx +``` +## Simple Example + +This example demonstrates how to use a `radial_bar_chart` with a `radial_bar`. The `radial_bar_chart` takes in `data` and then the `radial_bar` takes in a `data_key`. A radial bar chart is a circular visualization where data categories are represented by bars extending outward from a central point, with the length of each bar proportional to its value. + +```md alert info +# Fill color supports `rx.color()`, which automatically adapts to dark/light mode changes. +``` + +```python demo graphing +data = [ + {"name": "C", "x": 3, "fill": rx.color("cyan", 9)}, + {"name": "D", "x": 4, "fill": rx.color("blue", 9)}, + {"name": "E", "x": 5, "fill": rx.color("orange", 9)}, + {"name": "F", "x": 6, "fill": rx.color("red", 9)}, + {"name": "G", "x": 7, "fill": rx.color("gray", 9)}, + {"name": "H", "x": 8, "fill": rx.color("green", 9)}, + {"name": "I", "x": 9, "fill": rx.color("accent", 6)}, +] + +def radial_bar_simple(): + return rx.recharts.radial_bar_chart( + rx.recharts.radial_bar( + data_key="x", + min_angle=15, + ), + data=data, + width = "100%", + height = 500, + ) +``` + +## Advanced Example + +The `start_angle` and `end_angle` define the circular arc over which the bars are distributed, while `inner_radius` and `outer_radius` determine the radial extent of the bars from the center. + +```python demo graphing + +data_radial_bar = [ + { + "name": "18-24", + "uv": 31.47, + "pv": 2400, + "fill": "#8884d8" + }, + { + "name": "25-29", + "uv": 26.69, + "pv": 4567, + "fill": "#83a6ed" + }, + { + "name": "30-34", + "uv": -15.69, + "pv": 1398, + "fill": "#8dd1e1" + }, + { + "name": "35-39", + "uv": 8.22, + "pv": 9800, + "fill": "#82ca9d" + }, + { + "name": "40-49", + "uv": -8.63, + "pv": 3908, + "fill": "#a4de6c" + }, + { + "name": "50+", + "uv": -2.63, + "pv": 4800, + "fill": "#d0ed57" + }, + { + "name": "unknown", + "uv": 6.67, + "pv": 4800, + "fill": "#ffc658" + } +] + +def radial_bar_advanced(): + return rx.recharts.radial_bar_chart( + rx.recharts.radial_bar( + data_key="uv", + min_angle=90, + background=True, + label={"fill": '#666', "position": 'insideStart'}, + ), + data=data_radial_bar, + inner_radius="10%", + outer_radius="80%", + start_angle=180, + end_angle=0, + width="100%", + height=300, + ) +``` \ No newline at end of file diff --git a/docs/library/graphing/charts/scatterchart.md b/docs/library/graphing/charts/scatterchart.md new file mode 100644 index 00000000000..03afdd264b8 --- /dev/null +++ b/docs/library/graphing/charts/scatterchart.md @@ -0,0 +1,296 @@ +--- +components: + - rx.recharts.ScatterChart + - rx.recharts.Scatter +--- + +# Scatter Chart + +```python exec +import reflex as rx +from pcweb.templates.docpage import docgraphing +from pcweb.pages.docs import library +``` + +A scatter chart always has two value axes to show one set of numerical data along a horizontal (value) axis and another set of numerical values along a vertical (value) axis. The chart displays points at the intersection of an x and y numerical value, combining these values into single data points. + +## Simple Example + +For a scatter chart we must define an `rx.recharts.scatter()` component for each set of values we wish to plot. Each `rx.recharts.scatter()` component has a `data` prop which clearly states which data source we plot. We also must define `rx.recharts.x_axis()` and `rx.recharts.y_axis()` so that the graph knows what data to plot on each axis. + +```python demo graphing +data01 = [ + { + "x": 100, + "y": 200, + "z": 200 + }, + { + "x": 120, + "y": 100, + "z": 260 + }, + { + "x": 170, + "y": 300, + "z": 400 + }, + { + "x": 170, + "y": 250, + "z": 280 + }, + { + "x": 150, + "y": 400, + "z": 500 + }, + { + "x": 110, + "y": 280, + "z": 200 + } +] + +def scatter_simple(): + return rx.recharts.scatter_chart( + rx.recharts.scatter( + data=data01, + fill="#8884d8",), + rx.recharts.x_axis(data_key="x", type_="number"), + rx.recharts.y_axis(data_key="y"), + width = "100%", + height = 300, + ) +``` + +## Multiple Scatters + +We can also add two scatters on one chart by using two `rx.recharts.scatter()` components, and we can define an `rx.recharts.z_axis()` which represents a third column of data and is represented by the size of the dots in the scatter plot. + +```python demo graphing +data01 = [ + { + "x": 100, + "y": 200, + "z": 200 + }, + { + "x": 120, + "y": 100, + "z": 260 + }, + { + "x": 170, + "y": 300, + "z": 400 + }, + { + "x": 170, + "y": 250, + "z": 280 + }, + { + "x": 150, + "y": 350, + "z": 500 + }, + { + "x": 110, + "y": 280, + "z": 200 + } +] + +data02 = [ + { + "x": 200, + "y": 260, + "z": 240 + }, + { + "x": 240, + "y": 290, + "z": 220 + }, + { + "x": 190, + "y": 290, + "z": 250 + }, + { + "x": 198, + "y": 250, + "z": 210 + }, + { + "x": 180, + "y": 280, + "z": 260 + }, + { + "x": 210, + "y": 220, + "z": 230 + } +] + +def scatter_double(): + return rx.recharts.scatter_chart( + rx.recharts.scatter( + data=data01, + fill="#8884d8", + name="A" + ), + rx.recharts.scatter( + data=data02, + fill="#82ca9d", + name="B" + ), + rx.recharts.cartesian_grid(stroke_dasharray="3 3"), + rx.recharts.x_axis(data_key="x", type_="number"), + rx.recharts.y_axis(data_key="y"), + rx.recharts.z_axis(data_key="z", range=[60, 400], name="score"), + rx.recharts.legend(), + rx.recharts.graphing_tooltip(), + width="100%", + height=300, + ) +``` + +To learn how to use the `x_axis_id` and `y_axis_id` props, check out the Multiple Axis section of the area chart [documentation]({library.graphing.charts.areachart.path}). + +## Dynamic Data + +Chart data tied to a State var causes the chart to automatically update when the +state changes, providing a nice way to visualize data in response to user +interface elements. View the "Data" tab to see the substate driving this +calculation of iterations in the Collatz Conjecture for a given starting number. +Enter a starting number in the box below the chart to recalculate. + +```python demo exec +class ScatterChartState(rx.State): + data: list[dict[str, int]] = [] + + @rx.event + def compute_collatz(self, form_data: dict) -> int: + n = int(form_data.get("start") or 1) + yield rx.set_value("start", "") + self.data = [] + for ix in range(400): + self.data.append({"x": ix, "y": n}) + if n == 1: + break + if n % 2 == 0: + n = n // 2 + else: + n = 3 * n + 1 + + +def scatter_dynamic(): + return rx.vstack( + rx.recharts.scatter_chart( + rx.recharts.scatter( + data=ScatterChartState.data, + fill="#8884d8", + ), + rx.recharts.x_axis(data_key="x", type_="number"), + rx.recharts.y_axis(data_key="y", type_="number"), + ), + rx.form.root( + rx.input(placeholder="Enter a number", id="start"), + rx.button("Compute", type="submit"), + on_submit=ScatterChartState.compute_collatz, + ), + width="100%", + height="15em", + on_mount=ScatterChartState.compute_collatz({"start": "15"}), + ) +``` + +## Legend Type and Shape + +```python demo exec +class ScatterChartState2(rx.State): + + legend_types: list[str] = ["square", "circle", "cross", "diamond", "star", "triangle", "wye"] + + legend_type: str = "circle" + + shapes: list[str] = ["square", "circle", "cross", "diamond", "star", "triangle", "wye"] + + shape: str = "circle" + + data01 = [ + { + "x": 100, + "y": 200, + "z": 200 + }, + { + "x": 120, + "y": 100, + "z": 260 + }, + { + "x": 170, + "y": 300, + "z": 400 + }, + { + "x": 170, + "y": 250, + "z": 280 + }, + { + "x": 150, + "y": 400, + "z": 500 + }, + { + "x": 110, + "y": 280, + "z": 200 + } + ] + + @rx.event + def set_shape(self, shape: str): + self.shape = shape + + @rx.event + def set_legend_type(self, legend_type: str): + self.legend_type = legend_type + +def scatter_shape(): + return rx.vstack( + rx.recharts.scatter_chart( + rx.recharts.scatter( + data=data01, + fill="#8884d8", + legend_type=ScatterChartState2.legend_type, + shape=ScatterChartState2.shape, + ), + rx.recharts.x_axis(data_key="x", type_="number"), + rx.recharts.y_axis(data_key="y"), + rx.recharts.legend(), + width = "100%", + height = 300, + ), + rx.hstack( + rx.text("Legend Type: "), + rx.select( + ScatterChartState2.legend_types, + value=ScatterChartState2.legend_type, + on_change=ScatterChartState2.set_legend_type, + ), + rx.text("Shape: "), + rx.select( + ScatterChartState2.shapes, + value=ScatterChartState2.shape, + on_change=ScatterChartState2.set_shape, + ), + ), + width="100%", + ) +``` diff --git a/docs/library/graphing/general/axis.md b/docs/library/graphing/general/axis.md new file mode 100644 index 00000000000..4c2d3d0b75f --- /dev/null +++ b/docs/library/graphing/general/axis.md @@ -0,0 +1,296 @@ +--- +components: + - rx.recharts.XAxis + - rx.recharts.YAxis + - rx.recharts.ZAxis +--- + +```python exec +import reflex as rx +``` + +# Axis + +The Axis component in Recharts is a powerful tool for customizing and configuring the axes of your charts. It provides a wide range of props that allow you to control the appearance, behavior, and formatting of the axis. Whether you're working with an AreaChart, LineChart, or any other chart type, the Axis component enables you to create precise and informative visualizations. + +## Basic Example + +```python demo graphing + +data = [ + { + "name": "Page A", + "uv": 4000, + "pv": 2400, + "amt": 2400 + }, + { + "name": "Page B", + "uv": 3000, + "pv": 1398, + "amt": 2210 + }, + { + "name": "Page C", + "uv": 2000, + "pv": 9800, + "amt": 2290 + }, + { + "name": "Page D", + "uv": 2780, + "pv": 3908, + "amt": 2000 + }, + { + "name": "Page E", + "uv": 1890, + "pv": 4800, + "amt": 2181 + }, + { + "name": "Page F", + "uv": 2390, + "pv": 3800, + "amt": 2500 + }, + { + "name": "Page G", + "uv": 3490, + "pv": 4300, + "amt": 2100 + } +] + +def axis_simple(): + return rx.recharts.area_chart( + rx.recharts.area( + data_key="uv", + stroke=rx.color("accent", 9), + fill=rx.color("accent", 8), + ), + rx.recharts.x_axis( + data_key="name", + label={"value": 'Pages', "position": "bottom"}, + ), + rx.recharts.y_axis( + data_key="uv", + label={"value": 'Views', "angle": -90, "position": "left"}, + ), + data=data, + width="100%", + height=300, + margin={ + "bottom": 40, + "left": 40, + "right": 40, + }, + ) +``` + +## Multiple Axes + +Multiple axes can be used for displaying different data series with varying scales or units on the same chart. This allows for a more comprehensive comparison and analysis of the data. + +```python demo graphing + +data = [ + { + "name": "Page A", + "uv": 4000, + "pv": 2400, + "amt": 2400 + }, + { + "name": "Page B", + "uv": 3000, + "pv": 1398, + "amt": 2210 + }, + { + "name": "Page C", + "uv": 2000, + "pv": 9800, + "amt": 2290 + }, + { + "name": "Page D", + "uv": 2780, + "pv": 3908, + "amt": 2000 + }, + { + "name": "Page E", + "uv": 1890, + "pv": 4800, + "amt": 2181 + }, + { + "name": "Page F", + "uv": 2390, + "pv": 3800, + "amt": 2500 + }, + { + "name": "Page G", + "uv": 3490, + "pv": 4300, + "amt": 2100 + } +] + +def multi_axis(): + return rx.recharts.area_chart( + rx.recharts.area( + data_key="uv", stroke="#8884d8", fill="#8884d8", y_axis_id="left", + ), + rx.recharts.area( + data_key="pv", y_axis_id="right", type_="monotone", stroke="#82ca9d", fill="#82ca9d" + ), + rx.recharts.x_axis(data_key="name"), + rx.recharts.y_axis(data_key="uv", y_axis_id="left"), + rx.recharts.y_axis(data_key="pv", y_axis_id="right", orientation="right"), + rx.recharts.graphing_tooltip(), + rx.recharts.legend(), + data=data, + width = "100%", + height = 300, + ) +``` + +## Choosing Location of Labels for Axes + +The axes `label` can take several positions. The example below allows you to try out different locations for the x and y axis labels. + +```python demo graphing + +class AxisState(rx.State): + + label_positions: list[str] = ["center", "insideTopLeft", "insideTopRight", "insideBottomRight", "insideBottomLeft", "insideTop", "insideBottom", "insideLeft", "insideRight", "outside", "top", "bottom", "left", "right"] + + label_offsets: list[str] = ["-30", "-20", "-10", "0", "10", "20", "30"] + + x_axis_postion: str = "bottom" + + x_axis_offset: int + + y_axis_postion: str = "left" + + y_axis_offset: int + + @rx.event + @rx.event + def set_y_axis_position(self, position: str): + self.y_axis_position = position + + @rx.event + def set_x_axis_position(self, position: str): + self.x_axis_position = position + + @rx.event + def set_x_axis_offset(self, offset: str): + self.x_axis_offset = int(offset) + + @rx.event + def set_y_axis_offset(self, offset: str): + self.y_axis_offset = int(offset) + +data = [ + { + "name": "Page A", + "uv": 4000, + "pv": 2400, + "amt": 2400 + }, + { + "name": "Page B", + "uv": 3000, + "pv": 1398, + "amt": 2210 + }, + { + "name": "Page C", + "uv": 2000, + "pv": 9800, + "amt": 2290 + }, + { + "name": "Page D", + "uv": 2780, + "pv": 3908, + "amt": 2000 + }, + { + "name": "Page E", + "uv": 1890, + "pv": 4800, + "amt": 2181 + }, + { + "name": "Page F", + "uv": 2390, + "pv": 3800, + "amt": 2500 + }, + { + "name": "Page G", + "uv": 3490, + "pv": 4300, + "amt": 2100 + } +] + +def axis_labels(): + return rx.vstack( + rx.recharts.area_chart( + rx.recharts.area( + data_key="uv", + stroke=rx.color("accent", 9), + fill=rx.color("accent", 8), + ), + rx.recharts.x_axis( + data_key="name", + label={"value": 'Pages', "position": AxisState.x_axis_postion, "offset": AxisState.x_axis_offset}, + ), + rx.recharts.y_axis( + data_key="uv", + label={"value": 'Views', "angle": -90, "position": AxisState.y_axis_postion, "offset": AxisState.y_axis_offset}, + ), + data=data, + width="100%", + height=300, + margin={ + "bottom": 40, + "left": 40, + "right": 40, + } + ), + rx.hstack( + rx.text("X Label Position: "), + rx.select( + AxisState.label_positions, + value=AxisState.x_axis_postion, + on_change=AxisState.set_x_axis_postion, + ), + rx.text("X Label Offset: "), + rx.select( + AxisState.label_offsets, + value=AxisState.x_axis_offset.to_string(), + on_change=AxisState.set_x_axis_offset, + ), + rx.text("Y Label Position: "), + rx.select( + AxisState.label_positions, + value=AxisState.y_axis_postion, + on_change=AxisState.set_y_axis_postion, + ), + rx.text("Y Label Offset: "), + rx.select( + AxisState.label_offsets, + value=AxisState.y_axis_offset.to_string(), + on_change=AxisState.set_y_axis_offset, + ), + ), + width="100%", + ) +``` diff --git a/docs/library/graphing/general/brush.md b/docs/library/graphing/general/brush.md new file mode 100644 index 00000000000..62fcc666087 --- /dev/null +++ b/docs/library/graphing/general/brush.md @@ -0,0 +1,153 @@ +--- +components: + - rx.recharts.Brush +--- + +# Brush + +```python exec +import reflex as rx +``` +## Simple Example + +The brush component allows us to view charts that have a large number of data points. To view and analyze them efficiently, the brush provides a slider with two handles that helps the viewer to select some range of data points to be displayed. + +```python demo graphing +data = [ + { "name": '1', "uv": 300, "pv": 456 }, + { "name": '2', "uv": -145, "pv": 230 }, + { "name": '3', "uv": -100, "pv": 345 }, + { "name": '4', "uv": -8, "pv": 450 }, + { "name": '5', "uv": 100, "pv": 321 }, + { "name": '6', "uv": 9, "pv": 235 }, + { "name": '7', "uv": 53, "pv": 267 }, + { "name": '8', "uv": 252, "pv": -378 }, + { "name": '9', "uv": 79, "pv": -210 }, + { "name": '10', "uv": 294, "pv": -23 }, + { "name": '12', "uv": 43, "pv": 45 }, + { "name": '13', "uv": -74, "pv": 90 }, + { "name": '14', "uv": -71, "pv": 130 }, + { "name": '15', "uv": -117, "pv": 11 }, + { "name": '16', "uv": -186, "pv": 107 }, + { "name": '17', "uv": -16, "pv": 926 }, + { "name": '18', "uv": -125, "pv": 653 }, + { "name": '19', "uv": 222, "pv": 366 }, + { "name": '20', "uv": 372, "pv": 486 }, + { "name": '21', "uv": 182, "pv": 512 }, + { "name": '22', "uv": 164, "pv": 302 }, + { "name": '23', "uv": 316, "pv": 425 }, + { "name": '24', "uv": 131, "pv": 467 }, + { "name": '25', "uv": 291, "pv": -190 }, + { "name": '26', "uv": -47, "pv": 194 }, + { "name": '27', "uv": -415, "pv": 371 }, + { "name": '28', "uv": -182, "pv": 376 }, + { "name": '29', "uv": -93, "pv": 295 }, + { "name": '30', "uv": -99, "pv": 322 }, + { "name": '31', "uv": -52, "pv": 246 }, + { "name": '32', "uv": 154, "pv": 33 }, + { "name": '33', "uv": 205, "pv": 354 }, + { "name": '34', "uv": 70, "pv": 258 }, + { "name": '35', "uv": -25, "pv": 359 }, + { "name": '36', "uv": -59, "pv": 192 }, + { "name": '37', "uv": -63, "pv": 464 }, + { "name": '38', "uv": -91, "pv": -2 }, + { "name": '39', "uv": -66, "pv": 154 }, + { "name": '40', "uv": -50, "pv": 186 }, +] + +def brush_simple(): + return rx.recharts.bar_chart( + rx.recharts.bar( + data_key="uv", + stroke="#8884d8", + fill="#8884d8" + ), + rx.recharts.bar( + data_key="pv", + stroke="#82ca9d", + fill="#82ca9d" + ), + rx.recharts.brush(data_key="name", height=30, stroke="#8884d8"), + rx.recharts.x_axis(data_key="name"), + rx.recharts.y_axis(), + data=data, + width="100%", + height=300, + ) +``` + +## Position, Size, and Range + +This example showcases ways to set the Position, Size, and Range. The `gap` prop provides the spacing between stops on the brush when the graph will refresh. The `start_index` and `end_index` props defines the default range of the brush. `traveller_width` prop specifies the width of each handle ("traveller" in recharts lingo). + +```python demo graphing +data = [ + { "name": '1', "uv": 300, "pv": 456 }, + { "name": '2', "uv": -145, "pv": 230 }, + { "name": '3', "uv": -100, "pv": 345 }, + { "name": '4', "uv": -8, "pv": 450 }, + { "name": '5', "uv": 100, "pv": 321 }, + { "name": '6', "uv": 9, "pv": 235 }, + { "name": '7', "uv": 53, "pv": 267 }, + { "name": '8', "uv": 252, "pv": -378 }, + { "name": '9', "uv": 79, "pv": -210 }, + { "name": '10', "uv": 294, "pv": -23 }, + { "name": '12', "uv": 43, "pv": 45 }, + { "name": '13', "uv": -74, "pv": 90 }, + { "name": '14', "uv": -71, "pv": 130 }, + { "name": '15', "uv": -117, "pv": 11 }, + { "name": '16', "uv": -186, "pv": 107 }, + { "name": '17', "uv": -16, "pv": 926 }, + { "name": '18', "uv": -125, "pv": 653 }, + { "name": '19', "uv": 222, "pv": 366 }, + { "name": '20', "uv": 372, "pv": 486 }, + { "name": '21', "uv": 182, "pv": 512 }, + { "name": '22', "uv": 164, "pv": 302 }, + { "name": '23', "uv": 316, "pv": 425 }, + { "name": '24', "uv": 131, "pv": 467 }, + { "name": '25', "uv": 291, "pv": -190 }, + { "name": '26', "uv": -47, "pv": 194 }, + { "name": '27', "uv": -415, "pv": 371 }, + { "name": '28', "uv": -182, "pv": 376 }, + { "name": '29', "uv": -93, "pv": 295 }, + { "name": '30', "uv": -99, "pv": 322 }, + { "name": '31', "uv": -52, "pv": 246 }, + { "name": '32', "uv": 154, "pv": 33 }, + { "name": '33', "uv": 205, "pv": 354 }, + { "name": '34', "uv": 70, "pv": 258 }, + { "name": '35', "uv": -25, "pv": 359 }, + { "name": '36', "uv": -59, "pv": 192 }, + { "name": '37', "uv": -63, "pv": 464 }, + { "name": '38', "uv": -91, "pv": -2 }, + { "name": '39', "uv": -66, "pv": 154 }, + { "name": '40', "uv": -50, "pv": 186 }, +] + +def brush_pos_size_range(): + return rx.recharts.area_chart( + rx.recharts.area( + data_key="uv", + stroke="#8884d8", + fill="#8884d8", + ), + rx.recharts.area( + data_key="pv", + stroke="#82ca9d", + fill="#82ca9d", + ), + rx.recharts.brush( + data_key="name", + traveller_width=15, + start_index=3, + end_index=10, + stroke=rx.color("mauve", 10), + fill=rx.color("mauve", 3), + ), + rx.recharts.x_axis(data_key="name"), + rx.recharts.y_axis(), + data=data, + width="100%", + height=200, + ) + +``` diff --git a/docs/library/graphing/general/cartesiangrid.md b/docs/library/graphing/general/cartesiangrid.md new file mode 100644 index 00000000000..75e474a38bf --- /dev/null +++ b/docs/library/graphing/general/cartesiangrid.md @@ -0,0 +1,203 @@ +--- +components: + - rx.recharts.CartesianGrid + # - rx.recharts.CartesianAxis +--- + +```python exec +import reflex as rx +``` + +# Cartesian Grid + +The Cartesian Grid is a component in Recharts that provides a visual reference for data points in charts. It helps users to better interpret the data by adding horizontal and vertical lines across the chart area. + +## Simple Example + +The `stroke_dasharray` prop in Recharts is used to create dashed or dotted lines for various chart elements like lines, axes, or grids. It's based on the SVG stroke-dasharray attribute. The `stroke_dasharray` prop accepts a comma-separated string of numbers that define a repeating pattern of dashes and gaps along the length of the stroke. + +- `stroke_dasharray="5,5"`: creates a line with 5-pixel dashes and 5-pixel gaps +- `stroke_dasharray="10,5,5,5"`: creates a more complex pattern with 10-pixel dashes, 5-pixel gaps, 5-pixel dashes, and 5-pixel gaps + +Here's a simple example using it on a Line component: + +```python demo graphing +data = [ + { + "name": "Page A", + "uv": 4000, + "pv": 2400, + "amt": 2400 + }, + { + "name": "Page B", + "uv": 3000, + "pv": 1398, + "amt": 2210 + }, + { + "name": "Page C", + "uv": 2000, + "pv": 9800, + "amt": 2290 + }, + { + "name": "Page D", + "uv": 2780, + "pv": 3908, + "amt": 2000 + }, + { + "name": "Page E", + "uv": 1890, + "pv": 4800, + "amt": 2181 + }, + { + "name": "Page F", + "uv": 2390, + "pv": 3800, + "amt": 2500 + }, + { + "name": "Page G", + "uv": 3490, + "pv": 4300, + "amt": 2100 + } +] + +def cgrid_simple(): + return rx.recharts.line_chart( + rx.recharts.line( + data_key="pv", + ), + rx.recharts.x_axis(data_key="name"), + rx.recharts.y_axis(), + rx.recharts.cartesian_grid(stroke_dasharray="4 4"), + data=data, + width = "100%", + height = 300, + ) +``` + +## Hidden Axes + +A `cartesian_grid` component can be used to hide the horizontal and vertical grid lines in a chart by setting the `horizontal` and `vertical` props to `False`. This can be useful when you want to show the grid lines only on one axis or when you want to create a cleaner look for the chart. + +```python demo graphing +data = [ + { + "name": "Page A", + "uv": 4000, + "pv": 2400, + "amt": 2400 + }, + { + "name": "Page B", + "uv": 3000, + "pv": 1398, + "amt": 2210 + }, + { + "name": "Page C", + "uv": 2000, + "pv": 9800, + "amt": 2290 + }, + { + "name": "Page D", + "uv": 2780, + "pv": 3908, + "amt": 2000 + }, + { + "name": "Page E", + "uv": 1890, + "pv": 4800, + "amt": 2181 + }, + { + "name": "Page F", + "uv": 2390, + "pv": 3800, + "amt": 2500 + }, + { + "name": "Page G", + "uv": 3490, + "pv": 4300, + "amt": 2100 + } +] + +def cgrid_hidden(): + return rx.recharts.area_chart( + rx.recharts.area( + data_key="uv", + stroke="#8884d8", + fill="#8884d8" + ), + rx.recharts.x_axis(data_key="name"), + rx.recharts.y_axis(), + rx.recharts.cartesian_grid( + stroke_dasharray="2 4", + vertical=False, + horizontal=True, + ), + data=data, + width = "100%", + height = 300, + ) +``` + +## Custom Grid Lines + +The `horizontal_points` and `vertical_points` props allow you to specify custom grid lines on the chart, offering fine-grained control over the grid's appearance. + +These props accept arrays of numbers, where each number represents a pixel offset: +- For `horizontal_points`, the offset is measured from the top edge of the chart +- For `vertical_points`, the offset is measured from the left edge of the chart + +```md alert info +# **Important**: The values provided to these props are not directly related to the axis values. They represent pixel offsets within the chart's rendering area. +``` + +Here's an example demonstrating custom grid lines in a scatter chart: + +```python demo graphing + +data2 = [ + {"x": 100, "y": 200, "z": 200}, + {"x": 120, "y": 100, "z": 260}, + {"x": 170, "y": 300, "z": 400}, + {"x": 170, "y": 250, "z": 280}, + {"x": 150, "y": 400, "z": 500}, + {"x": 110, "y": 280, "z": 200}, + {"x": 200, "y": 150, "z": 300}, + {"x": 130, "y": 350, "z": 450}, + {"x": 90, "y": 220, "z": 180}, + {"x": 180, "y": 320, "z": 350}, + {"x": 140, "y": 230, "z": 320}, + {"x": 160, "y": 180, "z": 240}, +] + +def cgrid_custom(): + return rx.recharts.scatter_chart( + rx.recharts.scatter( + data=data2, + fill="#8884d8", + ), + rx.recharts.x_axis(data_key="x", type_="number"), + rx.recharts.y_axis(data_key="y"), + rx.recharts.cartesian_grid( + stroke_dasharray="3 3", + horizontal_points=[0, 25, 50], + vertical_points=[65, 90, 115], + ), + width = "100%", + height = 200, + ) +``` + +Use these props judiciously to enhance data visualization without cluttering the chart. They're particularly useful for highlighting specific data ranges or creating visual reference points. diff --git a/docs/library/graphing/general/label.md b/docs/library/graphing/general/label.md new file mode 100644 index 00000000000..b6b73132986 --- /dev/null +++ b/docs/library/graphing/general/label.md @@ -0,0 +1,182 @@ +--- +components: + - rx.recharts.Label + - rx.recharts.LabelList +--- + +# Label + +```python exec +import reflex as rx +``` + +Label is a component used to display a single label at a specific position within a chart or axis, while LabelList is a component that automatically renders a list of labels for each data point in a chart series, providing a convenient way to display multiple labels without manually positioning each one. + +## Simple Example + +Here's a simple example that demonstrates how you can customize the label of your axis using `rx.recharts.label`. The `value` prop represents the actual text of the label, the `position` prop specifies where the label is positioned within the axis component, and the `offset` prop is used to fine-tune the label's position. + +```python demo graphing +data = [ + { + "name": "Page A", + "uv": 4000, + "pv": 2400, + "amt": 2400 + }, + { + "name": "Page B", + "uv": 3000, + "pv": 1398, + "amt": 2210 + }, + { + "name": "Page C", + "uv": 2000, + "pv": 5800, + "amt": 2290 + }, + { + "name": "Page D", + "uv": 2780, + "pv": 3908, + "amt": 2000 + }, + { + "name": "Page E", + "uv": 1890, + "pv": 4800, + "amt": 2181 + }, + { + "name": "Page F", + "uv": 2390, + "pv": 3800, + "amt": 2500 + }, + { + "name": "Page G", + "uv": 3490, + "pv": 4300, + "amt": 2100 + } +] + +def label_simple(): + return rx.recharts.bar_chart( + rx.recharts.cartesian_grid( + stroke_dasharray="3 3" + ), + rx.recharts.bar( + rx.recharts.label_list( + data_key="uv", position="top" + ), + data_key="uv", + fill=rx.color("accent", 8), + ), + rx.recharts.x_axis( + rx.recharts.label( + value="center", + position="center", + offset=30, + ), + rx.recharts.label( + value="inside left", + position="insideLeft", + offset=10, + ), + rx.recharts.label( + value="inside right", + position="insideRight", + offset=10, + ), + height=50, + ), + data=data, + margin={ + "left": 20, + "right": 20, + "top": 20, + "bottom": 20, + }, + width="100%", + height=250, + ) +``` + +## Label List Example + +`rx.recharts.label_list` takes in a `data_key` where we define the data column to plot. + +```python demo graphing +data = [ + { + "name": "Page A", + "uv": 4000, + "pv": 2400, + "amt": 2400 + }, + { + "name": "Page B", + "uv": 3000, + "pv": 1398, + "amt": 2210 + }, + { + "name": "Page C", + "uv": 2000, + "pv": 5800, + "amt": 2290 + }, + { + "name": "Page D", + "uv": 2780, + "pv": 3908, + "amt": 2000 + }, + { + "name": "Page E", + "uv": 1890, + "pv": 4800, + "amt": 2181 + }, + { + "name": "Page F", + "uv": 2390, + "pv": 3800, + "amt": 2500 + }, + { + "name": "Page G", + "uv": 3490, + "pv": 4300, + "amt": 2100 + } +] + +def label_list(): + return rx.recharts.bar_chart( + rx.recharts.bar( + rx.recharts.label_list(data_key="uv", position="top"), + data_key="uv", + stroke="#8884d8", + fill="#8884d8" + ), + rx.recharts.bar( + rx.recharts.label_list(data_key="pv", position="top"), + data_key="pv", + stroke="#82ca9d", + fill="#82ca9d" + ), + rx.recharts.x_axis( + data_key="name" + ), + rx.recharts.y_axis(), + margin={"left": 10, "right": 0, "top": 20, "bottom": 10}, + data=data, + width="100%", + height = 300, + ) +``` + + diff --git a/docs/library/graphing/general/legend.md b/docs/library/graphing/general/legend.md new file mode 100644 index 00000000000..edf1db283af --- /dev/null +++ b/docs/library/graphing/general/legend.md @@ -0,0 +1,171 @@ +--- +components: + - rx.recharts.Legend +--- + +# Legend + +```python exec +import reflex as rx +``` + +A legend tells what each plot represents. Just like on a map, the legend helps the reader understand what they are looking at. For a line graph for example it tells us what each line represents. + +## Simple Example + +```python demo graphing +data = [ + { + "name": "Page A", + "uv": 4000, + "pv": 2400, + "amt": 2400 + }, + { + "name": "Page B", + "uv": 3000, + "pv": 1398, + "amt": 2210 + }, + { + "name": "Page C", + "uv": 2000, + "pv": 9800, + "amt": 2290 + }, + { + "name": "Page D", + "uv": 2780, + "pv": 3908, + "amt": 2000 + }, + { + "name": "Page E", + "uv": 1890, + "pv": 4800, + "amt": 2181 + }, + { + "name": "Page F", + "uv": 2390, + "pv": 3800, + "amt": 2500 + }, + { + "name": "Page G", + "uv": 3490, + "pv": 4300, + "amt": 2100 + } +] + +def legend_simple(): + return rx.recharts.composed_chart( + rx.recharts.area( + data_key="uv", + stroke="#8884d8", + fill="#8884d8" + ), + rx.recharts.bar( + data_key="amt", + bar_size=20, + fill="#413ea0" + ), + rx.recharts.line( + data_key="pv", + type_="monotone", + stroke="#ff7300" + ), + rx.recharts.x_axis(data_key="name"), + rx.recharts.y_axis(), + rx.recharts.legend(), + data=data, + width = "100%", + height = 300, + ) +``` + +## Example with Props + +The style and layout of the legend can be customized using a set of props. `width` and `height` set the dimensions of the container that wraps the legend, and `layout` can set the legend to display vertically or horizontally. `align` and `vertical_align` set the position relative to the chart container. The type and size of icons can be set using `icon_size` and `icon_type`. + +```python demo graphing +data = [ + { + "name": "Page A", + "uv": 4000, + "pv": 2400, + "amt": 2400 + }, + { + "name": "Page B", + "uv": 3000, + "pv": 1398, + "amt": 2210 + }, + { + "name": "Page C", + "uv": 2000, + "pv": 9800, + "amt": 2290 + }, + { + "name": "Page D", + "uv": 2780, + "pv": 3908, + "amt": 2000 + }, + { + "name": "Page E", + "uv": 1890, + "pv": 4800, + "amt": 2181 + }, + { + "name": "Page F", + "uv": 2390, + "pv": 3800, + "amt": 2500 + }, + { + "name": "Page G", + "uv": 3490, + "pv": 4300, + "amt": 2100 + } +] + +def legend_props(): + return rx.recharts.composed_chart( + rx.recharts.line( + data_key="pv", + type_="monotone", + stroke=rx.color("accent", 7), + ), + rx.recharts.line( + data_key="amt", + type_="monotone", + stroke=rx.color("green", 7), + ), + rx.recharts.line( + data_key="uv", + type_="monotone", + stroke=rx.color("red", 7), + ), + rx.recharts.x_axis(data_key="name"), + rx.recharts.y_axis(), + rx.recharts.legend( + width=60, + height=100, + layout="vertical", + align="right", + vertical_align="top", + icon_size=15, + icon_type="square", + ), + data=data, + width="100%", + height=300, + ) + +``` \ No newline at end of file diff --git a/docs/library/graphing/general/reference.md b/docs/library/graphing/general/reference.md new file mode 100644 index 00000000000..3721b3b8c2a --- /dev/null +++ b/docs/library/graphing/general/reference.md @@ -0,0 +1,258 @@ +--- +components: + - rx.recharts.ReferenceLine + - rx.recharts.ReferenceDot + - rx.recharts.ReferenceArea +--- + +# Reference + +```python exec +import reflex as rx +``` + +The Reference components in Recharts, including ReferenceLine, ReferenceArea, and ReferenceDot, are used to add visual aids and annotations to the chart, helping to highlight specific data points, ranges, or thresholds for better data interpretation and analysis. + +## Reference Area + +The `rx.recharts.reference_area` component in Recharts is used to highlight a specific area or range on the chart by drawing a rectangular region. It is defined by specifying the coordinates (x1, x2, y1, y2) and can be used to emphasize important data ranges or intervals on the chart. + +```python demo graphing +data = [ + { + "x": 45, + "y": 100, + "z": 150, + "errorY": [ + 30, + 20 + ], + "errorX": 5 + }, + { + "x": 100, + "y": 200, + "z": 200, + "errorY": [ + 20, + 30 + ], + "errorX": 3 + }, + { + "x": 120, + "y": 100, + "z": 260, + "errorY": 20, + "errorX": [ + 10, + 3 + ] + }, + { + "x": 170, + "y": 300, + "z": 400, + "errorY": [ + 15, + 18 + ], + "errorX": 4 + }, + { + "x": 140, + "y": 250, + "z": 280, + "errorY": 23, + "errorX": [ + 6, + 7 + ] + }, + { + "x": 150, + "y": 400, + "z": 500, + "errorY": [ + 21, + 10 + ], + "errorX": 4 + }, + { + "x": 110, + "y": 280, + "z": 200, + "errorY": 21, + "errorX": [ + 1, + 8 + ] + } +] + +def reference(): + return rx.recharts.scatter_chart( + rx.recharts.scatter( + data=data, + fill="#8884d8", + name="A"), + rx.recharts.reference_area(x1= 150, x2=180, y1=150, y2=300, fill="#8884d8", fill_opacity=0.3), + rx.recharts.x_axis(data_key="x", name="x", type_="number"), + rx.recharts.y_axis(data_key="y", name="y", type_="number"), + rx.recharts.graphing_tooltip(), + width = "100%", + height = 300, + ) +``` + +## Reference Line + +The `rx.recharts.reference_line` component in rx.recharts is used to draw a horizontal or vertical line on the chart at a specified position. It helps to highlight important values, thresholds, or ranges on the axis, providing visual reference points for better data interpretation. + +```python demo graphing +data_2 = [ + {"name": "Page A", "uv": 4000, "pv": 2400, "amt": 2400}, + {"name": "Page B", "uv": 3000, "pv": 1398, "amt": 2210}, + {"name": "Page C", "uv": 2000, "pv": 9800, "amt": 2290}, + {"name": "Page D", "uv": 2780, "pv": 3908, "amt": 2000}, + {"name": "Page E", "uv": 1890, "pv": 4800, "amt": 2181}, + {"name": "Page F", "uv": 2390, "pv": 3800, "amt": 2500}, + {"name": "Page G", "uv": 3490, "pv": 4300, "amt": 2100}, +] + +def reference_line(): + return rx.recharts.area_chart( + rx.recharts.area( + data_key="pv", stroke=rx.color("accent", 8), fill=rx.color("accent", 7), + ), + rx.recharts.reference_line( + x = "Page C", + stroke = rx.color("accent", 10), + label="Max PV PAGE", + ), + rx.recharts.reference_line( + y = 9800, + stroke = rx.color("green", 10), + label="Max" + ), + rx.recharts.reference_line( + y = 4343, + stroke = rx.color("green", 10), + label="Average" + ), + rx.recharts.x_axis(data_key="name"), + rx.recharts.y_axis(), + data=data_2, + height = 300, + width = "100%", + ) + +``` + +## Reference Dot + +The `rx.recharts.reference_dot` component in Recharts is used to mark a specific data point on the chart with a customizable dot. It allows you to highlight important values, outliers, or thresholds by providing a visual reference marker at the specified coordinates (x, y) on the chart. + +```python demo graphing + +data_3 = [ + { + "x": 45, + "y": 100, + "z": 150, + }, + { + "x": 100, + "y": 200, + "z": 200, + }, + { + "x": 120, + "y": 100, + "z": 260, + }, + { + "x": 170, + "y": 300, + "z": 400, + }, + { + "x": 140, + "y": 250, + "z": 280, + }, + { + "x": 150, + "y": 400, + "z": 500, + }, + { + "x": 110, + "y": 280, + "z": 200, + }, + { + "x": 80, + "y": 150, + "z": 180, + }, + { + "x": 200, + "y": 350, + "z": 450, + }, + { + "x": 90, + "y": 220, + "z": 240, + }, + { + "x": 130, + "y": 320, + "z": 380, + }, + { + "x": 180, + "y": 120, + "z": 300, + }, +] + +def reference_dot(): + return rx.recharts.scatter_chart( + rx.recharts.scatter( + data=data_3, + fill=rx.color("accent", 9), + name="A", + ), + rx.recharts.x_axis( + data_key="x", name="x", type_="number" + ), + rx.recharts.y_axis( + data_key="y", name="y", type_="number" + ), + rx.recharts.reference_dot( + x = 160, + y = 350, + r = 15, + fill = rx.color("accent", 5), + stroke = rx.color("accent", 10), + ), + rx.recharts.reference_dot( + x = 170, + y = 300, + r = 20, + fill = rx.color("accent", 7), + ), + rx.recharts.reference_dot( + x = 90, + y = 220, + r = 18, + fill = rx.color("green", 7), + ), + height = 200, + width = "100%", + ) + +``` \ No newline at end of file diff --git a/docs/library/graphing/general/tooltip.md b/docs/library/graphing/general/tooltip.md new file mode 100644 index 00000000000..33025831c96 --- /dev/null +++ b/docs/library/graphing/general/tooltip.md @@ -0,0 +1,172 @@ +--- +components: + - rx.recharts.GraphingTooltip +--- + +# Tooltip + +```python exec +import reflex as rx +``` + +Tooltips are the little boxes that pop up when you hover over something. Tooltips are always attached to something, like a dot on a scatter chart, or a bar on a bar chart. + +```python demo graphing +data = [ + { + "name": "Page A", + "uv": 4000, + "pv": 2400, + "amt": 2400 + }, + { + "name": "Page B", + "uv": 3000, + "pv": 1398, + "amt": 2210 + }, + { + "name": "Page C", + "uv": 2000, + "pv": 9800, + "amt": 2290 + }, + { + "name": "Page D", + "uv": 2780, + "pv": 3908, + "amt": 2000 + }, + { + "name": "Page E", + "uv": 1890, + "pv": 4800, + "amt": 2181 + }, + { + "name": "Page F", + "uv": 2390, + "pv": 3800, + "amt": 2500 + }, + { + "name": "Page G", + "uv": 3490, + "pv": 4300, + "amt": 2100 + } +] + +def tooltip_simple(): + return rx.recharts.composed_chart( + rx.recharts.area( + data_key="uv", + stroke="#8884d8", + fill="#8884d8" + ), + rx.recharts.bar( + data_key="amt", + bar_size=20, + fill="#413ea0" + ), + rx.recharts.line( + data_key="pv", + type_="monotone", + stroke="#ff7300" + ), + rx.recharts.x_axis(data_key="name"), + rx.recharts.y_axis(), + rx.recharts.cartesian_grid(stroke_dasharray="3 3"), + rx.recharts.graphing_tooltip(), + data=data, + width = "100%", + height = 300, + ) +``` + +## Custom Styling + +The `rx.recharts.graphing_tooltip` component allows for customization of the tooltip's style, position, and layout. `separator` sets the separator between the data key and value. `view_box` prop defines the dimensions of the chart's viewbox while `allow_escape_view_box` determines whether the tooltip can extend beyond the viewBox horizontally (x) or vertically (y). `wrapper_style` prop allows you to style the outer container or wrapper of the tooltip. `content_style` prop allows you to style the inner content area of the tooltip. `is_animation_active` prop determines if the tooltip animation is active or not. + +```python demo graphing + +data = [ + { + "name": "Page A", + "uv": 4000, + "pv": 2400, + "amt": 2400 + }, + { + "name": "Page B", + "uv": 3000, + "pv": 1398, + "amt": 2210 + }, + { + "name": "Page C", + "uv": 2000, + "pv": 9800, + "amt": 2290 + }, + { + "name": "Page D", + "uv": 2780, + "pv": 3908, + "amt": 2000 + }, + { + "name": "Page E", + "uv": 1890, + "pv": 4800, + "amt": 2181 + }, + { + "name": "Page F", + "uv": 2390, + "pv": 3800, + "amt": 2500 + }, + { + "name": "Page G", + "uv": 3490, + "pv": 4300, + "amt": 2100 + } +] + +def tooltip_custom_styling(): + return rx.recharts.composed_chart( + rx.recharts.area( + data_key="uv", stroke="#8884d8", fill="#8884d8" + ), + rx.recharts.bar( + data_key="amt", bar_size=20, fill="#413ea0" + ), + rx.recharts.line( + data_key="pv", type_="monotone", stroke="#ff7300" + ), + rx.recharts.x_axis(data_key="name"), + rx.recharts.y_axis(), + rx.recharts.graphing_tooltip( + separator = " - ", + view_box = {"width" : 675, " height" : 300 }, + allow_escape_view_box={"x": True, "y": False}, + wrapper_style={ + "backgroundColor": rx.color("accent", 3), + "borderRadius": "8px", + "padding": "10px", + }, + content_style={ + "backgroundColor": rx.color("accent", 4), + "borderRadius": "4px", + "padding": "8px", + }, + position = {"x" : 600, "y" : 0}, + is_animation_active = False, + ), + data=data, + height = 300, + width = "100%", + ) +``` diff --git a/docs/library/graphing/other-charts/plotly.md b/docs/library/graphing/other-charts/plotly.md new file mode 100644 index 00000000000..3d036fac706 --- /dev/null +++ b/docs/library/graphing/other-charts/plotly.md @@ -0,0 +1,144 @@ +--- +components: + - rx.plotly +--- + +# Plotly + +```python exec +import reflex as rx +import pandas as pd +import plotly.express as px +import plotly.graph_objects as go +``` + +Plotly is a graphing library that can be used to create interactive graphs. Use the rx.plotly component to wrap Plotly as a component for use in your web page. Checkout [Plotly](https://plotly.com/graphing-libraries/) for more information. + +```md alert info +# When integrating Plotly graphs into your UI code, note that the method for displaying the graph differs from a regular Python script. Instead of using `fig.show()`, use `rx.plotly(data=fig)` within your UI code to ensure the graph is properly rendered and displayed within the user interface +``` + +## Basic Example + +Let's create a line graph of life expectancy in Canada. + +```python demo exec +import plotly.express as px + +df = px.data.gapminder().query("country=='Canada'") +fig = px.line(df, x="year", y="lifeExp", title='Life expectancy in Canada') + +def line_chart(): + return rx.center( + rx.plotly(data=fig), + ) +``` + +## 3D graphing example + +Let's create a 3D surface plot of Mount Bruno. This is a slightly more complicated example, but it wraps in Reflex using the same method. In fact, you can wrap any figure using the same approach. + +```python demo exec +import plotly.graph_objects as go +import pandas as pd + +# Read data from a csv +z_data = pd.read_csv('data/mt_bruno_elevation.csv') + +fig = go.Figure(data=[go.Surface(z=z_data.values)]) +fig.update_traces(contours_z=dict(show=True, usecolormap=True, + highlightcolor="limegreen", project_z=True)) +fig.update_layout( + scene_camera_eye=dict(x=1.87, y=0.88, z=-0.64), + margin=dict(l=65, r=50, b=65, t=90) +) + +def mountain_surface(): + return rx.center( + rx.plotly(data=fig), + ) +``` + +📊 **Dataset source:** [mt_bruno_elevation.csv](https://raw.githubusercontent.com/plotly/datasets/master/api_docs/mt_bruno_elevation.csv) + +## Plot as State Var + +If the figure is set as a state var, it can be updated during run time. + +```python demo exec +import plotly.express as px +import plotly.graph_objects as go +import pandas as pd + +class PlotlyState(rx.State): + df: pd.DataFrame + figure: go.Figure = px.line() + + @rx.event + def create_figure(self): + self.df = px.data.gapminder().query("country=='Canada'") + self.figure = px.line( + self.df, + x="year", + y="lifeExp", + title="Life expectancy in Canada", + ) + + @rx.event + def set_selected_country(self, country): + self.df = px.data.gapminder().query(f"country=='{country}'") + self.figure = px.line( + self.df, + x="year", + y="lifeExp", + title=f"Life expectancy in {country}", + ) + + + +def line_chart_with_state(): + return rx.vstack( + rx.select( + ['China', 'France', 'United Kingdom', 'United States', 'Canada'], + default_value="Canada", + on_change=PlotlyState.set_selected_country, + ), + rx.plotly( + data=PlotlyState.figure, + on_mount=PlotlyState.create_figure, + ), + ) +``` + +## Adding Styles and Layouts + +Use `update_layout()` method to update the layout of your chart. Checkout [Plotly Layouts](https://plotly.com/python/reference/layout/) for all layouts props. + +```md alert info +Note that the width and height props are not recommended to ensure the plot remains size responsive to its container. The size of plot will be determined by it's outer container. +``` + +```python demo exec +df = px.data.gapminder().query("country=='Canada'") +fig_1 = px.line( + df, + x="year", + y="lifeExp", + title="Life expectancy in Canada", +) +fig_1.update_layout( + title_x=0.5, + plot_bgcolor="#c3d7f7", + paper_bgcolor="rgba(128, 128, 128, 0.1)", + showlegend=True, + title_font_family="Open Sans", + title_font_size=25, +) + +def add_styles(): + return rx.center( + rx.plotly(data=fig_1), + width="100%", + height="100%", + ) +``` diff --git a/docs/library/graphing/other-charts/pyplot.md b/docs/library/graphing/other-charts/pyplot.md new file mode 100644 index 00000000000..701366d2591 --- /dev/null +++ b/docs/library/graphing/other-charts/pyplot.md @@ -0,0 +1,155 @@ +--- +components: + - pyplot +--- + +```python exec +import reflex as rx +from reflex_pyplot import pyplot +import numpy as np +import random +import matplotlib.pyplot as plt +from matplotlib.figure import Figure +from reflex.style import toggle_color_mode +``` + +# Pyplot + +Pyplot (`reflex-pyplot`) is a graphing library that wraps Matplotlib. Use the `pyplot` component to display any Matplotlib plot in your app. Check out [Matplotlib](https://matplotlib.org/) for more information. + +## Installation + +Install the `reflex-pyplot` package using pip. + +```bash +pip install reflex-pyplot +``` + +## Basic Example + +To display a Matplotlib plot in your app, you can use the `pyplot` component. Pass in the figure you created with Matplotlib to the `pyplot` component as a child. + +```python demo exec +import matplotlib.pyplot as plt +import reflex as rx +from reflex_pyplot import pyplot +import numpy as np + +def create_contour_plot(): + X, Y = np.meshgrid(np.linspace(-3, 3, 256), np.linspace(-3, 3, 256)) + Z = (1 - X/2 + X**5 + Y**3) * np.exp(-X**2 - Y**2) + levels = np.linspace(Z.min(), Z.max(), 7) + + fig, ax = plt.subplots() + ax.contourf(X, Y, Z, levels=levels) + plt.close(fig) + return fig + +def pyplot_simple_example(): + return rx.card( + pyplot(create_contour_plot(), width="100%", height="400px"), + bg_color='#ffffff', + width="100%", + ) +``` + +```md alert info +# You must close the figure after creating + +Not closing the figure could cause memory issues. +``` + +## Stateful Example + +Lets create a scatter plot of random data. We'll also allow the user to randomize the data and change the number of points. + +In this example, we'll use a `color_mode_cond` to display the plot in both light and dark mode. We need to do this manually here because the colors are determined by the matplotlib chart and not the theme. + +```python demo exec +import random +from typing import Literal +import matplotlib.pyplot as plt +import reflex as rx +from reflex_pyplot import pyplot +import numpy as np + + +def create_plot(theme: str, plot_data: tuple, scale: list): + bg_color, text_color = ('#1e1e1e', 'white') if theme == 'dark' else ('white', 'black') + grid_color = '#555555' if theme == 'dark' else '#cccccc' + + fig, ax = plt.subplots(facecolor=bg_color) + ax.set_facecolor(bg_color) + + for (x, y), color in zip(plot_data, ["#4e79a7", "#f28e2b"]): + ax.scatter(x, y, c=color, s=scale, label=color, alpha=0.6, edgecolors="none") + + ax.legend(loc="upper right", facecolor=bg_color, edgecolor='none', labelcolor=text_color) + ax.grid(True, color=grid_color) + ax.tick_params(colors=text_color) + for spine in ax.spines.values(): + spine.set_edgecolor(text_color) + + for item in [ax.xaxis.label, ax.yaxis.label, ax.title]: + item.set_color(text_color) + plt.close(fig) + + return fig + +class PyplotState(rx.State): + num_points: int = 25 + plot_data: tuple = tuple(np.random.rand(2, 25) for _ in range(2)) + scale: list = [random.uniform(0, 100) for _ in range(25)] + + @rx.event(temporal=True, throttle=500) + def randomize(self): + self.plot_data = tuple(np.random.rand(2, self.num_points) for _ in range(2)) + self.scale = [random.uniform(0, 100) for _ in range(self.num_points)] + + @rx.event(temporal=True, throttle=500) + def set_num_points(self, num_points: list[int | float]): + self.num_points = int(num_points[0]) + yield PyplotState.randomize() + + @rx.var + def fig_light(self) -> Figure: + fig = create_plot("light", self.plot_data, self.scale) + return fig + + @rx.var + def fig_dark(self) -> Figure: + fig = create_plot("dark", self.plot_data, self.scale) + return fig + +def pyplot_example(): + return rx.vstack( + rx.card( + rx.color_mode_cond( + pyplot(PyplotState.fig_light, width="100%", height="100%"), + pyplot(PyplotState.fig_dark, width="100%", height="100%"), + ), + rx.vstack( + rx.hstack( + rx.button( + "Randomize", + on_click=PyplotState.randomize, + ), + rx.text("Number of Points:"), + rx.slider( + default_value=25, + min_=10, + max=100, + on_value_commit=PyplotState.set_num_points, + ), + width="100%", + ), + width="100%", + ), + width="100%", + ), + justify_content="center", + align_items="center", + height="100%", + width="100%", + ) +``` diff --git a/docs/library/layout/aspect_ratio.md b/docs/library/layout/aspect_ratio.md new file mode 100644 index 00000000000..cc5713af5aa --- /dev/null +++ b/docs/library/layout/aspect_ratio.md @@ -0,0 +1,85 @@ +--- +components: + - rx.aspect_ratio +--- + +```python exec +import reflex as rx +from pcweb.constants import REFLEX_ASSETS_CDN +``` + +# Aspect Ratio + +Displays content with a desired ratio. + +## Basic Example + +Setting the `ratio` prop will adjust the width or height +of the content such that the `width` divided by the `height` equals the `ratio`. +For responsive scaling, set the `width` or `height` of the content to `"100%"`. + +```python demo +rx.grid( + rx.aspect_ratio( + rx.box( + "Widescreen 16:9", + background_color="papayawhip", + width="100%", + height="100%", + ), + ratio=16 / 9, + ), + rx.aspect_ratio( + rx.box( + "Letterbox 4:3", + background_color="orange", + width="100%", + height="100%", + ), + ratio=4 / 3, + ), + rx.aspect_ratio( + rx.box( + "Square 1:1", + background_color="green", + width="100%", + height="100%", + ), + ratio=1, + ), + rx.aspect_ratio( + rx.box( + "Portrait 5:7", + background_color="lime", + width="100%", + height="100%", + ), + ratio=5 / 7, + ), + spacing="2", + width="25%", +) +``` + +```md alert warning +# Never set `height` or `width` directly on an `aspect_ratio` component or its contents. + +Instead, wrap the `aspect_ratio` in a `box` that constrains either the width or the height, then set the content width and height to `"100%"`. +``` + +```python demo +rx.flex( + *[ + rx.box( + rx.aspect_ratio( + rx.image(src=f"{REFLEX_ASSETS_CDN}other/logo.jpg", width="100%", height="100%"), + ratio=ratio, + ), + width="20%", + ) + for ratio in [16 / 9, 3 / 2, 2 / 3, 1] + ], + justify="between", + width="100%", +) +``` diff --git a/docs/library/layout/box.md b/docs/library/layout/box.md new file mode 100644 index 00000000000..188e791208a --- /dev/null +++ b/docs/library/layout/box.md @@ -0,0 +1,47 @@ +--- +components: + - rx.box +--- + +```python exec +import reflex as rx +from pcweb.constants import REFLEX_ASSETS_CDN +``` + +# Box + +Box is a generic container component that can be used to group other components. + +By default, the Box component is based on the `div` and rendered as a block element. It's primary use is for applying styles. + +## Basic Example + +```python demo +rx.box( + rx.box("CSS color", background_color="yellow", border_radius="2px", width="20%", margin="4px", padding="4px"), + rx.box("CSS color", background_color="orange", border_radius="5px", width="40%", margin="8px", padding="8px"), + rx.box("Radix Color", background_color="var(--tomato-3)", border_radius="5px", width="60%", margin="12px", padding="12px"), + rx.box("Radix Color", background_color="var(--plum-3)", border_radius="10px", width="80%", margin="16px", padding="16px"), + rx.box("Radix Theme Color", background_color="var(--accent-2)", radius="full", width="100%", margin="24px", padding="25px"), + flex_grow="1", + text_align="center", +) +``` + +## Background + +To set a background [image](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_images) or +[gradient](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_images/Using_CSS_gradients), +use the [`background` CSS prop](https://developer.mozilla.org/en-US/docs/Web/CSS/background). + +```python demo +rx.flex( + rx.box(background="linear-gradient(45deg, var(--tomato-9), var(--plum-9))", width="20%", height="100%"), + rx.box(background="linear-gradient(red, yellow, blue, orange)", width="20%", height="100%"), + rx.box(background="radial-gradient(at 0% 30%, red 10px, yellow 30%, #1e90ff 50%)", width="20%", height="100%"), + rx.box(background=f"center/cover url('{REFLEX_ASSETS_CDN}other/reflex_banner.png')", width="20%", height="100%"), + spacing="2", + width="100%", + height="10vh", +) +``` diff --git a/docs/library/layout/card.md b/docs/library/layout/card.md new file mode 100644 index 00000000000..7f82ade7da7 --- /dev/null +++ b/docs/library/layout/card.md @@ -0,0 +1,59 @@ +--- +components: + - rx.card + +Card: | + lambda **props: rx.card("Basic Card ", **props) +--- + +```python exec +import reflex as rx +from pcweb.constants import REFLEX_ASSETS_CDN +``` + +# Card + +A Card component is used for grouping related components. It is similar to the Box, except it has a +border, uses the theme colors and border radius, and provides a `size` prop to control spacing +and margin according to the Radix `"1"` - `"5"` scale. + +The Card requires less styling than a Box to achieve consistent visual results when used with +themes. + +## Basic Example + +```python demo +rx.flex( + rx.card("Card 1", size="1"), + rx.card("Card 2", size="2"), + rx.card("Card 3", size="3"), + rx.card("Card 4", size="4"), + rx.card("Card 5", size="5"), + spacing="2", + align_items="flex-start", + flex_wrap="wrap", +) +``` + +## Rendering as a Different Element + +The `as_child` prop may be used to render the Card as a different element. Link and Button are +commonly used to make a Card clickable. + +```python demo +rx.card( + rx.link( + rx.flex( + rx.avatar(src=f"{REFLEX_ASSETS_CDN}other/reflex_banner.png"), + rx.box( + rx.heading("Quick Start"), + rx.text("Get started with Reflex in 5 minutes."), + ), + spacing="2", + ), + ), + as_child=True, +) +``` + +## Using Inset Content diff --git a/docs/library/layout/center.md b/docs/library/layout/center.md new file mode 100644 index 00000000000..b52702f20cb --- /dev/null +++ b/docs/library/layout/center.md @@ -0,0 +1,21 @@ +--- +components: + - rx.center +--- + +```python exec +import reflex as rx +``` + +# Center + +`Center` is a component that centers its children within itself. It is based on the `flex` component and therefore inherits all of its props. + +```python demo +rx.center( + rx.text("Hello World!"), + border_radius="15px", + border_width="thick", + width="50%", +) +``` diff --git a/docs/library/layout/container.md b/docs/library/layout/container.md new file mode 100644 index 00000000000..caeab8c84a1 --- /dev/null +++ b/docs/library/layout/container.md @@ -0,0 +1,40 @@ +--- +components: + - rx.container +--- + +```python exec +import reflex as rx +``` + +# Container + +Constrains the maximum width of page content, while keeping flexible margins +for responsive layouts. + +A Container is generally used to wrap the main content for a page. + +## Basic Example + +```python demo +rx.box( + rx.container( + rx.card("This content is constrained to a max width of 448px.", width="100%"), + size="1", + ), + rx.container( + rx.card("This content is constrained to a max width of 688px.", width="100%"), + size="2", + ), + rx.container( + rx.card("This content is constrained to a max width of 880px.", width="100%"), + size="3", + ), + rx.container( + rx.card("This content is constrained to a max width of 1136px.", width="100%"), + size="4", + ), + background_color="var(--gray-3)", + width="100%", +) +``` diff --git a/docs/library/layout/flex.md b/docs/library/layout/flex.md new file mode 100644 index 00000000000..7abb0546199 --- /dev/null +++ b/docs/library/layout/flex.md @@ -0,0 +1,208 @@ +--- +components: + - rx.flex +--- + +```python exec +import reflex as rx +``` + +# Flex + +The Flex component is used to make [flexbox layouts](https://developer.mozilla.org/en-US/docs/Learn/CSS/CSS_layout/Flexbox). +It makes it simple to arrange child components in horizontal or vertical directions, apply wrapping, +justify and align content, and automatically size components based on available space, making it +ideal for building responsive layouts. + +By default, children are arranged horizontally (`direction="row"`) without wrapping. + +## Basic Example + +```python demo +rx.flex( + rx.card("Card 1"), + rx.card("Card 2"), + rx.card("Card 3"), + rx.card("Card 4"), + rx.card("Card 5"), + spacing="2", + width="100%", +) +``` + +## Wrapping + +With `flex_wrap="wrap"`, the children will wrap to the next line instead of being resized. + +```python demo +rx.flex( + rx.foreach( + rx.Var.range(10), + lambda i: rx.card(f"Card {i + 1}", width="16%"), + ), + spacing="2", + flex_wrap="wrap", + width="100%", +) +``` + +## Direction + +With `direction="column"`, the children will be arranged vertically. + +```python demo +rx.flex( + rx.card("Card 1"), + rx.card("Card 2"), + rx.card("Card 3"), + rx.card("Card 4"), + spacing="2", + direction="column", +) +``` + +## Alignment + +Two props control how children are aligned within the Flex component: + +* `align` controls how children are aligned along the cross axis (vertical for `row` and horizontal for `column`). +* `justify` controls how children are aligned along the main axis (horizontal for `row` and vertical for `column`). + +The following example visually demonstrates the effect of these props with different `wrap` and `direction` values. + +```python demo exec +class FlexPlaygroundState(rx.State): + align: str = "stretch" + justify: str = "start" + direction: str = "row" + wrap: str = "nowrap" + + @rx.event + def set_align(self, value: str): + self.align = value + + @rx.event + def set_justify(self, value: str): + self.justify = value + + @rx.event + def set_direction(self, value: str): + self.direction = value + + @rx.event + def set_wrap(self, value: str): + self.wrap = value + + +def select(label, items, value, on_change): + return rx.flex( + rx.text(label), + rx.select.root( + rx.select.trigger(), + rx.select.content( + *[ + rx.select.item(item, value=item) + for item in items + ] + ), + value=value, + on_change=on_change, + ), + align="center", + justify="center", + direction="column", + ) + + +def selectors(): + return rx.flex( + select("Wrap", ["nowrap", "wrap", "wrap-reverse"], FlexPlaygroundState.wrap, FlexPlaygroundState.set_wrap), + select("Direction", ["row", "column", "row-reverse", "column-reverse"], FlexPlaygroundState.direction, FlexPlaygroundState.set_direction), + select("Align", ["start", "center", "end", "baseline", "stretch"], FlexPlaygroundState.align, FlexPlaygroundState.set_align), + select("Justify", ["start", "center", "end", "between"], FlexPlaygroundState.justify, FlexPlaygroundState.set_justify), + width="100%", + spacing="2", + justify="between", + ) + + +def example1(): + return rx.box( + selectors(), + rx.flex( + rx.foreach( + rx.Var.range(10), + lambda i: rx.card(f"Card {i + 1}", width="16%"), + ), + spacing="2", + direction=FlexPlaygroundState.direction, + align=FlexPlaygroundState.align, + justify=FlexPlaygroundState.justify, + wrap=FlexPlaygroundState.wrap, + width="100%", + height="20vh", + margin_top="16px", + ), + width="100%", + ) +``` + +## Size Hinting + +When a child component is included in a flex container, +the `flex_grow` (default `"0"`) and `flex_shrink` (default `"1"`) props control +how the box is sized relative to other components in the same container. + +The resizing always applies to the main axis of the flex container. If the direction is +`row`, then the sizing applies to the `width`. If the direction is `column`, then the sizing +applies to the `height`. To set the optimal size along the main axis, the `flex_basis` prop +is used and may be either a percentage or CSS size units. When unspecified, the +corresponding `width` or `height` value is used if set, otherwise the content size is used. + +When `flex_grow="0"`, the box will not grow beyond the `flex_basis`. + +When `flex_shrink="0"`, the box will not shrink to less than the `flex_basis`. + +These props are used when creating flexible responsive layouts. + +Move the slider below and see how adjusting the width of the flex container +affects the computed sizes of the flex items based on the props that are set. + +```python demo exec +class FlexGrowShrinkState(rx.State): + width_pct: list[int] = [100] + + @rx.event + def set_width_pct(self, value: list[int | float]): + self.width_pct = [int(value[0])] + + +def border_box(*children, **props): + return rx.box( + *children, + border="1px solid var(--gray-10)", + border_radius="2px", + **props, + ) + + +def example2(): + return rx.box( + rx.flex( + border_box("flex_shrink=0", flex_shrink="0", width="100px"), + border_box("flex_shrink=1", flex_shrink="1", width="200px"), + border_box("flex_grow=0", flex_grow="0"), + border_box("flex_grow=1", flex_grow="1"), + width=f"{FlexGrowShrinkState.width_pct}%", + margin_bottom="16px", + spacing="2", + ), + rx.slider( + min=0, + max=100, + value=FlexGrowShrinkState.width_pct, + on_change=FlexGrowShrinkState.set_width_pct, + ), + width="100%", + ) +``` diff --git a/docs/library/layout/fragment.md b/docs/library/layout/fragment.md new file mode 100644 index 00000000000..66b3e301fce --- /dev/null +++ b/docs/library/layout/fragment.md @@ -0,0 +1,27 @@ +--- +components: + - rx.fragment +--- + +# Fragment + +```python exec +import reflex as rx +from pcweb import constants +``` + +A Fragment is a Component that allow you to group multiple Components without a wrapper node. + +Refer to the React docs at [React/Fragment]({constants.FRAGMENT_COMPONENT_INFO_URL}) for more information on its use-case. + +```python demo +rx.fragment( + rx.text("Component1"), + rx.text("Component2") +) +``` + + +```md video https://youtube.com/embed/ITOZkzjtjUA?start=3196&end=3340 +# Video: Fragment +``` \ No newline at end of file diff --git a/docs/library/layout/grid.md b/docs/library/layout/grid.md new file mode 100644 index 00000000000..f09071a2d71 --- /dev/null +++ b/docs/library/layout/grid.md @@ -0,0 +1,40 @@ +--- +components: + - rx.grid +--- + +```python exec +import reflex as rx +``` + +# Grid + +Component for creating grid layouts. Either `rows` or `columns` may be specified. + +## Basic Example + +```python demo +rx.grid( + rx.foreach( + rx.Var.range(12), + lambda i: rx.card(f"Card {i + 1}", height="10vh"), + ), + columns="3", + spacing="4", + width="100%", +) +``` + +```python demo +rx.grid( + rx.foreach( + rx.Var.range(12), + lambda i: rx.card(f"Card {i + 1}", height="10vh"), + ), + rows="3", + flow="column", + justify="between", + spacing="4", + width="100%", +) +``` diff --git a/docs/library/layout/inset.md b/docs/library/layout/inset.md new file mode 100644 index 00000000000..09df465f714 --- /dev/null +++ b/docs/library/layout/inset.md @@ -0,0 +1,91 @@ +--- +components: + - rx.inset + +Inset: | + lambda **props: rx.card( + rx.inset( + rx.image(src=f"{REFLEX_ASSETS_CDN}other/reflex_banner.png", height="auto"), + **props, + ), + width="500px", + ) + +--- + +```python exec +import reflex as rx +from pcweb.constants import REFLEX_ASSETS_CDN +``` + +# Inset + +Applies a negative margin to allow content to bleed into the surrounding container. + +## Basic Example + +Nesting an Inset component inside a Card will render the content from edge to edge of the card. + +```python demo +rx.card( + rx.inset( + rx.image(src=f"{REFLEX_ASSETS_CDN}other/reflex_banner.png", width="100%", height="auto"), + side="top", + pb="current", + ), + rx.text("Reflex is a web framework that allows developers to build their app in pure Python."), + width="25vw", +) +``` + +## Other Directions + +The `side` prop controls which side the negative margin is applied to. When using a specific side, +it is helpful to set the padding for the opposite side to `current` to retain the same padding the +content would have had if it went to the edge of the parent component. + +```python demo +rx.card( + rx.text("The inset below uses a bottom side."), + rx.inset( + rx.image(src=f"{REFLEX_ASSETS_CDN}other/reflex_banner.png", width="100%", height="auto"), + side="bottom", + pt="current", + ), + width="25vw", +) +``` + +```python demo +rx.card( + rx.flex( + rx.text("This inset uses a right side, which requires a flex with direction row."), + rx.inset( + rx.box(background=f"center/cover url('{REFLEX_ASSETS_CDN}other/reflex_banner.png')", height="100%"), + width="100%", + side="right", + pl="current", + ), + direction="row", + width="100%", + ), + width="25vw", +) +``` + +```python demo +rx.card( + rx.flex( + rx.inset( + rx.box(background=f"center/cover url('{REFLEX_ASSETS_CDN}other/reflex_banner.png')", height="100%"), + width="100%", + side="left", + pr="current", + ), + rx.text("This inset uses a left side, which also requires a flex with direction row."), + direction="row", + width="100%", + ), + width="25vw", +) +``` diff --git a/docs/library/layout/section.md b/docs/library/layout/section.md new file mode 100644 index 00000000000..05f818bd0e1 --- /dev/null +++ b/docs/library/layout/section.md @@ -0,0 +1,36 @@ +--- +components: + - rx.section +--- + +```python exec +import reflex as rx +``` + +# Section + +Denotes a section of page content, providing vertical padding by default. + +Primarily this is a semantic component that is used to group related textual content. + +## Basic Example + +```python demo +rx.box( + rx.section( + rx.heading("First"), + rx.text("This is the first content section"), + padding_left="12px", + padding_right="12px", + background_color="var(--gray-2)", + ), + rx.section( + rx.heading("Second"), + rx.text("This is the second content section"), + padding_left="12px", + padding_right="12px", + background_color="var(--gray-2)", + ), + width="100%", +) +``` diff --git a/docs/library/layout/separator.md b/docs/library/layout/separator.md new file mode 100644 index 00000000000..816382d233e --- /dev/null +++ b/docs/library/layout/separator.md @@ -0,0 +1,59 @@ +--- +components: + - rx.separator +Separator: | + lambda **props: rx.separator(**props) + +--- + +```python exec +import reflex as rx +``` + +# Separator + +Visually or semantically separates content. + +## Basic Example + +```python demo +rx.flex( + rx.card("Section 1"), + rx.divider(), + rx.card("Section 2"), + spacing="4", + direction="column", + align="center", +) +``` + +## Size + +The `size` prop controls how long the separator is. Using `size="4"` will make +the separator fill the parent container. Setting CSS `width` or `height` prop to `"100%"` +can also achieve this effect, but `size` works the same regardless of the orientation. + +```python demo +rx.flex( + rx.card("Section 1"), + rx.divider(size="4"), + rx.card("Section 2"), + spacing="4", + direction="column", +) +``` + +## Orientation + +Setting the orientation prop to `vertical` will make the separator appear vertically. + +```python demo +rx.flex( + rx.card("Section 1"), + rx.divider(orientation="vertical", size="4"), + rx.card("Section 2"), + spacing="4", + width="100%", + height="10vh", +) +``` diff --git a/docs/library/layout/spacer.md b/docs/library/layout/spacer.md new file mode 100644 index 00000000000..730076159ce --- /dev/null +++ b/docs/library/layout/spacer.md @@ -0,0 +1,25 @@ +--- +components: + - rx.spacer +--- + +```python exec +import reflex as rx +``` + +# Spacer + +Creates an adjustable, empty space that can be used to tune the spacing between child elements within `flex`. + +```python demo +rx.flex( + rx.center(rx.text("Example"), bg="lightblue"), + rx.spacer(), + rx.center(rx.text("Example"), bg="lightgreen"), + rx.spacer(), + rx.center(rx.text("Example"), bg="salmon"), + width="100%", +) +``` + +As `stack`, `vstack` and `hstack` are all built from `flex`, it is possible to also use `spacer` inside of these components. diff --git a/docs/library/layout/stack.md b/docs/library/layout/stack.md new file mode 100644 index 00000000000..d00160bdf4b --- /dev/null +++ b/docs/library/layout/stack.md @@ -0,0 +1,172 @@ +--- +components: + - rx.stack + - rx.hstack + - rx.vstack +Stack: | + lambda **props: rx.stack( + rx.card("Card 1", size="2"), rx.card("Card 2", size="2"), rx.card("Card 3", size="2"), + width="100%", + height="20vh", + **props, + ) +--- + +```python exec +import reflex as rx +``` + +# Stack + +`Stack` is a layout component used to group elements together and apply a space between them. + +`vstack` is used to stack elements in the vertical direction. + +`hstack` is used to stack elements in the horizontal direction. + +`stack` is used to stack elements in the vertical or horizontal direction. + +These components are based on the `flex` component and therefore inherit all of its props. + +The `stack` component can be used with the `flex_direction` prop to set to either `row` or `column` to set the direction. + +```python demo +rx.flex( + rx.stack( + rx.box( + "Example", + bg="orange", + border_radius="3px", + width="20%", + ), + rx.box( + "Example", + bg="lightblue", + border_radius="3px", + width="30%", + ), + rx.box( + "Example", + bg="lightgreen", + border_radius="3px", + width="50%", + ), + flex_direction="row", + width="100%", + ), + rx.stack( + rx.box( + "Example", + bg="orange", + border_radius="3px", + width="20%", + ), + rx.box( + "Example", + bg="lightblue", + border_radius="3px", + width="30%", + ), + rx.box( + "Example", + bg="lightgreen", + border_radius="3px", + width="50%", + ), + flex_direction="column", + width="100%", + ), + width="100%", +) +``` + +## Hstack + +```python demo +rx.hstack( + rx.box( + "Example", bg="red", border_radius="3px", width="10%" + ), + rx.box( + "Example", + bg="orange", + border_radius="3px", + width="10%", + ), + rx.box( + "Example", + bg="yellow", + border_radius="3px", + width="10%", + ), + rx.box( + "Example", + bg="lightblue", + border_radius="3px", + width="10%", + ), + rx.box( + "Example", + bg="lightgreen", + border_radius="3px", + width="60%", + ), + width="100%", +) +``` + +## Vstack + +```python demo +rx.vstack( + rx.box( + "Example", bg="red", border_radius="3px", width="20%" + ), + rx.box( + "Example", + bg="orange", + border_radius="3px", + width="40%", + ), + rx.box( + "Example", + bg="yellow", + border_radius="3px", + width="60%", + ), + rx.box( + "Example", + bg="lightblue", + border_radius="3px", + width="80%", + ), + rx.box( + "Example", + bg="lightgreen", + border_radius="3px", + width="100%", + ), + width="100%", +) +``` + +## Real World Example + +```python demo +rx.hstack( + rx.box( + rx.heading("Saving Money"), + rx.text("Saving money is an art that combines discipline, strategic planning, and the wisdom to foresee future needs and emergencies. It begins with the simple act of setting aside a portion of one's income, creating a buffer that can grow over time through interest or investments.", margin_top="0.5em"), + padding="1em", + border_width="1px", + ), + rx.box( + rx.heading("Spending Money"), + rx.text("Spending money is a balancing act between fulfilling immediate desires and maintaining long-term financial health. It's about making choices, sometimes indulging in the pleasures of the moment, and at other times, prioritizing essential expenses.", margin_top="0.5em"), + padding="1em", + border_width="1px", + ), + gap="2em", +) + +``` diff --git a/docs/library/media/audio.md b/docs/library/media/audio.md new file mode 100644 index 00000000000..e842fe162ed --- /dev/null +++ b/docs/library/media/audio.md @@ -0,0 +1,28 @@ +--- +components: + - rx.audio +--- + +# Audio + +```python exec +import reflex as rx +from pcweb.pages.docs import library +``` + +The audio component can display an audio given an src path as an argument. This could either be a local path from the assets folder or an external link. + +```python demo +rx.audio( + src="https://www.learningcontainer.com/wp-content/uploads/2020/02/Kalimba.mp3", + width="400px", + height="32px", +) +``` + +If we had a local file in the `assets` folder named `test.mp3` we could set `src="/test.mp3"` to view the audio file. + +```md alert info +# How to let your user upload an audio file +To let a user upload an audio file to your app check out the [upload docs]({library.forms.upload.path}). +``` diff --git a/docs/library/media/image.md b/docs/library/media/image.md new file mode 100644 index 00000000000..55d99f703c5 --- /dev/null +++ b/docs/library/media/image.md @@ -0,0 +1,63 @@ +--- +components: + - rx.image +--- + +```python exec +import reflex as rx +from pcweb.constants import REFLEX_ASSETS_CDN +from pcweb.pages.docs import library +``` + +# Image + +The Image component can display an image given a `src` path as an argument. +This could either be a local path from the assets folder or an external link. + +```python demo +rx.image(src=f"{REFLEX_ASSETS_CDN}other/logo.jpg", width="100px", height="auto") +``` + +Image composes a box and can be styled similarly. + +```python demo +rx.image( + src=f"{REFLEX_ASSETS_CDN}other/logo.jpg", + width="100px", + height="auto", + border_radius="15px 50px", + border="5px solid #555", +) +``` + +You can also pass a `PIL` image object as the `src`. + +```python demo box +rx.image(src="https://picsum.photos/id/1/200/300", alt="An Unsplash Image") +``` + +```python +from PIL import Image +import requests + + +class ImageState(rx.State): + url: str = f"https://picsum.photos/id/1/200/300" + image: Image.Image = Image.open(requests.get(url, stream=True).raw) + + +def image_pil_example(): + return rx.vstack( + rx.image(src=ImageState.image) + ) +``` + +```md alert info +# rx.image only accepts URLs and Pillow Images +A cv2 image must be converted to a PIL image to be passed directly to `rx.image` as a State variable, or saved to the `assets` folder and then passed to the `rx.image` component. +``` + +```md alert info +# How to let your user upload an image +To let a user upload an image to your app check out the [upload docs]({library.forms.upload.path}). +``` diff --git a/docs/library/media/video.md b/docs/library/media/video.md new file mode 100644 index 00000000000..328e44f8133 --- /dev/null +++ b/docs/library/media/video.md @@ -0,0 +1,29 @@ +--- +components: + - rx.video +--- + +# Video + +```python exec +import reflex as rx +from pcweb.pages.docs import library +``` + +The video component can display a video given an src path as an argument. This could either be a local path from the assets folder or an external link. + +```python demo +rx.video( + src="https://www.youtube.com/embed/9bZkp7q19f0", + width="400px", + height="auto" +) +``` + +If we had a local file in the `assets` folder named `test.mp4` we could set `url="/test.mp4"` to view the video. + + +```md alert info +# How to let your user upload a video +To let a user upload a video to your app check out the [upload docs]({library.forms.upload.path}). +``` diff --git a/docs/library/other/clipboard.md b/docs/library/other/clipboard.md new file mode 100644 index 00000000000..061dc5d33a0 --- /dev/null +++ b/docs/library/other/clipboard.md @@ -0,0 +1,79 @@ +--- +components: + - rx.clipboard +--- + +```python exec +import reflex as rx +from pcweb.pages.docs import styling +``` + +# Clipboard + +_New in 0.5.6_ + +The Clipboard component can be used to respond to paste events with complex data. + +If the Clipboard component is included in a page without children, +`rx.clipboard()`, then it will attach to the document's `paste` event handler +and will be triggered when data is pasted anywhere into the page. + +```python demo exec +class ClipboardPasteState(rx.State): + @rx.event + def on_paste(self, data: list[tuple[str, str]]): + for mime_type, item in data: + yield rx.toast(f"Pasted {mime_type} data: {item}") + + +def clipboard_example(): + return rx.fragment( + rx.clipboard(on_paste=ClipboardPasteState.on_paste), + "Paste Content Here", + ) +``` + +The `data` argument passed to the `on_paste` method is a list of tuples, where +each tuple contains the MIME type of the pasted data and the data itself. Binary +data will be base64 encoded as a data URI, and can be decoded using python's +`urlopen` or used directly as the `src` prop of an image. + +## Scoped Paste Events + +If you want to limit the scope of the paste event to a specific element, wrap +the `rx.clipboard` component around the elements that should trigger the paste +event. + +To avoid having outer paste handlers also trigger the event, you can use the +event action `.stop_propagation` to prevent the paste from bubbling up through +the DOM. + +If you need to also prevent the default action of pasting the data into a text +box, you can also attach the `.prevent_default` action. + +```python demo exec +class ClipboardPasteImageState(rx.State): + last_image_uri: str = "" + + def on_paste(self, data: list[tuple[str, str]]): + for mime_type, item in data: + if mime_type.startswith("image/"): + self.last_image_uri = item + break + else: + return rx.toast("Did not find an image in the pasted data") + + +def clipboard_image_example(): + return rx.vstack( + rx.clipboard( + rx.input(placeholder="Paste Image (stop propagation)"), + on_paste=ClipboardPasteImageState.on_paste.stop_propagation + ), + rx.clipboard( + rx.input(placeholder="Paste Image (prevent default)"), + on_paste=ClipboardPasteImageState.on_paste.prevent_default + ), + rx.image(src=ClipboardPasteImageState.last_image_uri), + ) +``` diff --git a/docs/library/other/html.md b/docs/library/other/html.md new file mode 100644 index 00000000000..3fc169fb5cb --- /dev/null +++ b/docs/library/other/html.md @@ -0,0 +1,119 @@ +--- +components: + - rx.el.a + - rx.el.abbr + - rx.el.address + - rx.el.area + - rx.el.article + - rx.el.aside + - rx.el.audio + - rx.el.b + - rx.el.bdi + - rx.el.bdo + - rx.el.blockquote + - rx.el.body + - rx.el.br + - rx.el.button + - rx.el.canvas + - rx.el.caption + - rx.el.cite + - rx.el.code + - rx.el.col + - rx.el.colgroup + - rx.el.data + - rx.el.dd + - rx.el.Del + - rx.el.details + - rx.el.dfn + - rx.el.dialog + - rx.el.div + - rx.el.dl + - rx.el.dt + - rx.el.em + - rx.el.embed + - rx.el.fieldset + - rx.el.figcaption + - rx.el.footer + - rx.el.form + - rx.el.h1 + - rx.el.h2 + - rx.el.h3 + - rx.el.h4 + - rx.el.h5 + - rx.el.h6 + - rx.el.head + - rx.el.header + - rx.el.hr + - rx.el.html + - rx.el.i + - rx.el.iframe + - rx.el.img + - rx.el.input + - rx.el.ins + - rx.el.kbd + - rx.el.label + - rx.el.legend + - rx.el.li + - rx.el.link + - rx.el.main + - rx.el.mark + - rx.el.math + - rx.el.meta + - rx.el.meter + - rx.el.nav + - rx.el.noscript + - rx.el.object + - rx.el.ol + - rx.el.optgroup + - rx.el.option + - rx.el.output + - rx.el.p + - rx.el.picture + - rx.el.portal + - rx.el.pre + - rx.el.progress + - rx.el.q + - rx.el.rp + - rx.el.rt + - rx.el.ruby + - rx.el.s + - rx.el.samp + - rx.el.script + - rx.el.section + - rx.el.select + - rx.el.small + - rx.el.source + - rx.el.span + - rx.el.strong + - rx.el.sub + - rx.el.sup + - rx.el.svg.circle + - rx.el.svg.defs + - rx.el.svg.linear_gradient + - rx.el.svg.polygon + - rx.el.svg.path + - rx.el.svg.rect + - rx.el.svg.stop + - rx.el.table + - rx.el.tbody + - rx.el.td + - rx.el.template + - rx.el.textarea + - rx.el.tfoot + - rx.el.th + - rx.el.thead + - rx.el.time + - rx.el.title + - rx.el.tr + - rx.el.track + - rx.el.u + - rx.el.ul + - rx.el.video + - rx.el.wbr +--- + +# HTML + +Reflex also provides a set of HTML elements that can be used to create web pages. These elements are the same as the HTML elements that are used in web development. These elements come unstyled bhy default. You can style them using style props or tailwindcss classes. + +The following is a list of the HTML elements that are available in Reflex: \ No newline at end of file diff --git a/docs/library/other/html_embed.md b/docs/library/other/html_embed.md new file mode 100644 index 00000000000..dfa79f90fde --- /dev/null +++ b/docs/library/other/html_embed.md @@ -0,0 +1,38 @@ +--- +components: + - rx.html +--- + +```python exec +import reflex as rx +from pcweb.pages.docs import styling +``` + +# HTML Embed + +The HTML component can be used to render raw HTML code. + +Before you reach for this component, consider using Reflex's raw HTML element support instead. + +```python demo +rx.vstack( + rx.html("

    Hello World

    "), + rx.html("

    Hello World

    "), + rx.html("

    Hello World

    "), + rx.html("

    Hello World

    "), + rx.html("
    Hello World
    "), + rx.html("
    Hello World
    "), +) +``` + +```md alert +# Missing Styles? +Reflex uses Radix-UI and tailwind for styling, both of which reset default styles for headings. +If you are using the html component and want pretty default styles, consider setting `class_name='prose'`, adding `@tailwindcss/typography` package to `frontend_packages` and enabling it via `tailwind` config in `rxconfig.py`. See the [Tailwind docs]({styling.overview.path}) for an example of adding this plugin. +``` + +In this example, we render an image. + +```python demo +rx.html("") +``` diff --git a/docs/library/other/memo.md b/docs/library/other/memo.md new file mode 100644 index 00000000000..2fe0798d174 --- /dev/null +++ b/docs/library/other/memo.md @@ -0,0 +1,165 @@ +```python exec +import reflex as rx +from pcweb.pages.docs import styling +``` + +# Memo + +The `memo` decorator is used to optimize component rendering by memoizing components that don't need to be re-rendered. This is particularly useful for expensive components that depend on specific props and don't need to be re-rendered when other state changes in your application. + +## Requirements + +When using `rx.memo`, you must follow these requirements: + +1. **Type all arguments**: All arguments to a memoized component must have type annotations. +2. **Use keyword arguments**: When calling a memoized component, you must use keyword arguments (not positional arguments). + +## Basic Usage + +When you wrap a component function with `@rx.memo`, the component will only re-render when its props change. This helps improve performance by preventing unnecessary re-renders. + +```python +# Define a state class to track count +class DemoState(rx.State): + count: int = 0 + + @rx.event + def increment(self): + self.count += 1 + +# Define a memoized component +@rx.memo +def expensive_component(label: str) -> rx.Component: + return rx.vstack( + rx.heading(label), + rx.text("This component only re-renders when props change!"), + rx.divider(), + ) + +# Use the memoized component in your app +def index(): + return rx.vstack( + rx.heading("Memo Example"), + rx.text("Count: 0"), # This will update with state.count + rx.button("Increment", on_click=DemoState.increment), + rx.divider(), + expensive_component(label="Memoized Component"), # Must use keyword arguments + spacing="4", + padding="4", + border_radius="md", + border="1px solid #eaeaea", + ) +``` + +In this example, the `expensive_component` will only re-render when the `label` prop changes, not when the `count` state changes. + +## With Event Handlers + +You can also use `rx.memo` with components that have event handlers: + +```python +# Define a state class to track clicks +class ButtonState(rx.State): + clicks: int = 0 + + @rx.event + def increment(self): + self.clicks += 1 + +# Define a memoized button component +@rx.memo +def my_button(text: str, on_click: rx.EventHandler) -> rx.Component: + return rx.button(text, on_click=on_click) + +# Use the memoized button in your app +def index(): + return rx.vstack( + rx.text("Clicks: 0"), # This will update with state.clicks + my_button( + text="Click me", + on_click=ButtonState.increment + ), + spacing="4", + ) +``` + +## With State Variables + +When used with state variables, memoized components will only re-render when the specific state variables they depend on change: + +```python +# Define a state class with multiple variables +class AppState(rx.State): + name: str = "World" + count: int = 0 + + @rx.event + def increment(self): + self.count += 1 + + @rx.event + def set_name(self, name: str): + self.name = name + +# Define a memoized greeting component +@rx.memo +def greeting(name: str) -> rx.Component: + return rx.heading("Hello, " + name) # Will display the name prop + +# Use the memoized component with state variables +def index(): + return rx.vstack( + greeting(name=AppState.name), # Must use keyword arguments + rx.text("Count: 0"), # Will display the count + rx.button("Increment Count", on_click=AppState.increment), + rx.input( + placeholder="Enter your name", + on_change=AppState.set_name, + value="World", # Will be bound to AppState.name + ), + spacing="4", + ) +``` + +## Advanced Event Handler Example + +You can also pass arguments to event handlers in memoized components: + +```python +# Define a state class to track messages +class MessageState(rx.State): + message: str = "" + + @rx.event + def set_message(self, text: str): + self.message = text + +# Define a memoized component with event handlers that pass arguments +@rx.memo +def action_buttons(on_action: rx.EventHandler[rx.event.passthrough_event_spec(str)]) -> rx.Component: + return rx.hstack( + rx.button("Save", on_click=on_action("Saved!")), + rx.button("Delete", on_click=on_action("Deleted!")), + rx.button("Cancel", on_click=on_action("Cancelled!")), + spacing="2", + ) + +# Use the memoized component with event handlers +def index(): + return rx.vstack( + rx.text("Status: "), # Will display the message + action_buttons(on_action=MessageState.set_message), + spacing="4", + ) +``` + +## Performance Considerations + +Use `rx.memo` for: +- Components with expensive rendering logic +- Components that render the same result given the same props +- Components that re-render too often due to parent component updates + +Avoid using `rx.memo` for: +- Simple components where the memoization overhead might exceed the performance gain +- Components that almost always receive different props on re-render diff --git a/docs/library/other/script.md b/docs/library/other/script.md new file mode 100644 index 00000000000..8d9356e7c41 --- /dev/null +++ b/docs/library/other/script.md @@ -0,0 +1,38 @@ +--- +components: + - rx.script +--- + +```python exec +import reflex as rx +``` + +# Script + +The Script component can be used to include inline javascript or javascript files by URL. + +It uses the [`next/script` component](https://nextjs.org/docs/app/api-reference/components/script) to inject the script and can be safely used with conditional rendering to allow script side effects to be controlled by the state. + +```python +rx.script("console.log('inline javascript')") +``` + +Complex inline scripting should be avoided. +If the code to be included is more than a couple lines, it is more maintainable to implement it in a separate javascript file in the `assets` directory and include it via the `src` prop. + +```python +rx.script(src="/my-custom.js") +``` + +This component is particularly helpful for including tracking and social scripts. +Any additional attrs needed for the script tag can be supplied via `custom_attrs` prop. + +```python +rx.script(src="//gc.zgo.at/count.js", custom_attrs=\{"data-goatcounter": "https://reflextoys.goatcounter.com/count"}) +``` + +This code renders to something like the following to enable stat counting with a third party service. + +```jsx + +``` diff --git a/docs/library/other/skeleton.md b/docs/library/other/skeleton.md new file mode 100644 index 00000000000..46c6a08f6e7 --- /dev/null +++ b/docs/library/other/skeleton.md @@ -0,0 +1,27 @@ +--- +description: Skeleton, a loading placeholder component for content that is not yet available. +components: + - rx.skeleton +--- + +```python exec +import reflex as rx +``` + +# Skeleton (loading placeholder) + +`Skeleton` is a loading placeholder component that serves as a visual placeholder while content is loading. +It is useful for maintaining the layout's structure and providing users with a sense of progression while awaiting the final content. + +```python demo +rx.vstack( + rx.skeleton(rx.button("button-small"), height="10px"), + rx.skeleton(rx.button("button-big"), height="20px"), + rx.skeleton(rx.text("Text is loaded."), loading=True,), + rx.skeleton(rx.text("Text is already loaded."), loading=False,), +), +``` + +When using `Skeleton` with text, wrap the text itself instead of the parent element to have a placeholder of the same size. + +Use the loading prop to control whether the skeleton or its children are displayed. Skeleton preserves the dimensions of children when they are hidden and disables interactive elements. diff --git a/docs/library/other/theme.md b/docs/library/other/theme.md new file mode 100644 index 00000000000..3ba92af8783 --- /dev/null +++ b/docs/library/other/theme.md @@ -0,0 +1,31 @@ +--- + components: + - rx.theme + - rx.theme_panel +--- + +# Theme + + The `Theme` component is used to change the theme of the application. The `Theme` can be set directly in the rx.App. + + ```python + app = rx.App( + theme=rx.theme( + appearance="light", has_background=True, radius="large", accent_color="teal" + ) + ) + ``` + + # Theme Panel + + The `ThemePanel` component is a container for the `Theme` component. It provides a way to change the theme of the application. + + ```python + rx.theme_panel() + ``` + + The theme panel is closed by default. You can set it open `default_open=True`. + + ```python + rx.theme_panel(default_open=True) + ``` diff --git a/docs/library/overlay/alert_dialog.md b/docs/library/overlay/alert_dialog.md new file mode 100644 index 00000000000..6316a6ee743 --- /dev/null +++ b/docs/library/overlay/alert_dialog.md @@ -0,0 +1,367 @@ +--- +components: + - rx.alert_dialog.root + - rx.alert_dialog.content + - rx.alert_dialog.trigger + - rx.alert_dialog.title + - rx.alert_dialog.description + - rx.alert_dialog.action + - rx.alert_dialog.cancel + +only_low_level: + - True + +AlertDialogRoot: | + lambda **props: rx.alert_dialog.root( + rx.alert_dialog.trigger( + rx.button("Revoke access"), + ), + rx.alert_dialog.content( + rx.alert_dialog.title("Revoke access"), + rx.alert_dialog.description( + "Are you sure? This application will no longer be accessible and any existing sessions will be expired.", + ), + rx.flex( + rx.alert_dialog.cancel( + rx.button("Cancel"), + ), + rx.alert_dialog.action( + rx.button("Revoke access"), + ), + spacing="3", + ), + ), + **props + ) + +AlertDialogContent: | + lambda **props: rx.alert_dialog.root( + rx.alert_dialog.trigger( + rx.button("Revoke access"), + ), + rx.alert_dialog.content( + rx.alert_dialog.title("Revoke access"), + rx.alert_dialog.description( + "Are you sure? This application will no longer be accessible and any existing sessions will be expired.", + ), + rx.flex( + rx.alert_dialog.cancel( + rx.button("Cancel"), + ), + rx.alert_dialog.action( + rx.button("Revoke access"), + ), + spacing="3", + ), + **props + ), + ) +--- + +```python exec +import reflex as rx +``` + +# Alert Dialog + +An alert dialog is a modal confirmation dialog that interrupts the user and expects a response. + +The `alert_dialog.root` contains all the parts of the dialog. + +The `alert_dialog.trigger` wraps the control that will open the dialog. + +The `alert_dialog.content` contains the content of the dialog. + +The `alert_dialog.title` is the title that is announced when the dialog is opened. + +The `alert_dialog.description` is an optional description that is announced when the dialog is opened. + +The `alert_dialog.action` wraps the control that will close the dialog. This should be distinguished visually from the `alert_dialog.cancel` control. + +The `alert_dialog.cancel` wraps the control that will close the dialog. This should be distinguished visually from the `alert_dialog.action` control. + +## Basic Example + +```python demo +rx.alert_dialog.root( + rx.alert_dialog.trigger( + rx.button("Revoke access"), + ), + rx.alert_dialog.content( + rx.alert_dialog.title("Revoke access"), + rx.alert_dialog.description( + "Are you sure? This application will no longer be accessible and any existing sessions will be expired.", + ), + rx.flex( + rx.alert_dialog.cancel( + rx.button("Cancel"), + ), + rx.alert_dialog.action( + rx.button("Revoke access"), + ), + spacing="3", + ), + ), +) +``` + +This example has a different color scheme and the `cancel` and `action` buttons are right aligned. + +```python demo +rx.alert_dialog.root( + rx.alert_dialog.trigger( + rx.button("Revoke access", color_scheme="red"), + ), + rx.alert_dialog.content( + rx.alert_dialog.title("Revoke access"), + rx.alert_dialog.description( + "Are you sure? This application will no longer be accessible and any existing sessions will be expired.", + size="2", + ), + rx.flex( + rx.alert_dialog.cancel( + rx.button("Cancel", variant="soft", color_scheme="gray"), + ), + rx.alert_dialog.action( + rx.button("Revoke access", color_scheme="red", variant="solid"), + ), + spacing="3", + margin_top="16px", + justify="end", + ), + style={"max_width": 450}, + ), +) +``` + +Use the `inset` component to align content flush with the sides of the dialog. + +```python demo +rx.alert_dialog.root( + rx.alert_dialog.trigger( + rx.button("Delete Users", color_scheme="red"), + ), + rx.alert_dialog.content( + rx.alert_dialog.title("Delete Users"), + rx.alert_dialog.description( + "Are you sure you want to delete these users? This action is permanent and cannot be undone.", + size="2", + ), + rx.inset( + rx.table.root( + rx.table.header( + rx.table.row( + rx.table.column_header_cell("Full Name"), + rx.table.column_header_cell("Email"), + rx.table.column_header_cell("Group"), + ), + ), + rx.table.body( + rx.table.row( + rx.table.row_header_cell("Danilo Rosa"), + rx.table.cell("danilo@example.com"), + rx.table.cell("Developer"), + ), + rx.table.row( + rx.table.row_header_cell("Zahra Ambessa"), + rx.table.cell("zahra@example.com"), + rx.table.cell("Admin"), + ), + ), + ), + side="x", + margin_top="24px", + margin_bottom="24px", + ), + rx.flex( + rx.alert_dialog.cancel( + rx.button("Cancel", variant="soft", color_scheme="gray"), + ), + rx.alert_dialog.action( + rx.button("Delete users", color_scheme="red"), + ), + spacing="3", + justify="end", + ), + style={"max_width": 500}, + ), +) +``` + +## Events when the Alert Dialog opens or closes + +The `on_open_change` event is called when the `open` state of the dialog changes. It is used in conjunction with the `open` prop. + +```python demo exec +class AlertDialogState(rx.State): + num_opens: int = 0 + opened: bool = False + + @rx.event + def count_opens(self, value: bool): + self.opened = value + self.num_opens += 1 + + +def alert_dialog(): + return rx.flex( + rx.heading(f"Number of times alert dialog opened or closed: {AlertDialogState.num_opens}"), + rx.heading(f"Alert Dialog open: {AlertDialogState.opened}"), + rx.alert_dialog.root( + rx.alert_dialog.trigger( + rx.button("Revoke access", color_scheme="red"), + ), + rx.alert_dialog.content( + rx.alert_dialog.title("Revoke access"), + rx.alert_dialog.description( + "Are you sure? This application will no longer be accessible and any existing sessions will be expired.", + size="2", + ), + rx.flex( + rx.alert_dialog.cancel( + rx.button("Cancel", variant="soft", color_scheme="gray"), + ), + rx.alert_dialog.action( + rx.button("Revoke access", color_scheme="red", variant="solid"), + ), + spacing="3", + margin_top="16px", + justify="end", + ), + style={"max_width": 450}, + ), + on_open_change=AlertDialogState.count_opens, + ), + direction="column", + spacing="3", + ) +``` + +## Controlling Alert Dialog with State + +This example shows how to control whether the dialog is open or not with state. This is an easy way to show the dialog without needing to use the `rx.alert_dialog.trigger`. + +`rx.alert_dialog.root` has a prop `open` that can be set to a boolean value to control whether the dialog is open or not. + +We toggle this `open` prop with a button outside of the dialog and the `rx.alert_dialog.cancel` and `rx.alert_dialog.action` buttons inside the dialog. + +```python demo exec +class AlertDialogState2(rx.State): + opened: bool = False + + @rx.event + def dialog_open(self): + self.opened = ~self.opened + + +def alert_dialog2(): + return rx.box( + rx.alert_dialog.root( + rx.alert_dialog.content( + rx.alert_dialog.title("Revoke access"), + rx.alert_dialog.description( + "Are you sure? This application will no longer be accessible and any existing sessions will be expired.", + ), + rx.flex( + rx.alert_dialog.cancel( + rx.button("Cancel", on_click=AlertDialogState2.dialog_open), + ), + rx.alert_dialog.action( + rx.button("Revoke access", on_click=AlertDialogState2.dialog_open), + ), + spacing="3", + ), + ), + open=AlertDialogState2.opened, + ), + rx.button("Button to Open the Dialog", on_click=AlertDialogState2.dialog_open), +) +``` + + +## Form Submission to a Database from an Alert Dialog + +This example adds new users to a database from an alert dialog using a form. + +1. It defines a User1 model with name and email fields. +2. The `add_user_to_db` method adds a new user to the database, checking for existing emails. +3. On form submission, it calls the `add_user_to_db` method. +4. The UI component has: +- A button to open an alert dialog +- An alert dialog containing a form to add a new user +- Input fields for name and email +- Submit and Cancel buttons + + +```python demo exec +class User1(rx.Model, table=True): + """The user model.""" + name: str + email: str + +class State(rx.State): + + current_user: User1 = User1() + + @rx.event + def add_user_to_db(self, form_data: dict): + self.current_user = form_data + ### Uncomment the code below to add your data to a database ### + # with rx.session() as session: + # if session.exec( + # select(User1).where(user.email == self.current_user["email"]) + # ).first(): + # return rx.window_alert("User with this email already exists") + # session.add(User1(**self.current_user)) + # session.commit() + + return rx.toast.info(f"User {self.current_user['name']} has been added.", position="bottom-right") + + +def index() -> rx.Component: + return rx.alert_dialog.root( + rx.alert_dialog.trigger( + rx.button( + rx.icon("plus", size=26), + rx.text("Add User", size="4"), + ), + ), + rx.alert_dialog.content( + rx.alert_dialog.title( + "Add New User", + ), + rx.alert_dialog.description( + "Fill the form with the user's info", + ), + rx.form( + rx.flex( + rx.input( + placeholder="User Name", name="name" + ), + rx.input( + placeholder="user@reflex.dev", name="email" + ), + rx.flex( + rx.alert_dialog.cancel( + rx.button( + "Cancel", + variant="soft", + color_scheme="gray", + ), + ), + rx.alert_dialog.action( + rx.button("Submit", type="submit"), + ), + spacing="3", + justify="end", + ), + direction="column", + spacing="4", + ), + on_submit=State.add_user_to_db, + reset_on_submit=False, + ), + max_width="450px", + ), + ) +``` diff --git a/docs/library/overlay/context_menu.md b/docs/library/overlay/context_menu.md new file mode 100644 index 00000000000..b284a82986c --- /dev/null +++ b/docs/library/overlay/context_menu.md @@ -0,0 +1,343 @@ +--- +components: + - rx.context_menu.root + - rx.context_menu.item + - rx.context_menu.separator + - rx.context_menu.trigger + - rx.context_menu.content + - rx.context_menu.sub + - rx.context_menu.sub_trigger + - rx.context_menu.sub_content + +only_low_level: + - True + +ContextMenuRoot: | + lambda **props: rx.context_menu.root( + rx.context_menu.trigger( + rx.text("Context Menu (right click)") + ), + rx.context_menu.content( + rx.context_menu.item("Copy", shortcut="⌘ C"), + rx.context_menu.item("Share"), + rx.context_menu.item("Delete", shortcut="⌘ ⌫", color="red"), + rx.context_menu.sub( + rx.context_menu.sub_trigger("More"), + rx.context_menu.sub_content( + rx.context_menu.item("Eradicate"), + rx.context_menu.item("Duplicate"), + rx.context_menu.item("Archive"), + ), + ), + ), + **props + ) + +ContextMenuTrigger: | + lambda **props: rx.context_menu.root( + rx.context_menu.trigger( + rx.text("Context Menu (right click)"), + **props + ), + rx.context_menu.content( + rx.context_menu.item("Copy", shortcut="⌘ C"), + rx.context_menu.item("Share"), + rx.context_menu.item("Delete", shortcut="⌘ ⌫", color="red"), + rx.context_menu.sub( + rx.context_menu.sub_trigger("More"), + rx.context_menu.sub_content( + rx.context_menu.item("Eradicate"), + rx.context_menu.item("Duplicate"), + rx.context_menu.item("Archive"), + ), + ), + ), + ) + +ContextMenuContent: | + lambda **props: rx.context_menu.root( + rx.context_menu.trigger( + rx.text("Context Menu (right click)") + ), + rx.context_menu.content( + rx.context_menu.item("Copy", shortcut="⌘ C"), + rx.context_menu.item("Share"), + rx.context_menu.item("Delete", shortcut="⌘ ⌫", color="red"), + rx.context_menu.sub( + rx.context_menu.sub_trigger("More"), + rx.context_menu.sub_content( + rx.context_menu.item("Eradicate"), + rx.context_menu.item("Duplicate"), + rx.context_menu.item("Archive"), + ), + ), + **props + ), + ) + +ContextMenuSub: | + lambda **props: rx.context_menu.root( + rx.context_menu.trigger( + rx.text("Context Menu (right click)") + ), + rx.context_menu.content( + rx.context_menu.item("Copy", shortcut="⌘ C"), + rx.context_menu.item("Share"), + rx.context_menu.item("Delete", shortcut="⌘ ⌫", color="red"), + rx.context_menu.sub( + rx.context_menu.sub_trigger("More"), + rx.context_menu.sub_content( + rx.context_menu.item("Eradicate"), + rx.context_menu.item("Duplicate"), + rx.context_menu.item("Archive"), + ), + **props + ), + ), + ) + +ContextMenuSubTrigger: | + lambda **props: rx.context_menu.root( + rx.context_menu.trigger( + rx.text("Context Menu (right click)") + ), + rx.context_menu.content( + rx.context_menu.item("Copy", shortcut="⌘ C"), + rx.context_menu.item("Share"), + rx.context_menu.item("Delete", shortcut="⌘ ⌫", color="red"), + rx.context_menu.sub( + rx.context_menu.sub_trigger("More", **props), + rx.context_menu.sub_content( + rx.context_menu.item("Eradicate"), + rx.context_menu.item("Duplicate"), + rx.context_menu.item("Archive"), + ), + ), + ), + ) + +ContextMenuSubContent: | + lambda **props: rx.context_menu.root( + rx.context_menu.trigger( + rx.text("Context Menu (right click)") + ), + rx.context_menu.content( + rx.context_menu.item("Copy", shortcut="⌘ C"), + rx.context_menu.item("Share"), + rx.context_menu.item("Delete", shortcut="⌘ ⌫", color="red"), + rx.context_menu.sub( + rx.context_menu.sub_trigger("More"), + rx.context_menu.sub_content( + rx.context_menu.item("Eradicate"), + rx.context_menu.item("Duplicate"), + rx.context_menu.item("Archive"), + **props + ), + ), + ), + ) + +ContextMenuItem: | + lambda **props: rx.context_menu.root( + rx.context_menu.trigger( + rx.text("Context Menu (right click)") + ), + rx.context_menu.content( + rx.context_menu.item("Copy", shortcut="⌘ C", **props), + rx.context_menu.item("Share", **props), + rx.context_menu.item("Delete", shortcut="⌘ ⌫", color="red", **props), + rx.context_menu.sub( + rx.context_menu.sub_trigger("More"), + rx.context_menu.sub_content( + rx.context_menu.item("Eradicate", **props), + rx.context_menu.item("Duplicate", **props), + rx.context_menu.item("Archive", **props), + ), + ), + ), + ) +--- + +```python exec +import reflex as rx +``` + +# Context Menu + +A Context Menu is a popup menu that appears upon user interaction, such as a right-click or a hover. + +## Basic Usage + +A Context Menu is composed of a `context_menu.root`, a `context_menu.trigger` and a `context_menu.content`. The `context_menu_root` contains all the parts of a context menu. The `context_menu.trigger` is the element that the user interacts with to open the menu. It wraps the element that will open the context menu. The `context_menu.content` is the component that pops out when the context menu is open. + +The `context_menu.item` contains the actual context menu items and sits under the `context_menu.content`. + +The `context_menu.sub` contains all the parts of a submenu. There is a `context_menu.sub_trigger`, which is an item that opens a submenu. It must be rendered inside a `context_menu.sub` component. The `context_menu.sub_content` is the component that pops out when a submenu is open. It must also be rendered inside a `context_menu.sub` component. + +The `context_menu.separator` is used to visually separate items in a context menu. + +```python demo +rx.context_menu.root( + rx.context_menu.trigger( + rx.button("Right click me"), + ), + rx.context_menu.content( + rx.context_menu.item("Edit", shortcut="⌘ E"), + rx.context_menu.item("Duplicate", shortcut="⌘ D"), + rx.context_menu.separator(), + rx.context_menu.item("Archive", shortcut="⌘ N"), + rx.context_menu.sub( + rx.context_menu.sub_trigger("More"), + rx.context_menu.sub_content( + rx.context_menu.item("Move to project…"), + rx.context_menu.item("Move to folder…"), + rx.context_menu.separator(), + rx.context_menu.item("Advanced options…"), + ), + ), + rx.context_menu.separator(), + rx.context_menu.item("Share"), + rx.context_menu.item("Add to favorites"), + rx.context_menu.separator(), + rx.context_menu.item("Delete", shortcut="⌘ ⌫", color="red"), + ), +) +``` + +````md alert warning +# `rx.context_menu.item` must be a DIRECT child of `rx.context_menu.content` + +The code below for example is not allowed: + +```python +rx.context_menu.root( + rx.context_menu.trigger( + rx.button("Right click me"), + ), + rx.context_menu.content( + rx.cond( + State.count % 2 == 0, + rx.vstack( + rx.context_menu.item("Even Option 1", on_click=State.set_selected_option("Even Option 1")), + rx.context_menu.item("Even Option 2", on_click=State.set_selected_option("Even Option 2")), + rx.context_menu.item("Even Option 3", on_click=State.set_selected_option("Even Option 3")), + ), + rx.vstack( + rx.context_menu.item("Odd Option A", on_click=State.set_selected_option("Odd Option A")), + rx.context_menu.item("Odd Option B", on_click=State.set_selected_option("Odd Option B")), + rx.context_menu.item("Odd Option C", on_click=State.set_selected_option("Odd Option C")), + ) + ) + ), +) +``` +```` + +## Opening a Dialog from Context Menu using State + +Accessing an overlay component from within another overlay component is a common use case but does not always work exactly as expected. + +The code below will not work as expected as because the dialog is within the menu and the dialog will only be open when the menu is open, rendering the dialog unusable. + +```python +rx.context_menu.root( + rx.context_menu.trigger(rx.icon("ellipsis-vertical")), + rx.context_menu.content( + rx.context_menu.item( + rx.dialog.root( + rx.dialog.trigger(rx.text("Edit")), + rx.dialog.content(....), + ..... + ), + ), + ), +) +``` + +In this example, we will show how to open a dialog box from a context menu, where the menu will close and the dialog will open and be functional. + +```python demo exec +class ContextMenuState(rx.State): + which_dialog_open: str = "" + + @rx.event + def set_which_dialog_open(self, value: str): + self.which_dialog_open = value + + @rx.event + def delete(self): + yield rx.toast("Deleted item") + + @rx.event + def save_settings(self): + yield rx.toast("Saved settings") + + +def delete_dialog(): + return rx.alert_dialog.root( + rx.alert_dialog.content( + rx.alert_dialog.title("Are you Sure?"), + rx.alert_dialog.description( + rx.text( + "This action cannot be undone. Are you sure you want to delete this item?", + ), + margin_bottom="20px", + ), + rx.hstack( + rx.alert_dialog.action( + rx.button( + "Delete", + color_scheme="red", + on_click=ContextMenuState.delete, + ), + ), + rx.spacer(), + rx.alert_dialog.cancel(rx.button("Cancel")), + ), + ), + open=ContextMenuState.which_dialog_open == "delete", + on_open_change=ContextMenuState.set_which_dialog_open(""), + ) + + +def settings_dialog(): + return rx.dialog.root( + rx.dialog.content( + rx.dialog.title("Settings"), + rx.dialog.description( + rx.text("Set your settings in this settings dialog."), + margin_bottom="20px", + ), + rx.dialog.close( + rx.button("Close", on_click=ContextMenuState.save_settings), + ), + ), + open=ContextMenuState.which_dialog_open == "settings", + on_open_change=ContextMenuState.set_which_dialog_open(""), + ) + + +def context_menu_call_dialog() -> rx.Component: + return rx.vstack( + rx.context_menu.root( + rx.context_menu.trigger(rx.icon("ellipsis-vertical")), + rx.context_menu.content( + rx.context_menu.item( + "Delete", + on_click=ContextMenuState.set_which_dialog_open("delete"), + ), + rx.context_menu.item( + "Settings", + on_click=ContextMenuState.set_which_dialog_open("settings"), + ), + ), + ), + rx.cond( + ContextMenuState.which_dialog_open, + rx.heading(f"{ContextMenuState.which_dialog_open} dialog is open"), + ), + delete_dialog(), + settings_dialog(), + align="center", + ) +``` diff --git a/docs/library/overlay/dialog.md b/docs/library/overlay/dialog.md new file mode 100644 index 00000000000..604dc5c43c9 --- /dev/null +++ b/docs/library/overlay/dialog.md @@ -0,0 +1,283 @@ +--- +components: + - rx.dialog.root + - rx.dialog.trigger + - rx.dialog.title + - rx.dialog.content + - rx.dialog.description + - rx.dialog.close + +only_low_level: + - True + +DialogRoot: | + lambda **props: rx.dialog.root( + rx.dialog.trigger(rx.button("Open Dialog")), + rx.dialog.content( + rx.dialog.title("Welcome to Reflex!"), + rx.dialog.description( + "This is a dialog component. You can render anything you want in here.", + ), + rx.dialog.close( + rx.button("Close Dialog"), + ), + ), + **props, + ) + +DialogContent: | + lambda **props: rx.dialog.root( + rx.dialog.trigger(rx.button("Open Dialog")), + rx.dialog.content( + rx.dialog.title("Welcome to Reflex!"), + rx.dialog.description( + "This is a dialog component. You can render anything you want in here.", + ), + rx.dialog.close( + rx.button("Close Dialog"), + ), + **props, + ), + ) +--- + +```python exec +import reflex as rx +from pcweb.pages.docs import library +``` + +# Dialog + +The `dialog.root` contains all the parts of a dialog. + +The `dialog.trigger` wraps the control that will open the dialog. + +The `dialog.content` contains the content of the dialog. + +The `dialog.title` is a title that is announced when the dialog is opened. + +The `dialog.description` is a description that is announced when the dialog is opened. + +The `dialog.close` wraps the control that will close the dialog. + +```python demo +rx.dialog.root( + rx.dialog.trigger(rx.button("Open Dialog")), + rx.dialog.content( + rx.dialog.title("Welcome to Reflex!"), + rx.dialog.description( + "This is a dialog component. You can render anything you want in here.", + ), + rx.dialog.close( + rx.button("Close Dialog", size="3"), + ), + ), +) +``` + +## In context examples + +```python demo +rx.dialog.root( + rx.dialog.trigger( + rx.button("Edit Profile", size="4") + ), + rx.dialog.content( + rx.dialog.title("Edit Profile"), + rx.dialog.description( + "Change your profile details and preferences.", + size="2", + margin_bottom="16px", + ), + rx.flex( + rx.text("Name", as_="div", size="2", margin_bottom="4px", weight="bold"), + rx.input(default_value="Freja Johnson", placeholder="Enter your name"), + rx.text("Email", as_="div", size="2", margin_bottom="4px", weight="bold"), + rx.input(default_value="freja@example.com", placeholder="Enter your email"), + direction="column", + spacing="3", + ), + rx.flex( + rx.dialog.close( + rx.button("Cancel", color_scheme="gray", variant="soft"), + ), + rx.dialog.close( + rx.button("Save"), + ), + spacing="3", + margin_top="16px", + justify="end", + ), + ), +) +``` + +```python demo +rx.dialog.root( + rx.dialog.trigger(rx.button("View users", size="4")), + rx.dialog.content( + rx.dialog.title("Users"), + rx.dialog.description("The following users have access to this project."), + + rx.inset( + rx.table.root( + rx.table.header( + rx.table.row( + rx.table.column_header_cell("Full Name"), + rx.table.column_header_cell("Email"), + rx.table.column_header_cell("Group"), + ), + ), + rx.table.body( + rx.table.row( + rx.table.row_header_cell("Danilo Rosa"), + rx.table.cell("danilo@example.com"), + rx.table.cell("Developer"), + ), + rx.table.row( + rx.table.row_header_cell("Zahra Ambessa"), + rx.table.cell("zahra@example.com"), + rx.table.cell("Admin"), + ), + ), + ), + side="x", + margin_top="24px", + margin_bottom="24px", + ), + rx.flex( + rx.dialog.close( + rx.button("Close", variant="soft", color_scheme="gray"), + ), + spacing="3", + justify="end", + ), + ), +) +``` + +## Events when the Dialog opens or closes + +The `on_open_change` event is called when the `open` state of the dialog changes. It is used in conjunction with the `open` prop, which is passed to the event handler. + +```python demo exec +class DialogState(rx.State): + num_opens: int = 0 + opened: bool = False + + @rx.event + def count_opens(self, value: bool): + self.opened = value + self.num_opens += 1 + + +def dialog_example(): + return rx.flex( + rx.heading(f"Number of times dialog opened or closed: {DialogState.num_opens}"), + rx.heading(f"Dialog open: {DialogState.opened}"), + rx.dialog.root( + rx.dialog.trigger(rx.button("Open Dialog")), + rx.dialog.content( + rx.dialog.title("Welcome to Reflex!"), + rx.dialog.description( + "This is a dialog component. You can render anything you want in here.", + ), + rx.dialog.close( + rx.button("Close Dialog", size="3"), + ), + ), + on_open_change=DialogState.count_opens, + ), + direction="column", + spacing="3", + ) +``` + +Check out the [menu docs]({library.overlay.dropdown_menu.path}) for an example of opening a dialog from within a dropdown menu. + +## Form Submission to a Database from a Dialog + +This example adds new users to a database from a dialog using a form. + +1. It defines a User model with name and email fields. +2. The `add_user_to_db` method adds a new user to the database, checking for existing emails. +3. On form submission, it calls the `add_user_to_db` method. +4. The UI component has: + +- A button to open a dialog +- A dialog containing a form to add a new user +- Input fields for name and email +- Submit and Cancel buttons + +```python demo exec +class User(rx.Model, table=True): + """The user model.""" + name: str + email: str + +class State(rx.State): + + current_user: User = User() + + @rx.event + def add_user_to_db(self, form_data: dict): + self.current_user = form_data + ### Uncomment the code below to add your data to a database ### + # with rx.session() as session: + # if session.exec( + # select(User).where(user.email == self.current_user["email"]) + # ).first(): + # return rx.window_alert("User with this email already exists") + # session.add(User(**self.current_user)) + # session.commit() + + return rx.toast.info(f"User {self.current_user['name']} has been added.", position="bottom-right") + + +def index() -> rx.Component: + return rx.dialog.root( + rx.dialog.trigger( + rx.button( + rx.icon("plus", size=26), + rx.text("Add User", size="4"), + ), + ), + rx.dialog.content( + rx.dialog.title( + "Add New User", + ), + rx.dialog.description( + "Fill the form with the user's info", + ), + rx.form( + rx.flex( + rx.input( + placeholder="User Name", name="name" + ), + rx.input( + placeholder="user@reflex.dev", name="email" + ), + rx.flex( + rx.dialog.close( + rx.button( + "Cancel", + variant="soft", + color_scheme="gray", + ), + ), + rx.dialog.close( + rx.button("Submit", type="submit"), + ), + spacing="3", + justify="end", + ), + direction="column", + spacing="4", + ), + on_submit=State.add_user_to_db, + reset_on_submit=False, + ), + max_width="450px", + ), + ) +``` diff --git a/docs/library/overlay/drawer.md b/docs/library/overlay/drawer.md new file mode 100644 index 00000000000..a8beb954514 --- /dev/null +++ b/docs/library/overlay/drawer.md @@ -0,0 +1,119 @@ +--- +components: + - rx.drawer.root + - rx.drawer.trigger + - rx.drawer.overlay + - rx.drawer.portal + - rx.drawer.content + - rx.drawer.close + +only_low_level: + - True + +DrawerRoot: | + lambda **props: rx.drawer.root( + rx.drawer.trigger(rx.button("Open Drawer")), + rx.drawer.overlay(z_index="5"), + rx.drawer.portal( + rx.drawer.content( + rx.flex( + rx.drawer.close(rx.button("Close")), + ), + height="100%", + width="20em", + background_color="#FFF" + ), + ), + **props, + ) +--- + +```python exec +import reflex as rx +``` + +# Drawer + +```python demo +rx.drawer.root( + rx.drawer.trigger( + rx.button("Open Drawer") + ), + rx.drawer.overlay( + z_index="5" + ), + rx.drawer.portal( + rx.drawer.content( + rx.flex( + rx.drawer.close(rx.box(rx.button("Close"))), + align_items="start", + direction="column", + ), + top="auto", + right="auto", + height="100%", + width="20em", + padding="2em", + background_color="#FFF" + #background_color=rx.color("green", 3) + ) + ), + direction="left", +) +``` + +## Sidebar Menu with a Drawer and State + +This example shows how to create a sidebar menu with a drawer. The drawer is opened by clicking a button. The drawer contains links to different sections of the page. When a link is clicked the drawer closes and the page scrolls to the section. + +The `rx.drawer.root` component has an `open` prop that is set by the state variable `is_open`. Setting the `modal` prop to `False` allows the user to interact with the rest of the page while the drawer is open and allows the page to be scrolled when a user clicks one of the links. + +```python demo exec +class DrawerState(rx.State): + is_open: bool = False + + @rx.event + def toggle_drawer(self): + self.is_open = not self.is_open + +def drawer_content(): + return rx.drawer.content( + rx.flex( + rx.drawer.close(rx.button("Close", on_click=DrawerState.toggle_drawer)), + rx.link("Link 1", href="#test1", on_click=DrawerState.toggle_drawer), + rx.link("Link 2", href="#test2", on_click=DrawerState.toggle_drawer), + align_items="start", + direction="column", + ), + height="100%", + width="20%", + padding="2em", + background_color=rx.color("grass", 7), + ) + + +def lateral_menu(): + return rx.drawer.root( + rx.drawer.trigger(rx.button("Open Drawer", on_click=DrawerState.toggle_drawer)), + rx.drawer.overlay(), + rx.drawer.portal(drawer_content()), + open=DrawerState.is_open, + direction="left", + modal=False, + ) + +def drawer_sidebar(): + return rx.vstack( + lateral_menu(), + rx.section( + rx.heading("Test1", size="8"), + id='test1', + height="400px", + ), + rx.section( + rx.heading("Test2", size="8"), + id='test2', + height="400px", + ) + ) +``` diff --git a/docs/library/overlay/dropdown_menu.md b/docs/library/overlay/dropdown_menu.md new file mode 100644 index 00000000000..1db023e87cf --- /dev/null +++ b/docs/library/overlay/dropdown_menu.md @@ -0,0 +1,315 @@ +--- +components: + - rx.dropdown_menu.root + - rx.dropdown_menu.content + - rx.dropdown_menu.trigger + - rx.dropdown_menu.item + - rx.dropdown_menu.separator + - rx.dropdown_menu.sub_content + +only_low_level: + - True + +DropdownMenuRoot: | + lambda **props: rx.menu.root( + rx.menu.trigger(rx.button("drop down menu")), + rx.menu.content( + rx.menu.item("Edit", shortcut="⌘ E"), + rx.menu.item("Share"), + rx.menu.item("Delete", shortcut="⌘ ⌫", color="red"), + rx.menu.sub( + rx.menu.sub_trigger("More"), + rx.menu.sub_content( + rx.menu.item("Eradicate"), + rx.menu.item("Duplicate"), + rx.menu.item("Archive"), + ), + ), + ), + **props + ) + +DropdownMenuContent: | + lambda **props: rx.menu.root( + rx.menu.trigger(rx.button("drop down menu")), + rx.menu.content( + rx.menu.item("Edit", shortcut="⌘ E"), + rx.menu.item("Share"), + rx.menu.item("Delete", shortcut="⌘ ⌫", color="red"), + rx.menu.sub( + rx.menu.sub_trigger("More"), + rx.menu.sub_content( + rx.menu.item("Eradicate"), + rx.menu.item("Duplicate"), + rx.menu.item("Archive"), + ), + ), + **props, + ), + ) + +DropdownMenuItem: | + lambda **props: rx.menu.root( + rx.menu.trigger(rx.button("drop down menu")), + rx.menu.content( + rx.menu.item("Edit", shortcut="⌘ E", **props), + rx.menu.item("Share", **props), + rx.menu.item("Delete", shortcut="⌘ ⌫", color="red", **props), + rx.menu.sub( + rx.menu.sub_trigger("More"), + rx.menu.sub_content( + rx.menu.item("Eradicate", **props), + rx.menu.item("Duplicate", **props), + rx.menu.item("Archive", **props), + ), + ), + ), + ) + +DropdownMenuSub: | + lambda **props: rx.menu.root( + rx.menu.trigger(rx.button("drop down menu")), + rx.menu.content( + rx.menu.item("Edit", shortcut="⌘ E"), + rx.menu.item("Share"), + rx.menu.item("Delete", shortcut="⌘ ⌫", color="red"), + rx.menu.sub( + rx.menu.sub_trigger("More"), + rx.menu.sub_content( + rx.menu.item("Eradicate"), + rx.menu.item("Duplicate"), + rx.menu.item("Archive"), + ), + **props, + ), + ), + ) + +DropdownMenuSubTrigger: | + lambda **props: rx.menu.root( + rx.menu.trigger(rx.button("drop down menu")), + rx.menu.content( + rx.menu.item("Edit", shortcut="⌘ E"), + rx.menu.item("Share"), + rx.menu.item("Delete", shortcut="⌘ ⌫", color="red"), + rx.menu.sub( + rx.menu.sub_trigger("More", **props), + rx.menu.sub_content( + rx.menu.item("Eradicate"), + rx.menu.item("Duplicate"), + rx.menu.item("Archive"), + ), + ), + ), + ) + +DropdownMenuSubContent: | + lambda **props: rx.menu.root( + rx.menu.trigger(rx.button("drop down menu")), + rx.menu.content( + rx.menu.item("Edit", shortcut="⌘ E"), + rx.menu.item("Share"), + rx.menu.item("Delete", shortcut="⌘ ⌫", color="red"), + rx.menu.sub( + rx.menu.sub_trigger("More"), + rx.menu.sub_content( + rx.menu.item("Eradicate"), + rx.menu.item("Duplicate"), + rx.menu.item("Archive"), + **props, + ), + ), + ), + ) +--- + +```python exec +import reflex as rx +``` + +# Dropdown Menu + +A Dropdown Menu is a menu that offers a list of options that a user can select from. They are typically positioned near a button that will control their appearance and disappearance. + +A Dropdown Menu is composed of a `menu.root`, a `menu.trigger` and a `menu.content`. The `menu.trigger` is the element that the user interacts with to open the menu. It wraps the element that will open the dropdown menu. The `menu.content` is the component that pops out when the dropdown menu is open. + +The `menu.item` contains the actual dropdown menu items and sits under the `menu.content`. The `shortcut` prop is an optional shortcut command displayed next to the item text. + +The `menu.sub` contains all the parts of a submenu. There is a `menu.sub_trigger`, which is an item that opens a submenu. It must be rendered inside a `menu.sub` component. The `menu.sub_component` is the component that pops out when a submenu is open. It must also be rendered inside a `menu.sub` component. + +The `menu.separator` is used to visually separate items in a dropdown menu. + +```python demo +rx.menu.root( + rx.menu.trigger( + rx.button("Options", variant="soft"), + ), + rx.menu.content( + rx.menu.item("Edit", shortcut="⌘ E"), + rx.menu.item("Duplicate", shortcut="⌘ D"), + rx.menu.separator(), + rx.menu.item("Archive", shortcut="⌘ N"), + rx.menu.sub( + rx.menu.sub_trigger("More"), + rx.menu.sub_content( + rx.menu.item("Move to project…"), + rx.menu.item("Move to folder…"), + rx.menu.separator(), + rx.menu.item("Advanced options…"), + ), + ), + rx.menu.separator(), + rx.menu.item("Share"), + rx.menu.item("Add to favorites"), + rx.menu.separator(), + rx.menu.item("Delete", shortcut="⌘ ⌫", color="red"), + ), +) +``` + +## Events when the Dropdown Menu opens or closes + +The `on_open_change` event, from the `menu.root`, is called when the `open` state of the dropdown menu changes. It is used in conjunction with the `open` prop, which is passed to the event handler. + +```python demo exec +class DropdownMenuState(rx.State): + num_opens: int = 0 + opened: bool = False + + @rx.event + def count_opens(self, value: bool): + self.opened = value + self.num_opens += 1 + + +def dropdown_menu_example(): + return rx.flex( + rx.heading(f"Number of times Dropdown Menu opened or closed: {DropdownMenuState.num_opens}"), + rx.heading(f"Dropdown Menu open: {DropdownMenuState.opened}"), + rx.menu.root( + rx.menu.trigger( + rx.button("Options", variant="soft", size="2"), + ), + rx.menu.content( + rx.menu.item("Edit", shortcut="⌘ E"), + rx.menu.item("Duplicate", shortcut="⌘ D"), + rx.menu.separator(), + rx.menu.item("Archive", shortcut="⌘ N"), + rx.menu.separator(), + rx.menu.item("Delete", shortcut="⌘ ⌫", color="red"), + ), + on_open_change=DropdownMenuState.count_opens, + ), + direction="column", + spacing="3", + ) +``` + +## Opening a Dialog from Menu using State + +Accessing an overlay component from within another overlay component is a common use case but does not always work exactly as expected. + +The code below will not work as expected as because the dialog is within the menu and the dialog will only be open when the menu is open, rendering the dialog unusable. + +```python +rx.menu.root( + rx.menu.trigger(rx.icon("ellipsis-vertical")), + rx.menu.content( + rx.menu.item( + rx.dialog.root( + rx.dialog.trigger(rx.text("Edit")), + rx.dialog.content(....), + ..... + ), + ), + ), +) +``` + +In this example, we will show how to open a dialog box from a dropdown menu, where the menu will close and the dialog will open and be functional. + +```python demo exec +class DropdownMenuState2(rx.State): + which_dialog_open: str = "" + + @rx.event + def set_which_dialog_open(self, value: str): + self.which_dialog_open = value + + @rx.event + def delete(self): + yield rx.toast("Deleted item") + + @rx.event + def save_settings(self): + yield rx.toast("Saved settings") + + +def delete_dialog(): + return rx.alert_dialog.root( + rx.alert_dialog.content( + rx.alert_dialog.title("Are you Sure?"), + rx.alert_dialog.description( + rx.text( + "This action cannot be undone. Are you sure you want to delete this item?", + ), + margin_bottom="20px", + ), + rx.hstack( + rx.alert_dialog.action( + rx.button( + "Delete", + color_scheme="red", + on_click=DropdownMenuState2.delete, + ), + ), + rx.spacer(), + rx.alert_dialog.cancel(rx.button("Cancel")), + ), + ), + open=DropdownMenuState2.which_dialog_open == "delete", + on_open_change=DropdownMenuState2.set_which_dialog_open(""), + ) + + +def settings_dialog(): + return rx.dialog.root( + rx.dialog.content( + rx.dialog.title("Settings"), + rx.dialog.description( + rx.text("Set your settings in this settings dialog."), + margin_bottom="20px", + ), + rx.dialog.close( + rx.button("Close", on_click=DropdownMenuState2.save_settings), + ), + ), + open=DropdownMenuState2.which_dialog_open == "settings", + on_open_change=DropdownMenuState2.set_which_dialog_open(""), + ) + + +def menu_call_dialog() -> rx.Component: + return rx.vstack( + rx.menu.root( + rx.menu.trigger(rx.icon("menu")), + rx.menu.content( + rx.menu.item( + "Delete", + on_click=DropdownMenuState2.set_which_dialog_open("delete"), + ), + rx.menu.item( + "Settings", + on_click=DropdownMenuState2.set_which_dialog_open("settings"), + ), + ), + ), + rx.cond( + DropdownMenuState2.which_dialog_open, + rx.heading(f"{DropdownMenuState2.which_dialog_open} dialog is open"), + ), + delete_dialog(), + settings_dialog(), + align="center", + ) +``` diff --git a/docs/library/overlay/hover_card.md b/docs/library/overlay/hover_card.md new file mode 100644 index 00000000000..6a23341ca74 --- /dev/null +++ b/docs/library/overlay/hover_card.md @@ -0,0 +1,126 @@ +--- +components: + - rx.hover_card.root + - rx.hover_card.content + - rx.hover_card.trigger + +only_low_level: + - True + +HoverCardRoot: | + lambda **props: rx.hover_card.root( + rx.hover_card.trigger( + rx.link("Hover over me"), + ), + rx.hover_card.content( + rx.text("This is the tooltip content."), + ), + **props + ) + +HoverCardContent: | + lambda **props: rx.hover_card.root( + rx.hover_card.trigger( + rx.link("Hover over me"), + ), + rx.hover_card.content( + rx.text("This is the tooltip content."), + **props + ), + ) +--- + +```python exec +import reflex as rx +``` + +# Hovercard + +The `hover_card.root` contains all the parts of a hover card. + +The `hover_card.trigger` wraps the link that will open the hover card. + +The `hover_card.content` contains the content of the open hover card. + +```python demo +rx.text( + "Hover over the text to see the tooltip. ", + rx.hover_card.root( + rx.hover_card.trigger( + rx.link("Hover over me", color_scheme="blue", underline="always"), + ), + rx.hover_card.content( + rx.text("This is the hovercard content."), + ), + ), +) +``` + +```python demo +rx.text( + "Hover over the text to see the tooltip. ", + rx.hover_card.root( + rx.hover_card.trigger( + rx.link("Hover over me", color_scheme="blue", underline="always"), + ), + rx.hover_card.content( + rx.grid( + rx.inset( + side="left", + pr="current", + background="url('https://images.unsplash.com/5/unsplash-kitsune-4.jpg') center/cover", + height="full", + ), + rx.box( + rx.text_area(placeholder="Write a comment…", style={"height": 80}), + rx.flex( + rx.checkbox("Send to group"), + spacing="3", + margin_top="12px", + justify="between", + ), + padding_left="12px", + ), + columns="120px 1fr", + ), + style={"width": 360}, + ), + ), +) +``` + +## Events when the Hovercard opens or closes + +The `on_open_change` event is called when the `open` state of the hovercard changes. It is used in conjunction with the `open` prop, which is passed to the event handler. + +```python demo exec +class HovercardState(rx.State): + num_opens: int = 0 + opened: bool = False + + @rx.event + def count_opens(self, value: bool): + self.opened = value + self.num_opens += 1 + + +def hovercard_example(): + return rx.flex( + rx.heading(f"Number of times hovercard opened or closed: {HovercardState.num_opens}"), + rx.heading(f"Hovercard open: {HovercardState.opened}"), + rx.text( + "Hover over the text to see the hover card. ", + rx.hover_card.root( + rx.hover_card.trigger( + rx.link("Hover over me", color_scheme="blue", underline="always"), + ), + rx.hover_card.content( + rx.text("This is the tooltip content."), + ), + on_open_change=HovercardState.count_opens, + ), + ), + direction="column", + spacing="3", + ) +``` diff --git a/docs/library/overlay/popover.md b/docs/library/overlay/popover.md new file mode 100644 index 00000000000..2d6b81f4728 --- /dev/null +++ b/docs/library/overlay/popover.md @@ -0,0 +1,224 @@ +--- +components: + - rx.popover.root + - rx.popover.content + - rx.popover.trigger + - rx.popover.close + +only_low_level: + - True + +PopoverRoot: | + lambda **props: rx.popover.root( + rx.popover.trigger( + rx.button("Popover"), + ), + rx.popover.content( + rx.flex( + rx.text("Simple Example"), + rx.popover.close( + rx.button("Close"), + ), + direction="column", + spacing="3", + ), + ), + **props + ) + +PopoverContent: | + lambda **props: rx.popover.root( + rx.popover.trigger( + rx.button("Popover"), + ), + rx.popover.content( + rx.flex( + rx.text("Simple Example"), + rx.popover.close( + rx.button("Close"), + ), + direction="column", + spacing="3", + ), + **props + ), + ) +--- + +```python exec +import reflex as rx +``` + +# Popover + +A popover displays content, triggered by a button. + +The `popover.root` contains all the parts of a popover. + +The `popover.trigger` contains the button that toggles the popover. + +The `popover.content` is the component that pops out when the popover is open. + +The `popover.close` is the button that closes an open popover. + +## Basic Example + +```python demo +rx.popover.root( + rx.popover.trigger( + rx.button("Popover"), + ), + rx.popover.content( + rx.flex( + rx.text("Simple Example"), + rx.popover.close( + rx.button("Close"), + ), + direction="column", + spacing="3", + ), + ), +) +``` + +## Examples in Context + +```python demo + +rx.popover.root( + rx.popover.trigger( + rx.button("Comment", variant="soft"), + ), + rx.popover.content( + rx.flex( + rx.avatar( + "2", + fallback="RX", + radius="full" + ), + rx.box( + rx.text_area(placeholder="Write a comment…", style={"height": 80}), + rx.flex( + rx.checkbox("Send to group"), + rx.popover.close( + rx.button("Comment", size="1") + ), + spacing="3", + margin_top="12px", + justify="between", + ), + flex_grow="1", + ), + spacing="3" + ), + style={"width": 360}, + ) +) +``` + +```python demo +rx.popover.root( + rx.popover.trigger( + rx.button("Feedback", variant="classic"), + ), + rx.popover.content( + rx.inset( + side="top", + background="url('https://images.unsplash.com/5/unsplash-kitsune-4.jpg') center/cover", + height="100px", + ), + rx.box( + rx.text_area(placeholder="Write a comment…", style={"height": 80}), + rx.flex( + rx.checkbox("Send to group"), + rx.popover.close( + rx.button("Comment", size="1") + ), + spacing="3", + margin_top="12px", + justify="between", + ), + padding_top="12px", + ), + style={"width": 360}, + ) +) +``` + +## Popover with dynamic title + +Code like below will not work as expected and it is necessary to place the dynamic title (`Index2State.language`) inside of an `rx.text` component. + +```python +class Index2State(rx.State): + language: str = "EN" + +def index() -> rx.Component: + return rx.popover.root( + rx.popover.trigger( + rx.button(Index2State.language), + ), + rx.popover.content( + rx.text('Success') + ) + ) +``` + +This code will work: + +```python demo exec +class Index2State(rx.State): + language: str = "EN" + +def index() -> rx.Component: + return rx.popover.root( + rx.popover.trigger( + rx.button( + rx.text(Index2State.language) + ), + ), + rx.popover.content( + rx.text('Success') + ) + ) +``` + +## Events when the Popover opens or closes + +The `on_open_change` event is called when the `open` state of the popover changes. It is used in conjunction with the `open` prop, which is passed to the event handler. + +```python demo exec +class PopoverState(rx.State): + num_opens: int = 0 + opened: bool = False + + @rx.event + def count_opens(self, value: bool): + self.opened = value + self.num_opens += 1 + + +def popover_example(): + return rx.flex( + rx.heading(f"Number of times popover opened or closed: {PopoverState.num_opens}"), + rx.heading(f"Popover open: {PopoverState.opened}"), + rx.popover.root( + rx.popover.trigger( + rx.button("Popover"), + ), + rx.popover.content( + rx.flex( + rx.text("Simple Example"), + rx.popover.close( + rx.button("Close"), + ), + direction="column", + spacing="3", + ), + ), + on_open_change=PopoverState.count_opens, + ), + direction="column", + spacing="3", + ) +``` diff --git a/docs/library/overlay/toast.md b/docs/library/overlay/toast.md new file mode 100644 index 00000000000..7c5c666060c --- /dev/null +++ b/docs/library/overlay/toast.md @@ -0,0 +1,105 @@ +--- +components: + - rx.toast.provider +--- + +```python exec +import reflex as rx +``` + +# Toast + +A `rx.toast` is a non-blocking notification that disappears after a certain amount of time. It is often used to show a message to the user without interrupting their workflow. + +## Usage + +You can use `rx.toast` as an event handler for any component that triggers an action. + +```python demo +rx.button("Show Toast", on_click=rx.toast("Hello, World!")) +``` + +### Usage in State + +You can also use `rx.toast` in a state to show a toast when a specific action is triggered, using `yield`. + +```python demo exec +import asyncio +class ToastState(rx.State): + + @rx.event + async def fetch_data(self): + # Simulate fetching data for a 2-second delay + await asyncio.sleep(2) + # Shows a toast when the data is fetched + yield rx.toast("Data fetched!") + + +def render(): + return rx.button("Get Data", on_click=ToastState.fetch_data) +``` + +## Interaction + +If you want to interact with a toast, a few props are available to customize the behavior. + +By passing a `ToastAction` to the `action` or `cancel` prop, you can trigger an action when the toast is clicked or when it is closed. + +```python demo +rx.button("Show Toast", on_click=rx.toast("Hello, World!", duration=5000, close_button=True)) +``` + +### Presets + +`rx.toast` has some presets that you can use to show different types of toasts. + +```python demo +rx.hstack( + rx.button("Success", on_click=rx.toast.success("Success!"), color_scheme="green"), + rx.button("Error", on_click=rx.toast.error("Error!"), color_scheme="red"), + rx.button("Warning", on_click=rx.toast.warning("Warning!"), color_scheme="orange"), + rx.button("Info", on_click=rx.toast.info("Info!"), color_scheme="blue"), +) +``` + +### Customization + +If the presets don't fit your needs, you can customize the toasts by passing to `rx.toast` or to `rx.toast.options` some kwargs. + +```python demo +rx.button( + "Custom", + on_click=rx.toast( + "Custom Toast!", + position="top-right", + style={"background-color": "green", "color": "white", "border": "1px solid green", "border-radius": "0.53m"} + ) +) +``` + +The following props are available for customization: + +- `description`: `str | Var`: Toast's description, renders underneath the title. +- `close_button`: `bool`: Whether to show the close button. +- `invert`: `bool`: Dark toast in light mode and vice versa. +- `important`: `bool`: Control the sensitivity of the toast for screen readers. +- `duration`: `int`: Time in milliseconds that should elapse before automatically closing the toast. +- `position`: `LiteralPosition`: Position of the toast. +- `dismissible`: `bool`: If false, it'll prevent the user from dismissing the toast. +- `action`: `ToastAction`: Renders a primary button, clicking it will close the toast. +- `cancel`: `ToastAction`: Renders a secondary button, clicking it will close the toast. +- `id`: `str | Var`: Custom id for the toast. +- `unstyled`: `bool`: Removes the default styling, which allows for easier customization. +- `style`: `Style`: Custom style for the toast. +- `on_dismiss`: `Any`: The function gets called when either the close button is clicked, or the toast is swiped. +- `on_auto_close`: `Any`: Function that gets called when the toast disappears automatically after it's timeout (`duration` prop). + +## Toast Provider + +Using the `rx.toast` function require to have a toast provider in your app. + +`rx.toast.provider` is a component that provides a context for displaying toasts. It should be placed at the root of your app. + +```md alert warning +# In most case you will not need to include this component directly, as it is already included in `rx.app` as the `overlay_component` for displaying connections errors. +``` diff --git a/docs/library/overlay/tooltip.md b/docs/library/overlay/tooltip.md new file mode 100644 index 00000000000..32b9993e6cd --- /dev/null +++ b/docs/library/overlay/tooltip.md @@ -0,0 +1,60 @@ +--- +components: + - rx.tooltip + +Tooltip: | + lambda **props: rx.tooltip( + rx.button("Hover over me"), + content="This is the tooltip content.", + **props, + ) +--- + +```python exec +import reflex as rx +``` + +# Tooltip + +A `tooltip` displays informative information when users hover over or focus on an element. + +It takes a `content` prop, which is the content associated with the tooltip. + +```python demo +rx.tooltip( + rx.button("Hover over me"), + content="This is the tooltip content.", +) +``` + +## Events when the Tooltip opens or closes + +The `on_open_change` event is called when the `open` state of the tooltip changes. It is used in conjunction with the `open` prop, which is passed to the event handler. + +```python demo exec +class TooltipState(rx.State): + num_opens: int = 0 + opened: bool = False + + @rx.event + def count_opens(self, value: bool): + self.opened = value + self.num_opens += 1 + + +def index(): + return rx.flex( + rx.heading(f"Number of times tooltip opened or closed: {TooltipState.num_opens}"), + rx.heading(f"Tooltip open: {TooltipState.opened}"), + rx.text( + "Hover over the button to see the tooltip.", + rx.tooltip( + rx.button("Hover over me"), + content="This is the tooltip content.", + on_open_change=TooltipState.count_opens, + ), + ), + direction="column", + spacing="3", + ) +``` diff --git a/docs/library/tables-and-data-grids/data_editor.md b/docs/library/tables-and-data-grids/data_editor.md new file mode 100644 index 00000000000..5c98c858c3f --- /dev/null +++ b/docs/library/tables-and-data-grids/data_editor.md @@ -0,0 +1,356 @@ +--- +components: + - rx.data_editor +--- + +# Data Editor + +A datagrid editor based on [Glide Data Grid](https://grid.glideapps.com/) + +```python exec +import reflex as rx +from pcweb.pages.docs import library +from typing import Any + +columns: list[dict[str, str]] = [ + { + "title":"Code", + "type": "str", + }, + { + "title":"Value", + "type": "int", + }, + { + "title":"Activated", + "type": "bool", + }, +] +data: list[list[Any]] = [ + ["A", 1, True], + ["B", 2, False], + ["C", 3, False], + ["D", 4, True], + ["E", 5, True], + ["F", 6, False], +] + +``` + +This component is introduced as an alternative to the [datatable]({library.tables_and_data_grids.data_table.path}) to support editing the displayed data. + +## Columns + +The columns definition should be a `list` of `dict`, each `dict` describing the associated columns. +Property of a column dict: + +- `title`: The text to display in the header of the column. +- `id`: An id for the column, if not defined, will default to a lower case of `title` +- `width`: The width of the column. +- `type`: The type of the columns, default to `"str"`. + +## Data + +The `data` props of `rx.data_editor` accept a `list` of `list`, where each `list` represent a row of data to display in the table. + +## Simple Example + +Here is a basic example of using the data_editor representing data with no interaction and no styling. Below we define the `columns` and the `data` which are taken in by the `rx.data_editor` component. When we define the `columns` we must define a `title` and a `type` for each column we create. The columns in the `data` must then match the defined `type` or errors will be thrown. + +```python demo box +rx.data_editor( + columns=columns, + data=data, +) +``` + +```python +columns: list[dict[str, str]] = [ + { + "title":"Code", + "type": "str", + }, + { + "title":"Value", + "type": "int", + }, + { + "title":"Activated", + "type": "bool", + }, +] +data: list[list[Any]] = [ + ["A", 1, True], + ["B", 2, False], + ["C", 3, False], + ["D", 4, True], + ["E", 5, True], + ["F", 6, False], +] +``` + +```python +rx.data_editor( + columns=columns, + data=data, +) +``` + +## Interactive Example + +```python exec +class DataEditorState_HP(rx.State): + + clicked_data: str = "Cell clicked: " + cols: list[Any] = [ + {"title": "Title", "type": "str"}, + { + "title": "Name", + "type": "str", + "group": "Data", + "width": 300, + }, + { + "title": "Birth", + "type": "str", + "id": "date", + "group": "Data", + "width": 150, + }, + { + "title": "Human", + "type": "bool", + "group": "Data", + "width": 80, + }, + { + "title": "House", + "type": "str", + "id": "date", + "group": "Data", + }, + { + "title": "Wand", + "type": "str", + "id": "date", + "group": "Data", + "width": 250, + }, + { + "title": "Patronus", + "type": "str", + "id": "date", + "group": "Data", + }, + { + "title": "Blood status", + "type": "str", + "id": "date", + "group": "Data", + "width": 200, + } + ] + + data = [ + ["1", "Harry James Potter", "31 July 1980", True, "Gryffindor", "11' Holly phoenix feather", "Stag", "Half-blood"], + ["2", "Ronald Bilius Weasley", "1 March 1980", True,"Gryffindor", "12' Ash unicorn tail hair", "Jack Russell terrier", "Pure-blood"], + ["3", "Hermione Jean Granger", "19 September, 1979", True, "Gryffindor", "10¾' vine wood dragon heartstring", "Otter", "Muggle-born"], + ["4", "Albus Percival Wulfric Brian Dumbledore", "Late August 1881", True, "Gryffindor", "15' Elder Thestral tail hair core", "Phoenix", "Half-blood"], + ["5", "Rubeus Hagrid", "6 December 1928", False, "Gryffindor", "16' Oak unknown core", "None", "Part-Human (Half-giant)"], + ["6", "Fred Weasley", "1 April, 1978", True, "Gryffindor", "Unknown", "Unknown", "Pure-blood"], + ] + + def click_cell(self, pos): + col, row = pos + yield self.get_clicked_data(pos) + + + def get_clicked_data(self, pos) -> str: + self.clicked_data = f"Cell clicked: {pos}" + +``` + +Here we define a State, as shown below, that allows us to print the location of the cell as a heading when we click on it, using the `on_cell_clicked` `event trigger`. Check out all the other `event triggers` that you can use with datatable at the bottom of this page. We also define a `group` with a label `Data`. This groups all the columns with this `group` label under a larger group `Data` as seen in the table below. + +```python demo box +rx.heading(DataEditorState_HP.clicked_data) +``` + +```python demo box +rx.data_editor( + columns=DataEditorState_HP.cols, + data=DataEditorState_HP.data, + on_cell_clicked=DataEditorState_HP.click_cell, +) +``` + +```python +class DataEditorState_HP(rx.State): + + clicked_data: str = "Cell clicked: " + + cols: list[Any] = [ + { + "title": "Title", + "type": "str" + }, + { + "title": "Name", + "type": "str", + "group": "Data", + "width": 300, + }, + { + "title": "Birth", + "type": "str", + "group": "Data", + "width": 150, + }, + { + "title": "Human", + "type": "bool", + "group": "Data", + "width": 80, + }, + { + "title": "House", + "type": "str", + "group": "Data", + }, + { + "title": "Wand", + "type": "str", + "group": "Data", + "width": 250, + }, + { + "title": "Patronus", + "type": "str", + "group": "Data", + }, + { + "title": "Blood status", + "type": "str", + "group": "Data", + "width": 200, + } + ] + + data = [ + ["1", "Harry James Potter", "31 July 1980", True, "Gryffindor", "11' Holly phoenix feather", "Stag", "Half-blood"], + ["2", "Ronald Bilius Weasley", "1 March 1980", True,"Gryffindor", "12' Ash unicorn tail hair", "Jack Russell terrier", "Pure-blood"], + ["3", "Hermione Jean Granger", "19 September, 1979", True, "Gryffindor", "10¾' vine wood dragon heartstring", "Otter", "Muggle-born"], + ["4", "Albus Percival Wulfric Brian Dumbledore", "Late August 1881", True, "Gryffindor", "15' Elder Thestral tail hair core", "Phoenix", "Half-blood"], + ["5", "Rubeus Hagrid", "6 December 1928", False, "Gryffindor", "16' Oak unknown core", "None", "Part-Human (Half-giant)"], + ["6", "Fred Weasley", "1 April, 1978", True, "Gryffindor", "Unknown", "Unknown", "Pure-blood"], + ] + + + def click_cell(self, pos): + col, row = pos + yield self.get_clicked_data(pos) + + + def get_clicked_data(self, pos) -> str: + self.clicked_data = f"Cell clicked: \{pos}" +``` + +```python +rx.data_editor( + columns=DataEditorState_HP.cols, + data=DataEditorState_HP.data, + on_cell_clicked=DataEditorState_HP.click_cell, +) +``` + +## Styling Example + +Now let's style our datatable to make it look more aesthetic and easier to use. We must first import `DataEditorTheme` and then we can start setting our style props as seen below in `dark_theme`. + +We then set these themes using `theme=DataEditorTheme(**dark_theme)`. On top of the styling we can also set some `props` to make some other aesthetic changes to our datatable. We have set the `row_height` to equal `50` so that the content is easier to read. We have also made the `smooth_scroll_x` and `smooth_scroll_y` equal `True` so that we can smoothly scroll along the columns and rows. Finally, we added `column_select=single`, where column select can take any of the following values `none`, `single` or `multiple`. + +```python exec +from reflex.components.datadisplay.dataeditor import DataEditorTheme +dark_theme = { + "accentColor": "#8c96ff", + "accentLight": "rgba(202, 206, 255, 0.253)", + "textDark": "#ffffff", + "textMedium": "#b8b8b8", + "textLight": "#a0a0a0", + "textBubble": "#ffffff", + "bgIconHeader": "#b8b8b8", + "fgIconHeader": "#000000", + "textHeader": "#a1a1a1", + "textHeaderSelected": "#000000", + "bgCell": "#16161b", + "bgCellMedium": "#202027", + "bgHeader": "#212121", + "bgHeaderHasFocus": "#474747", + "bgHeaderHovered": "#404040", + "bgBubble": "#212121", + "bgBubbleSelected": "#000000", + "bgSearchResult": "#423c24", + "borderColor": "rgba(225,225,225,0.2)", + "drilldownBorder": "rgba(225,225,225,0.4)", + "linkColor": "#4F5DFF", + "headerFontStyle": "bold 14px", + "baseFontStyle": "13px", + "fontFamily": "Inter, Roboto, -apple-system, BlinkMacSystemFont, avenir next, avenir, segoe ui, helvetica neue, helvetica, Ubuntu, noto, arial, sans-serif", +} +``` + +```python demo box +rx.data_editor( + columns=DataEditorState_HP.cols, + data=DataEditorState_HP.data, + row_height=80, + smooth_scroll_x=True, + smooth_scroll_y=True, + column_select="single", + theme=DataEditorTheme(**dark_theme), + height="30vh", +) +``` + +```python +from reflex.components.datadisplay.dataeditor import DataEditorTheme +dark_theme_snake_case = { + "accent_color": "#8c96ff", + "accent_light": "rgba(202, 206, 255, 0.253)", + "text_dark": "#ffffff", + "text_medium": "#b8b8b8", + "text_light": "#a0a0a0", + "text_bubble": "#ffffff", + "bg_icon_header": "#b8b8b8", + "fg_icon_header": "#000000", + "text_header": "#a1a1a1", + "text_header_selected": "#000000", + "bg_cell": "#16161b", + "bg_cell_medium": "#202027", + "bg_header": "#212121", + "bg_header_has_focus": "#474747", + "bg_header_hovered": "#404040", + "bg_bubble": "#212121", + "bg_bubble_selected": "#000000", + "bg_search_result": "#423c24", + "border_color": "rgba(225,225,225,0.2)", + "drilldown_border": "rgba(225,225,225,0.4)", + "link_color": "#4F5DFF", + "header_font_style": "bold 14px", + "base_font_style": "13px", + "font_family": "Inter, Roboto, -apple-system, BlinkMacSystemFont, avenir next, avenir, segoe ui, helvetica neue, helvetica, Ubuntu, noto, arial, sans-serif", +} +``` + +```python +rx.data_editor( + columns=DataEditorState_HP.cols, + data=DataEditorState_HP.data, + row_height=80, + smooth_scroll_x=True, + smooth_scroll_y=True, + column_select="single", + theme=DataEditorTheme(**dark_theme), + height="30vh", +) +``` diff --git a/docs/library/tables-and-data-grids/data_table.md b/docs/library/tables-and-data-grids/data_table.md new file mode 100644 index 00000000000..539ef559259 --- /dev/null +++ b/docs/library/tables-and-data-grids/data_table.md @@ -0,0 +1,73 @@ +--- +components: + - rx.data_table +--- + +```python exec +import reflex as rx +from pcweb.pages.docs import library +``` + +# Data Table + +The data table component is a great way to display static data in a table format. +You can pass in a pandas dataframe to the data prop to create the table. + +In this example we will read data from a csv file, convert it to a pandas dataframe and display it in a data_table. + +We will also add a search, pagination, sorting to the data_table to make it more accessible. + +If you want to [add, edit or remove data]({library.tables_and_data_grids.table.path}) in your app or deal with anything but static data then the [`rx.table`]({library.tables_and_data_grids.table.path}) might be a better fit for your use case. + + +```python demo box +rx.data_table( + data=[ + ["Avery Bradley", "6-2", 25.0], + ["Jae Crowder", "6-6", 25.0], + ["John Holland", "6-5", 27.0], + ["R.J. Hunter", "6-5", 22.0], + ["Jonas Jerebko", "6-10", 29.0], + ["Amir Johnson", "6-9", 29.0], + ["Jordan Mickey", "6-8", 21.0], + ["Kelly Olynyk", "7-0", 25.0], + ["Terry Rozier", "6-2", 22.0], + ["Marcus Smart", "6-4", 22.0], + ], + columns=["Name", "Height", "Age"], + pagination=True, + search=True, + sort=True, +) +``` + +```python +import pandas as pd +nba_data = pd.read_csv("data/nba.csv") +... +rx.data_table( + data = nba_data[["Name", "Height", "Age"]], + pagination= True, + search= True, + sort= True, +) +``` + +📊 **Dataset source:** [nba.csv](https://media.geeksforgeeks.org/wp-content/uploads/nba.csv) + +The example below shows how to create a data table from from a list. + +```python +class State(rx.State): + data: List = [ + ["Lionel", "Messi", "PSG"], + ["Christiano", "Ronaldo", "Al-Nasir"] + ] + columns: List[str] = ["First Name", "Last Name"] + +def index(): + return rx.data_table( + data=State.data, + columns=State.columns, + ) +``` diff --git a/docs/library/tables-and-data-grids/table.md b/docs/library/tables-and-data-grids/table.md new file mode 100644 index 00000000000..8e2509100af --- /dev/null +++ b/docs/library/tables-and-data-grids/table.md @@ -0,0 +1,1206 @@ +--- +components: + - rx.table.root + - rx.table.header + - rx.table.row + - rx.table.column_header_cell + - rx.table.body + - rx.table.cell + - rx.table.row_header_cell + +only_low_level: + - True + +TableRoot: | + lambda **props: rx.table.root( + rx.table.header( + rx.table.row( + rx.table.column_header_cell("Full Name"), + rx.table.column_header_cell("Email"), + rx.table.column_header_cell("Group"), + ), + ), + rx.table.body( + rx.table.row( + rx.table.row_header_cell("Danilo Rosa"), + rx.table.cell("danilo@example.com"), + rx.table.cell("Developer"), + ), + rx.table.row( + rx.table.row_header_cell("Zahra Ambessa"), + rx.table.cell("zahra@example.com"), + rx.table.cell("Admin"), + ), + ), + width="80%", + **props, + ) + +TableRow: | + lambda **props: rx.table.root( + rx.table.header( + rx.table.row( + rx.table.column_header_cell("Full Name"), + rx.table.column_header_cell("Email"), + rx.table.column_header_cell("Group"), + **props, + ), + ), + rx.table.body( + rx.table.row( + rx.table.row_header_cell("Danilo Rosa"), + rx.table.cell(rx.text("danilo@example.com", as_="p"), rx.text("danilo@yahoo.com", as_="p"), rx.text("danilo@gmail.com", as_="p"),), + rx.table.cell("Developer"), + **props, + ), + rx.table.row( + rx.table.row_header_cell("Zahra Ambessa"), + rx.table.cell("zahra@example.com"), + rx.table.cell("Admin"), + **props, + ), + ), + width="80%", + ) + +TableColumnHeaderCell: | + lambda **props: rx.table.root( + rx.table.header( + rx.table.row( + rx.table.column_header_cell("Full Name", **props,), + rx.table.column_header_cell("Email", **props,), + rx.table.column_header_cell("Group", **props,), + ), + ), + rx.table.body( + rx.table.row( + rx.table.row_header_cell("Danilo Rosa"), + rx.table.cell("danilo@example.com"), + rx.table.cell("Developer"), + ), + rx.table.row( + rx.table.row_header_cell("Zahra Ambessa"), + rx.table.cell("zahra@example.com"), + rx.table.cell("Admin"), + ), + ), + width="80%", + ) + +TableCell: | + lambda **props: rx.table.root( + rx.table.header( + rx.table.row( + rx.table.column_header_cell("Full Name"), + rx.table.column_header_cell("Email"), + rx.table.column_header_cell("Group"), + ), + ), + rx.table.body( + rx.table.row( + rx.table.row_header_cell("Danilo Rosa"), + rx.table.cell("danilo@example.com", **props,), + rx.table.cell("Developer", **props,), + ), + rx.table.row( + rx.table.row_header_cell("Zahra Ambessa"), + rx.table.cell("zahra@example.com", **props,), + rx.table.cell("Admin", **props,), + ), + ), + width="80%", + ) + +TableRowHeaderCell: | + lambda **props: rx.table.root( + rx.table.header( + rx.table.row( + rx.table.column_header_cell("Full Name"), + rx.table.column_header_cell("Email"), + rx.table.column_header_cell("Group"), + ), + ), + rx.table.body( + rx.table.row( + rx.table.row_header_cell("Danilo Rosa", **props,), + rx.table.cell("danilo@example.com"), + rx.table.cell("Developer"), + ), + rx.table.row( + rx.table.row_header_cell("Zahra Ambessa", **props,), + rx.table.cell("zahra@example.com"), + rx.table.cell("Admin"), + ), + ), + width="80%", + ) +--- + +```python exec +import reflex as rx +from pcweb.models import Customer +from pcweb.pages.docs import vars, events, database, library, components +``` + +# Table + +A semantic table for presenting tabular data. + +If you just want to [represent static data]({library.tables_and_data_grids.data_table.path}) then the [`rx.data_table`]({library.tables_and_data_grids.data_table.path}) might be a better fit for your use case as it comes with in-built pagination, search and sorting. + +## Basic Example + +```python demo +rx.table.root( + rx.table.header( + rx.table.row( + rx.table.column_header_cell("Full name"), + rx.table.column_header_cell("Email"), + rx.table.column_header_cell("Group"), + ), + ), + rx.table.body( + rx.table.row( + rx.table.row_header_cell("Danilo Sousa"), + rx.table.cell("danilo@example.com"), + rx.table.cell("Developer"), + ), + rx.table.row( + rx.table.row_header_cell("Zahra Ambessa"), + rx.table.cell("zahra@example.com"), + rx.table.cell("Admin"), + ),rx.table.row( + rx.table.row_header_cell("Jasper Eriks"), + rx.table.cell("jasper@example.com"), + rx.table.cell("Developer"), + ), + ), + width="100%", +) +``` + +```md alert info +# Set the table `width` to fit within its container and prevent it from overflowing. +``` + +## Showing State data (using foreach) + +Many times there is a need for the data we represent in our table to be dynamic. Dynamic data must be in `State`. Later we will show an example of how to access data from a database and how to load data from a source file. + +In this example there is a `people` data structure in `State` that is [iterated through using `rx.foreach`]({components.rendering_iterables.path}). + +```python demo exec +class TableForEachState(rx.State): + people: list[list] = [ + ["Danilo Sousa", "danilo@example.com", "Developer"], + ["Zahra Ambessa", "zahra@example.com", "Admin"], + ["Jasper Eriks", "jasper@example.com", "Developer"], + ] + +def show_person(person: list): + """Show a person in a table row.""" + return rx.table.row( + rx.table.cell(person[0]), + rx.table.cell(person[1]), + rx.table.cell(person[2]), + ) + +def foreach_table_example(): + return rx.table.root( + rx.table.header( + rx.table.row( + rx.table.column_header_cell("Full name"), + rx.table.column_header_cell("Email"), + rx.table.column_header_cell("Group"), + ), + ), + rx.table.body(rx.foreach(TableForEachState.people, show_person)), + width="100%", + ) +``` + +It is also possible to define a `class` such as `Person` below and then iterate through this data structure, as a `list[Person]`. + +```python +import dataclasses + +@dataclasses.dataclass +class Person: + full_name: str + email: str + group: str +``` +## Sorting and Filtering (Searching) + +In this example we show two approaches to sort and filter data: +1. Using SQL-like operations for database-backed models (simulated) +2. Using Python operations for in-memory data + +Both approaches use the same UI components: `rx.select` for sorting and `rx.input` for filtering. + +### Approach 1: Database Filtering and Sorting + +For database-backed models, we typically use SQL queries with `select`, `where`, and `order_by`. In this example, we'll simulate this behavior with mock data. + + +```python demo exec +# Simulating database operations with mock data +class DatabaseTableState(rx.State): + # Mock data to simulate database records + users: list = [ + {"name": "John Doe", "email": "john@example.com", "phone": "555-1234", "address": "123 Main St"}, + {"name": "Jane Smith", "email": "jane@example.com", "phone": "555-5678", "address": "456 Oak Ave"}, + {"name": "Bob Johnson", "email": "bob@example.com", "phone": "555-9012", "address": "789 Pine Rd"}, + {"name": "Alice Brown", "email": "alice@example.com", "phone": "555-3456", "address": "321 Maple Dr"}, + ] + filtered_users: list[dict] = [] + sort_value = "" + search_value = "" + + + @rx.event + def load_entries(self): + """Simulate querying the database with filter and sort.""" + # Start with all users + result = self.users.copy() + + # Apply filtering if search value exists + if self.search_value != "": + search_term = self.search_value.lower() + result = [ + user for user in result + if any(search_term in str(value).lower() for value in user.values()) + ] + + # Apply sorting if sort column is selected + if self.sort_value != "": + result = sorted(result, key=lambda x: x[self.sort_value]) + + self.filtered_users = result + yield + + @rx.event + def sort_values(self, sort_value): + """Update sort value and reload data.""" + self.sort_value = sort_value + yield DatabaseTableState.load_entries() + + @rx.event + def filter_values(self, search_value): + """Update search value and reload data.""" + self.search_value = search_value + yield DatabaseTableState.load_entries() + + +def show_customer(user): + """Show a customer in a table row.""" + return rx.table.row( + rx.table.cell(user["name"]), + rx.table.cell(user["email"]), + rx.table.cell(user["phone"]), + rx.table.cell(user["address"]), + ) + + +def database_table_example(): + return rx.vstack( + rx.select( + ["name", "email", "phone", "address"], + placeholder="Sort By: Name", + on_change=lambda value: DatabaseTableState.sort_values(value), + ), + rx.input( + placeholder="Search here...", + on_change=lambda value: DatabaseTableState.filter_values(value), + ), + rx.table.root( + rx.table.header( + rx.table.row( + rx.table.column_header_cell("Name"), + rx.table.column_header_cell("Email"), + rx.table.column_header_cell("Phone"), + rx.table.column_header_cell("Address"), + ), + ), + rx.table.body(rx.foreach(DatabaseTableState.filtered_users, show_customer)), + on_mount=DatabaseTableState.load_entries, + width="100%", + ), + width="100%", + ) +``` + +### Approach 2: In-Memory Filtering and Sorting + +For in-memory data, we use Python operations like `sorted()` and list comprehensions. + +The state variable `_people` is set to be a backend-only variable. This is done in case the variable is very large in order to reduce network traffic and improve performance. + +When a `select` item is selected, the `on_change` event trigger is hooked up to the `set_sort_value` event handler. Every base var has a built-in event handler to set its value for convenience, called `set_VARNAME`. + +`current_people` is an `rx.var(cache=True)`. It is a var that is only recomputed when the other state vars it depends on change. This ensures that the `People` shown in the table are always up to date whenever they are searched or sorted. + +```python demo exec +import dataclasses + +@dataclasses.dataclass +class Person: + full_name: str + email: str + group: str + + +class InMemoryTableState(rx.State): + + _people: list[Person] = [ + Person(full_name="Danilo Sousa", email="danilo@example.com", group="Developer"), + Person(full_name="Zahra Ambessa", email="zahra@example.com", group="Admin"), + Person(full_name="Jasper Eriks", email="zjasper@example.com", group="B-Developer"), + ] + + sort_value = "" + search_value = "" + + @rx.event + def set_sort_value(self, value: str): + self.sort_value = value + + @rx.event + def set_search_value(self, value: str): + self.search_value = value + + @rx.var(cache=True) + def current_people(self) -> list[Person]: + people = self._people + + if self.sort_value != "": + people = sorted( + people, key=lambda user: getattr(user, self.sort_value).lower() + ) + + if self.search_value != "": + people = [ + person for person in people + if any( + self.search_value.lower() in getattr(person, attr).lower() + for attr in ['full_name', 'email', 'group'] + ) + ] + return people + + +def show_person(person: Person): + """Show a person in a table row.""" + return rx.table.row( + rx.table.cell(person.full_name), + rx.table.cell(person.email), + rx.table.cell(person.group), + ) + +def in_memory_table_example(): + return rx.vstack( + rx.select( + ["full_name", "email", "group"], + placeholder="Sort By: full_name", + on_change=InMemoryTableState.set_sort_value, + ), + rx.input( + placeholder="Search here...", + on_change=InMemoryTableState.set_search_value, + ), + rx.table.root( + rx.table.header( + rx.table.row( + rx.table.column_header_cell("Full name"), + rx.table.column_header_cell("Email"), + rx.table.column_header_cell("Group"), + ), + ), + rx.table.body(rx.foreach(InMemoryTableState.current_people, show_person)), + width="100%", + ), + width="100%", + ) +``` + +### When to Use Each Approach + +- **Database Approach**: Best for large datasets or when the data already exists in a database +- **In-Memory Approach**: Best for smaller datasets, prototyping, or when the data is static or loaded from an API + +Both approaches provide the same user experience with filtering and sorting functionality. + +# Database + +The more common use case for building an `rx.table` is to use data from a database. + +The code below shows how to load data from a database and place it in an `rx.table`. + +## Loading data into table + +A `Customer` [model]({database.tables.path}) is defined that inherits from `rx.Model`. + +The `load_entries` event handler executes a [query]({database.queries.path}) that is used to request information from a database table. This `load_entries` event handler is called on the `on_mount` event trigger of the `rx.table.root`. + +If you want to load the data when the page in the app loads you can set `on_load` in `app.add_page()` to equal this event handler, like `app.add_page(page_name, on_load=State.load_entries)`. + +```python +class Customer(rx.Model, table=True): + """The customer model.""" + name: str + email: str + phone: str + address: str +``` + +```python exec +class DatabaseTableState(rx.State): + + users: list[dict] = [] + + @rx.event + def load_entries(self): + """Get all users from the database.""" + customers_json = [ + { + "name": "John Doe", + "email": "john@example.com", + "phone": "555-1234", + "address": "123 Main St" + }, + { + "name": "Jane Smith", + "email": "jane@example.com", + "phone": "555-5678", + "address": "456 Oak Ave" + }, + { + "name": "Bob Johnson", + "email": "bob@example.com", + "phone": "555-9012", + "address": "789 Pine Blvd" + }, + { + "name": "Alice Williams", + "email": "alice@example.com", + "phone": "555-3456", + "address": "321 Maple Dr" + } + ] + self.users = customers_json + +def show_customer(user: dict): + return rx.table.row( + rx.table.cell(user["name"]), + rx.table.cell(user["email"]), + rx.table.cell(user["phone"]), + rx.table.cell(user["address"]), + ) + +def loading_data_table_example(): + return rx.table.root( + rx.table.header( + rx.table.row( + rx.table.column_header_cell("Name"), + rx.table.column_header_cell("Email"), + rx.table.column_header_cell("Phone"), + rx.table.column_header_cell("Address"), + ), + ), + rx.table.body(rx.foreach(DatabaseTableState.users, show_customer)), + on_mount=DatabaseTableState.load_entries, + width="100%", + margin_bottom="1em", +) +``` + +```python eval +loading_data_table_example() +``` + +```python +from sqlmodel import select + +class DatabaseTableState(rx.State): + + users: list[Customer] = [] + + @rx.event + def load_entries(self): + """Get all users from the database.""" + with rx.session() as session: + self.users = session.exec(select(Customer)).all() + + +def show_customer(user: Customer): + """Show a customer in a table row.""" + return rx.table.row( + rx.table.cell(user.name), + rx.table.cell(user.email), + rx.table.cell(user.phone), + rx.table.cell(user.address), + ) + +def loading_data_table_example(): + return rx.table.root( + rx.table.header( + rx.table.row( + rx.table.column_header_cell("Name"), + rx.table.column_header_cell("Email"), + rx.table.column_header_cell("Phone"), + rx.table.column_header_cell("Address"), + ), + ), + rx.table.body(rx.foreach(DatabaseTableState.users, show_customer)), + on_mount=DatabaseTableState.load_entries, + width="100%", + ) + +``` + +## Filtering (Searching) and Sorting + +In this example we sort and filter the data. + +For sorting the `rx.select` component is used. The data is sorted based on the attributes of the `Customer` class. When a select item is selected, as the `on_change` event trigger is hooked up to the `sort_values` event handler, the data is sorted based on the state variable `sort_value` attribute selected. + +The sorting query gets the `sort_column` based on the state variable `sort_value`, it gets the order using the `asc` function from sql and finally uses the `order_by` function. + +For filtering the `rx.input` component is used. The data is filtered based on the search query entered into the `rx.input` component. When a search query is entered, as the `on_change` event trigger is hooked up to the `filter_values` event handler, the data is filtered based on if the state variable `search_value` is present in any of the data in that specific `Customer`. + +The `%` character before and after `search_value` makes it a wildcard pattern that matches any sequence of characters before or after the `search_value`. `query.where(...)` modifies the existing query to include a filtering condition. The `or_` operator is a logical OR operator that combines multiple conditions. The query will return results that match any of these conditions. `Customer.name.ilike(search_value)` checks if the `name` column of the `Customer` table matches the `search_value` pattern in a case-insensitive manner (`ilike` stands for "case-insensitive like"). + +```python +class Customer(rx.Model, table=True): + """The customer model.""" + + name: str + email: str + phone: str + address: str +``` + +```python exec +class DatabaseTableState2(rx.State): + + # Mock data to simulate database records + _users: list[dict] = [ + {"name": "John Doe", "email": "john@example.com", "phone": "555-1234", "address": "123 Main St"}, + {"name": "Jane Smith", "email": "jane@example.com", "phone": "555-5678", "address": "456 Oak Ave"}, + {"name": "Bob Johnson", "email": "bob@example.com", "phone": "555-9012", "address": "789 Pine Rd"}, + {"name": "Alice Brown", "email": "alice@example.com", "phone": "555-3456", "address": "321 Maple Dr"}, + {"name": "Charlie Wilson", "email": "charlie@example.com", "phone": "555-7890", "address": "654 Elm St"}, + {"name": "Emily Davis", "email": "emily@example.com", "phone": "555-2345", "address": "987 Cedar Ln"}, + ] + + users: list[dict] = [] + sort_value = "" + search_value = "" + + @rx.event + def load_entries(self): + # Start with all users + result = self._users.copy() + + # Apply filtering if search value exists + if self.search_value != "": + search_term = self.search_value.lower() + result = [ + user for user in result + if any(search_term in str(value).lower() for value in user.values()) + ] + + # Apply sorting if sort column is selected + if self.sort_value != "": + result = sorted(result, key=lambda x: x[self.sort_value]) + + self.users = result + + @rx.event + def sort_values(self, sort_value): + self.sort_value = sort_value + yield DatabaseTableState2.load_entries() + + @rx.event + def filter_values(self, search_value): + self.search_value = search_value + yield DatabaseTableState2.load_entries() + + +def show_customer_2(user: dict): + return rx.table.row( + rx.table.cell(user["name"]), + rx.table.cell(user["email"]), + rx.table.cell(user["phone"]), + rx.table.cell(user["address"]), + ) + +def loading_data_table_example_2(): + return rx.vstack( + rx.select( + ["name", "email", "phone", "address"], + placeholder="Sort By: Name", + on_change= lambda value: DatabaseTableState2.sort_values(value), + ), + rx.input( + placeholder="Search here...", + on_change= lambda value: DatabaseTableState2.filter_values(value), + ), + rx.table.root( + rx.table.header( + rx.table.row( + rx.table.column_header_cell("Name"), + rx.table.column_header_cell("Email"), + rx.table.column_header_cell("Phone"), + rx.table.column_header_cell("Address"), + ), + ), + rx.table.body(rx.foreach(DatabaseTableState2.users, show_customer_2)), + on_mount=DatabaseTableState2.load_entries, + width="100%", + ), + width="100%", + margin_bottom="1em", +) +``` + +```python eval +loading_data_table_example_2() +``` + +```python +from sqlmodel import select, asc, or_ + + +class DatabaseTableState2(rx.State): + + users: list[Customer] = [] + + sort_value = "" + search_value = "" + + @rx.event + def load_entries(self): + """Get all users from the database.""" + with rx.session() as session: + query = select(Customer) + + if self.search_value != "": + search_value = self.search_value.lower() + query = query.where( + or_( + Customer.name.ilike(search_value), + Customer.email.ilike(search_value), + Customer.phone.ilike(search_value), + Customer.address.ilike(search_value), + ) + ) + + if self.sort_value != "": + sort_column = getattr(Customer, self.sort_value) + order = asc(sort_column) + query = query.order_by(order) + + self.users = session.exec(query).all() + + @rx.event + def sort_values(self, sort_value): + print(sort_value) + self.sort_value = sort_value + self.load_entries() + + @rx.event + def filter_values(self, search_value): + print(search_value) + self.search_value = search_value + self.load_entries() + + +def show_customer(user: Customer): + """Show a customer in a table row.""" + return rx.table.row( + rx.table.cell(user.name), + rx.table.cell(user.email), + rx.table.cell(user.phone), + rx.table.cell(user.address), + ) + + +def loading_data_table_example2(): + return rx.vstack( + rx.select( + ["name", "email", "phone", "address"], + placeholder="Sort By: Name", + on_change= lambda value: DatabaseTableState2.sort_values(value), + ), + rx.input( + placeholder="Search here...", + on_change= lambda value: DatabaseTableState2.filter_values(value), + ), + rx.table.root( + rx.table.header( + rx.table.row( + rx.table.column_header_cell("Name"), + rx.table.column_header_cell("Email"), + rx.table.column_header_cell("Phone"), + rx.table.column_header_cell("Address"), + ), + ), + rx.table.body(rx.foreach(DatabaseTableState2.users, show_customer)), + on_mount=DatabaseTableState2.load_entries, + width="100%", + ), + width="100%", + ) + +``` + + +## Pagination + +Pagination is an important part of database management, especially when working with large datasets. It helps in enabling efficient data retrieval by breaking down results into manageable loads. + +The purpose of this code is to retrieve a specific subset of rows from the `Customer` table based on the specified pagination parameters `offset` and `limit`. + +`query.offset(self.offset)` modifies the query to skip a certain number of rows before returning the results. The number of rows to skip is specified by `self.offset`. + +`query.limit(self.limit)` modifies the query to limit the number of rows returned. The maximum number of rows to return is specified by `self.limit`. + +```python exec +class DatabaseTableState3(rx.State): + + _mock_data: list[Customer] = [ + Customer(name="John Doe", email="john@example.com", phone="555-1234", address="123 Main St"), + Customer(name="Jane Smith", email="jane@example.com", phone="555-5678", address="456 Oak Ave"), + Customer(name="Bob Johnson", email="bob@example.com", phone="555-9012", address="789 Pine Rd"), + Customer(name="Alice Brown", email="alice@example.com", phone="555-3456", address="321 Maple Dr"), + Customer(name="Charlie Wilson", email="charlie@example.com", phone="555-7890", address="654 Elm St"), + Customer(name="Emily Davis", email="emily@example.com", phone="555-2345", address="987 Cedar Ln"), + ] + users: list[Customer] = [] + + total_items: int + offset: int = 0 + limit: int = 3 + + @rx.var(cache=True) + def page_number(self) -> int: + return ( + (self.offset // self.limit) + + 1 + + (1 if self.offset % self.limit else 0) + ) + + @rx.var(cache=True) + def total_pages(self) -> int: + return self.total_items // self.limit + ( + 1 if self.total_items % self.limit else 0 + ) + + @rx.event + def prev_page(self): + self.offset = max(self.offset - self.limit, 0) + self.load_entries() + + @rx.event + def next_page(self): + if self.offset + self.limit < self.total_items: + self.offset += self.limit + self.load_entries() + + def _get_total_items(self, session): + self.total_items = session.exec(select(func.count(Customer.id))).one() + + @rx.event + def load_entries(self): + self.users = self._mock_data[self.offset:self.offset + self.limit] + self.total_items = len(self._mock_data) + + +def show_customer(user: Customer): + return rx.table.row( + rx.table.cell(user.name), + rx.table.cell(user.email), + rx.table.cell(user.phone), + rx.table.cell(user.address), + ) + + +def loading_data_table_example3(): + return rx.vstack( + rx.hstack( + rx.button( + "Prev", + on_click=DatabaseTableState3.prev_page, + ), + rx.text( + f"Page {DatabaseTableState3.page_number} / {DatabaseTableState3.total_pages}" + ), + rx.button( + "Next", + on_click=DatabaseTableState3.next_page, + ), + ), + rx.table.root( + rx.table.header( + rx.table.row( + rx.table.column_header_cell("Name"), + rx.table.column_header_cell("Email"), + rx.table.column_header_cell("Phone"), + rx.table.column_header_cell("Address"), + ), + ), + rx.table.body(rx.foreach(DatabaseTableState3.users, show_customer)), + on_mount=DatabaseTableState3.load_entries, + width="100%", + ), + width="100%", + margin_bottom="1em", + ) +``` + +```python eval +loading_data_table_example3() +``` + +```python +from sqlmodel import select, func + + +class DatabaseTableState3(rx.State): + + users: list[Customer] = [] + + total_items: int + offset: int = 0 + limit: int = 3 + + @rx.var(cache=True) + def page_number(self) -> int: + return ( + (self.offset // self.limit) + + 1 + + (1 if self.offset % self.limit else 0) + ) + + @rx.var(cache=True) + def total_pages(self) -> int: + return self.total_items // self.limit + ( + 1 if self.total_items % self.limit else 0 + ) + + @rx.event + def prev_page(self): + self.offset = max(self.offset - self.limit, 0) + self.load_entries() + + @rx.event + def next_page(self): + if self.offset + self.limit < self.total_items: + self.offset += self.limit + self.load_entries() + + def _get_total_items(self, session): + """Return the total number of items in the Customer table.""" + self.total_items = session.exec(select(func.count(Customer.id))).one() + + @rx.event + def load_entries(self): + """Get all users from the database.""" + with rx.session() as session: + query = select(Customer) + + # Apply pagination + query = query.offset(self.offset).limit(self.limit) + + self.users = session.exec(query).all() + self._get_total_items(session) + + +def show_customer(user: Customer): + """Show a customer in a table row.""" + return rx.table.row( + rx.table.cell(user.name), + rx.table.cell(user.email), + rx.table.cell(user.phone), + rx.table.cell(user.address), + ) + + +def loading_data_table_example3(): + return rx.vstack( + rx.hstack( + rx.button( + "Prev", + on_click=DatabaseTableState3.prev_page, + ), + rx.text( + f"Page {DatabaseTableState3.page_number} / {DatabaseTableState3.total_pages}" + ), + rx.button( + "Next", + on_click=DatabaseTableState3.next_page, + ), + ), + rx.table.root( + rx.table.header( + rx.table.row( + rx.table.column_header_cell("Name"), + rx.table.column_header_cell("Email"), + rx.table.column_header_cell("Phone"), + rx.table.column_header_cell("Address"), + ), + ), + rx.table.body(rx.foreach(DatabaseTableState3.users, show_customer)), + on_mount=DatabaseTableState3.load_entries, + width="100%", + ), + width="100%", + ) + +``` +## More advanced examples + +The real power of the `rx.table` comes where you are able to visualise, add and edit data live in your app. Check out these apps and code to see how this is done: app: https://customer-data-app.reflex.run code: https://github.com/reflex-dev/templates/tree/main/customer_data_app and code: https://github.com/reflex-dev/templates/tree/main/sales. + +# Download + +Most users will want to download their data after they have got the subset that they would like in their table. + +In this example there are buttons to download the data as a `json` and as a `csv`. + +For the `json` download the `rx.download` is in the frontend code attached to the `on_click` event trigger for the button. This works because if the `Var` is not already a string, it will be converted to a string using `JSON.stringify`. + +For the `csv` download the `rx.download` is in the backend code as an event_handler `download_csv_data`. There is also a helper function `_convert_to_csv` that converts the data in `self.users` to `csv` format. + +```python exec +import io +import csv +import json + +class TableDownloadState(rx.State): + _mock_data: list[Customer] = [ + Customer(name="John Doe", email="john@example.com", phone="555-1234", address="123 Main St"), + Customer(name="Jane Smith", email="jane@example.com", phone="555-5678", address="456 Oak Ave"), + Customer(name="Bob Johnson", email="bob@example.com", phone="555-9012", address="789 Pine Rd"), + ] + users: list[Customer] = [] + + @rx.event + def load_entries(self): + self.users = self._mock_data + + def _convert_to_csv(self) -> str: + if not self.users: + self.load_entries() + + fieldnames = ["id", "name", "email", "phone", "address"] + output = io.StringIO() + writer = csv.DictWriter(output, fieldnames=fieldnames) + writer.writeheader() + for user in self.users: + writer.writerow(user.dict()) + + csv_data = output.getvalue() + output.close() + return csv_data + + + def _convert_to_json(self) -> str: + return json.dumps([u.dict() for u in self._mock_data], indent=2) + + @rx.event + def download_csv_data(self): + csv_data = self._convert_to_csv() + return rx.download( + data=csv_data, + filename="data.csv", + ) + + @rx.event + def download_json_data(self): + json_data = self._convert_to_json() + return rx.download( + data=json_data, + filename="data.json", + ) + +def show_customer(user: Customer): + return rx.table.row( + rx.table.cell(user.name), + rx.table.cell(user.email), + rx.table.cell(user.phone), + rx.table.cell(user.address), + ) + +def download_data_table_example(): + return rx.vstack( + rx.table.root( + rx.table.header( + rx.table.row( + rx.table.column_header_cell("Name"), + rx.table.column_header_cell("Email"), + rx.table.column_header_cell("Phone"), + rx.table.column_header_cell("Address"), + ), + ), + rx.table.body(rx.foreach(TableDownloadState.users, show_customer)), + width="100%", + on_mount=TableDownloadState.load_entries, + ), + rx.hstack( + rx.button( + "Download as JSON", + on_click=TableDownloadState.download_json_data, + ), + rx.button( + "Download as CSV", + on_click=TableDownloadState.download_csv_data, + ), + spacing="7", + ), + width="100%", + spacing="5", + margin_bottom="1em", + ) + +``` + +```python eval +download_data_table_example() +``` + +```python +import io +import csv +from sqlmodel import select + +class TableDownloadState(rx.State): + + users: list[Customer] = [] + + @rx.event + def load_entries(self): + """Get all users from the database.""" + with rx.session() as session: + self.users = session.exec(select(Customer)).all() + + + def _convert_to_csv(self) -> str: + """Convert the users data to CSV format.""" + + # Make sure to load the entries first + if not self.users: + self.load_entries() + + # Define the CSV file header based on the Customer model's attributes + fieldnames = list(Customer.__fields__) + + # Create a string buffer to hold the CSV data + output = io.StringIO() + writer = csv.DictWriter(output, fieldnames=fieldnames) + writer.writeheader() + for user in self.users: + writer.writerow(user.dict()) + + # Get the CSV data as a string + csv_data = output.getvalue() + output.close() + return csv_data + + @rx.event + def download_csv_data(self): + csv_data = self._convert_to_csv() + return rx.download( + data=csv_data, + filename="data.csv", + ) + + +def show_customer(user: Customer): + """Show a customer in a table row.""" + return rx.table.row( + rx.table.cell(user.name), + rx.table.cell(user.email), + rx.table.cell(user.phone), + rx.table.cell(user.address), + ) + +def download_data_table_example(): + return rx.vstack( + rx.table.root( + rx.table.header( + rx.table.row( + rx.table.column_header_cell("Name"), + rx.table.column_header_cell("Email"), + rx.table.column_header_cell("Phone"), + rx.table.column_header_cell("Address"), + ), + ), + rx.table.body(rx.foreach(TableDownloadState.users, show_customer)), + width="100%", + on_mount=TableDownloadState.load_entries, + ), + rx.hstack( + rx.button( + "Download as JSON", + on_click=rx.download( + data=TableDownloadState.users, + filename="data.json", + ), + ), + rx.button( + "Download as CSV", + on_click=TableDownloadState.download_csv_data, + ), + spacing="7", + ), + width="100%", + spacing="5", + ) + +``` + +# Real World Example UI + +```python demo +rx.flex( + rx.heading("Your Team"), + rx.text("Invite and manage your team members"), + rx.flex( + rx.input(placeholder="Email Address"), + rx.button("Invite"), + justify="center", + spacing="2", + ), + rx.table.root( + rx.table.body( + rx.table.row( + rx.table.cell(rx.avatar(fallback="DS")), + rx.table.row_header_cell(rx.link("Danilo Sousa")), + rx.table.cell("danilo@example.com"), + rx.table.cell("Developer"), + align="center", + ), + rx.table.row( + rx.table.cell(rx.avatar(fallback="ZA")), + rx.table.row_header_cell(rx.link("Zahra Ambessa")), + rx.table.cell("zahra@example.com"), + rx.table.cell("Admin"), + align="center", + ), + rx.table.row( + rx.table.cell(rx.avatar(fallback="JE")), + rx.table.row_header_cell(rx.link("Jasper Eriksson")), + rx.table.cell("jasper@example.com"), + rx.table.cell("Developer"), + align="center", + ), + ), + width="100%", + ), + width="100%", + direction="column", + spacing="2", +) +``` diff --git a/docs/library/typography/blockquote.md b/docs/library/typography/blockquote.md new file mode 100644 index 00000000000..5070c1d0029 --- /dev/null +++ b/docs/library/typography/blockquote.md @@ -0,0 +1,77 @@ +--- +components: + - rx.blockquote +--- + +```python exec +import reflex as rx +``` + +# Blockquote + +```python demo +rx.blockquote("Perfect typography is certainly the most elusive of all arts.") +``` + +## Size + +Use the `size` prop to control the size of the blockquote. The prop also provides correct line height and corrective letter spacing—as text size increases, the relative line height and letter spacing decrease. + +```python demo +rx.flex( + rx.blockquote("Perfect typography is certainly the most elusive of all arts.", size="1"), + rx.blockquote("Perfect typography is certainly the most elusive of all arts.", size="2"), + rx.blockquote("Perfect typography is certainly the most elusive of all arts.", size="3"), + rx.blockquote("Perfect typography is certainly the most elusive of all arts.", size="4"), + rx.blockquote("Perfect typography is certainly the most elusive of all arts.", size="5"), + rx.blockquote("Perfect typography is certainly the most elusive of all arts.", size="6"), + rx.blockquote("Perfect typography is certainly the most elusive of all arts.", size="7"), + rx.blockquote("Perfect typography is certainly the most elusive of all arts.", size="8"), + rx.blockquote("Perfect typography is certainly the most elusive of all arts.", size="9"), + direction="column", + spacing="3", +) +``` + +## Weight + +Use the `weight` prop to set the blockquote weight. + +```python demo +rx.flex( + rx.blockquote("Perfect typography is certainly the most elusive of all arts.", weight="light"), + rx.blockquote("Perfect typography is certainly the most elusive of all arts.", weight="regular"), + rx.blockquote("Perfect typography is certainly the most elusive of all arts.", weight="medium"), + rx.blockquote("Perfect typography is certainly the most elusive of all arts.", weight="bold"), + direction="column", + spacing="3", +) +``` + +## Color + +Use the `color_scheme` prop to assign a specific color, ignoring the global theme. + +```python demo +rx.flex( + rx.blockquote("Perfect typography is certainly the most elusive of all arts.", color_scheme="indigo"), + rx.blockquote("Perfect typography is certainly the most elusive of all arts.", color_scheme="cyan"), + rx.blockquote("Perfect typography is certainly the most elusive of all arts.", color_scheme="crimson"), + rx.blockquote("Perfect typography is certainly the most elusive of all arts.", color_scheme="orange"), + direction="column", + spacing="3", +) +``` + +## High Contrast + +Use the `high_contrast` prop to increase color contrast with the background. + +```python demo +rx.flex( + rx.blockquote("Perfect typography is certainly the most elusive of all arts."), + rx.blockquote("Perfect typography is certainly the most elusive of all arts.", high_contrast=True), + direction="column", + spacing="3", +) +``` diff --git a/docs/library/typography/code.md b/docs/library/typography/code.md new file mode 100644 index 00000000000..e823a2cdf4a --- /dev/null +++ b/docs/library/typography/code.md @@ -0,0 +1,110 @@ +--- +components: + - rx.code +--- + +```python exec +import reflex as rx +``` + +# Code + +```python demo +rx.code("console.log()") +``` + +## Size + +Use the `size` prop to control text size. This prop also provides correct line height and corrective letter spacing—as text size increases, the relative line height and letter spacing decrease. + +```python demo +rx.flex( + rx.code("console.log()", size="1"), + rx.code("console.log()", size="2"), + rx.code("console.log()", size="3"), + rx.code("console.log()", size="4"), + rx.code("console.log()", size="5"), + rx.code("console.log()", size="6"), + rx.code("console.log()", size="7"), + rx.code("console.log()", size="8"), + rx.code("console.log()", size="9"), + direction="column", + spacing="3", + align="start", +) +``` + +## Weight + +Use the `weight` prop to set the text weight. + +```python demo +rx.flex( + rx.code("console.log()", weight="light"), + rx.code("console.log()", weight="regular"), + rx.code("console.log()", weight="medium"), + rx.code("console.log()", weight="bold"), + direction="column", + spacing="3", +) +``` + +## Variant + +Use the `variant` prop to control the visual style. + +```python demo +rx.flex( + rx.code("console.log()", variant="solid"), + rx.code("console.log()", variant="soft"), + rx.code("console.log()", variant="outline"), + rx.code("console.log()", variant="ghost"), + direction="column", + spacing="2", + align="start", +) +``` + +## Color + +Use the `color_scheme` prop to assign a specific color, ignoring the global theme. + +```python demo +rx.flex( + rx.code("console.log()", color_scheme="indigo"), + rx.code("console.log()", color_scheme="crimson"), + rx.code("console.log()", color_scheme="orange"), + rx.code("console.log()", color_scheme="cyan"), + direction="column", + spacing="2", + align="start", +) +``` + +## High Contrast + +Use the `high_contrast` prop to increase color contrast with the background. + +```python demo +rx.flex( + rx.flex( + rx.code("console.log()", variant="solid"), + rx.code("console.log()", variant="soft"), + rx.code("console.log()", variant="outline"), + rx.code("console.log()", variant="ghost"), + direction="column", + align="start", + spacing="2", + ), + rx.flex( + rx.code("console.log()", variant="solid", high_contrast=True), + rx.code("console.log()", variant="soft", high_contrast=True), + rx.code("console.log()", variant="outline", high_contrast=True), + rx.code("console.log()", variant="ghost", high_contrast=True), + direction="column", + align="start", + spacing="2", + ), + spacing="3", +) +``` diff --git a/docs/library/typography/em.md b/docs/library/typography/em.md new file mode 100644 index 00000000000..2fd67aeb76e --- /dev/null +++ b/docs/library/typography/em.md @@ -0,0 +1,16 @@ +--- +components: + - rx.text.em +--- + +```python exec +import reflex as rx +``` + +# Em (Emphasis) + +Marks text to stress emphasis. + +```python demo +rx.text("We ", rx.text.em("had"), " to do something about it.") +``` diff --git a/docs/library/typography/heading.md b/docs/library/typography/heading.md new file mode 100644 index 00000000000..44cd6c3f8d0 --- /dev/null +++ b/docs/library/typography/heading.md @@ -0,0 +1,152 @@ +--- +components: + - rx.heading +--- + +```python exec +import reflex as rx +``` + +# Heading + +```python demo +rx.heading("The quick brown fox jumps over the lazy dog.") +``` + +## As another element + +Use the `as_` prop to change the heading level. This prop is purely semantic and does not change the visual appearance. + +```python demo +rx.flex( + rx.heading("Level 1", as_="h1"), + rx.heading("Level 2", as_="h2"), + rx.heading("Level 3", as_="h3"), + direction="column", + spacing="3", +) +``` + +## Size + +Use the `size` prop to control the size of the heading. The prop also provides correct line height and corrective letter spacing—as text size increases, the relative line height and letter spacing decrease + +```python demo +rx.flex( + rx.heading("The quick brown fox jumps over the lazy dog.", size="1"), + rx.heading("The quick brown fox jumps over the lazy dog.", size="2"), + rx.heading("The quick brown fox jumps over the lazy dog.", size="3"), + rx.heading("The quick brown fox jumps over the lazy dog.", size="4"), + rx.heading("The quick brown fox jumps over the lazy dog.", size="5"), + rx.heading("The quick brown fox jumps over the lazy dog.", size="6"), + rx.heading("The quick brown fox jumps over the lazy dog.", size="7"), + rx.heading("The quick brown fox jumps over the lazy dog.", size="8"), + rx.heading("The quick brown fox jumps over the lazy dog.", size="9"), + direction="column", + spacing="3", +) +``` + +## Weight + +Use the `weight` prop to set the text weight. + +```python demo +rx.flex( + rx.heading("The quick brown fox jumps over the lazy dog.", weight="light"), + rx.heading("The quick brown fox jumps over the lazy dog.", weight="regular"), + rx.heading("The quick brown fox jumps over the lazy dog.", weight="medium"), + rx.heading("The quick brown fox jumps over the lazy dog.", weight="bold"), + direction="column", + spacing="3", +) +``` + +## Align + +Use the `align` prop to set text alignment. + +```python demo +rx.flex( + rx.heading("Left-aligned", align="left"), + rx.heading("Center-aligned", align="center"), + rx.heading("Right-aligned", align="right"), + direction="column", + spacing="3", + width="100%", +) +``` + +## Trim + +Use the `trim` prop to trim the leading space at the start, end, or both sides of the text. + +```python demo +rx.flex( + rx.heading("Without Trim", + trim="normal", + style={"background": "var(--gray-a2)", + "border_top": "1px dashed var(--gray-a7)", + "border_bottom": "1px dashed var(--gray-a7)",} + ), + rx.heading("With Trim", + trim="both", + style={"background": "var(--gray-a2)", + "border_top": "1px dashed var(--gray-a7)", + "border_bottom": "1px dashed var(--gray-a7)",} + ), + direction="column", + spacing="3", +) +``` + +Trimming the leading is useful when dialing in vertical spacing in cards or other “boxy” components. Otherwise, padding looks larger on top and bottom than on the sides. + +```python demo +rx.flex( + rx.box( + rx.heading("Without trim", margin_bottom="4px", size="3",), + rx.text("The goal of typography is to relate font size, line height, and line width in a proportional way that maximizes beauty and makes reading easier and more pleasant."), + style={"background": "var(--gray-a2)", + "border": "1px dashed var(--gray-a7)",}, + padding="16px", + ), + rx.box( + rx.heading("With trim", margin_bottom="4px", size="3", trim="start"), + rx.text("The goal of typography is to relate font size, line height, and line width in a proportional way that maximizes beauty and makes reading easier and more pleasant."), + style={"background": "var(--gray-a2)", + "border": "1px dashed var(--gray-a7)",}, + padding="16px", + ), + direction="column", + spacing="3", +) +``` + +## Color + +Use the `color_scheme` prop to assign a specific color, ignoring the global theme. + +```python demo +rx.flex( + rx.heading("The quick brown fox jumps over the lazy dog.", color_scheme="indigo"), + rx.heading("The quick brown fox jumps over the lazy dog.", color_scheme="cyan"), + rx.heading("The quick brown fox jumps over the lazy dog.", color_scheme="crimson"), + rx.heading("The quick brown fox jumps over the lazy dog.", color_scheme="orange"), + direction="column", +) +``` + +## High Contrast + +Use the `high_contrast` prop to increase color contrast with the background. + +```python demo +rx.flex( + rx.heading("The quick brown fox jumps over the lazy dog.", color_scheme="indigo", high_contrast=True), + rx.heading("The quick brown fox jumps over the lazy dog.", color_scheme="cyan", high_contrast=True), + rx.heading("The quick brown fox jumps over the lazy dog.", color_scheme="crimson", high_contrast=True), + rx.heading("The quick brown fox jumps over the lazy dog.", color_scheme="orange", high_contrast=True), + direction="column", +) +``` diff --git a/docs/library/typography/kbd.md b/docs/library/typography/kbd.md new file mode 100644 index 00000000000..c37771268ec --- /dev/null +++ b/docs/library/typography/kbd.md @@ -0,0 +1,36 @@ +--- +components: + - rx.text.kbd +--- + +```python exec +import reflex as rx +``` + +# rx.text.kbd (Keyboard) + +Represents keyboard input or a hotkey. + +```python demo +rx.text.kbd("Shift + Tab") +``` + +## Size + +Use the `size` prop to control text size. This prop also provides correct line height and corrective letter spacing—as text size increases, the relative line height and letter spacing decrease. + +```python demo +rx.flex( + rx.text.kbd("Shift + Tab", size="1"), + rx.text.kbd("Shift + Tab", size="2"), + rx.text.kbd("Shift + Tab", size="3"), + rx.text.kbd("Shift + Tab", size="4"), + rx.text.kbd("Shift + Tab", size="5"), + rx.text.kbd("Shift + Tab", size="6"), + rx.text.kbd("Shift + Tab", size="7"), + rx.text.kbd("Shift + Tab", size="8"), + rx.text.kbd("Shift + Tab", size="9"), + direction="column", + spacing="3", +) +``` diff --git a/docs/library/typography/link.md b/docs/library/typography/link.md new file mode 100644 index 00000000000..a05087f8447 --- /dev/null +++ b/docs/library/typography/link.md @@ -0,0 +1,147 @@ +--- +components: + - rx.link +--- + +```python exec +import reflex as rx +from pcweb.pages.docs import api_reference +``` + +# Link + +Links are accessible elements used primarily for navigation. Use the `href` prop to specify the location for the link to navigate to. + +```python demo +rx.link("Reflex Home Page.", href="https://reflex.dev/") +``` + +You can also provide local links to other pages in your project without writing the full url. + +```python demo +rx.link("Example", href="/docs/library",) +``` + + +The `link` component can be used to wrap other components to make them link to other pages. + +```python demo +rx.link(rx.button("Example"), href="https://reflex.dev/") +``` + +You can also create anchors to link to specific parts of a page using the `id` prop. + +```python demo +rx.box("Example", id="example") +``` + +To reference an anchor, you can use the `href` prop of the `link` component. The `href` should be in the format of the page you want to link to followed by a # and the id of the anchor. + +```python demo +rx.link("Example", href="/docs/library/typography/link#example") +``` + +```md alert info +# Redirecting the user using State +It is also possible to redirect the user to a new path within the application, using `rx.redirect()`. Check out the docs [here]({api_reference.special_events.path}). +``` + +# Style + +## Size + +Use the `size` prop to control the size of the link. The prop also provides correct line height and corrective letter spacing—as text size increases, the relative line height and letter spacing decrease. + +```python demo +rx.flex( + rx.link("The quick brown fox jumps over the lazy dog.", size="1"), + rx.link("The quick brown fox jumps over the lazy dog.", size="2"), + rx.link("The quick brown fox jumps over the lazy dog.", size="3"), + rx.link("The quick brown fox jumps over the lazy dog.", size="4"), + rx.link("The quick brown fox jumps over the lazy dog.", size="5"), + rx.link("The quick brown fox jumps over the lazy dog.", size="6"), + rx.link("The quick brown fox jumps over the lazy dog.", size="7"), + rx.link("The quick brown fox jumps over the lazy dog.", size="8"), + rx.link("The quick brown fox jumps over the lazy dog.", size="9"), + direction="column", + spacing="3", +) +``` + +## Weight + +Use the `weight` prop to set the text weight. + +```python demo +rx.flex( + rx.link("The quick brown fox jumps over the lazy dog.", weight="light"), + rx.link("The quick brown fox jumps over the lazy dog.", weight="regular"), + rx.link("The quick brown fox jumps over the lazy dog.", weight="medium"), + rx.link("The quick brown fox jumps over the lazy dog.", weight="bold"), + direction="column", + spacing="3", +) +``` + +## Trim + +Use the `trim` prop to trim the leading space at the start, end, or both sides of the rendered text. + +```python demo +rx.flex( + rx.link("Without Trim", + trim="normal", + style={"background": "var(--gray-a2)", + "border_top": "1px dashed var(--gray-a7)", + "border_bottom": "1px dashed var(--gray-a7)",} + ), + rx.link("With Trim", + trim="both", + style={"background": "var(--gray-a2)", + "border_top": "1px dashed var(--gray-a7)", + "border_bottom": "1px dashed var(--gray-a7)",} + ), + direction="column", + spacing="3", +) +``` + +## Underline + +Use the `underline` prop to manage the visibility of the underline affordance. It defaults to `auto`. + +```python demo +rx.flex( + rx.link("The quick brown fox jumps over the lazy dog.", underline="auto"), + rx.link("The quick brown fox jumps over the lazy dog.", underline="hover"), + rx.link("The quick brown fox jumps over the lazy dog.", underline="always"), + direction="column", + spacing="3", +) +``` + +## Color + +Use the `color_scheme` prop to assign a specific color, ignoring the global theme. + +```python demo +rx.flex( + rx.link("The quick brown fox jumps over the lazy dog.", color_scheme="indigo"), + rx.link("The quick brown fox jumps over the lazy dog.", color_scheme="cyan"), + rx.link("The quick brown fox jumps over the lazy dog.", color_scheme="crimson"), + rx.link("The quick brown fox jumps over the lazy dog.", color_scheme="orange"), + direction="column", +) +``` + +## High Contrast + +Use the `high_contrast` prop to increase color contrast with the background. + +```python demo +rx.flex( + rx.link("The quick brown fox jumps over the lazy dog."), + rx.link("The quick brown fox jumps over the lazy dog.", high_contrast=True), + direction="column", +) +``` diff --git a/docs/library/typography/markdown.md b/docs/library/typography/markdown.md new file mode 100644 index 00000000000..b308c15f03a --- /dev/null +++ b/docs/library/typography/markdown.md @@ -0,0 +1,195 @@ +--- +components: + - rx.markdown +--- + +```python exec +import reflex as rx +``` + +# Markdown + +The `rx.markdown` component can be used to render markdown text. +It is based on [Github Flavored Markdown](https://github.github.com/gfm/). + +```python demo +rx.vstack( + rx.markdown("# Hello World!"), + rx.markdown("## Hello World!"), + rx.markdown("### Hello World!"), + rx.markdown("Support us on [Github](https://github.com/reflex-dev/reflex)."), + rx.markdown("Use `reflex deploy` to deploy your app with **a single command**."), +) +``` + +## Math Equations + +You can render math equations using LaTeX. +For inline equations, surround the equation with `$`: + +```python demo +rx.markdown("Pythagorean theorem: $a^2 + b^2 = c^2$.") +``` + +## Syntax Highlighting + +You can render code blocks with syntax highlighting using the \`\`\`\{language} syntax: + +````python demo +rx.markdown( +r""" +```python +import reflex as rx +from .pages import index + +app = rx.App() +app.add_page(index) +``` +""" +) +```` + +## Tables + +You can render tables using the `|` syntax: + +```python demo +rx.markdown( + """ +| Syntax | Description | +| ----------- | ----------- | +| Header | Title | +| Paragraph | Text | +""" +) +``` + +## Plugins + +Plugins can be used to extend the functionality of the markdown renderer. + +By default Reflex uses the following plugins: +- `remark-gfm` for Github Flavored Markdown support (`use_gfm`). +- `remark-math` and `rehype-katex` for math equation support (`use_math`, `use_katex`). +- `rehype-unwrap-images` to remove paragraph tags around images (`use_unwrap_images`). +- `rehype-raw` to render raw HTML in markdown (`use_raw`). NOTE: in a future release this will be disabled by default for security reasons. + +These default plugins can be disabled by passing `use_[plugin_name]=False` to the `rx.markdown` component. For example, to disable raw HTML rendering, use `rx.markdown(..., use_raw=False)`. + +## Arbitrary Plugins + +You can also add arbitrary remark or rehype plugins using the `remark_plugins` +and `rehype_plugins` props in conjunction with the `rx.markdown.plugin` helper. + +`rx.markdown.plugin` takes two arguments: + +1. The npm package name and version of the plugin (e.g. `remark-emoji@5.0.2`). +2. The named export to use from the plugin (e.g. `remarkEmoji`). + +### Remark Plugin Example + +For example, to add support for emojis using the `remark-emoji` plugin: + +```python demo +rx.markdown( + "Hello :smile:! :rocket: :tada:", + remark_plugins=[ + rx.markdown.plugin("remark-emoji@5.0.2", "remarkEmoji"), + ], +) +``` + +### Rehype Plugin Example + +To make `rehype-raw` safer for untrusted HTML input we can use `rehype-sanitize`, which defaults to a safe schema similar to that used by Github. + +```python demo +rx.markdown( + """Here is some **bold** text and a .""", + use_raw=True, + rehype_plugins=[ + rx.markdown.plugin("rehype-sanitize@5.0.1", "rehypeSanitize"), + ], +) +``` + +### Plugin Options + +Both `remark_plugins` and `rehype_plugins` accept a heterogeneous list of `plugin` +or tuple of `(plugin, options)` in case the plugin requires some kind of special +configuration. + +For example, `rehype-slug` is a simple plugin that adds ID attributes to the +headings, but the `rehype-autolink-headings` plugin accepts options to specify +how to render the links to those anchors. + +```python demo +rx.markdown( + """ +# Heading 1 +## Heading 2 +""", + rehype_plugins=[ + rx.markdown.plugin("rehype-slug@6.0.0", "rehypeSlug"), + ( + rx.markdown.plugin("rehype-autolink-headings@7.1.0", "rehypeAutolinkHeadings"), + { + "behavior": "wrap", + "properties": { + "className": ["heading-link"], + }, + }, + ), + ], +) +``` + +## Component Map + +You can specify which components to use for rendering markdown elements using the +`component_map` prop. + +Each key in the `component_map` prop is a markdown element, and the value is +a function that takes the text of the element as input and returns a Reflex component. + +```md alert +The `pre` and `a` tags are special cases. In addition to the `text`, they also receive a `props` argument containing additional props for the component. +``` + +````python demo exec +component_map = { + "h1": lambda text: rx.heading(text, size="5", margin_y="1em"), + "h2": lambda text: rx.heading(text, size="3", margin_y="1em"), + "h3": lambda text: rx.heading(text, size="1", margin_y="1em"), + "p": lambda text: rx.text(text, color="green", margin_y="1em"), + "code": lambda text: rx.code(text, color="purple"), + "pre": lambda text, **props: rx.code_block(text, **props, theme=rx.code_block.themes.dark, margin_y="1em"), + "a": lambda text, **props: rx.link(text, **props, color="blue", _hover={"color": "red"}), +} + +def index(): + return rx.box( + rx.markdown( +r""" +# Hello World! + +## This is a Subheader + +### And Another Subheader + +Here is some `code`: + +```python +import reflex as rx + +component = rx.text("Hello World!") +``` + +And then some more text here, +followed by a link to +[Reflex](https://reflex.dev/). +""", + component_map=component_map, +) + ) +```` diff --git a/docs/library/typography/quote.md b/docs/library/typography/quote.md new file mode 100644 index 00000000000..15290f5fc85 --- /dev/null +++ b/docs/library/typography/quote.md @@ -0,0 +1,19 @@ +--- +components: + - rx.text.quote +--- + +```python exec +import reflex as rx +``` + +# Quote + +A short inline quotation. + +```python demo +rx.text("His famous quote, ", + rx.text.quote("Styles come and go. Good design is a language, not a style"), + ", elegantly sums up Massimo’s philosophy of design." + ) +``` diff --git a/docs/library/typography/strong.md b/docs/library/typography/strong.md new file mode 100644 index 00000000000..85731243ea6 --- /dev/null +++ b/docs/library/typography/strong.md @@ -0,0 +1,16 @@ +--- +components: + - rx.text.strong +--- + +```python exec +import reflex as rx +``` + +# Strong + +Marks text to signify strong importance. + +```python demo +rx.text("The most important thing to remember is, ", rx.text.strong("stay positive"), ".") +``` diff --git a/docs/library/typography/text.md b/docs/library/typography/text.md new file mode 100644 index 00000000000..bede02dd0f4 --- /dev/null +++ b/docs/library/typography/text.md @@ -0,0 +1,204 @@ +--- +components: + - rx.text + - rx.text.em + +--- + +```python exec +import reflex as rx +``` + +# Text + +```python demo +rx.text("The quick brown fox jumps over the lazy dog.") +``` + +## As another element + +Use the `as_` prop to render text as a `p`, `label`, `div` or `span`. This prop is purely semantic and does not alter visual appearance. + +```python demo +rx.flex( + rx.text("This is a ", rx.text.strong("paragraph"), " element.", as_="p"), + rx.text("This is a ", rx.text.strong("label"), " element.", as_="label"), + rx.text("This is a ", rx.text.strong("div"), " element.", as_="div"), + rx.text("This is a ", rx.text.strong("span"), " element.", as_="span"), + direction="column", + spacing="3", +) +``` + +## Size + +Use the `size` prop to control text size. This prop also provides correct line height and corrective letter spacing—as text size increases, the relative line height and letter spacing decrease. + +```python demo +rx.flex( + rx.text("The quick brown fox jumps over the lazy dog.", size="1"), + rx.text("The quick brown fox jumps over the lazy dog.", size="2"), + rx.text("The quick brown fox jumps over the lazy dog.", size="3"), + rx.text("The quick brown fox jumps over the lazy dog.", size="4"), + rx.text("The quick brown fox jumps over the lazy dog.", size="5"), + rx.text("The quick brown fox jumps over the lazy dog.", size="6"), + rx.text("The quick brown fox jumps over the lazy dog.", size="7"), + rx.text("The quick brown fox jumps over the lazy dog.", size="8"), + rx.text("The quick brown fox jumps over the lazy dog.", size="9"), + direction="column", + spacing="3", +) +``` + +Sizes 2–4 are designed to work well for long-form content. Sizes 1–3 are designed to work well for UI labels. + +## Weight + +Use the `weight` prop to set the text weight. + +```python demo +rx.flex( + rx.text("The quick brown fox jumps over the lazy dog.", weight="light", as_="div"), + rx.text("The quick brown fox jumps over the lazy dog.", weight="regular", as_="div"), + rx.text("The quick brown fox jumps over the lazy dog.", weight="medium", as_="div"), + rx.text("The quick brown fox jumps over the lazy dog.", weight="bold", as_="div"), + direction="column", + spacing="3", +) +``` + +## Align + +Use the `align` prop to set text alignment. + +```python demo +rx.flex( + rx.text("Left-aligned", align="left", as_="div"), + rx.text("Center-aligned", align="center", as_="div"), + rx.text("Right-aligned", align="right", as_="div"), + direction="column", + spacing="3", + width="100%", +) +``` + +## Trim + +Use the `trim` prop to trim the leading space at the start, end, or both sides of the text box. + +```python demo +rx.flex( + rx.text("Without Trim", + trim="normal", + style={"background": "var(--gray-a2)", + "border_top": "1px dashed var(--gray-a7)", + "border_bottom": "1px dashed var(--gray-a7)",} + ), + rx.text("With Trim", + trim="both", + style={"background": "var(--gray-a2)", + "border_top": "1px dashed var(--gray-a7)", + "border_bottom": "1px dashed var(--gray-a7)",} + ), + direction="column", + spacing="3", +) +``` + +Trimming the leading is useful when dialing in vertical spacing in cards or other “boxy” components. Otherwise, padding looks larger on top and bottom than on the sides. + +```python demo +rx.flex( + rx.box( + rx.heading("Without trim", margin_bottom="4px", size="3",), + rx.text("The goal of typography is to relate font size, line height, and line width in a proportional way that maximizes beauty and makes reading easier and more pleasant."), + style={"background": "var(--gray-a2)", + "border": "1px dashed var(--gray-a7)",}, + padding="16px", + ), + rx.box( + rx.heading("With trim", margin_bottom="4px", size="3", trim="start"), + rx.text("The goal of typography is to relate font size, line height, and line width in a proportional way that maximizes beauty and makes reading easier and more pleasant."), + style={"background": "var(--gray-a2)", + "border": "1px dashed var(--gray-a7)",}, + padding="16px", + ), + direction="column", + spacing="3", +) +``` + +## Color + +Use the `color_scheme` prop to assign a specific color, ignoring the global theme. + +```python demo +rx.flex( + rx.text("The quick brown fox jumps over the lazy dog.", color_scheme="indigo"), + rx.text("The quick brown fox jumps over the lazy dog.", color_scheme="cyan"), + rx.text("The quick brown fox jumps over the lazy dog.", color_scheme="crimson"), + rx.text("The quick brown fox jumps over the lazy dog.", color_scheme="orange"), + direction="column", +) +``` + +## High Contrast + +Use the `high_contrast` prop to increase color contrast with the background. + +```python demo +rx.flex( + rx.text("The quick brown fox jumps over the lazy dog.", color_scheme="indigo", high_contrast=True), + rx.text("The quick brown fox jumps over the lazy dog.", color_scheme="cyan", high_contrast=True), + rx.text("The quick brown fox jumps over the lazy dog.", color_scheme="crimson", high_contrast=True), + rx.text("The quick brown fox jumps over the lazy dog.", color_scheme="orange", high_contrast=True), + direction="column", +) +``` + +## With formatting + +Compose `Text` with formatting components to add emphasis and structure to content. + +```python demo +rx.text( + "Look, such a helpful ", + rx.link("link", href="#"), + ", an ", + rx.text.em("italic emphasis"), + " a piece of computer ", + rx.code("code"), + ", and even a hotkey combination ", + rx.text.kbd("⇧⌘A"), + " within the text.", + size="5", +) +``` + +## Preformmatting +By Default, the browser renders multiple white spaces into one. To preserve whitespace, use the `white_space = "pre"` css prop. + +```python demo +rx.hstack( + rx.text("This is not pre formatted"), + rx.text("This is pre formatted", white_space="pre"), +) +``` + +## With form controls + +Composing `text` with a form control like `checkbox`, `radiogroup`, or `switch` automatically centers the control with the first line of text, even when the text is multi-line. + +```python demo +rx.box( + rx.text( + rx.flex( + rx.checkbox(default_checked=True), + "I understand that these documents are confidential and cannot be shared with a third party.", + ), + as_="label", + size="3", + ), + style={"max_width": 300}, +) +``` diff --git a/docs/pages/dynamic_routing.md b/docs/pages/dynamic_routing.md new file mode 100644 index 00000000000..72e555b62cc --- /dev/null +++ b/docs/pages/dynamic_routing.md @@ -0,0 +1,110 @@ +```python exec +import reflex as rx +from pcweb import constants, styles +``` + +# Dynamic Routes + +Dynamic routes in Reflex allow you to handle varying URL structures, enabling you to create flexible +and adaptable web applications. This section covers regular dynamic routes, catch-all routes, +and optional catch-all routes, each with detailed examples. + +## Regular Dynamic Routes + +Regular dynamic routes in Reflex allow you to match specific segments in a URL dynamically. A regular dynamic route is defined by square brackets in a route string / url pattern. For example `/users/[id]` or `/products/[category]`. These dynamic route arguments can be accessed through a state var. For the examples above they would be `rx.State.id` and `rx.State.category` respectively. + +```md alert info +# Why is the state var accessed as `rx.State.id`? + +The dynamic route arguments are accessible as `rx.State.id` and `rx.State.category` here as the var is added to the root state, so that it is accessible from any state. +``` + +Example: + +```python +@rx.page(route='/post/[pid]') +def post(): + '''A page that updates based on the route.''' + # Displays the dynamic part of the URL, the post ID + return rx.heading(rx.State.pid) + +app = rx.App() +``` + +The [pid] part in the route is a dynamic segment, meaning it can match any value provided in the URL. For instance, `/post/5`, `/post/10`, or `/post/abc` would all match this route. + +If a user navigates to `/post/5`, `State.post_id` will return `5`, and the page will display `5` as the heading. If the URL is `/post/xyz`, it will display `xyz`. If the URL is `/post/` without any additional parameter, it will display `""`. + +### Adding Dynamic Routes + +Adding dynamic routes uses the `add_page` method like any other page. The only difference is that the route string contains dynamic segments enclosed in square brackets. + +If you are using the `app.add_page` method to define pages, it is necessary to add the dynamic routes first, especially if they use the same function as a non dynamic route. + +For example the code snippet below will: + +```python +app.add_page(index, route="/page/[page_id]", on_load=DynamicState.on_load) +app.add_page(index, route="/static/x", on_load=DynamicState.on_load) +app.add_page(index) +``` + +But if we switch the order of adding the pages, like in the example below, it will not work: + +```python +app.add_page(index, route="/static/x", on_load=DynamicState.on_load) +app.add_page(index) +app.add_page(index, route="/page/[page_id]", on_load=DynamicState.on_load) +``` + +## Catch-All Routes + +Catch-all routes in Reflex allow you to match any number of segments in a URL dynamically. + +Example: + +```python +class State(rx.State): + @rx.var + def user_post(self) -> str: + args = self.router.page.params + usernames = args.get('splat', []) + return f"Posts by \{', '.join(usernames)}" + +@rx.page(route='/users/[id]/posts/[[...splat]]') +def post(): + return rx.center( + rx.text(State.user_post) + ) + + +app = rx.App() +``` + +In this case, the `...splat` catch-all pattern captures any number of segments after +`/users/`, allowing URLs like `/users/2/posts/john/` and `/users/1/posts/john/doe/` to match the route. + +```md alert +# Catch-all routes must be named `splat` and be placed at the end of the URL pattern to ensure proper route matching. +``` + +### Routes Validation Table + +| Route Pattern | Example URl | valid | +| :----------------------------------------------- | :---------------------------------------------- | ------: | +| `/users/posts` | `/users/posts` | valid | +| `/products/[category]` | `/products/electronics` | valid | +| `/users/[username]/posts/[id]` | `/users/john/posts/5` | valid | +| `/users/[[...splat]]/posts` | `/users/john/posts` | invalid | +| | `/users/john/doe/posts` | invalid | +| `/users/[[...splat]]` | `/users/john/` | valid | +| | `/users/john/doe` | valid | +| `/products/[category]/[[...splat]]` | `/products/electronics/laptops` | valid | +| | `/products/electronics/laptops/lenovo` | valid | +| `/products/[category]/[[...splat]]` | `/products/electronics` | valid | +| | `/products/electronics/laptops` | valid | +| | `/products/electronics/laptops/lenovo` | valid | +| | `/products/electronics/laptops/lenovo/thinkpad` | valid | +| `/products/[category]/[[...splat]]/[[...splat]]` | `/products/electronics/laptops` | invalid | +| | `/products/electronics/laptops/lenovo` | invalid | +| | `/products/electronics/laptops/lenovo/thinkpad` | invalid | diff --git a/docs/pages/overview.md b/docs/pages/overview.md new file mode 100644 index 00000000000..2a28e8b4ddc --- /dev/null +++ b/docs/pages/overview.md @@ -0,0 +1,241 @@ +```python exec +import reflex as rx +from pcweb import constants, styles +from pcweb.pages import docs +from pcweb.pages.docs import api_reference, library +``` + +# Pages + +Pages map components to different URLs in your app. This section covers creating pages, handling URL arguments, accessing query parameters, managing page metadata, and handling page load events. + +## Adding a Page + +You can create a page by defining a function that returns a component. +By default, the function name will be used as the route, but you can also specify a route. + +```python +def index(): + return rx.text('Root Page') + +def about(): + return rx.text('About Page') + + +def custom(): + return rx.text('Custom Route') + +app = rx.App() + +app.add_page(index) +app.add_page(about) +app.add_page(custom, route="/custom-route") +``` + +In this example we create three pages: + +- `index` - The root route, available at `/` +- `about` - available at `/about` +- `custom` - available at `/custom-route` + +```md alert +# Index is a special exception where it is available at both `/` and `/index`. All other pages are only available at their specified route. +``` + +```md video https://youtube.com/embed/ITOZkzjtjUA?start=3853&end=4083 +# Video: Pages and URL Routes +``` + +## Page Decorator + +You can also use the `@rx.page` decorator to add a page. + +```python +@rx.page(route='/', title='My Beautiful App') +def index(): + return rx.text('A Beautiful App') +``` + +This is equivalent to calling `app.add_page` with the same arguments. + +```md alert warning +# Remember to import the modules defining your decorated pages. + +This is necessary for the pages to be registered with the app. + +You can directly import the module or import another module that imports the decorated pages. +``` + +## Navigating Between Pages + +### Links + +[Links]({library.typography.link.path}) are accessible elements used primarily for navigation. Use the `href` prop to specify the location for the link to navigate to. + +```python demo +rx.link("Reflex Home Page.", href="https://reflex.dev/") +``` + +You can also provide local links to other pages in your project without writing the full url. + +```python demo +rx.link("Example", href="/docs/library") +``` + +To open the link in a new tab, set the `is_external` prop to `True`. + +```python demo +rx.link("Open in new tab", href="https://reflex.dev/", is_external=True) +``` + +Check out the [link docs]({library.typography.link.path}) to learn more. + +```md video https://youtube.com/embed/ITOZkzjtjUA?start=4083&end=4423 +# Video: Link-based Navigation +``` + +### Redirect + +Redirect the user to a new path within the application using `rx.redirect()`. + +- `path`: The destination path or URL to which the user should be redirected. +- `external`: If set to True, the redirection will open in a new tab. Defaults to `False`. + +```python demo +rx.vstack( + rx.button("open in tab", on_click=rx.redirect("/docs/api-reference/special_events")), + rx.button("open in new tab", on_click=rx.redirect('https://github.com/reflex-dev/reflex/', is_external=True)) +) +``` + +Redirect can also be run from an event handler in State, meaning logic can be added behind it. It is necessary to `return` the `rx.redirect()`. + +```python demo exec +class Redirect2ExampleState(rx.State): + redirect_to_org: bool = False + + @rx.event + def change_redirect(self): + self.redirect_to_org = not self.redirect_to_org + + @rx.var + def url(self) -> str: + return 'https://github.com/reflex-dev/' if self.redirect_to_org else 'https://github.com/reflex-dev/reflex/' + + @rx.event + def change_page(self): + return rx.redirect(self.url, is_external=True) + +def redirect_example(): + return rx.vstack( + rx.text(f"{Redirect2ExampleState.url}"), + rx.button("Change redirect location", on_click=Redirect2ExampleState.change_redirect), + rx.button("Redirect to new page in State", on_click=Redirect2ExampleState.change_page), + + ) +``` + +```md video https://youtube.com/embed/ITOZkzjtjUA?start=4423&end=4903 +# Video: Redirecting to a New Page +``` + +## Nested Routes + +Pages can also have nested routes. + +```python +def nested_page(): + return rx.text('Nested Page') + +app = rx.App() +app.add_page(nested_page, route='/nested/page') +``` + +This component will be available at `/nested/page`. + +## Page Metadata + +```python exec + +import reflex as rx + +meta_data = ( +""" +@rx.page( + title='My Beautiful App', + description='A beautiful app built with Reflex', + image='https://web.reflex-assets.dev/other/logo.jpg', + meta=meta, +) +def index(): + return rx.text('A Beautiful App') + +@rx.page(title='About Page') +def about(): + return rx.text('About Page') + + +meta = [ + {'name': 'theme_color', 'content': '#FFFFFF'}, + {'char_set': 'UTF-8'}, + {'property': 'og:url', 'content': 'url'}, +] + +app = rx.App() +""" + +) + +``` + +You can add page metadata such as: + +- The title to be shown in the browser tab +- The description as shown in search results +- The preview image to be shown when the page is shared on social media +- Any additional metadata + +```python +{meta_data} +``` + +## Getting the Current Page + +You can access the current page from the `router` attribute in any state. See the [router docs]({docs.utility_methods.router_attributes.path}) for all available attributes. + +```python +class State(rx.State): + def some_method(self): + current_page_route = self.router.page.path + current_page_url = self.router.page.raw_path + # ... Your logic here ... +``` + +The `router.page.path` attribute allows you to obtain the path of the current page from the router data, +for [dynamic pages]({docs.pages.dynamic_routing.path}) this will contain the slug rather than the actual value used to load the page. + +To get the actual URL displayed in the browser, use `router.page.raw_path`. This +will contain all query parameters and dynamic path segments. + + +In the above example, `current_page_route` will contain the route pattern (e.g., `/posts/[id]`), while `current_page_url` +will contain the actual URL (e.g., `/posts/123`). + +To get the full URL, access the same attributes with `full_` prefix. + +Example: + +```python +class State(rx.State): + @rx.var + def current_url(self) -> str: + return self.router.page.full_raw_path + +def index(): + return rx.text(State.current_url) + +app = rx.App() +app.add_page(index, route='/posts/[id]') +``` + +In this example, running on `localhost` should display `http://localhost:3000/posts/123/` diff --git a/docs/pe/README.md b/docs/pe/README.md deleted file mode 100644 index efe1231ab68..00000000000 --- a/docs/pe/README.md +++ /dev/null @@ -1,251 +0,0 @@ -
    -Reflex Logo -
    - -### **✨ برنامه های تحت وب قابل تنظیم، کارآمد تماما پایتونی که در چند ثانیه مستقر(دپلوی) می‎شود. ✨** - -[![PyPI version](https://badge.fury.io/py/reflex.svg)](https://badge.fury.io/py/reflex) -![versions](https://img.shields.io/pypi/pyversions/reflex.svg) -[![Documentation](https://img.shields.io/badge/Documentation%20-Introduction%20-%20%23007ec6)](https://reflex.dev/docs/getting-started/introduction) -[![PyPI Downloads](https://static.pepy.tech/badge/reflex)](https://pepy.tech/projects/reflex) -[![Discord](https://img.shields.io/discord/1029853095527727165?color=%237289da&label=Discord)](https://discord.gg/T5WSbC2YtQ) - -
    - ---- - -[English](https://github.com/reflex-dev/reflex/blob/main/README.md) | [简体中文](https://github.com/reflex-dev/reflex/blob/main/docs/zh/zh_cn/README.md) | [繁體中文](https://github.com/reflex-dev/reflex/blob/main/docs/zh/zh_tw/README.md) | [Türkçe](https://github.com/reflex-dev/reflex/blob/main/docs/tr/README.md) | [हिंदी](https://github.com/reflex-dev/reflex/blob/main/docs/in/README.md) | [Português (Brasil)](https://github.com/reflex-dev/reflex/blob/main/docs/pt/pt_br/README.md) | [Italiano](https://github.com/reflex-dev/reflex/blob/main/docs/it/README.md) | [Español](https://github.com/reflex-dev/reflex/blob/main/docs/es/README.md) | [한국어](https://github.com/reflex-dev/reflex/blob/main/docs/kr/README.md) | [日本語](https://github.com/reflex-dev/reflex/blob/main/docs/ja/README.md) | [Deutsch](https://github.com/reflex-dev/reflex/blob/main/docs/de/README.md) | [Persian (پارسی)](https://github.com/reflex-dev/reflex/blob/main/docs/pe/README.md) | [Tiếng Việt](https://github.com/reflex-dev/reflex/blob/main/docs/vi/README.md) - ---- - -# Reflex - رفلکس - -رفلکس(Reflex) یک کتابخانه برای ساخت برنامه های وب فول استک تماما پایتونی است. - -ویژگی های کلیدی: - -- **تماما پایتونی** - فرانت اند و بک اند برنامه خود را همه در پایتون بنویسید، بدون نیاز به یادگیری جاوا اسکریپت. -- **انعطاف پذیری کامل** - شروع به کار با Reflex آسان است، اما می تواند به برنامه های پیچیده نیز تبدیل شود. -- **دپلوی فوری** - پس از ساخت، برنامه خود را با [یک دستور](https://reflex.dev/docs/hosting/deploy-quick-start/) دپلوی کنید یا آن را روی سرور خود میزبانی کنید. - -برای آشنایی با نحوه عملکرد Reflex [صفحه معماری](https://reflex.dev/blog/2024-03-21-reflex-architecture/#the-reflex-architecture) را ببینید. - -## ⚙️ Installation - نصب و راه اندازی - -یک ترمینال را باز کنید و اجرا کنید (نیازمند Python 3.10+): - -```bash -pip install reflex -``` - -## 🥳 اولین برنامه خود را ایجاد کنید - -نصب `reflex` همچنین `reflex` در خط فرمان را نصب میکند. - -با ایجاد یک پروژه جدید موفقیت آمیز بودن نصب را تست کنید. (`my_app_name` را با اسم پروژه خودتان جایگزین کنید): - -```bash -mkdir my_app_name -cd my_app_name -reflex init -``` - -این دستور یک برنامه الگو(تمپلیت) را در فهرست(دایرکتوری) جدید شما مقداردهی اولیه می کند - -می توانید این برنامه را در حالت توسعه(development) اجرا کنید: - -```bash -reflex run -``` - -باید برنامه خود را در حال اجرا ببینید در http://localhost:3000. - -اکنون می‌توانید کد منبع را در «my_app_name/my_app_name.py» تغییر دهید. Reflex دارای تازه‌سازی‌های سریعی است، بنابراین می‌توانید تغییرات خود را بلافاصله پس از ذخیره کد خود مشاهده کنید. - -## 🫧 Example App - برنامه نمونه - -بیایید یک مثال بزنیم: ایجاد یک رابط کاربری برای تولید تصویر [DALL·E](https://platform.openai.com/docs/guides/images/image-generation?context=node). برای سادگی، ما فراخوانی میکنیم [OpenAI API](https://platform.openai.com/docs/api-reference/authentication), اما می توانید آن را با یک مدل ML که به صورت محلی اجرا می شود جایگزین کنید. - -  - -
    -A frontend wrapper for DALL·E, shown in the process of generating an image. -
    - -  - -در اینجا کد کامل برای ایجاد این پروژه آمده است. همه اینها در یک فایل پایتون انجام می شود! - -```python -import reflex as rx -import openai - -openai_client = openai.OpenAI() - - -class State(rx.State): - """The app state.""" - - prompt = "" - image_url = "" - processing = False - complete = False - - def get_image(self): - """Get the image from the prompt.""" - if self.prompt == "": - return rx.window_alert("Prompt Empty") - - self.processing, self.complete = True, False - yield - response = openai_client.images.generate( - prompt=self.prompt, n=1, size="1024x1024" - ) - self.image_url = response.data[0].url - self.processing, self.complete = False, True - - -def index(): - return rx.center( - rx.vstack( - rx.heading("DALL-E", font_size="1.5em"), - rx.input( - placeholder="Enter a prompt..", - on_blur=State.set_prompt, - width="25em", - ), - rx.button( - "Generate Image", - on_click=State.get_image, - width="25em", - loading=State.processing - ), - rx.cond( - State.complete, - rx.image(src=State.image_url, width="20em"), - ), - align="center", - ), - width="100%", - height="100vh", - ) - -# Add state and page to the app. -app = rx.App() -app.add_page(index, title="Reflex:DALL-E") -``` - -## بیاید سادش کنیم - -
    -Explaining the differences between backend and frontend parts of the DALL-E app. -
    - -### **Reflex UI - رابط کاربری رفلکس** - -بیایید با رابط کاربری شروع کنیم. - -```python -def index(): - return rx.center( - ... - ) -``` - -تابع `index` قسمت فرانت اند برنامه را تعریف می کند. - -ما از اجزای مختلفی مثل `center`, `vstack`, `input` و `button` استفاده میکنیم تا فرانت اند را بسازیم. اجزاء را می توان درون یکدیگر قرار داد -برای ایجاد طرح بندی های پیچیده می توانید از args کلمات کلیدی برای استایل دادن به آنها از CSS استفاده کنید. - -رفلکس دارای [بیش از 60 جزء](https://reflex.dev/docs/library) برای کمک به شما برای شروع. ما به طور فعال اجزای بیشتری را اضافه می کنیم, و این خیلی آسان است که [اجزا خود را بسازید](https://reflex.dev/docs/wrapping-react/overview/). - -### **State - حالت** - -رفلکس رابط کاربری شما را به عنوان تابعی از وضعیت شما نشان می دهد. - -```python -class State(rx.State): - """The app state.""" - prompt = "" - image_url = "" - processing = False - complete = False - -``` - -حالت تمام متغیرها(variables) (به نام vars) را در یک برنامه که می توانند تغییر دهند و توابعی که آنها را تغییر می دهند تعریف می کند.. - -در اینجا حالت از یک `prompt` و `image_url` تشکیل شده است. همچنین دو بولی `processing` و `complete` برای نشان دادن زمان غیرفعال کردن دکمه (در طول تولید تصویر) و زمان نمایش تصویر نتیجه وجود دارد. - -### **Event Handlers - کنترل کنندگان رویداد** - -```python -def get_image(self): - """Get the image from the prompt.""" - if self.prompt == "": - return rx.window_alert("Prompt Empty") - - self.processing, self.complete = True, False - yield - response = openai_client.images.generate( - prompt=self.prompt, n=1, size="1024x1024" - ) - self.image_url = response.data[0].url - self.processing, self.complete = False, True -``` - -در داخل حالت، توابعی به نام کنترل کننده رویداد تعریف می کنیم که متغیرهای حالت را تغییر می دهند. کنترل کننده های رویداد راهی هستند که می توانیم وضعیت را در Reflex تغییر دهیم. آنها را می توان در پاسخ به اقدامات کاربر، مانند کلیک کردن روی یک دکمه یا تایپ کردن در یک متن، فراخوانی کرد. به این اعمال وقایع می گویند. - -برنامه DALL·E ما دارای یک کنترل کننده رویداد، `get_image` است که این تصویر را از OpenAI API دریافت می کند. استفاده از `yield` در وسط کنترل‌کننده رویداد باعث به‌روزرسانی رابط کاربری می‌شود. در غیر این صورت رابط کاربری در پایان کنترل کننده رویداد به روز می شود. - -### **Routing - مسیریابی** - -بالاخره اپلیکیشن خود را تعریف می کنیم. - -```python -app = rx.App() -``` - -ما یک صفحه از root برنامه را به جزء index اضافه می کنیم. ما همچنین عنوانی را اضافه می کنیم که در برگه پیش نمایش/مرورگر صفحه نمایش داده می شود. - -```python -app.add_page(index, title="DALL-E") -``` - -با افزودن صفحات بیشتر می توانید یک برنامه چند صفحه ای ایجاد کنید. - -## 📑 Resources - منابع - -
    - -📑 [اسناد](https://reflex.dev/docs/getting-started/introduction)   |   🗞️ [وبلاگ](https://reflex.dev/blog)   |   📱 [کتابخانه جزء](https://reflex.dev/docs/library)   |   🖼️ [قالب ها](https://reflex.dev/templates/)   |   🛸 [استقرار](https://reflex.dev/docs/hosting/deploy-quick-start)   - -
    - -## ✅ Status - وضعیت - -رفلکس(reflex) در دسامبر 2022 با نام Pynecone راه اندازی شد. - -از سال 2025، [Reflex Cloud](https://cloud.reflex.dev) برای فراهم کردن بهترین تجربه میزبانی برای برنامه های Reflex راه‌اندازی شده است. ما به توسعه آن ادامه خواهیم داد و ویژگی‌های بیشتری را پیاده‌سازی خواهیم کرد. - -رفلکس(reflex) هر هفته نسخه ها و ویژگی های جدیدی دارد! مطمئن شوید که :star: ستاره و :eyes: این مخزن را تماشا کنید تا به روز بمانید. - -## Contributing - مشارکت کردن - -ما از مشارکت در هر اندازه استقبال می کنیم! در زیر چند راه خوب برای شروع در انجمن رفلکس آورده شده است. - -- **به Discord ما بپیوندید**: [Discord](https://discord.gg/T5WSbC2YtQ) ما بهترین مکان برای دریافت کمک در مورد پروژه Reflex و بحث در مورد اینکه چگونه می توانید کمک کنید است. -- **بحث های GitHub**: راهی عالی برای صحبت در مورد ویژگی هایی که می خواهید اضافه کنید یا چیزهایی که گیج کننده هستند/نیاز به توضیح دارند. -- **قسمت مشکلات GitHub**: [قسمت مشکلات](https://github.com/reflex-dev/reflex/issues) یک راه عالی برای گزارش اشکال هستند. علاوه بر این، می توانید یک مشکل موجود را حل کنید و یک PR(pull request) ارسال کنید. - -ما فعالانه به دنبال مشارکت کنندگان هستیم، فارغ از سطح مهارت یا تجربه شما. برای مشارکت [CONTRIBUTING.md](https://github.com/reflex-dev/reflex/blob/main/CONTRIBUTING.md) را بررسی کنید. - -## All Thanks To Our Contributors - با تشکر از همکاران ما: - - - - - -## License - مجوز - -رفلکس متن باز و تحت مجوز [Apache License 2.0](/LICENSE) است. diff --git a/docs/pt/pt_br/README.md b/docs/pt/pt_br/README.md deleted file mode 100644 index 1c970df9884..00000000000 --- a/docs/pt/pt_br/README.md +++ /dev/null @@ -1,251 +0,0 @@ -
    -Reflex Logo -
    - -### **✨ Web apps customizáveis, performáticos, em Python puro. Faça deploy em segundos. ✨** - -[![Versão PyPI](https://badge.fury.io/py/reflex.svg)](https://badge.fury.io/py/reflex) -![versões](https://img.shields.io/pypi/pyversions/reflex.svg) -[![Documentação](https://img.shields.io/badge/Documentation%20-Introduction%20-%20%23007ec6)](https://reflex.dev/docs/getting-started/introduction) -[![PyPI Downloads](https://static.pepy.tech/badge/reflex)](https://pepy.tech/projects/reflex) -[![Discord](https://img.shields.io/discord/1029853095527727165?color=%237289da&label=Discord)](https://discord.gg/T5WSbC2YtQ) - -
    - ---- - -[English](https://github.com/reflex-dev/reflex/blob/main/README.md) | [简体中文](https://github.com/reflex-dev/reflex/blob/main/docs/zh/zh_cn/README.md) | [繁體中文](https://github.com/reflex-dev/reflex/blob/main/docs/zh/zh_tw/README.md) | [Türkçe](https://github.com/reflex-dev/reflex/blob/main/docs/tr/README.md) | [हिंदी](https://github.com/reflex-dev/reflex/blob/main/docs/in/README.md) | [Português (Brasil)](https://github.com/reflex-dev/reflex/blob/main/docs/pt/pt_br/README.md) | [Italiano](https://github.com/reflex-dev/reflex/blob/main/docs/it/README.md) | [Español](https://github.com/reflex-dev/reflex/blob/main/docs/es/README.md) | [한국어](https://github.com/reflex-dev/reflex/blob/main/docs/kr/README.md) | [日本語](https://github.com/reflex-dev/reflex/blob/main/docs/ja/README.md) | [Deutsch](https://github.com/reflex-dev/reflex/blob/main/docs/de/README.md) | [Persian (پارسی)](https://github.com/reflex-dev/reflex/blob/main/docs/pe/README.md) | [Tiếng Việt](https://github.com/reflex-dev/reflex/blob/main/docs/vi/README.md) - ---- - -# Reflex - -Reflex é uma biblioteca para construir aplicações web full-stack em Python puro. - -Principais características: - -- **Python Puro** - Escreva o frontend e o backend da sua aplicação inteiramente em Python, sem necessidade de aprender Javascript. -- **Flexibilidade Total** - O Reflex é fácil de começar a usar, mas também pode escalar para aplicações complexas. -- **Deploy Instantâneo** - Após a construção, faça o deploy da sua aplicação com um [único comando](https://reflex.dev/docs/hosting/deploy-quick-start/) ou hospede-a em seu próprio servidor. - -Veja nossa [página de arquitetura](https://reflex.dev/blog/2024-03-21-reflex-architecture/#the-reflex-architecture) para aprender como o Reflex funciona internamente. - -## ⚙️ Instalação - -Abra um terminal e execute (Requer Python 3.10+): - -```bash -pip install reflex -``` - -## 🥳 Crie o seu primeiro app - -Instalar `reflex` também instala a ferramenta de linha de comando `reflex`. - -Crie um novo projeto para verificar se a instalação foi bem sucedida. (Mude `nome_do_meu_app` com o nome do seu projeto): - -```bash -mkdir nome_do_meu_app -cd nome_do_meu_app -reflex init -``` - -Este comando inicializa um app base no seu novo diretório. - -Você pode executar este app em modo desenvolvimento: - -```bash -reflex run -``` - -Você deve conseguir verificar seu app sendo executado em http://localhost:3000. - -Agora, você pode modificar o código fonte em `nome_do_meu_app/nome_do_meu_app.py`. O Reflex apresenta recarregamento rápido para que você possa ver suas mudanças instantaneamente quando você salva o seu código. - -## 🫧 Exemplo de App - -Veja o seguinte exemplo: criar uma interface de criação de imagens por meio do [DALL·E](https://platform.openai.com/docs/guides/images/image-generation?context=node). Para fins de simplicidade, vamos apenas chamar a [API da OpenAI](https://platform.openai.com/docs/api-reference/authentication), mas você pode substituir esta solução por um modelo de ML executado localmente. - -  - -
    -Um encapsulador frontend para o DALL-E, mostrado no processo de criação de uma imagem. -
    - -  - -Aqui está o código completo para criar este projeto. Isso tudo foi feito apenas em um arquivo Python! - -```python -import reflex as rx -import openai - -openai_client = openai.OpenAI() - - -class State(rx.State): - """Estado da aplicação.""" - - prompt = "" - image_url = "" - processing = False - complete = False - - def get_image(self): - """Obtenção da imagem a partir do prompt.""" - if self.prompt == "": - return rx.window_alert("Prompt Empty") - - self.processing, self.complete = True, False - yield - response = openai_client.images.generate( - prompt=self.prompt, n=1, size="1024x1024" - ) - self.image_url = response.data[0].url - self.processing, self.complete = False, True - - -def index(): - return rx.center( - rx.vstack( - rx.heading("DALL-E", font_size="1.5em"), - rx.input( - placeholder="Enter a prompt..", - on_blur=State.set_prompt, - width="25em", - ), - rx.button( - "Generate Image", - on_click=State.get_image, - width="25em", - loading=State.processing - ), - rx.cond( - State.complete, - rx.image(src=State.image_url, width="20em"), - ), - align="center", - ), - width="100%", - height="100vh", - ) - -# Adição do estado e da página no app. -app = rx.App() -app.add_page(index, title="Reflex:DALL-E") -``` - -## Vamos por partes. - -
    -Explicando as diferenças entre as partes de backend e frontend do app DALL-E. -
    - -### **Reflex UI** - -Vamos começar com a UI (Interface de Usuário) - -```python -def index(): - return rx.center( - ... - ) -``` - -Esta função `index` define o frontend do app. - -Usamos diferentes componentes, como `center`, `vstack`, `input` e `button`, para construir o frontend. Componentes podem ser aninhados um no do outro -para criar layouts mais complexos. E você pode usar argumentos de chave-valor para estilizá-los com todo o poder do CSS. - -O Reflex vem com [60+ componentes nativos](https://reflex.dev/docs/library) para te ajudar a começar. Estamos adicionando ativamente mais componentes, e é fácil [criar seus próprios componentes](https://reflex.dev/docs/wrapping-react/overview/). - -### **Estado** - -O Reflex representa a sua UI como uma função do seu estado. - -```python -class State(rx.State): - """Estado da aplicação.""" - prompt = "" - image_url = "" - processing = False - complete = False - -``` - -O estado define todas as variáveis (chamadas de vars) em um app que podem mudar e as funções que as alteram. - -Aqui, o estado é composto por um `prompt` e uma `image_url`. Há também os booleanos `processing` e `complete` para indicar quando desabilitar o botão (durante a geração da imagem) e quando mostrar a imagem resultante. - -### **Handlers de Eventos** - -```python -def get_image(self): - """Obtenção da imagem a partir do prompt.""" - if self.prompt == "": - return rx.window_alert("Prompt Empty") - - self.processing, self.complete = True, False - yield - response = openai_client.images.generate( - prompt=self.prompt, n=1, size="1024x1024" - ) - self.image_url = response.data[0].url - self.processing, self.complete = False, True -``` - -Dentro do estado, são definidas funções chamadas de Handlers de Eventos, que podem mudar as variáveis do estado. Handlers de Eventos são a forma com a qual podemos modificar o estado dentro do Reflex. Eles podem ser chamados como resposta a uma ação do usuário, como o clique de um botão ou a escrita em uma caixa de texto. Estas ações são chamadas de eventos. - -Nosso app DALL-E possui um Handler de Evento chamado `get_image`, que obtêm a imagem da API da OpenAI. Usar `yield` no meio de um Handler de Evento causa a atualização da UI do seu app. Caso contrário, a UI seria atualizada no fim da execução de um Handler de Evento. - -### **Rotas** - -Finalmente, definimos nosso app. - -```python -app = rx.App() -``` - -Adicionamos uma página na raíz do app, apontando para o componente index. Também adicionamos um título que irá aparecer na visualização da página/aba do navegador. - -```python -app.add_page(index, title="DALL-E") -``` - -Você pode criar um app com múltiplas páginas adicionando mais páginas. - -## 📑 Recursos - -
    - -📑 [Docs](https://reflex.dev/docs/getting-started/introduction)   |   🗞️ [Blog](https://reflex.dev/blog)   |   📱 [Biblioteca de Componentes](https://reflex.dev/docs/library)   |   🖼️ [Templates](https://reflex.dev/templates/)   |   🛸 [Deployment](https://reflex.dev/docs/hosting/deploy-quick-start)   - -
    - -## ✅ Status - -O Reflex foi lançado em Dezembro de 2022 com o nome Pynecone. - -A partir de 2025, o [Reflex Cloud](https://cloud.reflex.dev) foi lançado para fornecer a melhor experiência de hospedagem para apps Reflex. Continuaremos a desenvolvê-lo e implementar mais recursos. - -O Reflex tem novas versões e recursos chegando toda semana! Certifique-se de marcar com :star: estrela e :eyes: observar este repositório para se manter atualizado. - -## Contribuições - -Nós somos abertos a contribuições de qualquer tamanho! Abaixo, seguem algumas boas formas de começar a contribuir para a comunidade do Reflex. - -- **Entre no nosso Discord**: Nosso [Discord](https://discord.gg/T5WSbC2YtQ) é o melhor lugar para conseguir ajuda no seu projeto Reflex e para discutir como você pode contribuir. -- **Discussões no GitHub**: Uma boa forma de conversar sobre funcionalidades que você gostaria de ver ou coisas que ainda estão confusas/exigem ajuda. -- **Issues no GitHub**: [Issues](https://github.com/reflex-dev/reflex/issues) são uma excelente forma de reportar bugs. Além disso, você pode tentar resolver alguma issue existente e enviar um PR. - -Estamos ativamente buscando novos contribuidores, não importa o seu nível de habilidade ou experiência. Para contribuir, confira [CONTRIBUTING.md](https://github.com/reflex-dev/reflex/blob/main/CONTRIBUTING.md). - -## Todo Agradecimento aos Nossos Contribuidores: - - - - - -## Licença - -O Reflex é de código aberto e licenciado sob a [Apache License 2.0](/LICENSE). diff --git a/docs/recipes/auth/login_form.md b/docs/recipes/auth/login_form.md new file mode 100644 index 00000000000..9189800e4ad --- /dev/null +++ b/docs/recipes/auth/login_form.md @@ -0,0 +1,243 @@ +```python exec +import reflex as rx +from pcweb.constants import REFLEX_ASSETS_CDN +``` + +# Login Form + +The login form is a common component in web applications. It allows users to authenticate themselves and access their accounts. This recipe provides examples of login forms with different elements, such as third-party authentication providers. + +## Default + +```python demo exec toggle +def login_default() -> rx.Component: + return rx.card( + rx.vstack( + rx.center( + rx.image(src=f"{REFLEX_ASSETS_CDN}other/logo.jpg", width="2.5em", height="auto", border_radius="25%"), + rx.heading("Sign in to your account", size="6", as_="h2", text_align="center", width="100%"), + direction="column", + spacing="5", + width="100%" + ), + rx.vstack( + rx.text("Email address", size="3", weight="medium", text_align="left", width="100%"), + rx.input(placeholder="user@reflex.dev", type="email", size="3", width="100%"), + justify="start", + spacing="2", + width="100%" + ), + rx.vstack( + rx.hstack( + rx.text("Password", size="3", weight="medium"), + rx.link("Forgot password?", href="#", size="3"), + justify="between", + width="100%" + ), + rx.input(placeholder="Enter your password", type="password", size="3", width="100%"), + spacing="2", + width="100%" + ), + rx.button("Sign in", size="3", width="100%"), + rx.center( + rx.text("New here?", size="3"), + rx.link("Sign up", href="#", size="3"), + opacity="0.8", + spacing="2", + direction="row" + ), + spacing="6", + width="100%" + ), + size="4", + max_width="28em", + width="100%" + ) +``` + +## Icons + +```python demo exec toggle +def login_default_icons() -> rx.Component: + return rx.card( + rx.vstack( + rx.center( + rx.image(src=f"{REFLEX_ASSETS_CDN}other/logo.jpg", width="2.5em", height="auto", border_radius="25%"), + rx.heading("Sign in to your account", size="6", as_="h2", text_align="center", width="100%"), + direction="column", + spacing="5", + width="100%" + ), + rx.vstack( + rx.text("Email address", size="3", weight="medium", text_align="left", width="100%"), + rx.input(rx.input.slot(rx.icon("user")), placeholder="user@reflex.dev", type="email", size="3", width="100%"), + spacing="2", + width="100%" + ), + rx.vstack( + rx.hstack( + rx.text("Password", size="3", weight="medium"), + rx.link("Forgot password?", href="#", size="3"), + justify="between", + width="100%" + ), + rx.input(rx.input.slot(rx.icon("lock")), placeholder="Enter your password", type="password", size="3", width="100%"), + spacing="2", + width="100%" + ), + rx.button("Sign in", size="3", width="100%"), + rx.center( + rx.text("New here?", size="3"), + rx.link("Sign up", href="#", size="3"), + opacity="0.8", + spacing="2", + direction="row", + width="100%" + ), + spacing="6", + width="100%" + ), + max_width="28em", + size="4", + width="100%" + ) +``` + +## Third-party auth + +```python demo exec toggle +def login_single_thirdparty() -> rx.Component: + return rx.card( + rx.vstack( + rx.flex( + rx.image(src=f"{REFLEX_ASSETS_CDN}other/logo.jpg", width="2.5em", height="auto", border_radius="25%"), + rx.heading("Sign in to your account", size="6", as_="h2", text_align="left", width="100%"), + rx.hstack( + rx.text("New here?", size="3", text_align="left"), + rx.link("Sign up", href="#", size="3"), + spacing="2", + opacity="0.8", + width="100%" + ), + direction="column", + justify="start", + spacing="4", + width="100%" + ), + rx.vstack( + rx.text("Email address", size="3", weight="medium", text_align="left", width="100%"), + rx.input(rx.input.slot(rx.icon("user")), placeholder="user@reflex.dev", type="email", size="3", width="100%"), + justify="start", + spacing="2", + width="100%" + ), + rx.vstack( + rx.hstack( + rx.text("Password", size="3", weight="medium"), + rx.link("Forgot password?", href="#", size="3"), + justify="between", + width="100%" + ), + rx.input(rx.input.slot(rx.icon("lock")), placeholder="Enter your password", type="password", size="3", width="100%"), + spacing="2", + width="100%" + ), + rx.button("Sign in", size="3", width="100%"), + rx.hstack( + rx.divider(margin="0"), + rx.text("Or continue with", white_space="nowrap", weight="medium"), + rx.divider(margin="0"), + align="center", + width="100%" + ), + rx.button( + rx.icon(tag="github"), + "Sign in with Github", + variant="outline", + size="3", + width="100%" + ), + spacing="6", + width="100%" + ), + size="4", + max_width="28em", + width="100%" + ) +``` + +## Multiple third-party auth + +```python demo exec toggle +def login_multiple_thirdparty() -> rx.Component: + return rx.card( + rx.vstack( + rx.flex( + rx.image(src=f"{REFLEX_ASSETS_CDN}other/logo.jpg", width="2.5em", height="auto", border_radius="25%"), + rx.heading("Sign in to your account", size="6", as_="h2", width="100%"), + rx.hstack( + rx.text("New here?", size="3", text_align="left"), + rx.link("Sign up", href="#", size="3"), + spacing="2", + opacity="0.8", + width="100%" + ), + justify="start", + direction="column", + spacing="4", + width="100%" + ), + rx.vstack( + rx.text("Email address", size="3", weight="medium", text_align="left", width="100%"), + rx.input(rx.input.slot(rx.icon("user")), placeholder="user@reflex.dev", type="email", size="3", width="100%"), + spacing="2", + justify="start", + width="100%" + ), + rx.vstack( + rx.hstack( + rx.text("Password", size="3", weight="medium"), + rx.link("Forgot password?", href="#", size="3"), + justify="between", + width="100%" + ), + rx.input(rx.input.slot(rx.icon("lock")), placeholder="Enter your password", type="password", size="3", width="100%"), + spacing="2", + width="100%" + ), + rx.button("Sign in", size="3", width="100%"), + rx.hstack( + rx.divider(margin="0"), + rx.text("Or continue with", white_space="nowrap", weight="medium"), + rx.divider(margin="0"), + align="center", + width="100%" + ), + rx.center( + rx.icon_button( + rx.icon(tag="github"), + variant="soft", + size="3" + ), + rx.icon_button( + rx.icon(tag="facebook"), + variant="soft", + size="3" + ), + rx.icon_button( + rx.icon(tag="twitter"), + variant="soft", + size="3" + ), + spacing="4", + direction="row", + width="100%" + ), + spacing="6", + width="100%" + ), + size="4", + max_width="28em", + width="100%" + ) +``` diff --git a/docs/recipes/auth/signup_form.md b/docs/recipes/auth/signup_form.md new file mode 100644 index 00000000000..c45ae731a7e --- /dev/null +++ b/docs/recipes/auth/signup_form.md @@ -0,0 +1,260 @@ +```python exec +import reflex as rx +from pcweb.constants import REFLEX_ASSETS_CDN +``` + +# Sign up Form + +The sign up form is a common component in web applications. It allows users to create an account and access the application's features. This page provides a few examples of sign up forms that you can use in your application. +## Default + +```python demo exec toggle +def signup_default() -> rx.Component: + return rx.card( + rx.vstack( + rx.center( + rx.image(src=f"{REFLEX_ASSETS_CDN}other/logo.jpg", width="2.5em", height="auto", border_radius="25%"), + rx.heading("Create an account", size="6", as_="h2", text_align="center", width="100%"), + direction="column", + spacing="5", + width="100%" + ), + rx.vstack( + rx.text("Email address", size="3", weight="medium", text_align="left", width="100%"), + rx.input(placeholder="user@reflex.dev", type="email", size="3", width="100%"), + justify="start", + spacing="2", + width="100%" + ), + rx.vstack( + rx.text("Password", size="3", weight="medium", text_align="left", width="100%"), + rx.input(placeholder="Enter your password", type="password", size="3", width="100%"), + justify="start", + spacing="2", + width="100%" + ), + rx.box( + rx.checkbox( + "Agree to Terms and Conditions", + default_checked=True, + spacing="2" + ), + width="100%" + ), + rx.button("Register", size="3", width="100%"), + rx.center( + rx.text("Already registered?", size="3"), + rx.link("Sign in", href="#", size="3"), + opacity="0.8", + spacing="2", + direction="row" + ), + spacing="6", + width="100%" + ), + size="4", + max_width="28em", + width="100%" + ) +``` + +## Icons + +```python demo exec toggle +def signup_default_icons() -> rx.Component: + return rx.card( + rx.vstack( + rx.center( + rx.image(src=f"{REFLEX_ASSETS_CDN}other/logo.jpg", width="2.5em", height="auto", border_radius="25%"), + rx.heading("Create an account", size="6", as_="h2", text_align="center", width="100%"), + direction="column", + spacing="5", + width="100%" + ), + rx.vstack( + rx.text("Email address", size="3", weight="medium", text_align="left", width="100%"), + rx.input(rx.input.slot(rx.icon("user")), placeholder="user@reflex.dev", type="email", size="3", width="100%"), + justify="start", + spacing="2", + width="100%" + ), + rx.vstack( + rx.text("Password", size="3", weight="medium", text_align="left", width="100%"), + rx.input(rx.input.slot(rx.icon("lock")), placeholder="Enter your password", type="password", size="3", width="100%"), + justify="start", + spacing="2", + width="100%" + ), + rx.box( + rx.checkbox( + "Agree to Terms and Conditions", + default_checked=True, + spacing="2" + ), + width="100%" + ), + rx.button("Register", size="3", width="100%"), + rx.center( + rx.text("Already registered?", size="3"), + rx.link("Sign in", href="#", size="3"), + opacity="0.8", + spacing="2", + direction="row", + width="100%" + ), + spacing="6", + width="100%" + ), + max_width="28em", + size="4", + width="100%" + ) +``` + +## Third-party auth + + +```python demo exec toggle +def signup_single_thirdparty() -> rx.Component: + return rx.card( + rx.vstack( + rx.flex( + rx.image(src=f"{REFLEX_ASSETS_CDN}other/logo.jpg", width="2.5em", height="auto", border_radius="25%"), + rx.heading("Create an account", size="6", as_="h2", text_align="left", width="100%"), + rx.hstack( + rx.text("Already registered?", size="3", text_align="left"), + rx.link("Sign in", href="#", size="3"), + spacing="2", + opacity="0.8", + width="100%" + ), + direction="column", + justify="start", + spacing="4", + width="100%" + ), + rx.vstack( + rx.text("Email address", size="3", weight="medium", text_align="left", width="100%"), + rx.input(rx.input.slot(rx.icon("user")), placeholder="user@reflex.dev", type="email", size="3", width="100%"), + justify="start", + spacing="2", + width="100%" + ), + rx.vstack( + rx.text("Password", size="3", weight="medium", text_align="left", width="100%"), + rx.input(rx.input.slot(rx.icon("lock")), placeholder="Enter your password", type="password", size="3", width="100%"), + justify="start", + spacing="2", + width="100%" + ), + rx.box( + rx.checkbox( + "Agree to Terms and Conditions", + default_checked=True, + spacing="2" + ), + width="100%" + ), + rx.button("Register", size="3", width="100%"), + rx.hstack( + rx.divider(margin="0"), + rx.text("Or continue with", white_space="nowrap", weight="medium"), + rx.divider(margin="0"), + align="center", + width="100%" + ), + rx.button( + rx.icon(tag="github"), + "Sign in with Github", + variant="outline", + size="3", + width="100%" + ), + spacing="6", + width="100%" + ), + size="4", + max_width="28em", + width="100%" + ) +``` + +## Multiple third-party auth + +```python demo exec toggle +def signup_multiple_thirdparty() -> rx.Component: + return rx.card( + rx.vstack( + rx.flex( + rx.image(src=f"{REFLEX_ASSETS_CDN}other/logo.jpg", width="2.5em", height="auto", border_radius="25%"), + rx.heading("Create an account", size="6", as_="h2", width="100%"), + rx.hstack( + rx.text("Already registered?", size="3", text_align="left"), + rx.link("Sign in", href="#", size="3"), + spacing="2", + opacity="0.8", + width="100%" + ), + justify="start", + direction="column", + spacing="4", + width="100%" + ), + rx.vstack( + rx.text("Email address", size="3", weight="medium", text_align="left", width="100%"), + rx.input(rx.input.slot(rx.icon("user")), placeholder="user@reflex.dev", type="email", size="3", width="100%"), + justify="start", + spacing="2", + width="100%" + ), + rx.vstack( + rx.text("Password", size="3", weight="medium", text_align="left", width="100%"), + rx.input(rx.input.slot(rx.icon("lock")), placeholder="Enter your password", type="password", size="3", width="100%"), + justify="start", + spacing="2", + width="100%" + ), + rx.box( + rx.checkbox( + "Agree to Terms and Conditions", + default_checked=True, + spacing="2" + ), + width="100%" + ), + rx.button("Register", size="3", width="100%"), + rx.hstack( + rx.divider(margin="0"), + rx.text("Or continue with", white_space="nowrap", weight="medium"), + rx.divider(margin="0"), + align="center", + width="100%" + ), + rx.center( + rx.icon_button( + rx.icon(tag="github"), + variant="soft", + size="3" + ), + rx.icon_button( + rx.icon(tag="facebook"), + variant="soft", + size="3" + ), + rx.icon_button( + rx.icon(tag="twitter"), + variant="soft", + size="3" + ), + spacing="4", + direction="row", + width="100%" + ), + spacing="6", + width="100%" + ), + size="4", + max_width="28em", + width="100%" + ) +``` diff --git a/docs/recipes/content/forms.md b/docs/recipes/content/forms.md new file mode 100644 index 00000000000..57b6162aaf6 --- /dev/null +++ b/docs/recipes/content/forms.md @@ -0,0 +1,173 @@ +```python exec +import reflex as rx +from pcweb.pages.docs import library +``` + +## Forms + +Forms are a common way to gather information from users. Below are some examples. + +For more details, see the [form docs page]({library.forms.form.path}). + +## Event creation + +```python demo exec toggle +def form_field(label: str, placeholder: str, type: str, name: str) -> rx.Component: + return rx.form.field( + rx.flex( + rx.form.label(label), + rx.form.control( + rx.input( + placeholder=placeholder, + type=type + ), + as_child=True, + ), + direction="column", + spacing="1", + ), + name=name, + width="100%" + ) + +def event_form() -> rx.Component: + return rx.card( + rx.flex( + rx.hstack( + rx.badge( + rx.icon(tag="calendar-plus", size=32), + color_scheme="mint", + radius="full", + padding="0.65rem" + ), + rx.vstack( + rx.heading("Create an event", size="4", weight="bold"), + rx.text("Fill the form to create a custom event", size="2"), + spacing="1", + height="100%", + align_items="start" + ), + height="100%", + spacing="4", + align_items="center", + width="100%", + ), + rx.form.root( + rx.flex( + form_field("Event Name", "Event Name", + "text", "event_name"), + rx.flex( + form_field("Date", "", "date", "event_date"), + form_field("Time", "", "time", "event_time"), + spacing="3", + flex_direction="row", + ), + form_field("Description", "Optional", "text", "description"), + direction="column", + spacing="2", + ), + rx.form.submit( + rx.button("Create"), + as_child=True, + width="100%", + ), + on_submit=lambda form_data: rx.window_alert(form_data.to_string()), + reset_on_submit=False, + ), + width="100%", + direction="column", + spacing="4", + ), + size="3", + ) +``` + +## Contact + +```python demo exec toggle +def form_field(label: str, placeholder: str, type: str, name: str) -> rx.Component: + return rx.form.field( + rx.flex( + rx.form.label(label), + rx.form.control( + rx.input( + placeholder=placeholder, + type=type + ), + as_child=True, + ), + direction="column", + spacing="1", + ), + name=name, + width="100%" + ) + +def contact_form() -> rx.Component: + return rx.card( + rx.flex( + rx.hstack( + rx.badge( + rx.icon(tag="mail-plus", size=32), + color_scheme="blue", + radius="full", + padding="0.65rem" + ), + rx.vstack( + rx.heading("Send us a message", size="4", weight="bold"), + rx.text("Fill the form to contact us", size="2"), + spacing="1", + height="100%", + ), + height="100%", + spacing="4", + align_items="center", + width="100%", + ), + rx.form.root( + rx.flex( + rx.flex( + form_field("First Name", "First Name", + "text", "first_name"), + form_field("Last Name", "Last Name", + "text", "last_name"), + spacing="3", + flex_direction=["column", "row", "row"], + ), + rx.flex( + form_field("Email", "user@reflex.dev", + "email", "email"), + form_field("Phone", "Phone", "tel", "phone"), + spacing="3", + flex_direction=["column", "row", "row"], + ), + rx.flex( + rx.text("Message", style={ + "font-size": "15px", "font-weight": "500", "line-height": "35px"}), + rx.text_area( + placeholder="Message", + name="message", + resize="vertical", + ), + direction="column", + spacing="1", + ), + rx.form.submit( + rx.button("Submit"), + as_child=True, + ), + direction="column", + spacing="2", + width="100%", + ), + on_submit=lambda form_data: rx.window_alert( + form_data.to_string()), + reset_on_submit=False, + ), + width="100%", + direction="column", + spacing="4", + ), + size="3", + ) +``` \ No newline at end of file diff --git a/docs/recipes/content/grid.md b/docs/recipes/content/grid.md new file mode 100644 index 00000000000..5c629c5f65a --- /dev/null +++ b/docs/recipes/content/grid.md @@ -0,0 +1,52 @@ +```python exec +import reflex as rx +from pcweb.constants import REFLEX_ASSETS_CDN +from pcweb.pages.docs import styling +``` + +# Grid + +A simple responsive grid layout. We specify the number of columns to the `grid_template_columns` property as a list. The grid will automatically adjust the number of columns based on the screen size. + +For details, see the [responsive docs page]({styling.responsive.path}). + +## Cards + +```python demo +rx.grid( + rx.foreach( + rx.Var.range(12), + lambda i: rx.card(f"Card {i + 1}", height="10vh"), + ), + gap="1rem", + grid_template_columns=["1fr", "repeat(2, 1fr)", "repeat(2, 1fr)", "repeat(3, 1fr)", "repeat(4, 1fr)"], + width="100%" +) +``` + +## Inset cards + +```python demo +rx.grid( + rx.foreach( + rx.Var.range(12), + lambda i: rx.card( + rx.inset( + rx.image( + src=f"{REFLEX_ASSETS_CDN}other/reflex_banner.png", + width="100%", + height="auto", + ), + side="top", + pb="current" + ), + rx.text( + f"Card {i + 1}", + ), + ), + ), + gap="1rem", + grid_template_columns=["1fr", "repeat(2, 1fr)", "repeat(2, 1fr)", "repeat(3, 1fr)", "repeat(4, 1fr)"], + width="100%" +) +``` diff --git a/docs/recipes/content/multi_column_row.md b/docs/recipes/content/multi_column_row.md new file mode 100644 index 00000000000..71d6041df3d --- /dev/null +++ b/docs/recipes/content/multi_column_row.md @@ -0,0 +1,64 @@ +```python exec +import reflex as rx +from pcweb.pages.docs import styling +``` + +# Multi-column and row layout + +A simple responsive multi-column and row layout. We specify the number of columns/rows to the `flex_direction` property as a list. The layout will automatically adjust the number of columns/rows based on the screen size. + +For details, see the [responsive docs page]({styling.responsive.path}). + +## Column + +```python demo +rx.flex( + rx.box(bg=rx.color("accent", 3), width="100%", height="100%"), + rx.box(bg=rx.color("accent", 5), width="100%", height="100%"), + rx.box(bg=rx.color("accent", 7), width="100%", height="100%"), + bg=rx.color("accent", 10), + spacing="4", + padding="1em", + flex_direction=["column", "column", "row"], + height="600px", + width="100%", +) +``` + +```python demo +rx.flex( + rx.box(bg=rx.color("accent", 3), width="100%", height="100%"), + rx.box(bg=rx.color("accent", 5), width=["100%", "100%", "50%"], height=["50%", "50%", "100%"]), + rx.box(bg=rx.color("accent", 7), width="100%", height="100%"), + rx.box(bg=rx.color("accent", 9), width=["100%", "100%", "50%"], height=["50%", "50%", "100%"]), + bg=rx.color("accent", 10), + spacing="4", + padding="1em", + flex_direction=["column", "column", "row"], + height="600px", + width="100%", +) +``` + +## Row + +```python demo +rx.flex( + rx.flex( + rx.box(bg=rx.color("accent", 3), width=["100%", "100%", "50%"], height="100%"), + rx.box(bg=rx.color("accent", 5), width=["100%", "100%", "50%"], height="100%"), + width="100%", + height="100%", + spacing="4", + flex_direction=["column", "column", "row"], + ), + rx.box(bg=rx.color("accent", 7), width="100%", height="50%"), + rx.box(bg=rx.color("accent", 9), width="100%", height="50%"), + bg=rx.color("accent", 10), + spacing="4", + padding="1em", + flex_direction="column", + height="600px", + width="100%", +) +``` \ No newline at end of file diff --git a/docs/recipes/content/stats.md b/docs/recipes/content/stats.md new file mode 100644 index 00000000000..676e2e9a132 --- /dev/null +++ b/docs/recipes/content/stats.md @@ -0,0 +1,107 @@ +```python exec +import reflex as rx +``` + +# Stats + +Stats cards are used to display key metrics or data points. They are typically used in dashboards or admin panels. + +## Variant 1 + +```python demo exec toggle +from reflex.components.radix.themes.base import LiteralAccentColor + +def stats(stat_name: str = "Users", value: int = 4200, prev_value: int = 3000, icon: str = "users", badge_color: LiteralAccentColor = "blue") -> rx.Component: + percentage_change = round(((value - prev_value) / prev_value) * 100, 2) if prev_value != 0 else 0 if value == 0 else float('inf') + change = "increase" if value > prev_value else "decrease" + arrow_icon = "trending-up" if value > prev_value else "trending-down" + arrow_color = "grass" if value > prev_value else "tomato" + return rx.card( + rx.vstack( + rx.hstack( + rx.badge( + rx.icon(tag=icon, size=34), + color_scheme=badge_color, + radius="full", + padding="0.7rem" + ), + rx.vstack( + rx.heading(f"{value:,}", size="6", weight="bold"), + rx.text(stat_name, size="4", weight="medium"), + spacing="1", + height="100%", + align_items="start", + width="100%" + ), + height="100%", + spacing="4", + align="center", + width="100%", + ), + rx.hstack( + rx.hstack( + rx.icon(tag=arrow_icon, size=24, color=rx.color(arrow_color, 9)), + rx.text(f"{percentage_change}%", size="3", color=rx.color(arrow_color, 9), weight="medium"), + spacing="2", + align="center", + ), + rx.text(f"{change} from last month", size="2", color=rx.color("gray", 10)), + align="center", + width="100%", + ), + spacing="3", + ), + size="3", + width="100%", + max_width="21rem" + ) +``` + +## Variant 2 + +```python demo exec toggle +from reflex.components.radix.themes.base import LiteralAccentColor + +def stats_2(stat_name: str = "Orders", value: int = 6500, prev_value: int = 12000, icon: str = "shopping-cart", icon_color: LiteralAccentColor = "pink") -> rx.Component: + percentage_change = round(((value - prev_value) / prev_value) * 100, 2) if prev_value != 0 else 0 if value == 0 else float('inf') + arrow_icon = "trending-up" if value > prev_value else "trending-down" + arrow_color = "grass" if value > prev_value else "tomato" + return rx.card( + rx.hstack( + rx.vstack( + rx.hstack( + rx.hstack( + rx.icon(tag=icon, size=22, color=rx.color(icon_color, 11)), + rx.text(stat_name, size="4", weight="medium", color=rx.color("gray", 11)), + spacing="2", + align="center", + ), + rx.badge( + rx.icon(tag=arrow_icon, color=rx.color(arrow_color, 9)), + rx.text(f"{percentage_change}%", size="2", color=rx.color(arrow_color, 9), weight="medium"), + color_scheme=arrow_color, + radius="large", + align_items="center", + ), + justify="between", + width="100%", + ), + rx.hstack( + rx.heading(f"{value:,}", size="7", weight="bold"), + rx.text(f"from {prev_value:,}", size="3", color=rx.color("gray", 10)), + spacing="2", + align_items="end", + ), + align_items="start", + justify="between", + width="100%", + ), + align_items="start", + width="100%", + justify="between", + ), + size="3", + width="100%", + max_width="21rem", + ) +``` \ No newline at end of file diff --git a/docs/recipes/content/top_banner.md b/docs/recipes/content/top_banner.md new file mode 100644 index 00000000000..8dd0797048d --- /dev/null +++ b/docs/recipes/content/top_banner.md @@ -0,0 +1,271 @@ +```python exec +import reflex as rx +from pcweb.constants import REFLEX_ASSETS_CDN +``` + +# Top Banner + +Top banners are used to highlight important information or features at the top of a page. They are typically designed to grab the user's attention and can be used for announcements, navigation, or key messages. + +## Basic + +```python demo exec toggle +class TopBannerBasic(rx.ComponentState): + hide: bool = False + + @rx.event + def toggle(self): + self.hide = not self.hide + + @classmethod + def get_component(cls, **props): + return rx.cond( + ~cls.hide, + rx.hstack( + rx.flex( + rx.badge( + rx.icon("megaphone", size=18), + padding="0.30rem", + radius="full", + ), + rx.text( + "ReflexCon 2024 - ", + rx.link( + "Join us at the event!", + href="#", + underline="always", + display="inline", + underline_offset="2px", + ), + weight="medium", + ), + align="center", + margin="auto", + spacing="3", + ), + rx.icon( + "x", + cursor="pointer", + justify="end", + flex_shrink=0, + on_click=cls.toggle, + ), + wrap="nowrap", + # position="fixed", + justify="between", + width="100%", + # top="0", + align="center", + left="0", + # z_index="50", + padding="1rem", + background=rx.color("accent", 4), + **props, + ), + # Remove this in production + rx.icon_button( + rx.icon("eye"), + cursor="pointer", + on_click=cls.toggle, + ), + ) + +top_banner_basic = TopBannerBasic.create +``` + +## Sign up + +```python demo exec toggle +class TopBannerSignup(rx.ComponentState): + hide: bool = False + + @rx.event + def toggle(self): + self.hide = not self.hide + + @classmethod + def get_component(cls, **props): + return rx.cond( + ~cls.hide, + rx.flex( + rx.image( + src=f"{REFLEX_ASSETS_CDN}other/logo.jpg", + width="2em", + height="auto", + border_radius="25%", + ), + rx.text( + "Web apps in pure Python. Deploy with a single command.", + weight="medium", + ), + rx.flex( + rx.button( + "Sign up", + cursor="pointer", + radius="large", + ), + rx.icon( + "x", + cursor="pointer", + justify="end", + flex_shrink=0, + on_click=cls.toggle, + ), + spacing="4", + align="center", + ), + wrap="nowrap", + # position="fixed", + flex_direction=["column", "column", "row"], + justify_content=["start", "space-between"], + width="100%", + # top="0", + spacing="2", + align_items=["start", "start", "center"], + left="0", + # z_index="50", + padding="1rem", + background=rx.color("accent", 4), + **props, + ), + # Remove this in production + rx.icon_button( + rx.icon("eye"), + cursor="pointer", + on_click=cls.toggle, + ), + ) + +top_banner_signup = TopBannerSignup.create +``` + +## Gradient + +```python demo exec toggle +class TopBannerGradient(rx.ComponentState): + hide: bool = False + + @rx.event + def toggle(self): + self.hide = not self.hide + + @classmethod + def get_component(cls, **props): + return rx.cond( + ~cls.hide, + rx.flex( + rx.text( + "The new Reflex version is now available! ", + rx.link( + "Read the release notes", + href="#", + underline="always", + display="inline", + underline_offset="2px", + ), + align_items=["start", "center"], + margin="auto", + spacing="3", + weight="medium", + ), + rx.icon( + "x", + cursor="pointer", + justify="end", + flex_shrink=0, + on_click=cls.toggle, + ), + wrap="nowrap", + # position="fixed", + justify="between", + width="100%", + # top="0", + align="center", + left="0", + # z_index="50", + padding="1rem", + background=f"linear-gradient(99deg, {rx.color('blue', 4)}, {rx.color('pink', 3)}, {rx.color('mauve', 3)})", + **props, + ), + # Remove this in production + rx.icon_button( + rx.icon("eye"), + cursor="pointer", + on_click=cls.toggle, + ), + ) + +top_banner_gradient = TopBannerGradient.create +``` + +## Newsletter + +```python demo exec toggle +class TopBannerNewsletter(rx.ComponentState): + hide: bool = False + + @rx.event + def toggle(self): + self.hide = not self.hide + + @classmethod + def get_component(cls, **props): + return rx.cond( + ~cls.hide, + rx.flex( + rx.text( + "Join our newsletter", + text_wrap="nowrap", + weight="medium", + ), + rx.input( + rx.input.slot(rx.icon("mail")), + rx.input.slot( + rx.icon_button( + rx.icon( + "arrow-right", + padding="0.15em", + ), + cursor="pointer", + radius="large", + size="2", + justify="end", + ), + padding_right="0", + ), + placeholder="Your email address", + type="email", + size="2", + radius="large", + ), + rx.icon( + "x", + cursor="pointer", + justify="end", + flex_shrink=0, + on_click=cls.toggle, + ), + wrap="nowrap", + # position="fixed", + flex_direction=["column", "row", "row"], + justify_content=["start", "space-between"], + width="100%", + # top="0", + spacing="2", + align_items=["start", "center", "center"], + left="0", + # z_index="50", + padding="1rem", + background=rx.color("accent", 4), + **props, + ), + # Remove this in production + rx.icon_button( + rx.icon("eye"), + cursor="pointer", + on_click=cls.toggle, + ), + ) + +top_banner_newsletter = TopBannerNewsletter.create +``` \ No newline at end of file diff --git a/docs/recipes/layout/footer.md b/docs/recipes/layout/footer.md new file mode 100644 index 00000000000..d13e12bae91 --- /dev/null +++ b/docs/recipes/layout/footer.md @@ -0,0 +1,281 @@ +```python exec +import reflex as rx +from pcweb.constants import REFLEX_ASSETS_CDN +``` + +# Footer Bar + +A footer bar is a common UI element located at the bottom of a webpage. It typically contains information about the website, such as contact details and links to other pages or sections of the site. + +## Basic + +```python demo exec toggle +def footer_item(text: str, href: str) -> rx.Component: + return rx.link(rx.text(text, size="3"), href=href) + +def footer_items_1() -> rx.Component: + return rx.flex( + rx.heading("PRODUCTS", size="4", weight="bold", as_="h3"), + footer_item("Web Design", "/#"), + footer_item("Web Development", "/#"), + footer_item("E-commerce", "/#"), + footer_item("Content Management", "/#"), + footer_item("Mobile Apps", "/#"), + spacing="4", + text_align=["center", "center", "start"], + flex_direction="column" + ) + +def footer_items_2() -> rx.Component: + return rx.flex( + rx.heading("RESOURCES", size="4", weight="bold", as_="h3"), + footer_item("Blog", "/#"), + footer_item("Case Studies", "/#"), + footer_item("Whitepapers", "/#"), + footer_item("Webinars", "/#"), + footer_item("E-books", "/#"), + spacing="4", + text_align=["center", "center", "start"], + flex_direction="column" + ) + +def social_link(icon: str, href: str) -> rx.Component: + return rx.link(rx.icon(icon), href=href) + +def socials() -> rx.Component: + return rx.flex( + social_link("instagram", "/#"), + social_link("twitter", "/#"), + social_link("facebook", "/#"), + social_link("linkedin", "/#"), + spacing="3", + justify="end", + width="100%" + ) + +def footer() -> rx.Component: + return rx.el.footer( + rx.vstack( + rx.flex( + rx.vstack( + rx.hstack( + rx.image(src=f"{REFLEX_ASSETS_CDN}other/logo.jpg", width="2.25em", height="auto", border_radius="25%"), + rx.heading("Reflex", size="7", weight="bold"), + align_items="center" + ), + rx.text("© 2024 Reflex, Inc", size="3", white_space="nowrap", weight="medium"), + spacing="4", + align_items=["center", "center", "start"] + ), + footer_items_1(), + footer_items_2(), + justify="between", + spacing="6", + flex_direction=["column", "column", "row"], + width="100%" + ), + rx.divider(), + rx.hstack( + rx.hstack( + footer_item("Privacy Policy", "/#"), + footer_item("Terms of Service", "/#"), + spacing="4", + align="center", + width="100%" + ), + socials(), + justify="between", + width="100%" + ), + spacing="5", + width="100%" + ), + width="100%" + ) +``` + +## Newsletter form + +```python demo exec toggle +def footer_item(text: str, href: str) -> rx.Component: + return rx.link(rx.text(text, size="3"), href=href) + +def footer_items_1() -> rx.Component: + return rx.flex( + rx.heading("PRODUCTS", size="4", weight="bold", as_="h3"), + footer_item("Web Design", "/#"), + footer_item("Web Development", "/#"), + footer_item("E-commerce", "/#"), + footer_item("Content Management", "/#"), + footer_item("Mobile Apps", "/#"), + spacing="4", + text_align=["center", "center", "start"], + flex_direction="column" + ) + +def footer_items_2() -> rx.Component: + return rx.flex( + rx.heading("RESOURCES", size="4", weight="bold", as_="h3"), + footer_item("Blog", "/#"), + footer_item("Case Studies", "/#"), + footer_item("Whitepapers", "/#"), + footer_item("Webinars", "/#"), + footer_item("E-books", "/#"), + spacing="4", + text_align=["center", "center", "start"], + flex_direction="column" + ) + +def social_link(icon: str, href: str) -> rx.Component: + return rx.link(rx.icon(icon), href=href) + +def socials() -> rx.Component: + return rx.flex( + social_link("instagram", "/#"), + social_link("twitter", "/#"), + social_link("facebook", "/#"), + social_link("linkedin", "/#"), + spacing="3", + justify_content=["center", "center", "end"], + width="100%" + ) + +def footer_newsletter() -> rx.Component: + return rx.el.footer( + rx.vstack( + rx.flex( + footer_items_1(), + footer_items_2(), + rx.vstack( + rx.text("JOIN OUR NEWSLETTER", size="4", + weight="bold"), + rx.hstack( + rx.input(placeholder="Your email address", type="email", size="3"), + rx.icon_button(rx.icon("arrow-right", padding="0.15em"), size="3"), + spacing="1", + justify="center", + width="100%" + ), + align_items=["center", "center", "start"], + justify="center", + height="100%" + ), + justify="between", + spacing="6", + flex_direction=["column", "column", "row"], + width="100%" + ), + rx.divider(), + rx.flex( + rx.hstack( + rx.image(src=f"{REFLEX_ASSETS_CDN}other/logo.jpg", width="2em", height="auto", border_radius="25%"), + rx.text("© 2024 Reflex, Inc", size="3", white_space="nowrap", weight="medium"), + spacing="2", + align="center", + justify_content=["center", "center", "start"], + width="100%" + ), + socials(), + spacing="4", + flex_direction=["column", "column", "row"], + width="100%" + ), + spacing="5", + width="100%" + ), + width="100%" + ) +``` + +## Three columns + +```python demo exec toggle +def footer_item(text: str, href: str) -> rx.Component: + return rx.link(rx.text(text, size="3"), href=href) + +def footer_items_1() -> rx.Component: + return rx.flex( + rx.heading("PRODUCTS", size="4", weight="bold", as_="h3"), + footer_item("Web Design", "/#"), + footer_item("Web Development", "/#"), + footer_item("E-commerce", "/#"), + footer_item("Content Management", "/#"), + footer_item("Mobile Apps", "/#"), + spacing="4", + text_align=["center", "center", "start"], + flex_direction="column" + ) + +def footer_items_2() -> rx.Component: + return rx.flex( + rx.heading("RESOURCES", size="4", weight="bold", as_="h3"), + footer_item("Blog", "/#"), + footer_item("Case Studies", "/#"), + footer_item("Whitepapers", "/#"), + footer_item("Webinars", "/#"), + footer_item("E-books", "/#"), + spacing="4", + text_align=["center", "center", "start"], + flex_direction="column" + ) + +def footer_items_3() -> rx.Component: + return rx.flex( + rx.heading("ABOUT US", size="4", weight="bold", as_="h3"), + footer_item("Our Team", "/#"), + footer_item("Careers", "/#"), + footer_item("Contact Us", "/#"), + footer_item("Privacy Policy", "/#"), + footer_item("Terms of Service", "/#"), + spacing="4", + text_align=["center", "center", "start"], + flex_direction="column" + ) + +def social_link(icon: str, href: str) -> rx.Component: + return rx.link(rx.icon(icon), href=href) + +def socials() -> rx.Component: + return rx.flex( + social_link("instagram", "/#"), + social_link("twitter", "/#"), + social_link("facebook", "/#"), + social_link("linkedin", "/#"), + spacing="3", + justify_content=["center", "center", "end"], + width="100%" + ) + +def footer_three_columns() -> rx.Component: + return rx.el.footer( + rx.vstack( + rx.flex( + footer_items_1(), + footer_items_2(), + footer_items_3(), + justify="between", + spacing="6", + flex_direction=["column", "column", "row"], + width="100%" + ), + rx.divider(), + rx.flex( + rx.hstack( + rx.image(src=f"{REFLEX_ASSETS_CDN}other/logo.jpg", width="2em", height="auto", border_radius="25%"), + rx.text("© 2024 Reflex, Inc", size="3", white_space="nowrap", weight="medium"), + spacing="2", + align="center", + justify_content=["center", "center", "start"], + width="100%" + ), + socials(), + spacing="4", + flex_direction=["column", "column", "row"], + width="100%" + ), + spacing="5", + width="100%" + ), + width="100%" + ) +``` diff --git a/docs/recipes/layout/navbar.md b/docs/recipes/layout/navbar.md new file mode 100644 index 00000000000..84a7f29e3f8 --- /dev/null +++ b/docs/recipes/layout/navbar.md @@ -0,0 +1,367 @@ +```python exec +import reflex as rx +from pcweb.constants import REFLEX_ASSETS_CDN +``` + +# Navigation Bar + +A navigation bar, also known as a navbar, is a common UI element found at the top of a webpage or application. +It typically provides links or buttons to the main sections of a website or application, allowing users to easily navigate and access the different pages. + +Navigation bars are useful for web apps because they provide a consistent and intuitive way for users to navigate through the app. +Having a clear and consistent navigation structure can greatly improve the user experience by making it easy for users to find the information they need and access the different features of the app. + + +```md video https://youtube.com/embed/ITOZkzjtjUA?start=2365&end=2627 +# Video: Example of Using the Navbar Recipe +``` + +## Basic + +```python demo exec toggle +def navbar_link(text: str, url: str) -> rx.Component: + return rx.link(rx.text(text, size="4", weight="medium"), href=url) + +def navbar() -> rx.Component: + return rx.box( + rx.desktop_only( + rx.hstack( + rx.hstack( + rx.image(src=f"{REFLEX_ASSETS_CDN}other/logo.jpg", width="2.25em", height="auto", border_radius="25%"), + rx.heading("Reflex", size="7", weight="bold"), align_items="center"), + rx.hstack( + navbar_link("Home", "/#"), + navbar_link("About", "/#"), + navbar_link("Pricing", "/#"), + navbar_link("Contact", "/#"), + justify="end", + spacing="5" + ), + justify="between", + align_items="center" + ), + ), + rx.mobile_and_tablet( + rx.hstack( + rx.hstack( + rx.image(src=f"{REFLEX_ASSETS_CDN}other/logo.jpg", width="2em", + height="auto", border_radius="25%"), + rx.heading("Reflex", size="6", weight="bold"), align_items="center"), + rx.menu.root( + rx.menu.trigger(rx.icon("menu", size=30)), + rx.menu.content( + rx.menu.item("Home"), + rx.menu.item("About"), + rx.menu.item("Pricing"), + rx.menu.item("Contact"), + ), + justify="end" + ), + justify="between", + align_items="center" + ), + ), + bg=rx.color("accent", 3), + padding="1em", + # position="fixed", + # top="0px", + # z_index="5", + width="100%" + ) +``` + + +## Dropdown + +```python demo exec toggle +def navbar_link(text: str, url: str) -> rx.Component: + return rx.link(rx.text(text, size="4", weight="medium"), href=url) + +def navbar_dropdown() -> rx.Component: + return rx.box( + rx.desktop_only( + rx.hstack( + rx.hstack( + rx.image(src=f"{REFLEX_ASSETS_CDN}other/logo.jpg", width="2.25em", height="auto", border_radius="25%"), + rx.heading("Reflex", size="7", weight="bold"), align_items="center"), + rx.hstack( + navbar_link("Home", "/#"), + rx.menu.root( + rx.menu.trigger( + rx.button(rx.text("Services", size="4", weight="medium"), rx.icon( + "chevron-down"), weight="medium", variant="ghost", size="3"), + ), + rx.menu.content( + rx.menu.item("Service 1"), + rx.menu.item("Service 2"), + rx.menu.item("Service 3"), + ), + ), + navbar_link("Pricing", "/#"), + navbar_link("Contact", "/#"), + justify="end", + spacing="5" + ), + justify="between", + align_items="center" + ), + ), + rx.mobile_and_tablet( + rx.hstack( + rx.hstack( + rx.image(src=f"{REFLEX_ASSETS_CDN}other/logo.jpg", width="2em", height="auto", border_radius="25%"), + rx.heading("Reflex", size="6", weight="bold"), align_items="center"), + rx.menu.root( + rx.menu.trigger(rx.icon("menu", size=30)), + rx.menu.content( + rx.menu.item("Home"), + rx.menu.sub( + rx.menu.sub_trigger("Services"), + rx.menu.sub_content( + rx.menu.item("Service 1"), + rx.menu.item("Service 2"), + rx.menu.item("Service 3"), + ), + ), + rx.menu.item("About"), + rx.menu.item("Pricing"), + rx.menu.item("Contact"), + ), + justify="end", + ), + justify="between", + align_items="center" + ), + ), + bg=rx.color("accent", 3), + padding="1em", + # position="fixed", + # top="0px", + # z_index="5", + width="100%" + ) +``` + +## Search bar + +```python demo exec toggle +def navbar_searchbar() -> rx.Component: + return rx.box( + rx.desktop_only( + rx.hstack( + rx.hstack( + rx.image(src=f"{REFLEX_ASSETS_CDN}other/logo.jpg", width="2.25em", height="auto", border_radius="25%"), + rx.heading("Reflex", size="7", weight="bold"), align_items="center"), + rx.input( + rx.input.slot(rx.icon("search")), + placeholder="Search...", + type="search", size="2", + justify="end", + ), + justify="between", + align_items="center" + ), + ), + rx.mobile_and_tablet( + rx.hstack( + rx.hstack( + rx.image(src=f"{REFLEX_ASSETS_CDN}other/logo.jpg", width="2em", height="auto", border_radius="25%"), + rx.heading("Reflex", size="6", weight="bold"), align_items="center"), + rx.input( + rx.input.slot(rx.icon("search")), + placeholder="Search...", + type="search", size="2", + justify="end", + ), + justify="between", + align_items="center" + ), + ), + bg=rx.color("accent", 3), + padding="1em", + # position="fixed", + # top="0px", + # z_index="5", + width="100%" + ) +``` + +## Icons + +```python demo exec toggle +def navbar_icons_item(text: str, icon: str, url: str) -> rx.Component: + return rx.link(rx.hstack(rx.icon(icon), rx.text(text, size="4", weight="medium")), href=url) + +def navbar_icons_menu_item(text: str, icon: str, url: str) -> rx.Component: + return rx.link(rx.hstack(rx.icon(icon, size=16), rx.text(text, size="3", weight="medium")), href=url) + +def navbar_icons() -> rx.Component: + return rx.box( + rx.desktop_only( + rx.hstack( + rx.hstack( + rx.image(src=f"{REFLEX_ASSETS_CDN}other/logo.jpg", width="2.25em", height="auto", border_radius="25%"), + rx.heading("Reflex", size="7", weight="bold"), align_items="center"), + rx.hstack( + navbar_icons_item("Home", "home", "/#"), + navbar_icons_item("Pricing", "coins", "/#"), + navbar_icons_item("Contact", "mail", "/#"), + navbar_icons_item("Services", "layers", "/#"), + spacing="6", + ), + justify="between", + align_items="center" + ), + ), + rx.mobile_and_tablet( + rx.hstack( + rx.hstack( + rx.image(src=f"{REFLEX_ASSETS_CDN}other/logo.jpg", width="2em", height="auto", border_radius="25%"), + rx.heading("Reflex", size="6", weight="bold"), align_items="center"), + rx.menu.root( + rx.menu.trigger(rx.icon("menu", size=30)), + rx.menu.content( + navbar_icons_menu_item("Home", "home", "/#"), + navbar_icons_menu_item("Pricing", "coins", "/#"), + navbar_icons_menu_item("Contact", "mail", "/#"), + navbar_icons_menu_item("Services", "layers", "/#"), + ), + justify="end", + ), + justify="between", + align_items="center" + ), + ), + bg=rx.color("accent", 3), + padding="1em", + # position="fixed", + # top="0px", + # z_index="5", + width="100%" + ) +``` + +## Buttons + +```python demo exec toggle +def navbar_link(text: str, url: str) -> rx.Component: + return rx.link(rx.text(text, size="4", weight="medium"), href=url) + +def navbar_buttons() -> rx.Component: + return rx.box( + rx.desktop_only( + rx.hstack( + rx.hstack( + rx.image(src=f"{REFLEX_ASSETS_CDN}other/logo.jpg", width="2.25em", height="auto", border_radius="25%"), + rx.heading("Reflex", size="7", weight="bold"), align_items="center"), + rx.hstack( + navbar_link("Home", "/#"), + navbar_link("About", "/#"), + navbar_link("Pricing", "/#"), + navbar_link("Contact", "/#"), + spacing="5", + ), + rx.hstack( + rx.button("Sign Up", size="3", variant="outline"), + rx.button("Log In", size="3"), + spacing="4", + justify="end", + ), + justify="between", + align_items="center" + ), + ), + rx.mobile_and_tablet( + rx.hstack( + rx.hstack( + rx.image(src=f"{REFLEX_ASSETS_CDN}other/logo.jpg", width="2em", height="auto", border_radius="25%"), + rx.heading("Reflex", size="6", weight="bold"), align_items="center"), + rx.menu.root( + rx.menu.trigger(rx.icon("menu", size=30)), + rx.menu.content( + rx.menu.item("Home"), + rx.menu.item("About"), + rx.menu.item("Pricing"), + rx.menu.item("Contact"), + rx.menu.separator(), + rx.menu.item("Log in"), + rx.menu.item("Sign up"), + ), + justify="end", + ), + justify="between", + align_items="center" + ), + ), + bg=rx.color("accent", 3), + padding="1em", + # position="fixed", + # top="0px", + # z_index="5", + width="100%" + ) +``` + +## User profile + +```python demo exec toggle +def navbar_link(text: str, url: str) -> rx.Component: + return rx.link(rx.text(text, size="4", weight="medium"), href=url) + +def navbar_user() -> rx.Component: + return rx.box( + rx.desktop_only( + rx.hstack( + rx.hstack( + rx.image(src=f"{REFLEX_ASSETS_CDN}other/logo.jpg", width="2.25em", height="auto", border_radius="25%"), + rx.heading("Reflex", size="7", weight="bold"), align_items="center"), + rx.hstack( + navbar_link("Home", "/#"), + navbar_link("About", "/#"), + navbar_link("Pricing", "/#"), + navbar_link("Contact", "/#"), + spacing="5", + ), + rx.menu.root( + rx.menu.trigger(rx.icon_button( + rx.icon("user"), size="2", radius="full")), + rx.menu.content( + rx.menu.item("Settings"), + rx.menu.item("Earnings"), + rx.menu.separator(), + rx.menu.item("Log out"), + ), + justify="end", + ), + justify="between", + align_items="center" + ), + ), + rx.mobile_and_tablet( + rx.hstack( + rx.hstack( + rx.image(src=f"{REFLEX_ASSETS_CDN}other/logo.jpg", width="2em", height="auto", border_radius="25%"), + rx.heading("Reflex", size="6", weight="bold"), align_items="center"), + rx.menu.root( + rx.menu.trigger(rx.icon_button( + rx.icon("user"), size="2", radius="full")), + rx.menu.content( + rx.menu.item("Settings"), + rx.menu.item("Earnings"), + rx.menu.separator(), + rx.menu.item("Log out"), + ), + justify="end", + ), + justify="between", + align_items="center" + ), + ), + bg=rx.color("accent", 3), + padding="1em", + # position="fixed", + # top="0px", + # z_index="5", + width="100%" + ) +``` diff --git a/docs/recipes/layout/sidebar.md b/docs/recipes/layout/sidebar.md new file mode 100644 index 00000000000..e30f90d566c --- /dev/null +++ b/docs/recipes/layout/sidebar.md @@ -0,0 +1,421 @@ +```python exec +import reflex as rx +from pcweb.constants import REFLEX_ASSETS_CDN + +def sidebar_item(text: str, icon: str, href: str) -> rx.Component: + return rx.link( + rx.hstack( + rx.icon(icon), + rx.text(text, size="4"), + width="100%", + padding_x="0.5rem", + padding_y="0.75rem", + align="center", + style={ + "_hover": { + "bg": rx.color("accent", 4), + "color": rx.color("accent", 11), + }, + "border-radius": "0.5em", + }, + ), + href=href, + underline="none", + weight="medium", + width="100%" + ) + +def sidebar_items() -> rx.Component: + return rx.vstack( + sidebar_item("Dashboard", "layout-dashboard", "/#"), + sidebar_item("Projects", "square-library", "/#"), + sidebar_item("Analytics", "bar-chart-4", "/#"), + sidebar_item("Messages", "mail", "/#"), + spacing="1", + width="100%" + ) +``` + +# Sidebar + +Similar to a navigation bar, a sidebar is a common UI element found on the side of a webpage or application. It typically contains links to different sections of the site or app. + +## Basic + +```python demo exec toggle +def sidebar_item(text: str, icon: str, href: str) -> rx.Component: + return rx.link( + rx.hstack( + rx.icon(icon), + rx.text(text, size="4"), + width="100%", + padding_x="0.5rem", + padding_y="0.75rem", + align="center", + style={ + "_hover": { + "bg": rx.color("accent", 4), + "color": rx.color("accent", 11), + }, + "border-radius": "0.5em", + }, + ), + href=href, + underline="none", + weight="medium", + width="100%" + ) + +def sidebar_items() -> rx.Component: + return rx.vstack( + sidebar_item("Dashboard", "layout-dashboard", "/#"), + sidebar_item("Projects", "square-library", "/#"), + sidebar_item("Analytics", "bar-chart-4", "/#"), + sidebar_item("Messages", "mail", "/#"), + spacing="1", + width="100%" + ) + +def sidebar() -> rx.Component: + return rx.box( + rx.desktop_only( + rx.vstack( + rx.hstack( + rx.image(src=f"{REFLEX_ASSETS_CDN}other/logo.jpg", width="2.25em", + height="auto", border_radius="25%"), + rx.heading("Reflex", size="7", weight="bold"), + align="center", + justify="start", + padding_x="0.5rem", + width="100%" + ), + sidebar_items(), + spacing="5", + #position="fixed", + # left="0px", + # top="0px", + # z_index="5", + padding_x="1em", + padding_y="1.5em", + bg=rx.color("accent", 3), + align="start", + #height="100%", + height="650px", + width="16em", + ), + ), + rx.mobile_and_tablet( + rx.drawer.root( + rx.drawer.trigger(rx.icon("align-justify", size=30)), + rx.drawer.overlay(z_index="5"), + rx.drawer.portal( + rx.drawer.content( + rx.vstack( + rx.box( + rx.drawer.close(rx.icon("x", size=30)), + width="100%", + ), + sidebar_items(), + spacing="5", + width="100%", + ), + top="auto", + right="auto", + height="100%", + width="20em", + padding="1.5em", + bg=rx.color("accent", 2) + ), + width="100%", + ), + direction="left" + ), + padding="1em", + ), + ) +``` + +## Bottom user profile + +```python demo exec toggle +def sidebar_item(text: str, icon: str, href: str) -> rx.Component: + return rx.link( + rx.hstack( + rx.icon(icon), + rx.text(text, size="4"), + width="100%", + padding_x="0.5rem", + padding_y="0.75rem", + align="center", + style={ + "_hover": { + "bg": rx.color("accent", 4), + "color": rx.color("accent", 11), + }, + "border-radius": "0.5em", + }, + ), + href=href, + underline="none", + weight="medium", + width="100%" + ) + +def sidebar_items() -> rx.Component: + return rx.vstack( + sidebar_item("Dashboard", "layout-dashboard", "/#"), + sidebar_item("Projects", "square-library", "/#"), + sidebar_item("Analytics", "bar-chart-4", "/#"), + sidebar_item("Messages", "mail", "/#"), + spacing="1", + width="100%" + ) + +def sidebar_bottom_profile() -> rx.Component: + return rx.box( + rx.desktop_only( + rx.vstack( + rx.hstack( + rx.image(src=f"{REFLEX_ASSETS_CDN}other/logo.jpg", width="2.25em", + height="auto", border_radius="25%"), + rx.heading("Reflex", size="7", weight="bold"), + align="center", + justify="start", + padding_x="0.5rem", + width="100%" + ), + sidebar_items(), + rx.spacer(), + rx.vstack( + rx.vstack( + sidebar_item("Settings", "settings", "/#"), + sidebar_item("Log out", "log-out", "/#"), + spacing="1", + width="100%" + ), + rx.divider(), + rx.hstack( + rx.icon_button(rx.icon("user"), size="3", radius="full"), + rx.vstack( + rx.box( + rx.text("My account", size="3", weight="bold"), + rx.text("user@reflex.dev", size="2", weight="medium"), + width="100%" + ), + spacing="0", + align="start", + justify="start", + width="100%" + ), + padding_x="0.5rem", + align="center", + justify="start", + width="100%", + ), + width="100%", + spacing="5", + ), + spacing="5", + #position="fixed", + # left="0px", + # top="0px", + # z_index="5", + padding_x="1em", + padding_y="1.5em", + bg=rx.color("accent", 3), + align="start", + #height="100%", + height="650px", + width="16em", + ), + ), + rx.mobile_and_tablet( + rx.drawer.root( + rx.drawer.trigger(rx.icon("align-justify", size=30)), + rx.drawer.overlay(z_index="5"), + rx.drawer.portal( + rx.drawer.content( + rx.vstack( + rx.box( + rx.drawer.close(rx.icon("x", size=30)), + width="100%", + ), + sidebar_items(), + rx.spacer(), + rx.vstack( + rx.vstack( + sidebar_item("Settings", "settings", "/#"), + sidebar_item("Log out", "log-out", "/#"), + width="100%", + spacing="1", + ), + rx.divider(margin="0"), + rx.hstack( + rx.icon_button(rx.icon("user"), size="3", radius="full"), + rx.vstack( + rx.box( + rx.text("My account", size="3", weight="bold"), + rx.text("user@reflex.dev", size="2", weight="medium"), + width="100%" + ), + spacing="0", + justify="start", + width="100%", + ), + padding_x="0.5rem", + align="center", + justify="start", + width="100%", + ), + width="100%", + spacing="5", + ), + spacing="5", + width="100%", + ), + top="auto", + right="auto", + height="100%", + width="20em", + padding="1.5em", + bg=rx.color("accent", 2) + ), + width="100%", + ), + direction="left" + ), + padding="1em", + ), + ) +``` + +## Top user profile + +```python demo exec toggle +def sidebar_item(text: str, icon: str, href: str) -> rx.Component: + return rx.link( + rx.hstack( + rx.icon(icon), + rx.text(text, size="4"), + width="100%", + padding_x="0.5rem", + padding_y="0.75rem", + align="center", + style={ + "_hover": { + "bg": rx.color("accent", 4), + "color": rx.color("accent", 11), + }, + "border-radius": "0.5em", + }, + ), + href=href, + underline="none", + weight="medium", + width="100%" + ) + +def sidebar_items() -> rx.Component: + return rx.vstack( + sidebar_item("Dashboard", "layout-dashboard", "/#"), + sidebar_item("Projects", "square-library", "/#"), + sidebar_item("Analytics", "bar-chart-4", "/#"), + sidebar_item("Messages", "mail", "/#"), + spacing="1", + width="100%" + ) + +def sidebar_top_profile() -> rx.Component: + return rx.box( + rx.desktop_only( + rx.vstack( + rx.hstack( + rx.icon_button(rx.icon("user"), size="3", radius="full"), + rx.vstack( + rx.box( + rx.text("My account", size="3", weight="bold"), + rx.text("user@reflex.dev", size="2", weight="medium"), + width="100%" + ), + spacing="0", + justify="start", + width="100%", + ), + rx.spacer(), + rx.icon_button(rx.icon("settings"), size="2", + variant="ghost", color_scheme="gray"), + padding_x="0.5rem", + align="center", + width="100%", + ), + sidebar_items(), + rx.spacer(), + sidebar_item("Help & Support", "life-buoy", "/#"), + spacing="5", + #position="fixed", + # left="0px", + # top="0px", + # z_index="5", + padding_x="1em", + padding_y="1.5em", + bg=rx.color("accent", 3), + align="start", + #height="100%", + height="650px", + width="16em", + ), + ), + rx.mobile_and_tablet( + rx.drawer.root( + rx.drawer.trigger(rx.icon("align-justify", size=30)), + rx.drawer.overlay(z_index="5"), + rx.drawer.portal( + rx.drawer.content( + rx.vstack( + rx.box( + rx.drawer.close(rx.icon("x", size=30)), + width="100%", + ), + sidebar_items(), + rx.spacer(), + rx.vstack( + sidebar_item("Help & Support", "life-buoy", "/#"), + rx.divider(margin="0"), + rx.hstack( + rx.icon_button(rx.icon("user"), size="3", radius="full"), + rx.vstack( + rx.box( + rx.text("My account", size="3", weight="bold"), + rx.text("user@reflex.dev", size="2", weight="medium"), + width="100%" + ), + spacing="0", + justify="start", + width="100%", + ), + padding_x="0.5rem", + align="center", + justify="start", + width="100%", + ), + width="100%", + spacing="5", + ), + spacing="5", + width="100%", + ), + top="auto", + right="auto", + height="100%", + width="20em", + padding="1.5em", + bg=rx.color("accent", 2) + ), + width="100%", + ), + direction="left" + ), + padding="1em", + ), + ) +``` diff --git a/docs/recipes/others/checkboxes.md b/docs/recipes/others/checkboxes.md new file mode 100644 index 00000000000..5d6392fa2bd --- /dev/null +++ b/docs/recipes/others/checkboxes.md @@ -0,0 +1,68 @@ +```python exec +import reflex as rx +from pcweb.constants import REFLEX_ASSETS_CDN +``` + +# Smart Checkboxes Group + +A smart checkboxes group where you can track all checked boxes, as well as place a limit on how many checks are possible. + +## Recipe + +```python eval +rx.center(rx.image(src=f"{REFLEX_ASSETS_CDN}templates/smart_checkboxes.webp")) +``` + +This recipe use a `dict[str, bool]` for the checkboxes state tracking. +Additionally, the limit that prevent the user from checking more boxes than allowed with a computed var. + +```python +class CBoxeState(rx.State): + + choices: dict[str, bool] = \{k: False for k in ["Choice A", "Choice B", "Choice C"]} + _check_limit = 2 + + def check_choice(self, value, index): + self.choices[index] = value + + @rx.var + def choice_limit(self): + return sum(self.choices.values()) >= self._check_limit + + @rx.var + def checked_choices(self): + choices = [l for l, v in self.choices.items() if v] + return " / ".join(choices) if choices else "None" + +import reflex as rx + + +def render_checkboxes(values, limit, handler): + return rx.vstack( + rx.foreach( + values, + lambda choice: rx.checkbox( + choice[0], + checked=choice[1], + disabled=~choice[1] & limit, + on_change=lambda val: handler(val, choice[0]), + ), + ) + ) + + +def index() -> rx.Component: + + return rx.center( + rx.vstack( + rx.text("Make your choices (2 max):"), + render_checkboxes( + CBoxeState.choices, + CBoxeState.choice_limit, + CBoxeState.check_choice, + ), + rx.text("Your choices: ", CBoxeState.checked_choices), + ), + height="100vh", + ) +``` diff --git a/docs/recipes/others/chips.md b/docs/recipes/others/chips.md new file mode 100644 index 00000000000..c72ab54aa50 --- /dev/null +++ b/docs/recipes/others/chips.md @@ -0,0 +1,247 @@ +```python exec +import reflex as rx + +``` + +# Chips + +Chips are compact elements that represent small pieces of information, such as tags or categories. They are commonly used to select multiple items from a list or to filter content. + +## Status + +```python demo exec toggle +from reflex.components.radix.themes.base import LiteralAccentColor + +status_chip_props = { + "radius": "full", + "variant": "outline", + "size": "3", +} + +def status_chip(status: str, icon: str, color: LiteralAccentColor) -> rx.Component: + return rx.badge( + rx.icon(icon, size=18), + status, + color_scheme=color, + **status_chip_props, + ) + +def status_chips_group() -> rx.Component: + return rx.hstack( + status_chip("Info", "info", "blue"), + status_chip("Success", "circle-check", "green"), + status_chip("Warning", "circle-alert", "yellow"), + status_chip("Error", "circle-x", "red"), + wrap="wrap", + spacing="2", + ) +``` + +## Single selection + +```python demo exec toggle +chip_props = { + "radius": "full", + "variant": "soft", + "size": "3", + "cursor": "pointer", + "style": {"_hover": {"opacity": 0.75}}, +} + +available_items = ["2:00", "3:00", "4:00", "5:00"] + +class SingleSelectionChipsState(rx.State): + selected_item: str = "" + + @rx.event + def set_selected_item(self, value: str): + self.selected_item = value + +def unselected_item(item: str) -> rx.Component: + return rx.badge( + item, + color_scheme="gray", + **chip_props, + on_click=SingleSelectionChipsState.set_selected_item(item), + ) + +def selected_item(item: str) -> rx.Component: + return rx.badge( + rx.icon("check", size=18), + item, + color_scheme="mint", + **chip_props, + on_click=SingleSelectionChipsState.set_selected_item(""), + ) + +def item_chip(item: str) -> rx.Component: + return rx.cond( + SingleSelectionChipsState.selected_item == item, + selected_item(item), + unselected_item(item), + ) + +def item_selector() -> rx.Component: + return rx.vstack( + rx.hstack( + rx.icon("clock", size=20), + rx.heading( + "Select your reservation time:", size="4" + ), + spacing="2", + align="center", + width="100%", + ), + rx.hstack( + rx.foreach(available_items, item_chip), + wrap="wrap", + spacing="2", + ), + align_items="start", + spacing="4", + width="100%", + ) +``` + +## Multiple selection + +This example demonstrates selecting multiple skills from a list. It includes buttons to add all skills, clear selected skills, and select a random number of skills. + +```python demo exec toggle +import random +from reflex.components.radix.themes.base import LiteralAccentColor + +chip_props = { + "radius": "full", + "variant": "surface", + "size": "3", + "cursor": "pointer", + "style": {"_hover": {"opacity": 0.75}}, +} + +skills = [ + "Data Management", + "Networking", + "Security", + "Cloud", + "DevOps", + "Data Science", + "AI", + "ML", + "Robotics", + "Cybersecurity", +] + +class BasicChipsState(rx.State): + selected_items: list[str] = skills[:3] + + @rx.event + def add_selected(self, item: str): + self.selected_items.append(item) + + @rx.event + def remove_selected(self, item: str): + self.selected_items.remove(item) + + @rx.event + def add_all_selected(self): + self.selected_items = list(skills) + + @rx.event + def clear_selected(self): + self.selected_items.clear() + + @rx.event + def random_selected(self): + self.selected_items = random.sample(skills, k=random.randint(1, len(skills))) + +def action_button(icon: str, label: str, on_click: callable, color_scheme: LiteralAccentColor) -> rx.Component: + return rx.button( + rx.icon(icon, size=16), + label, + variant="soft", + size="2", + on_click=on_click, + color_scheme=color_scheme, + cursor="pointer", + ) + +def selected_item_chip(item: str) -> rx.Component: + return rx.badge( + item, + rx.icon("circle-x", size=18), + color_scheme="green", + **chip_props, + on_click=BasicChipsState.remove_selected(item), + ) + +def unselected_item_chip(item: str) -> rx.Component: + return rx.cond( + BasicChipsState.selected_items.contains(item), + rx.fragment(), + rx.badge( + item, + rx.icon("circle-plus", size=18), + color_scheme="gray", + **chip_props, + on_click=BasicChipsState.add_selected(item), + ), + ) + +def items_selector() -> rx.Component: + return rx.vstack( + rx.flex( + rx.hstack( + rx.icon("lightbulb", size=20), + rx.heading( + "Skills" + f" ({BasicChipsState.selected_items.length()})", size="4" + ), + spacing="1", + align="center", + width="100%", + justify_content=["end", "start"], + ), + rx.hstack( + action_button( + "plus", "Add All", BasicChipsState.add_all_selected, "green" + ), + action_button( + "trash", "Clear All", BasicChipsState.clear_selected, "tomato" + ), + action_button( + "shuffle", "", BasicChipsState.random_selected, "gray" + ), + spacing="2", + justify="end", + width="100%", + ), + justify="between", + flex_direction=["column", "row"], + align="center", + spacing="2", + margin_bottom="10px", + width="100%", + ), + # Selected Items + rx.flex( + rx.foreach( + BasicChipsState.selected_items, + selected_item_chip, + ), + wrap="wrap", + spacing="2", + justify_content="start", + ), + rx.divider(), + # Unselected Items + rx.flex( + rx.foreach(skills, unselected_item_chip), + wrap="wrap", + spacing="2", + justify_content="start", + ), + justify_content="start", + align_items="start", + width="100%", + ) +``` diff --git a/docs/recipes/others/dark_mode_toggle.md b/docs/recipes/others/dark_mode_toggle.md new file mode 100644 index 00000000000..c648941157e --- /dev/null +++ b/docs/recipes/others/dark_mode_toggle.md @@ -0,0 +1,33 @@ +```python exec +import reflex as rx +from reflex.style import set_color_mode, color_mode +``` + +# Dark Mode Toggle + +The Dark Mode Toggle component lets users switch between light and dark themes. + +```python demo exec toggle +import reflex as rx +from reflex.style import set_color_mode, color_mode + +def dark_mode_toggle() -> rx.Component: + return rx.segmented_control.root( + rx.segmented_control.item( + rx.icon(tag="monitor", size=20), + value="system", + ), + rx.segmented_control.item( + rx.icon(tag="sun", size=20), + value="light", + ), + rx.segmented_control.item( + rx.icon(tag="moon", size=20), + value="dark", + ), + on_change=set_color_mode, + variant="classic", + radius="large", + value=color_mode, + ) +``` diff --git a/docs/recipes/others/pricing_cards.md b/docs/recipes/others/pricing_cards.md new file mode 100644 index 00000000000..0efe8b9580b --- /dev/null +++ b/docs/recipes/others/pricing_cards.md @@ -0,0 +1,199 @@ +```python exec +import reflex as rx +``` + +# Pricing Cards + +A pricing card shows the price of a product or service. It typically includes a title, description, price, features, and a purchase button. + +## Basic + +```python demo exec toggle +def feature_item(text: str) -> rx.Component: + return rx.hstack(rx.icon("check", color=rx.color("grass", 9)), rx.text(text, size="4")) + +def features() -> rx.Component: + return rx.vstack( + feature_item("24/7 customer support"), + feature_item("Daily backups"), + feature_item("Advanced analytics"), + feature_item("Customizable templates"), + feature_item("Priority email support"), + width="100%", + align_items="start", + ) + +def pricing_card_beginner() -> rx.Component: + return rx.vstack( + rx.vstack( + rx.text("Beginner", weight="bold", size="6"), + rx.text("Ideal choice for personal use & for your next project.", size="4", opacity=0.8, align="center"), + rx.hstack( + rx.text("$39", weight="bold", font_size="3rem", trim="both"), + rx.text("/month", size="4", opacity=0.8, trim="both"), + width="100%", + align_items="end", + justify="center" + ), + width="100%", + align="center", + spacing="6", + ), + features(), + rx.button("Get started", size="3", variant="solid", width="100%", color_scheme="blue"), + spacing="6", + border=f"1.5px solid {rx.color('gray', 5)}", + background=rx.color("gray", 1), + padding="28px", + width="100%", + max_width="400px", + justify="center", + border_radius="0.5rem", + ) +``` + +## Comparison cards + +```python demo exec toggle +def feature_item(feature: str) -> rx.Component: + return rx.hstack( + rx.icon("check", color=rx.color("blue", 9), size=21), + rx.text(feature, size="4", weight="regular"), + ) + + +def standard_features() -> rx.Component: + return rx.vstack( + feature_item("40 credits for image generation"), + feature_item("Credits never expire"), + feature_item("High quality images"), + feature_item("Commercial license"), + spacing="3", + width="100%", + align_items="start", + ) + + +def popular_features() -> rx.Component: + return rx.vstack( + feature_item("250 credits for image generation"), + feature_item("+30% Extra free credits"), + feature_item("Credits never expire"), + feature_item("High quality images"), + feature_item("Commercial license"), + spacing="3", + width="100%", + align_items="start", + ) + + +def pricing_card_standard() -> rx.Component: + return rx.vstack( + rx.hstack( + rx.hstack( + rx.text( + "$14.99", + trim="both", + as_="s", + size="3", + weight="regular", + opacity=0.8, + ), + rx.text("$3.99", trim="both", size="6", weight="regular"), + width="100%", + spacing="2", + align_items="end", + ), + height="35px", + align_items="center", + justify="between", + width="100%", + ), + rx.text( + "40 Image Credits", + weight="bold", + size="7", + width="100%", + text_align="left", + ), + standard_features(), + rx.spacer(), + rx.button( + "Purchase", + size="3", + variant="outline", + width="100%", + color_scheme="blue", + ), + spacing="6", + border=f"1.5px solid {rx.color('gray', 5)}", + background=rx.color("gray", 1), + padding="28px", + width="100%", + max_width="400px", + min_height="475px", + border_radius="0.5rem", + ) + + +def pricing_card_popular() -> rx.Component: + return rx.vstack( + rx.hstack( + rx.hstack( + rx.text( + "$69.99", + trim="both", + as_="s", + size="3", + weight="regular", + opacity=0.8, + ), + rx.text("$18.99", trim="both", size="6", weight="regular"), + width="100%", + spacing="2", + align_items="end", + ), + rx.badge( + "POPULAR", + size="2", + radius="full", + variant="soft", + color_scheme="blue", + ), + align_items="center", + justify="between", + height="35px", + width="100%", + ), + rx.text( + "250 Image Credits", + weight="bold", + size="7", + width="100%", + text_align="left", + ), + popular_features(), + rx.spacer(), + rx.button("Purchase", size="3", width="100%", color_scheme="blue"), + spacing="6", + border=f"1.5px solid {rx.color('blue', 6)}", + background=rx.color("blue", 1), + padding="28px", + width="100%", + max_width="400px", + min_height="475px", + border_radius="0.5rem", + ) + + +def pricing_cards() -> rx.Component: + return rx.flex( + pricing_card_standard(), + pricing_card_popular(), + spacing="4", + flex_direction=["column", "column", "row"], + width="100%", + align_items="center", + ) +``` + diff --git a/docs/recipes/others/speed_dial.md b/docs/recipes/others/speed_dial.md new file mode 100644 index 00000000000..4d2086ec080 --- /dev/null +++ b/docs/recipes/others/speed_dial.md @@ -0,0 +1,454 @@ +```python exec +import reflex as rx +``` + +# Speed Dial + +A speed dial is a component that allows users to quickly access frequently used actions or pages. It is often used in the bottom right corner of the screen. + +# Vertical + +```python demo exec toggle +class SpeedDialVertical(rx.ComponentState): + is_open: bool = False + + @rx.event + def toggle(self, value: bool): + self.is_open = value + + @classmethod + def get_component(cls, **props): + def menu_item(icon: str, text: str) -> rx.Component: + return rx.tooltip( + rx.icon_button( + rx.icon(icon, padding="2px"), + variant="soft", + color_scheme="gray", + size="3", + cursor="pointer", + radius="full", + ), + side="left", + content=text, + ) + + def menu() -> rx.Component: + return rx.vstack( + menu_item("copy", "Copy"), + menu_item("download", "Download"), + menu_item("share-2", "Share"), + position="absolute", + bottom="100%", + spacing="2", + padding_bottom="10px", + left="0", + direction="column-reverse", + align_items="center", + ) + + return rx.box( + rx.box( + rx.icon_button( + rx.icon( + "plus", + style={ + "transform": rx.cond(cls.is_open, "rotate(45deg)", "rotate(0)"), + "transition": "transform 150ms cubic-bezier(0.4, 0, 0.2, 1)", + }, + ), + variant="solid", + color_scheme="blue", + size="3", + cursor="pointer", + radius="full", + position="relative", + ), + rx.cond( + cls.is_open, + menu(), + ), + position="relative", + ), + on_mouse_enter=cls.toggle(True), + on_mouse_leave=cls.toggle(False), + on_click=cls.toggle(~cls.is_open), + style={"bottom": "15px", "right": "15px"}, + position="absolute", + # z_index="50", + **props, + ) + +speed_dial_vertical = SpeedDialVertical.create + +def render_vertical(): + return rx.box( + speed_dial_vertical(), + height="250px", + position="relative", + width="100%", + ) +``` + +# Horizontal + +```python demo exec toggle +class SpeedDialHorizontal(rx.ComponentState): + is_open: bool = False + + @rx.event + def toggle(self, value: bool): + self.is_open = value + + @classmethod + def get_component(cls, **props): + def menu_item(icon: str, text: str) -> rx.Component: + return rx.tooltip( + rx.icon_button( + rx.icon(icon, padding="2px"), + variant="soft", + color_scheme="gray", + size="3", + cursor="pointer", + radius="full", + ), + side="top", + content=text, + ) + + def menu() -> rx.Component: + return rx.hstack( + menu_item("copy", "Copy"), + menu_item("download", "Download"), + menu_item("share-2", "Share"), + position="absolute", + bottom="0", + spacing="2", + padding_right="10px", + right="100%", + direction="row-reverse", + align_items="center", + ) + + return rx.box( + rx.box( + rx.icon_button( + rx.icon( + "plus", + style={ + "transform": rx.cond(cls.is_open, "rotate(45deg)", "rotate(0)"), + "transition": "transform 150ms cubic-bezier(0.4, 0, 0.2, 1)", + }, + class_name="dial", + ), + variant="solid", + color_scheme="green", + size="3", + cursor="pointer", + radius="full", + position="relative", + ), + rx.cond( + cls.is_open, + menu(), + ), + position="relative", + ), + on_mouse_enter=cls.toggle(True), + on_mouse_leave=cls.toggle(False), + on_click=cls.toggle(~cls.is_open), + style={"bottom": "15px", "right": "15px"}, + position="absolute", + # z_index="50", + **props, + ) + +speed_dial_horizontal = SpeedDialHorizontal.create + +def render_horizontal(): + return rx.box( + speed_dial_horizontal(), + height="250px", + position="relative", + width="100%", + ) +``` + +# Vertical with text + +```python demo exec toggle +class SpeedDialVerticalText(rx.ComponentState): + is_open: bool = False + + @rx.event + def toggle(self, value: bool): + self.is_open = value + + @classmethod + def get_component(cls, **props): + def menu_item(icon: str, text: str) -> rx.Component: + return rx.hstack( + rx.text(text, weight="medium"), + rx.icon_button( + rx.icon(icon, padding="2px"), + variant="soft", + color_scheme="gray", + size="3", + cursor="pointer", + radius="full", + position="relative", + ), + opacity="0.75", + _hover={ + "opacity": "1", + }, + align_items="center", + ) + + def menu() -> rx.Component: + return rx.vstack( + menu_item("copy", "Copy"), + menu_item("download", "Download"), + menu_item("share-2", "Share"), + position="absolute", + bottom="100%", + spacing="2", + padding_bottom="10px", + right="0", + direction="column-reverse", + align_items="end", + justify_content="end", + ) + + return rx.box( + rx.box( + rx.icon_button( + rx.icon( + "plus", + style={ + "transform": rx.cond(cls.is_open, "rotate(45deg)", "rotate(0)"), + "transition": "transform 150ms cubic-bezier(0.4, 0, 0.2, 1)", + }, + class_name="dial", + ), + variant="solid", + color_scheme="crimson", + size="3", + cursor="pointer", + radius="full", + position="relative", + ), + rx.cond( + cls.is_open, + menu(), + ), + position="relative", + ), + on_mouse_enter=cls.toggle(True), + on_mouse_leave=cls.toggle(False), + on_click=cls.toggle(~cls.is_open), + style={"bottom": "15px", "right": "15px"}, + position="absolute", + # z_index="50", + **props, + ) + +speed_dial_vertical_text = SpeedDialVerticalText.create + +def render_vertical_text(): + return rx.box( + speed_dial_vertical_text(), + height="250px", + position="relative", + width="100%", + ) +``` + +# Reveal animation + +```python demo exec toggle +class SpeedDialReveal(rx.ComponentState): + is_open: bool = False + + @rx.event + def toggle(self, value: bool): + self.is_open = value + + @classmethod + def get_component(cls, **props): + def menu_item(icon: str, text: str) -> rx.Component: + return rx.tooltip( + rx.icon_button( + rx.icon(icon, padding="2px"), + variant="soft", + color_scheme="gray", + size="3", + cursor="pointer", + radius="full", + style={ + "animation": rx.cond(cls.is_open, "reveal 0.3s ease both", "none"), + "@keyframes reveal": { + "0%": { + "opacity": "0", + "transform": "scale(0)", + }, + "100%": { + "opacity": "1", + "transform": "scale(1)", + }, + }, + }, + ), + side="left", + content=text, + ) + + def menu() -> rx.Component: + return rx.vstack( + menu_item("copy", "Copy"), + menu_item("download", "Download"), + menu_item("share-2", "Share"), + position="absolute", + bottom="100%", + spacing="2", + padding_bottom="10px", + left="0", + direction="column-reverse", + align_items="center", + ) + + return rx.box( + rx.box( + rx.icon_button( + rx.icon( + "plus", + style={ + "transform": rx.cond(cls.is_open, "rotate(45deg)", "rotate(0)"), + "transition": "transform 150ms cubic-bezier(0.4, 0, 0.2, 1)", + }, + class_name="dial", + ), + variant="solid", + color_scheme="violet", + size="3", + cursor="pointer", + radius="full", + position="relative", + ), + rx.cond( + cls.is_open, + menu(), + ), + position="relative", + ), + on_mouse_enter=cls.toggle(True), + on_mouse_leave=cls.toggle(False), + on_click=cls.toggle(~cls.is_open), + style={"bottom": "15px", "right": "15px"}, + position="absolute", + # z_index="50", + **props, + ) + +speed_dial_reveal = SpeedDialReveal.create + +def render_reveal(): + return rx.box( + speed_dial_reveal(), + height="250px", + position="relative", + width="100%", + ) +``` + +# Menu + +```python demo exec toggle +class SpeedDialMenu(rx.ComponentState): + is_open: bool = False + + @rx.event + def toggle(self, value: bool): + self.is_open = value + + @classmethod + def get_component(cls, **props): + def menu_item(icon: str, text: str) -> rx.Component: + return rx.hstack( + rx.icon(icon, padding="2px"), + rx.text(text, weight="medium"), + align="center", + opacity="0.75", + cursor="pointer", + position="relative", + _hover={ + "opacity": "1", + }, + width="100%", + align_items="center", + ) + + def menu() -> rx.Component: + return rx.box( + rx.card( + rx.vstack( + menu_item("copy", "Copy"), + rx.divider(margin="0"), + menu_item("download", "Download"), + rx.divider(margin="0"), + menu_item("share-2", "Share"), + direction="column-reverse", + align_items="end", + justify_content="end", + ), + box_shadow="0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1)", + ), + position="absolute", + bottom="100%", + right="0", + padding_bottom="10px", + ) + + return rx.box( + rx.box( + rx.icon_button( + rx.icon( + "plus", + style={ + "transform": rx.cond(cls.is_open, "rotate(45deg)", "rotate(0)"), + "transition": "transform 150ms cubic-bezier(0.4, 0, 0.2, 1)", + }, + class_name="dial", + ), + variant="solid", + color_scheme="orange", + size="3", + cursor="pointer", + radius="full", + position="relative", + ), + rx.cond( + cls.is_open, + menu(), + ), + position="relative", + ), + on_mouse_enter=cls.toggle(True), + on_mouse_leave=cls.toggle(False), + on_click=cls.toggle(~cls.is_open), + style={"bottom": "15px", "right": "15px"}, + position="absolute", + # z_index="50", + **props, + ) + + +speed_dial_menu = SpeedDialMenu.create + +def render_menu(): + return rx.box( + speed_dial_menu(), + height="250px", + position="relative", + width="100%", + ) +``` diff --git a/docs/state/overview.md b/docs/state/overview.md new file mode 100644 index 00000000000..a9397a3da19 --- /dev/null +++ b/docs/state/overview.md @@ -0,0 +1,189 @@ +```python exec +import reflex as rx +from pcweb.templates.docpage import definition +``` + +# State + +State allows us to create interactive apps that can respond to user input. +It defines the variables that can change over time, and the functions that can modify them. + +```md video https://youtube.com/embed/ITOZkzjtjUA?start=1206&end=1869 +# Video: State Overview +``` + +## State Basics + +You can define state by creating a class that inherits from `rx.State`: + +```python +import reflex as rx + + +class State(rx.State): + """Define your app state here.""" +``` + +A state class is made up of two parts: vars and event handlers. + +**Vars** are variables in your app that can change over time. + +**Event handlers** are functions that modify these vars in response to events. + +These are the main concepts to understand how state works in Reflex: + +```python eval +rx.grid( + definition( + "Base Var", + rx.list.unordered( + rx.list.item("Any variable in your app that can change over time."), + rx.list.item( + "Defined as a field in a ", rx.code("State"), " class" + ), + rx.list.item("Can only be modified by event handlers."), + ), + ), + definition( + "Computed Var", + rx.list.unordered( + rx.list.item("Vars that change automatically based on other vars."), + rx.list.item( + "Defined as functions using the ", + rx.code("@rx.var"), + " decorator.", + ), + rx.list.item( + "Cannot be set by event handlers, are always recomputed when the state changes." + ), + ), + ), + definition( + "Event Trigger", + rx.list.unordered( + rx.list.item( + "A user interaction that triggers an event, such as a button click." + ), + rx.list.item( + "Defined as special component props, such as ", + rx.code("on_click"), + ".", + ), + rx.list.item("Can be used to trigger event handlers."), + ), + ), + definition( + "Event Handlers", + rx.list.unordered( + rx.list.item( + "Functions that update the state in response to events." + ), + rx.list.item( + "Defined as methods in the ", rx.code("State"), " class." + ), + rx.list.item( + "Can be called by event triggers, or by other event handlers." + ), + ), + ), + margin_bottom="1em", + spacing="2", + columns="2", +) +``` + +## Example + +Here is a example of how to use state within a Reflex app. +Click the text to change its color. + +```python demo exec +class ExampleState(rx.State): + + # A base var for the list of colors to cycle through. + colors: list[str] = ["black", "red", "green", "blue", "purple"] + + # A base var for the index of the current color. + index: int = 0 + + @rx.event + def next_color(self): + """An event handler to go to the next color.""" + # Event handlers can modify the base vars. + # Here we reference the base vars `colors` and `index`. + self.index = (self.index + 1) % len(self.colors) + + @rx.var + def color(self)-> str: + """A computed var that returns the current color.""" + # Computed vars update automatically when the state changes. + return self.colors[self.index] + + +def index(): + return rx.heading( + "Welcome to Reflex!", + # Event handlers can be bound to event triggers. + on_click=ExampleState.next_color, + # State vars can be bound to component props. + color=ExampleState.color, + _hover={"cursor": "pointer"}, + ) +``` + +The base vars are `colors` and `index`. They are the only vars in the app that +may be directly modified within event handlers. + +There is a single computed var, `color`, that is a function of the base vars. It +will be computed automatically whenever the base vars change. + +The heading component links its `on_click` event to the +`ExampleState.next_color` event handler, which increments the color index. + +```md alert success +# With Reflex, you never have to write an API. + +All interactions between the frontend and backend are handled through events. +``` + +```md alert info +# State vs. Instance? + +When building the UI of your app, reference vars and event handlers via the state class (`ExampleState`). + +When writing backend event handlers, access and set vars via the instance (`self`). +``` + +```md alert warning +# Cannot print a State var. + +The code `print(ExampleState.index)` will not work because the State var values are only known at compile time. +``` + +## Client States + +Each user who opens your app has a unique ID and their own copy of the state. +This means that each user can interact with the app and modify the state +independently of other users. + +Because Reflex internally creates a new instance of the state for each user, your code should +never directly initialize a state class. + +```md alert info +# Try opening an app in multiple tabs to see how the state changes independently. +``` + +All user state is stored on the server, and all event handlers are executed on +the server. Reflex uses websockets to send events to the server, and to send +state updates back to the client. + +## Helper Methods + +Similar to backend vars, any method defined in a State class that begins with an +underscore `_` is considered a helper method. Such methods are not usable as +event triggers, but may be called from other event handler methods within the +state. + +Functionality that should only be available on the backend, such as an +authenticated action, should use helper methods to ensure it is not accidentally +or maliciously triggered by the client. diff --git a/docs/state_structure/component_state.md b/docs/state_structure/component_state.md new file mode 100644 index 00000000000..174f90d52b8 --- /dev/null +++ b/docs/state_structure/component_state.md @@ -0,0 +1,233 @@ +```python exec +import reflex as rx +from pcweb.pages.docs import events, ui, vars +``` + +# Component State + +_New in version 0.4.6_. + +Defining a subclass of `rx.ComponentState` creates a special type of state that is tied to an +instance of a component, rather than existing globally in the app. A Component State combines +[UI code]({ui.overview.path}) with state [Vars]({vars.base_vars.path}) and +[Event Handlers]({events.events_overview.path}), +and is useful for creating reusable components which operate independently of each other. + +```md alert warning +# ComponentState cannot be used inside `rx.foreach()` as it will only create one state instance for all elements in the loop. Each iteration of the foreach will share the same state, which may lead to unexpected behavior. +``` + +## Using ComponentState + +```python demo exec +class ReusableCounter(rx.ComponentState): + count: int = 0 + + @rx.event + def set_count(self, value: int): + self.count = value + + @rx.event + def increment(self): + self.count += 1 + + @rx.event + def decrement(self): + self.count -= 1 + + @classmethod + def get_component(cls, **props): + return rx.hstack( + rx.button("Decrement", on_click=cls.decrement), + rx.text(cls.count), + rx.button("Increment", on_click=cls.increment), + **props, + ) + +reusable_counter = ReusableCounter.create + +def multiple_counters(): + return rx.vstack( + reusable_counter(), + reusable_counter(), + reusable_counter(), + ) +``` + +The vars and event handlers defined on the `ReusableCounter` +class are treated similarly to a normal State class, but will be scoped to the component instance. Each time a +`reusable_counter` is created, a new state class for that instance of the component is also created. + +The `get_component` classmethod is used to define the UI for the component and link it up to the State, which +is accessed via the `cls` argument. Other states may also be referenced by the returned component, but +`cls` will always be the instance of the `ComponentState` that is unique to the component being returned. + +## Passing Props + +Similar to a normal Component, the `ComponentState.create` classmethod accepts the arbitrary +`*children` and `**props` arguments, and by default passes them to your `get_component` classmethod. +These arguments may be used to customize the component, either by applying defaults or +passing props to certain subcomponents. + +```python eval +rx.divider() +``` + +In the following example, we implement an editable text component that allows the user to click on +the text to turn it into an input field. If the user does not provide their own `value` or `on_change` +props, then the defaults defined in the `EditableText` class will be used. + +```python demo exec +class EditableText(rx.ComponentState): + text: str = "Click to edit" + original_text: str + editing: bool = False + + @rx.event + def set_text(self, value: str): + self.text = value + + @rx.event + def start_editing(self, original_text: str): + self.original_text = original_text + self.editing = True + + @rx.event + def stop_editing(self): + self.editing = False + self.original_text = "" + + @classmethod + def get_component(cls, **props): + # Pop component-specific props with defaults before passing **props + value = props.pop("value", cls.text) + on_change = props.pop("on_change", cls.set_text) + cursor = props.pop("cursor", "pointer") + + # Set the initial value of the State var. + initial_value = props.pop("initial_value", None) + if initial_value is not None: + # Update the pydantic model to use the initial value as default. + cls.__fields__["text"].default = initial_value + + # Form elements for editing, saving and reverting the text. + edit_controls = rx.hstack( + rx.input( + value=value, + on_change=on_change, + **props, + ), + rx.icon_button( + rx.icon("x"), + on_click=[ + on_change(cls.original_text), + cls.stop_editing, + ], + type="button", + color_scheme="red", + ), + rx.icon_button(rx.icon("check")), + align="center", + width="100%", + ) + + # Return the text or the form based on the editing Var. + return rx.cond( + cls.editing, + rx.form( + edit_controls, + on_submit=lambda _: cls.stop_editing(), + ), + rx.text( + value, + on_click=cls.start_editing(value), + cursor=cursor, + **props, + ), + ) + + +editable_text = EditableText.create + + +def editable_text_example(): + return rx.vstack( + editable_text(), + editable_text(initial_value="Edit me!", color="blue"), + editable_text(initial_value="Reflex is fun", font_family="monospace", width="100%"), + ) +``` + +```python eval +rx.divider() +``` + +Because this `EditableText` component is designed to be reusable, it can handle the case +where the `value` and `on_change` are linked to a normal global state. + +```python exec +# Hack because flexdown re-inits modules +EditableText._per_component_state_instance_count = 4 +``` + +```python demo exec +class EditableTextDemoState(rx.State): + value: str = "Global state text" + + @rx.event + def set_value(self, value: str): + self.value = value + +def editable_text_with_global_state(): + return rx.vstack( + editable_text(value=EditableTextDemoState.value, on_change=EditableTextDemoState.set_value), + rx.text(EditableTextDemoState.value.upper()), + ) +``` + +## Accessing the State + +The underlying state class of a `ComponentState` is accessible via the `.State` attribute. To use it, +assign an instance of the component to a local variable, then include that instance in the page. + +```python exec +# Hack because flexdown re-inits modules +ReusableCounter._per_component_state_instance_count = 4 +``` + +```python demo exec +def counter_sum(): + counter1 = reusable_counter() + counter2 = reusable_counter() + return rx.vstack( + rx.text(f"Total: {counter1.State.count + counter2.State.count}"), + counter1, + counter2, + ) +``` + +```python eval +rx.divider() +``` + +Other components can also affect a `ComponentState` by referencing its event handlers or vars +via the `.State` attribute. + +```python exec +# Hack because flexdown re-inits modules +ReusableCounter._per_component_state_instance_count = 6 +``` + +```python demo exec +def extended_counter(): + counter1 = reusable_counter() + return rx.vstack( + counter1, + rx.hstack( + rx.icon_button(rx.icon("step_back"), on_click=counter1.State.set_count(0)), + rx.icon_button(rx.icon("plus"), on_click=counter1.State.increment), + rx.button("Double", on_click=counter1.State.set_count(counter1.State.count * 2)), + rx.button("Triple", on_click=counter1.State.set_count(counter1.State.count * 3)), + ), + ) +``` diff --git a/docs/state_structure/mixins.md b/docs/state_structure/mixins.md new file mode 100644 index 00000000000..566f50de8f1 --- /dev/null +++ b/docs/state_structure/mixins.md @@ -0,0 +1,329 @@ +```python exec +import reflex as rx +from pcweb.templates.docpage import definition +``` + +# State Mixins + +State mixins allow you to define shared functionality that can be reused across multiple State classes. This is useful for creating reusable components, shared business logic, or common state patterns. + +## What are State Mixins? + +A state mixin is a State class marked with `mixin=True` that cannot be instantiated directly but can be inherited by other State classes. Mixins provide a way to share: + +- Base variables +- Computed variables +- Event handlers +- Backend variables + +## Basic Mixin Definition + +To create a state mixin, inherit from `rx.State` and pass `mixin=True`: + +```python demo exec +class CounterMixin(rx.State, mixin=True): + count: int = 0 + + @rx.var + def count_display(self) -> str: + return f"Count: {self.count}" + + @rx.event + def increment(self): + self.count += 1 + +class MyState(CounterMixin, rx.State): + name: str = "App" + +def counter_example(): + return rx.vstack( + rx.heading(MyState.name), + rx.text(MyState.count_display), + rx.button("Increment", on_click=MyState.increment), + spacing="4", + align="center", + ) +``` + +In this example, `MyState` automatically inherits the `count` variable, `count_display` computed variable, and `increment` event handler from `CounterMixin`. + +## Multiple Mixin Inheritance + +You can inherit from multiple mixins to combine different pieces of functionality: + +```python demo exec +class TimestampMixin(rx.State, mixin=True): + last_updated: str = "" + + @rx.event + def update_timestamp(self): + import datetime + self.last_updated = datetime.datetime.now().strftime("%H:%M:%S") + +class LoggingMixin(rx.State, mixin=True): + log_messages: list[str] = [] + + @rx.event + def log_message(self, message: str): + self.log_messages.append(message) + +class CombinedState(CounterMixin, TimestampMixin, LoggingMixin, rx.State): + app_name: str = "Multi-Mixin App" + + @rx.event + def increment_with_log(self): + self.increment() + self.update_timestamp() + self.log_message(f"Count incremented to {self.count}") + +def multi_mixin_example(): + return rx.vstack( + rx.heading(CombinedState.app_name), + rx.text(CombinedState.count_display), + rx.text(f"Last updated: {CombinedState.last_updated}"), + rx.button("Increment & Log", on_click=CombinedState.increment_with_log), + rx.cond( + CombinedState.log_messages.length() > 0, + rx.vstack( + rx.foreach( + CombinedState.log_messages[-3:], + rx.text + ), + spacing="1" + ), + rx.text("No logs yet") + ), + spacing="4", + align="center", + ) +``` + +## Backend Variables in Mixins + +Mixins can also include backend variables (prefixed with `_`) that are not sent to the client: + +```python demo exec +class DatabaseMixin(rx.State, mixin=True): + _db_connection: dict = {} # Backend only + user_count: int = 0 # Sent to client + + @rx.event + def fetch_user_count(self): + # Simulate database query + self.user_count = len(self._db_connection.get("users", [])) + +class AppState(DatabaseMixin, rx.State): + app_title: str = "User Management" + +def database_example(): + return rx.vstack( + rx.heading(AppState.app_title), + rx.text(f"User count: {AppState.user_count}"), + rx.button("Fetch Users", on_click=AppState.fetch_user_count), + spacing="4", + align="center", + ) +``` + +Backend variables are useful for storing sensitive data, database connections, or other server-side state that shouldn't be exposed to the client. + +## Computed Variables in Mixins + +Computed variables in mixins work the same as in regular State classes: + +```python demo exec +class FormattingMixin(rx.State, mixin=True): + value: float = 0.0 + + @rx.var + def formatted_value(self) -> str: + return f"${self.value:.2f}" + + @rx.var + def is_positive(self) -> bool: + return self.value > 0 + +class PriceState(FormattingMixin, rx.State): + product_name: str = "Widget" + + @rx.event + def set_price(self, price: str): + try: + self.value = float(price) + except ValueError: + self.value = 0.0 + +def formatting_example(): + return rx.vstack( + rx.heading(f"Product: {PriceState.product_name}"), + rx.text(f"Price: {PriceState.formatted_value}"), + rx.text(f"Positive: {PriceState.is_positive}"), + rx.input( + placeholder="Enter price", + on_blur=PriceState.set_price, + ), + spacing="4", + align="center", + ) +``` + +## Nested Mixin Inheritance + +Mixins can inherit from other mixins to create hierarchical functionality: + +```python demo exec +class BaseMixin(rx.State, mixin=True): + base_value: str = "base" + +class ExtendedMixin(BaseMixin, mixin=True): + extended_value: str = "extended" + + @rx.var + def combined_value(self) -> str: + return f"{self.base_value}-{self.extended_value}" + +class FinalState(ExtendedMixin, rx.State): + final_value: str = "final" + +def nested_mixin_example(): + return rx.vstack( + rx.text(f"Base: {FinalState.base_value}"), + rx.text(f"Extended: {FinalState.extended_value}"), + rx.text(f"Combined: {FinalState.combined_value}"), + rx.text(f"Final: {FinalState.final_value}"), + spacing="4", + align="center", + ) +``` + +This pattern allows you to build complex functionality by composing simpler mixins. + +## Best Practices + +```md alert info +# Mixin Design Guidelines + +- **Single Responsibility**: Each mixin should have a focused purpose +- **Avoid Deep Inheritance**: Keep mixin hierarchies shallow for clarity +- **Document Dependencies**: If mixins depend on specific variables, document them +- **Test Mixins**: Create test cases for mixin functionality +- **Naming Convention**: Use descriptive names ending with "Mixin" +``` + +## Limitations + +```md alert warning +# Important Limitations + +- Mixins cannot be instantiated directly - they must be inherited by concrete State classes +- Variable name conflicts between mixins are resolved by method resolution order (MRO) +- Mixins cannot override methods from the base State class +- The `mixin=True` parameter is required when defining a mixin +``` + +## Common Use Cases + +State mixins are particularly useful for: + +- **Form Validation**: Shared validation logic across forms +- **UI State Management**: Common modal, loading, or notification patterns +- **Logging**: Centralized logging and debugging +- **API Integration**: Shared HTTP client functionality +- **Data Formatting**: Consistent data presentation across components + +```python demo exec +class ValidationMixin(rx.State, mixin=True): + errors: dict[str, str] = {} + is_loading: bool = False + + @rx.event + def validate_email(self, email: str) -> bool: + if "@" not in email or "." not in email: + self.errors["email"] = "Invalid email format" + return False + self.errors.pop("email", None) + return True + + @rx.event + def validate_required(self, field: str, value: str) -> bool: + if not value.strip(): + self.errors[field] = f"{field.title()} is required" + return False + self.errors.pop(field, None) + return True + + @rx.event + def clear_errors(self): + self.errors = {} + +class ContactFormState(ValidationMixin, rx.State): + name: str = "" + email: str = "" + message: str = "" + + def set_name(self, value: str): + self.name = value + + def set_email(self, value: str): + self.email = value + + def set_message(self, value: str): + self.message = value + + @rx.event + def submit_form(self): + self.clear_errors() + valid_name = self.validate_required("name", self.name) + valid_email = self.validate_email(self.email) + valid_message = self.validate_required("message", self.message) + + if valid_name and valid_email and valid_message: + self.is_loading = True + yield rx.sleep(1) + self.is_loading = False + self.name = "" + self.email = "" + self.message = "" + +def validation_example(): + return rx.vstack( + rx.heading("Contact Form"), + rx.input( + placeholder="Name", + value=ContactFormState.name, + on_change=ContactFormState.set_name, + ), + rx.cond( + ContactFormState.errors.contains("name"), + rx.text(ContactFormState.errors["name"], color="red"), + ), + rx.input( + placeholder="Email", + value=ContactFormState.email, + on_change=ContactFormState.set_email, + ), + rx.cond( + ContactFormState.errors.contains("email"), + rx.text(ContactFormState.errors["email"], color="red"), + ), + rx.text_area( + placeholder="Message", + value=ContactFormState.message, + on_change=ContactFormState.set_message, + ), + rx.cond( + ContactFormState.errors.contains("message"), + rx.text(ContactFormState.errors["message"], color="red"), + ), + rx.button( + "Submit", + on_click=ContactFormState.submit_form, + loading=ContactFormState.is_loading, + ), + spacing="4", + align="center", + width="300px", + ) +``` + +By using state mixins, you can create modular, reusable state logic that keeps your application organized and reduces code duplication. diff --git a/docs/state_structure/overview.md b/docs/state_structure/overview.md new file mode 100644 index 00000000000..9c5bdf02e0d --- /dev/null +++ b/docs/state_structure/overview.md @@ -0,0 +1,234 @@ +```python exec +import reflex as rx +from typing import Any +``` + +# Substates + +Substates allow you to break up your state into multiple classes to make it more manageable. This is useful as your app +grows, as it allows you to think about each page as a separate entity. Substates also allow you to share common state +resources, such as variables or event handlers. + +When a particular state class becomes too large, breaking it up into several substates can bring performance +benefits by only loading parts of the state that are used to handle a certain event. + +## Multiple States + +One common pattern is to create a substate for each page in your app. +This allows you to think about each page as a separate entity, and makes it easier to manage your code as your app grows. + +To create a substate, simply inherit from `rx.State` multiple times: + +```python +# index.py +import reflex as rx + +class IndexState(rx.State): + """Define your main state here.""" + data: str = "Hello World" + + +@rx.page() +def index(): + return rx.box(rx.text(IndexState.data)) + +# signup.py +import reflex as rx + + +class SignupState(rx.State): + """Define your signup state here.""" + username: str = "" + password: str = "" + + def signup(self): + ... + + +@rx.page() +def signup_page(): + return rx.box( + rx.input(value=SignupState.username), + rx.input(value=SignupState.password), + ) + +# login.py +import reflex as rx + +class LoginState(rx.State): + """Define your login state here.""" + username: str = "" + password: str = "" + + def login(self): + ... + +@rx.page() +def login_page(): + return rx.box( + rx.input(value=LoginState.username), + rx.input(value=LoginState.password), + ) +``` + +Separating the states is purely a matter of organization. You can still access the state from other pages by importing the state class. + +```python +# index.py + +import reflex as rx + +from signup import SignupState + +... + +def index(): + return rx.box( + rx.text(IndexState.data), + rx.input(value=SignupState.username), + rx.input(value=SignupState.password), + ) +``` + +## Accessing Arbitrary States + +An event handler in a particular state can access and modify vars in another state instance by calling +the `get_state` async method and passing the desired state class. If the requested state is not already loaded, +it will be loaded and deserialized on demand. + +In the following example, the `GreeterState` accesses the `SettingsState` to get the `salutation` and uses it +to update the `message` var. + +Notably, the widget that sets the salutation does NOT have to load the `GreeterState` when handling the +input `on_change` event, which improves performance. + +```python demo exec +class SettingsState(rx.State): + salutation: str = "Hello" + + def set_salutation(self, value: str): + self.salutation = value + +def set_salutation_popover(): + return rx.popover.root( + rx.popover.trigger( + rx.icon_button(rx.icon("settings")), + ), + rx.popover.content( + rx.input( + value=SettingsState.salutation, + on_change=SettingsState.set_salutation + ), + ), + ) + + +class GreeterState(rx.State): + message: str = "" + + @rx.event + async def handle_submit(self, form_data: dict[str, Any]): + settings = await self.get_state(SettingsState) + self.message = f"{settings.salutation} {form_data['name']}" + + +def index(): + return rx.vstack( + rx.form( + rx.vstack( + rx.hstack( + rx.input(placeholder="Name", id="name"), + set_salutation_popover(), + ), + rx.button("Submit"), + ), + reset_on_submit=True, + on_submit=GreeterState.handle_submit, + ), + rx.text(GreeterState.message), + ) +``` + +### Accessing Individual Var Values + +In addition to accessing entire state instances with `get_state`, you can retrieve individual variable values using the `get_var_value` method: + +```python +# Access a var value from another state +value = await self.get_var_value(OtherState.some_var) +``` + +This async method is particularly useful when you only need a specific value rather than loading the entire state. Using `get_var_value` can be more efficient than `get_state` when: + +1. You only need to access a single variable from another state +2. The other state contains a large amount of data +3. You want to avoid loading unnecessary data into memory + +Here's an example that demonstrates how to use `get_var_value` to access data between states: + +```python demo exec +# Define a state that holds a counter value +class CounterState(rx.State): + # This variable will be accessed from another state + count: int = 0 + + @rx.event + async def increment(self): + # Increment the counter when the button is clicked + self.count += 1 + +# Define a separate state that will display information +class DisplayState(rx.State): + # This will show the current count value + message: str = "" + + @rx.event + async def show_count(self): + # Use get_var_value to access just the count variable from CounterState + # This is more efficient than loading the entire state with get_state + current = await self.get_var_value(CounterState.count) + self.message = f"Current count: {current}" + +def var_value_example(): + return rx.vstack( + rx.heading("Get Var Value Example"), + rx.hstack( + # This button calls DisplayState.show_count to display the current count + rx.button("Get Count Value", on_click=DisplayState.show_count), + # This button calls CounterState.increment to increase the counter + rx.button("Increment", on_click=CounterState.increment), + ), + # Display the message from DisplayState + rx.text(DisplayState.message), + width="100%", + align="center", + spacing="4", + ) +``` + +In this example: +1. We have two separate states: `CounterState` which manages a counter, and `DisplayState` which displays information +2. When you click "Increment", it calls `CounterState.increment()` to increase the counter value +3. When you click "Show Count", it calls `DisplayState.show_count()` which uses `get_var_value` to retrieve just the count value from `CounterState` without loading the entire state +4. The current count is then displayed in the message + +This pattern is useful when you have multiple states that need to interact with each other but don't need to access all of each other's data. + +If the var is not retrievable, `get_var_value` will raise an `UnretrievableVarValueError`. + +## Performance Implications + +When an event handler is called, Reflex will load the data not only for the substate containing +the event handler, but also all of its substates and parent states as well. +If a state has a large number of substates or contains a large amount of data, it can slow down processing +of events associated with that state. + +For optimal performance, keep a flat structure with most substate classes directly inheriting from `rx.State`. +Only inherit from another state when the parent holds data that is commonly used by the substate. +Implementing different parts of the app with separate, unconnected states ensures that only the necessary +data is loaded for processing events for a particular page or component. + +Avoid defining computed vars inside a state that contains a large amount of data, as +states with computed vars are always loaded to ensure the values are recalculated. +When using computed vars, it better to define them in a state that directly inherits from `rx.State` and +does not have other states inheriting from it, to avoid loading unnecessary data. diff --git a/docs/state_structure/shared_state.md b/docs/state_structure/shared_state.md new file mode 100644 index 00000000000..c8586cd1bc3 --- /dev/null +++ b/docs/state_structure/shared_state.md @@ -0,0 +1,216 @@ +```python exec +import reflex as rx +from pcweb.templates.docpage import definition +``` + +# Shared State + +_New in version 0.8.23_. + +Defining a subclass of `rx.SharedState` creates a special type of state that may be shared by multiple clients. Shared State is useful for creating real-time collaborative applications where multiple users need to see and interact with the same data simultaneously. + +## Using SharedState + +An `rx.SharedState` subclass behaves similarly to a normal `rx.State` subclass and will be private to each client until it is explicitly linked to a given token. Once linked, any changes made to the Shared State by one client will be propagated to all other clients sharing the same token. + +```md alert info +# What should be used as a token? + +A token can be any string that uniquely identifies a group of clients that should share the same state. Common choices include room IDs, document IDs, or user group IDs. Ensure that the token is securely generated and managed to prevent unauthorized access to shared state. +``` + +```md alert warning +# Linked token cannot contain underscore (_) characters. + +Underscore characters are currently used as an internal delimiter for tokens and will raise an exception if used for linked states. + +This is a temporary restriction and will be removed in a future release. +``` + +### Linking Shared State + +An `rx.SharedState` subclass can be linked to a token using the `_link_to` method, which is async and returns the linked state instance. After linking, subsequent events triggered against the shared state will be executed in the context of the linked state. To unlink from the token, return the result of awaiting the `_unlink` method. + +To try out the collaborative counter example, open this page in a second or third browser tab and click the "Link" button. You should see the count increment in all tabs when you click the "Increment" button in any of them. + +```python demo exec +class CollaborativeCounter(rx.SharedState): + count: int = 0 + + @rx.event + async def toggle_link(self): + if self._linked_to: + return await self._unlink() + else: + linked_state = await self._link_to("shared-global-counter") + linked_state.count += 1 # Increment count on link + + @rx.var + def is_linked(self) -> bool: + return bool(self._linked_to) + +def shared_state_example(): + return rx.vstack( + rx.text(f"Collaborative Count: {CollaborativeCounter.count}"), + rx.cond( + CollaborativeCounter.is_linked, + rx.button("Unlink", on_click=CollaborativeCounter.toggle_link), + rx.button("Link", on_click=CollaborativeCounter.toggle_link), + ), + rx.button("Increment", on_click=CollaborativeCounter.set_count(CollaborativeCounter.count + 1)), + ) +``` + +```md alert info +# Computed vars may reference SharedState + +Computed vars in other states may reference shared state data using `get_state`, just like private states. This allows private states to provide personalized views of shared data. + +Whenever the shared state is updated, any computed vars depending on it will be re-evaluated in the context of each client's private state. +``` + +### Identifying Clients + +Each client linked to a shared state can be uniquely identified by their `self.router.session.client_token`. Shared state events should _never_ rely on identifiers passed in as parameters, as these can be spoofed from the client. Instead, always use the `client_token` to identify the client triggering the event. + +```python demo exec +import uuid + +class SharedRoom(rx.SharedState): + shared_room: str = rx.LocalStorage() + _users: dict[str, str] = {} + + @rx.var + def user_list(self) -> str: + return ", ".join(self._users.values()) + + @rx.event + async def join(self, username: str): + if not self.shared_room: + self.shared_room = f"shared-room-{uuid.uuid4()}" + linked_state = await self._link_to(self.shared_room) + linked_state._users[self.router.session.client_token] = username + + @rx.event + async def leave(self): + if self._linked_to: + return await self._unlink() + + +class PrivateState(rx.State): + @rx.event + def handle_submit(self, form_data: dict): + return SharedRoom.join(form_data["username"]) + + @rx.var + async def user_in_room(self) -> bool: + shared_state = await self.get_state(SharedRoom) + return self.router.session.client_token in shared_state._users + + +def shared_room_example(): + return rx.vstack( + rx.text("Shared Room"), + rx.text(f"Users: {SharedRoom.user_list}"), + rx.cond( + PrivateState.user_in_room, + rx.button("Leave Room", on_click=SharedRoom.leave), + rx.form( + rx.input(placeholder="Enter your name", name="username"), + rx.button("Join Room"), + on_submit=PrivateState.handle_submit, + ), + ), + ) +``` + +```md alert warning +# Store sensitive data in backend-only vars with an underscore prefix + +Shared State data is synchronized to all linked clients, so avoid storing sensitive information (e.g., client_tokens, user credentials, personal data) in frontend vars, which would expose them to all users and allow them to be modified outside of explicit event handlers. Instead, use backend-only vars (prefixed with an underscore) to keep sensitive data secure on the server side and provide controlled access through event handlers and computed vars. +``` + +### Introspecting Linked Clients + +An `rx.SharedState` subclass has two attributes for determining link status and peers, which are updated during linking and unlinking, and come with some caveats. + +**`_linked_to: str`** + +Provides the token that the state is currently linked to, or empty string if not linked. + +This attribute is only set on the linked state instance returned by `_link_to`. It will be an empty string on any unlinked shared state instances. However, if another state links to a client's private token, then the `_linked_to` attribute will be set to the client's token rather than an empty string. + +When `_linked_to` equals `self.router.session.client_token`, it is assumed that the current client is unlinked, but another client has linked to this client's private state. Although this is possible, it is generally discouraged to link shared states to private client tokens. + +**`_linked_from: set[str]`** + +A set of client tokens that are currently linked to this shared state instance. + +This attribute is only updated during `_link_to` and `_unlink` calls. In situations where unlinking occurs otherwise, such as client disconnects, `self.reset()` is called, or state expires on the backend, `_linked_from` may contain stale client tokens that are no longer linked. These can be cleaned periodically by checking if the tokens still exist in `app.event_namespace.token_to_sid`. + +## Guidelines and Best Practices + +### Keep Shared State Minimal + +When defining a shared state, aim to keep it as minimal as possible. Only include the data and methods that need to be shared between clients. This helps reduce complexity and potential synchronization issues. + +Linked states are always loaded into the tree for each event on each linked client and large states take longer to serialize and transmit over the network. Because linked states are regularly loaded in the context of many clients, they incur higher lock contention, so minimizing loading time also reduces lock waiting time for other clients. + +### Prefer Backend-Only Vars in Shared State + +A shared state should primarily use backend-only vars (prefixed with an underscore) to store shared data. Often, not all users of the shared state need visibility into all of the data in the shared state. Use computed vars to provide sanitized access to shared data as needed. + +```python +from typing import Literal + +class SharedGameState(rx.SharedState): + # Sensitive user metadata stored in backend-only variable. + _players: dict[str, Literal["X", "O"]] = {} + + @rx.event + def make_move(self, x: int, y: int): + # Identify users by client_token, never by arguments passed to the event. + player_token = self.router.session.client_token + player_piece = self._players.get(player_token) +``` + +```md alert warning +# Do Not Trust Event Handler Arguments + +The client can send whatever data it wants to event handlers, so never rely on arguments passed to event handlers for sensitive information such as user identity or permissions. Always use secure identifiers like `self.router.session.client_token` to identify the client triggering the event. +``` + +### Expose Per-User Data via Private States + +If certain data in the shared state needs to be personalized for each user, prefer to expose that data through computed vars defined in private states. This allows each user to have their own view of the shared data without exposing sensitive information to other users. It also reduces the amount of unrelated data sent to each client and improves caching performance by keeping each user's view cached in their own private state, rather than always recomputing the shared state vars for each user that needs to have their information updated. + +Use async computed vars with `get_state` to access shared state data from private states. + +```python +class UserGameState(rx.State): + @rx.var + async def player_piece(self) -> str | None: + shared_state = await self.get_state(SharedGameState) + return shared_state._players.get(self.router.session.client_token) +``` + +### Use Dynamic Routes for Linked Tokens + +It is often convenient to define dynamic routes that include the linked token as part of the URL path. This allows users to easily share links to specific shared state instances. The dynamic route can use `on_load` to link the shared state to the token extracted from the URL. + +```python +class SharedRoom(rx.SharedState): + async def on_load(self): + # `self.room_id` is the automatically defined dynamic route var. + await self._link_to(self.room_id.replace("_", "-") or "default-room") + + +def room_page(): ... + + +app.add_route( + room_page, + path="/room/[room_id]", + on_load=SharedRoom.on_load, +) +``` diff --git a/docs/styling/common-props.md b/docs/styling/common-props.md new file mode 100644 index 00000000000..f1fb70c2faa --- /dev/null +++ b/docs/styling/common-props.md @@ -0,0 +1,227 @@ +# Style and Layout Props + +```python exec +import reflex as rx +from pcweb.styles.styles import get_code_style, cell_style +from pcweb.styles.colors import c_color + +props = { + "align": { + "description": "In a flex, it controls the alignment of items on the cross axis and in a grid layout, it controls the alignment of items on the block axis within their grid area (equivalent to align_items)", + "values": ["stretch", "center", "start", "end", "flex-start", "baseline"], + "link": "https://developer.mozilla.org/en-US/docs/Web/CSS/align-items", + }, + "backdrop_filter": { + "description": "Lets you apply graphical effects such as blurring or color shifting to the area behind an element", + "values": ["url(commonfilters.svg#filter)", "blur(2px)", "hue-rotate(120deg)", "drop-shadow(4px 4px 10px blue)"], + "link": "https://developer.mozilla.org/en-US/docs/Web/CSS/backdrop-filter", + }, + "background": { + "description": "Sets all background style properties at once, such as color, image, origin and size, or repeat method (equivalent to bg)", + "values": ["green", "radial-gradient(crimson, skyblue)", "no-repeat url('../lizard.png')"], + "link": "https://developer.mozilla.org/en-US/docs/Web/CSS/background", + }, + "background_color": { + "description": "Sets the background color of an element", + "values": ["brown", "rgb(255, 255, 128)", "#7499ee"], + "link": "https://developer.mozilla.org/en-US/docs/Web/CSS/background-color", + }, + "background_image": { + "description": "Sets one or more background images on an element", + "values": ["url('../lizard.png')", "linear-gradient(#e66465, #9198e5)"], + "link": "https://developer.mozilla.org/en-US/docs/Web/CSS/background-image", + }, + "border": { + "description": "Sets an element's border, which sets the values of border_width, border_style, and border_color.", + "values": ["solid", "dashed red", "thick double #32a1ce", "4mm ridge rgba(211, 220, 50, .6)"], + "link": "https://developer.mozilla.org/en-US/docs/Web/CSS/border", + }, + "border_top / border_bottom / border_right / border_left": { + "description": "Sets an element's top / bottom / right / left border. It sets the values of border-(top / bottom / right / left)-width, border-(top / bottom / right / left)-style and border-(top / bottom / right / left)-color", + "values": ["solid", "dashed red", "thick double #32a1ce", "4mm ridge rgba(211, 220, 50, .6)"], + "link": "https://developer.mozilla.org/en-US/docs/Web/CSS/border-bottom", + }, + "border_color": { + "description": "Sets the color of an element's border (each side can be set individually using border_top_color, border_right_color, border_bottom_color, and border_left_color)", + "values": ["red", "red #32a1ce", "red rgba(170, 50, 220, .6) green", "red yellow green transparent"], + "link": "https://developer.mozilla.org/en-US/docs/Web/CSS/border-color", + }, + "border_radius": { + "description": "Rounds the corners of an element's outer border edge and you can set a single radius to make circular corners, or two radii to make elliptical corners", + "values": ["30px", "25% 10%", "10% 30% 50% 70%", "10% / 50%"], + "link": "https://developer.mozilla.org/en-US/docs/Web/CSS/border-radius", + }, + "border_width": { + "description": "Sets the width of an element's border", + "values": ["thick", "1em", "4px 1.25em", "0 4px 8px 12px"], + "link": "https://developer.mozilla.org/en-US/docs/Web/CSS/border-width", + }, + "box_shadow": { + "description": "Adds shadow effects around an element's frame. You can set multiple effects separated by commas. A box shadow is described by X and Y offsets relative to the element, blur and spread radius, and color", + "values": ["10px 5px 5px red", "60px -16px teal", "12px 12px 2px 1px rgba(0, 0, 255, .2)", "3px 3px red, -1em 0 .4em olive;"], + "link": "https://developer.mozilla.org/en-US/docs/Web/CSS/box-shadow", + }, + + "color": { + "description": "Sets the foreground color value of an element's text", + "values": ["rebeccapurple", "rgb(255, 255, 128)", "#00a400"], + "link": "https://developer.mozilla.org/en-US/docs/Web/CSS/color", + }, + "display": { + "description": "Sets whether an element is treated as a block or inline box and the layout used for its children, such as flow layout, grid or flex", + "values": ["block", "inline", "inline-block", "flex", "inline-flex", "grid", "inline-grid", "flow-root"], + "link": "https://developer.mozilla.org/en-US/docs/Web/CSS/display", + }, + "flex_grow": { + "description": " Sets the flex grow factor, which specifies how much of the flex container's remaining space should be assigned to the flex item's main size", + "values": ["1", "2", "3"], + "link": "https://developer.mozilla.org/en-US/docs/Web/CSS/flex-grow", + }, + "height": { + "description": "Sets an element's height", + "values": ["150px", "20em", "75%", "auto"], + "link": "https://developer.mozilla.org/en-US/docs/Web/CSS/height", + }, + "justify": { + "description": "Defines how the browser distributes space between and around content items along the main-axis of a flex container, and the inline axis of a grid container (equivalent to justify_content)", + "values": ["start", "center", "flex-start", "space-between", "space-around", "space-evenly", "stretch"], + "link": "https://developer.mozilla.org/en-US/docs/Web/CSS/justify-content", + }, + "margin": { + "description": "Sets the margin area (creates extra space around an element) on all four sides of an element", + "values": ["1em", "5% 0", "10px 50px 20px", "10px 50px 20px 0"], + "link": "https://developer.mozilla.org/en-US/docs/Web/CSS/margin", + }, + "margin_x / margin_y": { + "description": "Sets the margin area (creates extra space around an element) along the x-axis / y-axis and a positive value places it farther from its neighbors, while a negative value places it closer", + "values": ["1em", "10%", "10px"], + "link": "https://developer.mozilla.org/en-US/docs/Web/CSS/margin", + }, + "margin_top / margin_right / margin_bottom / margin_left ": { + "description": "Sets the margin area (creates extra space around an element) on the top / right / bottom / left of an element", + "values": ["1em", "10%", "10px"], + "link": "https://developer.mozilla.org/en-US/docs/Web/CSS/margin-top", + }, + "max_height / min_height": { + "description": "Sets the maximum / minimum height of an element and prevents the used value of the height property from becoming larger / smaller than the value specified for max_height / min_height", + "values": ["150px", "7em", "75%"], + "link": "https://developer.mozilla.org/en-US/docs/Web/CSS/max-height", + }, + "max_width / min_width": { + "description": "Sets the maximum / minimum width of an element and prevents the used value of the width property from becoming larger / smaller than the value specified for max_width / min_width", + "values": ["150px", "20em", "75%"], + "link": "https://developer.mozilla.org/en-US/docs/Web/CSS/max-width", + }, + "padding": { + "description": "Sets the padding area (creates extra space within an element) on all four sides of an element at once", + "values": ["1em", "10px 50px 30px 0", "0", "10px 50px 20px"], + "link": "https://developer.mozilla.org/en-US/docs/Web/CSS/padding", + }, + "padding_x / padding_y": { + "description": "Creates extra space within an element along the x-axis / y-axis", + "values": ["1em", "10%", "10px"], + "link": "https://developer.mozilla.org/en-US/docs/Web/CSS/padding", + }, + "padding_top / padding_right / padding_bottom / padding_left ": { + "description": "Sets the height of the padding area on the top / right / bottom / left of an element", + "values": ["1em", "10%", "20px"], + "link": "https://developer.mozilla.org/en-US/docs/Web/CSS/padding-top", + }, + "position": { + "description": "Sets how an element is positioned in a document and the top, right, bottom, and left properties determine the final location of positioned elements", + "values": ["static", "relative", "absolute", "fixed", "sticky"], + "link": "https://developer.mozilla.org/en-US/docs/Web/CSS/position", + }, + "text_align": { + "description": "Sets the horizontal alignment of the inline-level content inside a block element or table-cell box", + "values": ["start", "end", "center", "justify", "left", "right"], + "link": "https://developer.mozilla.org/en-US/docs/Web/CSS/text-align", + }, + "text_wrap": { + "description": "Controls how text inside an element is wrapped", + "values": ["wrap", "nowrap", "balance", "pretty"], + "link": "https://developer.mozilla.org/en-US/docs/Web/CSS/text-wrap", + }, + "top / bottom / right / left": { + "description": "Sets the vertical / horizontal position of a positioned element. It does not effect non-positioned elements.", + "values": ["0", "4em", "10%", "20px"], + "link": "https://developer.mozilla.org/en-US/docs/Web/CSS/top", + }, + "width": { + "description": "Sets an element's width", + "values": ["150px", "20em", "75%", "auto"], + "link": "https://developer.mozilla.org/en-US/docs/Web/CSS/width", + }, + "white_space": { + "description": "Sets how white space inside an element is handled", + "values": ["normal", "nowrap", "pre", "break-spaces"], + "link": "https://developer.mozilla.org/en-US/docs/Web/CSS/white-space", + }, + "word_break": { + "description": "Sets whether line breaks appear wherever the text would otherwise overflow its content box", + "values": ["normal", "break-all", "keep-all", "break-word"], + "link": "https://developer.mozilla.org/en-US/docs/Web/CSS/word-break", + }, + "z_index": { + "description": "Sets the z-order of a positioned element and its descendants or flex and grid items, and overlapping elements with a larger z-index cover those with a smaller one", + "values": ["auto", "1", "5", "200"], + "link": "https://developer.mozilla.org/en-US/docs/Web/CSS/z-index", + }, + + +} + + +def show_props(key, props_dict): + prop_details = props_dict[key] + return rx.table.row( + rx.table.cell( + rx.link( + rx.hstack( + rx.code(key, style=get_code_style("violet")), + rx.icon("square_arrow_out_up_right", color=c_color("slate", 9), size=15, flex_shrink="0"), + align="center" + ), + href=prop_details["link"], + is_external=True, + ), + justify="start",), + rx.table.cell(prop_details["description"], justify="start", style=cell_style), + rx.table.cell(rx.hstack(*[rx.code(value, style=get_code_style("violet")) for value in prop_details["values"]], flex_wrap="wrap"), justify="start",), + justify="center", + align="center", + + ) + +``` + +Any [CSS](https://developer.mozilla.org/en-US/docs/Web/CSS) prop can be used in a component in Reflex. This is a short list of the most commonly used props. To see all CSS props that can be used check out this [documentation](https://developer.mozilla.org/en-US/docs/Web/CSS). + +Hyphens in CSS property names may be replaced by underscores to use as valid python identifiers, i.e. the CSS prop `z-index` would be used as `z_index` in Reflex. + +```python eval +rx.table.root( + rx.table.header( + rx.table.row( + rx.table.column_header_cell( + "Prop", justify="center" + ), + rx.table.column_header_cell( + "Description", + justify="center", + + ), + rx.table.column_header_cell( + "Potential Values", + justify="center", + ), + ) + ), + rx.table.body( + *[show_props(key, props) for key in props] + ), + width="100%", + padding_x="0", + size="1", +) +``` \ No newline at end of file diff --git a/docs/styling/custom-stylesheets.md b/docs/styling/custom-stylesheets.md new file mode 100644 index 00000000000..b57f5426fd6 --- /dev/null +++ b/docs/styling/custom-stylesheets.md @@ -0,0 +1,191 @@ +```python exec +import reflex as rx +from pcweb.pages.docs import assets +``` + +# Custom Stylesheets + +Reflex allows you to add custom stylesheets. Simply pass the URLs of the stylesheets to `rx.App`: + +```python +app = rx.App( + stylesheets=[ + "https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.1.1/animate.min.css", + ], +) +``` + +## Local Stylesheets + +You can also add local stylesheets. Just put the stylesheet under [`assets/`]({assets.upload_and_download_files.path}) and pass the path to the stylesheet to `rx.App`: + +```python +app = rx.App( + stylesheets=[ + "/styles.css", # This path is relative to assets/ + ], +) +``` + +```md alert warning +# Always use a leading slash (/) when referencing files in the assets directory. +Without a leading slash the path is considered relative to the current page route and may +not work for routes containing more than one path component, like `/blog/my-cool-post`. +``` + + +## Styling with CSS + +You can use CSS variables directly in your Reflex app by passing them alongside the appropriae props. Create a `style.css` file inside the `assets` folder with the following lines: + +```css +:root { + --primary-color: blue; + --accent-color: green; +} +``` + +Then, after referencing the CSS file within the `stylesheets` props of `rx.App`, you can access the CSS props directly like this + +```python +app = rx.App( + theme=rx.theme(appearance="light"), + stylesheets=["/style.css"], +) +app.add_page( + rx.center( + rx.text("CSS Variables!"), + width="100%", + height="100vh", + bg="var(--primary-color)", + ), + "/", +) +``` + +## SASS/SCSS Support + +Reflex supports SASS/SCSS stylesheets alongside regular CSS. This allows you to use more advanced styling features like variables, nesting, mixins, and more. + +### Using SASS/SCSS Files + +To use SASS/SCSS files in your Reflex app: + +1. Create a `.sass` or `.scss` file in your `assets` directory +2. Reference the file in your `rx.App` configuration just like you would with CSS files + +```python +app = rx.App( + stylesheets=[ + "/styles.scss", # This path is relative to assets/ + "/sass/main.sass", # You can organize files in subdirectories + ], +) +``` + +Reflex automatically detects the file extension and compiles these files to CSS using the `libsass` package. + +### Example SASS/SCSS File + +Here's an example of a SASS file (`assets/styles.scss`) that demonstrates some of the features: + +```scss +// Variables +$primary-color: #3498db; +$secondary-color: #2ecc71; +$padding: 16px; + +// Nesting +.container { + background-color: $primary-color; + padding: $padding; + + .button { + background-color: $secondary-color; + padding: $padding / 2; + + &:hover { + opacity: 0.8; + } + } +} + +// Mixins +@mixin flex-center { + display: flex; + justify-content: center; + align-items: center; +} + +.centered-box { + @include flex-center; + height: 100px; +} +``` + +### Dependency Requirement + +The `libsass` package is required for SASS/SCSS compilation. If it's not installed, Reflex will show an error message. You can install it with: + +```bash +pip install "libsass>=0.23.0" +``` + +This package is included in the default Reflex installation, so you typically don't need to install it separately. + +## Fonts + +You can take advantage of Reflex's support for custom stylesheets to add custom fonts to your app. + +In this example, we will use the [JetBrains Mono]({"https://fonts.google.com/specimen/JetBrains+Mono"}) font from Google Fonts. First, add the stylesheet with the font to your app. You can get this link from the "Get embed code" section of the Google font page. + +```python +app = rx.App( + stylesheets=[ + "https://fonts.googleapis.com/css2?family=JetBrains+Mono:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;1,100;1,200;1,300;1,400;1,500;1,600;1,700&display=swap", + ], +) +``` + +Then you can use the font in your component by setting the `font_family` prop. + +```python demo +rx.text( + "Check out my font", + font_family="JetBrains Mono", + font_size="1.5em", +) +``` + +## Local Fonts + +By making use of the two previous points, we can also make a stylesheet that allow you to use a font hosted on your server. + +If your font is called `MyFont.otf`, copy it in `assets/fonts`. + +Now we have the font ready, let's create the stylesheet `myfont.css`. + +```css +@font-face { + font-family: MyFont; + src: url("/fonts/MyFont.otf") format("opentype"); +} + +@font-face { + font-family: MyFont; + font-weight: bold; + src: url("/fonts/MyFont.otf") format("opentype"); +} +``` + +Add the reference to your new Stylesheet in your App. + +```python +app = rx.App( + stylesheets=[ + "/fonts/myfont.css", # This path is relative to assets/ + ], +) +``` + +And that's it! You can now use `MyFont` like any other FontFamily to style your components. diff --git a/docs/styling/layout.md b/docs/styling/layout.md new file mode 100644 index 00000000000..84282b14f83 --- /dev/null +++ b/docs/styling/layout.md @@ -0,0 +1,155 @@ +```python exec +import reflex as rx +``` + +# Layout Components + +Layout components such as `rx.flex`, `rx.container`, `rx.box`, etc. are used to organize and structure the visual presentation of your application. This page gives a breakdown of when and how each of these components might be used. + +```md video https://youtube.com/embed/ITOZkzjtjUA?start=3311&end=3853 +# Video: Example of Laying Out the Main Content of a Page +``` + +## Box + +`rx.box` is a generic component that can apply any CSS style to its children. It's a building block that can be used to apply a specific layout or style property. + +**When to use:** Use `rx.box` when you need to apply specific styles or constraints to a part of your interface. + + +```python demo +rx.box( + rx.box( + "CSS color", + background_color="red", + border_radius="2px", + width="50%", + margin="4px", + padding="4px", + ), + rx.box( + "Radix Color", + background_color=rx.color("tomato", 3), + border_radius="5px", + width="80%", + margin="12px", + padding="12px", + ), + text_align="center", + width="100%", +) +``` + +## Stack + +`rx.stack` is a layout component that arranges its children in a single column or row, depending on the direction. It’s useful for consistent spacing between elements. + +**When to use:** Use `rx.stack` when you need to lay out a series of components either vertically or horizontally with equal spacing. + +```python demo +rx.flex( + rx.stack( + rx.box( + "Example", + bg="orange", + border_radius="3px", + width="20%", + ), + rx.box( + "Example", + bg="lightblue", + border_radius="3px", + width="30%", + ), + flex_direction="row", + width="100%", + ), + rx.stack( + rx.box( + "Example", + bg="orange", + border_radius="3px", + width="20%", + ), + rx.box( + "Example", + bg="lightblue", + border_radius="3px", + width="30%", + ), + flex_direction="column", + width="100%", + ), + width="100%", +) +``` + +## Flex + +The `rx.flex` component is used to create a flexible box layout, inspired by [CSS Flexbox](https://developer.mozilla.org/en-US/docs/Learn/CSS/CSS_layout/Flexbox). It's ideal for designing a layout where the size of the items can grow and shrink dynamically based on the available space. + +**When to use:** Use `rx.flex` when you need a responsive layout that adjusts the size and position of child components dynamically. + + +```python demo +rx.flex( + rx.card("Card 1"), + rx.card("Card 2"), + rx.card("Card 3"), + spacing="2", + width="100%", +) +``` + + +## Grid + +`rx.grid` components are used to create complex responsive layouts based on a grid system, similar to [CSS Grid Layout](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_grid_layout). + +**When to use:** Use `rx.grid` when dealing with complex layouts that require rows and columns, especially when alignment and spacing among multiple axes are needed. + +```python demo +rx.grid( + rx.foreach( + rx.Var.range(12), + lambda i: rx.card(f"Card {i + 1}", height="10vh"), + ), + columns="3", + spacing="4", + width="100%", +) +``` + +## Container + +The `rx.container` component typically provides padding and fixes the maximum width of the content inside it, often used to center content on large screens. + +**When to use:** Use `rx.container` for wrapping your application’s content in a centered block with some padding. + +```python demo +rx.box( + rx.container( + rx.card( + "This content is constrained to a max width of 448px.", + width="100%", + ), + size="1", + ), + rx.container( + rx.card( + "This content is constrained to a max width of 688px.", + width="100%", + ), + size="2", + ), + rx.container( + rx.card( + "This content is constrained to a max width of 880px.", + width="100%", + ), + size="3", + ), + background_color="var(--gray-3)", + width="100%", +) +``` \ No newline at end of file diff --git a/docs/styling/overview.md b/docs/styling/overview.md new file mode 100644 index 00000000000..137f7f1bc67 --- /dev/null +++ b/docs/styling/overview.md @@ -0,0 +1,185 @@ +```python exec +import reflex as rx +from pcweb.pages.docs import styling, library +``` + +# Styling + +Reflex components can be styled using the full power of [CSS]({"https://www.w3schools.com/css/"}). + +There are three main ways to add style to your app and they take precedence in the following order: + +1. **Inline:** Styles applied to a single component instance. +2. **Component:** Styles applied to components of a specific type. +3. **Global:** Styles applied to all components. + +```md alert success +# Style keys can be any valid CSS property name. +To be consistent with Python standards, you can specify keys in `snake_case`. +``` + +## Global Styles + +You can pass a style dictionary to your app to apply base styles to all components. + +For example, you can set the default font family and font size for your app here just once rather than having to set it on every component. + +```python +style = { + "font_family": "Comic Sans MS", + "font_size": "16px", +} + +app = rx.App(style=style) +``` + +## Component Styles + +In your style dictionary, you can also specify default styles for specific component types or arbitrary CSS classes and IDs. + +```python +style = { + # Set the selection highlight color globally. + "::selection": { + "background_color": accent_color, + }, + # Apply global css class styles. + ".some-css-class": { + "text_decoration": "underline", + }, + # Apply global css id styles. + "#special-input": \{"width": "20vw"}, + # Apply styles to specific components. + rx.text: { + "font_family": "Comic Sans MS", + }, + rx.divider: { + "margin_bottom": "1em", + "margin_top": "0.5em", + }, + rx.heading: { + "font_weight": "500", + }, + rx.code: { + "color": "green", + }, +} + +app = rx.App(style=style) +``` + +Using style dictionaries like this, you can easily create a consistent theme for your app. + + +```md alert warning +# Watch out for underscores in class names and IDs +Reflex automatically converts `snake_case` identifiers into `camelCase` format when applying styles. To ensure consistency, it is recommended to use the dash character or camelCase identifiers in your own class names and IDs. To style third-party libraries relying on underscore class names, an external stylesheet should be used. See [custom stylesheets]({styling.custom_stylesheets.path}) for how to reference external CSS files. +``` + +## Inline Styles + +Inline styles apply to a single component instance. They are passed in as regular props to the component. + +```python demo +rx.text( + "Hello World", + background_image="linear-gradient(271.68deg, #EE756A 0.75%, #756AEE 88.52%)", + background_clip="text", + font_weight="bold", + font_size="2em", +) +``` + +Children components inherit inline styles unless they are overridden by their own inline styles. + +```python demo +rx.box( + rx.hstack( + rx.button("Default Button"), + rx.button("Red Button", color="red"), + ), + color="blue", +) +``` + +### Style Prop + +Inline styles can also be set with a `style` prop. This is useful for reusing styles between multiple components. + +```python exec +text_style = { + "color": "green", + "font_family": "Comic Sans MS", + "font_size": "1.2em", + "font_weight": "bold", + "box_shadow": "rgba(240, 46, 170, 0.4) 5px 5px, rgba(240, 46, 170, 0.3) 10px 10px", +} +``` + +```python +text_style={text_style} +``` + +```python demo +rx.vstack( + rx.text("Hello", style=text_style), + rx.text("World", style=text_style), +) +``` + +```python exec +style1 = { + "color": "green", + "font_family": "Comic Sans MS", + "border_radius": "10px", + "background_color": "rgb(107,99,246)", +} +style2 = { + "color": "white", + "border": "5px solid #EE756A", + "padding": "10px", +} +``` + +```python +style1={style1} +style2={style2} +``` + +```python demo +rx.box( + "Multiple Styles", + style=[style1, style2], +) +``` + +The style dictionaries are applied in the order they are passed in. This means that styles defined later will override styles defined earlier. + + +## Theming + +As of Reflex 'v0.4.0', you can now theme your Reflex web apps. To learn more checkout the [Theme docs]({styling.theming.path}). + +The `Theme` component is used to change the theme of the application. The `Theme` can be set directly in your rx.App. + +```python +app = rx.App( + theme=rx.theme( + appearance="light", has_background=True, radius="large", accent_color="teal" + ) +) +``` + +Additionally you can modify the theme of your app through using the `Theme Panel` component which can be found in the [Theme Panel docs]({library.other.theme.path}). + +## Special Styles + +We support all of Chakra UI's [pseudo styles]({"https://v2.chakra-ui.com/docs/styled-system/style-props#pseudo"}). + +Below is an example of text that changes color when you hover over it. + +```python demo +rx.box( + rx.text("Hover Me", _hover={"color": "red"}), +) +``` diff --git a/docs/styling/responsive.md b/docs/styling/responsive.md new file mode 100644 index 00000000000..ea7e5d2177c --- /dev/null +++ b/docs/styling/responsive.md @@ -0,0 +1,172 @@ +```python exec +import reflex as rx +from pcweb.styles.styles import get_code_style, cell_style +``` + +# Responsive + +Reflex apps can be made responsive to look good on mobile, tablet, and desktop. + +You can pass a list of values to any style property to specify its value on different screen sizes. + +```python demo +rx.text( + "Hello World", + color=["orange", "red", "purple", "blue", "green"], +) +``` + +The text will change color based on your screen size. If you are on desktop, try changing the size of your browser window to see the color change. + +_New in 0.5.6_ + +Responsive values can also be specified using `rx.breakpoints`. Each size maps to a corresponding key, the value of which will be applied when the screen size is greater than or equal to the named breakpoint. + +```python demo +rx.text( + "Hello World", + color=rx.breakpoints( + initial="orange", + sm="purple", + lg="green", + ), +) +``` + +Custom breakpoints in CSS units can be mapped to values per component using a dictionary instead of named parameters. + +```python +rx.text( + "Hello World", + color=rx.breakpoints({ + "0px": "orange", + "48em": "purple", + "80em": "green", + }), +) +``` + +For the Radix UI components' fields that supports responsive value, you can also use `rx.breakpoints` (note that custom breakpoints and list syntax aren't supported). + +```python demo +rx.grid( + rx.foreach( + list(range(6)), + lambda _: rx.box(bg_color="#a7db76", height="100px", width="100px") + ), + columns=rx.breakpoints( + initial="2", + sm="4", + lg="6" + ), + spacing="4" +) +``` + +## Setting Defaults + +The default breakpoints are shown below. + +```python eval +rx.table.root( + rx.table.header( + rx.table.row( + rx.table.column_header_cell("Size"), + rx.table.column_header_cell("Width"), + ), + ), + rx.table.body( + rx.table.row( + rx.table.cell(rx.code("initial", style=get_code_style("violet"))), + rx.table.cell("0px", style=cell_style), + ), + rx.table.row( + rx.table.cell(rx.code("xs", style=get_code_style("violet"))), + rx.table.cell("30em", style=cell_style), + ), + rx.table.row( + rx.table.cell(rx.code("sm", style=get_code_style("violet"))), + rx.table.cell("48em", style=cell_style), + ), + rx.table.row( + rx.table.cell(rx.code("md", style=get_code_style("violet"))), + rx.table.cell("62em", style=cell_style), + ), + rx.table.row( + rx.table.cell(rx.code("lg", style=get_code_style("violet"))), + rx.table.cell("80em", style=cell_style), + ), + rx.table.row( + rx.table.cell(rx.code("xl", style=get_code_style("violet"))), + rx.table.cell("96em", style=cell_style), + ), + ), + margin_bottom="1em", +) +``` + +You can customize them using the style property. + +```python +app = rx.App(style=\{"breakpoints": ["520px", "768px", "1024px", "1280px", "1640px"]\}) +``` + +## Showing Components Based on Display + +A common use case for responsive is to show different components based on the screen size. + +Reflex provides useful helper components for this. + +```python demo +rx.vstack( + rx.desktop_only( + rx.text("Desktop View"), + ), + rx.tablet_only( + rx.text("Tablet View"), + ), + rx.mobile_only( + rx.text("Mobile View"), + ), + rx.mobile_and_tablet( + rx.text("Visible on Mobile and Tablet"), + ), + rx.tablet_and_desktop( + rx.text("Visible on Desktop and Tablet"), + ), +) +``` + +## Specifying Display Breakpoints + +You can specify the breakpoints to use for the responsive components by using the `display` style property. + +```python demo +rx.vstack( + rx.text( + "Hello World", + color="green", + display=["none", "none", "none", "none", "flex"], + ), + rx.text( + "Hello World", + color="blue", + display=["none", "none", "none", "flex", "flex"], + ), + rx.text( + "Hello World", + color="red", + display=["none", "none", "flex", "flex", "flex"], + ), + rx.text( + "Hello World", + color="orange", + display=["none", "flex", "flex", "flex", "flex"], + ), + rx.text( + "Hello World", + color="yellow", + display=["flex", "flex", "flex", "flex", "flex"], + ), +) +``` diff --git a/docs/styling/tailwind.md b/docs/styling/tailwind.md new file mode 100644 index 00000000000..fe47f6a450b --- /dev/null +++ b/docs/styling/tailwind.md @@ -0,0 +1,256 @@ +```python exec +import reflex as rx +from pcweb.pages.docs import library + +``` + +# Tailwind + +Reflex supports [Tailwind CSS]({"https://tailwindcss.com/"}) through a plugin system that provides better control and supports multiple Tailwind versions. + +## Plugin-Based Configuration + +The recommended way to use Tailwind CSS is through the plugin system: + +```python +import reflex as rx + +config = rx.Config( + app_name="myapp", + plugins=[ + rx.plugins.TailwindV4Plugin(), + ], +) +``` + +You can customize the Tailwind configuration by passing a config dictionary to the plugin: + +```python +import reflex as rx + +tailwind_config = { + "plugins": ["@tailwindcss/typography"], + "theme": { + "extend": { + "colors": { + "primary": "#3b82f6", + "secondary": "#64748b", + } + } + }, +} + +config = rx.Config( + app_name="myapp", + plugins=[ + rx.plugins.TailwindV4Plugin(tailwind_config), + ], +) +``` + +```md alert info +## Migration from Legacy Configuration + +If you're currently using the legacy `tailwind` configuration parameter, you should migrate to using the plugin system: + +**Old approach (legacy):** +``````python +config = rx.Config( + app_name="my_app", + tailwind={ + "plugins": ["@tailwindcss/typography"], + "theme": {"extend": {"colors": {"primary": "#3b82f6"}}}, + }, +) +`````` + +**New approach (plugin-based):** +``````python +tailwind_config = { + "plugins": ["@tailwindcss/typography"], + "theme": {"extend": {"colors": {"primary": "#3b82f6"}}}, +} + +config = rx.Config( + app_name="my_app", + plugins=[ + rx.plugins.TailwindV4Plugin(tailwind_config), + ], +) +`````` +``` + +### Choosing Between Tailwind Versions + +Reflex supports both Tailwind CSS v3 and v4: + +- **TailwindV4Plugin**: The recommended choice for new projects. Includes the latest features and performance improvements and is used by default in new Reflex templates. +- **TailwindV3Plugin**: Still supported for existing projects. Use this if you need compatibility with older Tailwind configurations. + +```python +# For Tailwind CSS v4 (recommended for new projects) +config = rx.Config( + app_name="myapp", + plugins=[rx.plugins.TailwindV4Plugin()], +) + +# For Tailwind CSS v3 (existing projects) +config = rx.Config( + app_name="myapp", + plugins=[rx.plugins.TailwindV3Plugin()], +) +``` + +All Tailwind configuration options are supported. + +You can use any of the [utility classes]({"https://tailwindcss.com/docs/utility-first"}) under the `class_name` prop: + +```python demo +rx.box( + "Hello World", + class_name="text-4xl text-center text-blue-500", +) +``` + +## Disabling Tailwind + +To disable Tailwind in your project, simply don't include any Tailwind plugins in your configuration. This will prevent Tailwind styles from being applied to your application. + +## Custom theme + +You can integrate custom Tailwind themes within your Reflex app as well. The setup process is similar to the CSS Styling method mentioned above, with only a few minor variations. + +Begin by creating a CSS file inside your `assets` folder. Inside the CSS file, include the following Tailwind directives: + +```css +@tailwind base; +@tailwind components; +@tailwind utilities; + +:root { + --background: blue; + --foreground: green; +} + +.dark { + --background: darkblue; + --foreground: lightgreen; +} +``` + +We define a couple of custom CSS variables (`--background` and `--foreground`) that will be used throughout your app for styling. These variables can be dynamically updated based on the theme. + +Tailwind defaults to light mode, but to handle dark mode, you can define a separate set of CSS variables under the `.dark` class. + +Tailwind Directives (`@tailwind base`, `@tailwind components`, `@tailwind utilities`): These are essential Tailwind CSS imports that enable the default base styles, components, and utility classes. + +Next, you'll need to configure Tailwind in your `rxconfig.py` file to ensure that the Reflex app uses your custom Tailwind setup. + +```python +import reflex as rx + +tailwind_config = { + "plugins": ["@tailwindcss/typography"], + "theme": { + "extend": { + "colors": { + "background": "var(--background)", + "foreground": "var(--foreground)" + }, + } + }, +} + +config = rx.Config( + app_name="app", + plugins=[ + rx.plugins.TailwindV4Plugin(tailwind_config), + ], +) +``` + +In the theme section, we're extending the default Tailwind theme to include custom colors. Specifically, we're referencing the CSS variables (`--background` and `--foreground`) that were defined earlier in your CSS file. + +The `rx.Config` object is used to initialize and configure your Reflex app. Here, we're passing the `tailwind_config` dictionary to ensure Tailwind's custom setup is applied to the app. + +Finally, to apply your custom styles and Tailwind configuration, you need to reference the CSS file you created in your `assets` folder inside the `rx.App` setup. This will allow you to use the custom properties (variables) directly within your Tailwind classes. + +In your `app.py` (or main application file), make the following changes: + +```python +app = rx.App( + theme=rx.theme(appearance="light"), + stylesheets=["/style.css"], +) +app.add_page( + rx.center( + rx.text("Tailwind & Reflex!"), + class_name="bg-background w-full h-[100vh]", + ), + "/", +) +``` + +The `bg-background` class uses the `--background` variable (defined in the CSS file), which will be applied as the background color. + +## Dynamic Styling + +You can style a component based of a condition using `rx.cond` or `rx.match`. + +```python demo exec +class TailwindState(rx.State): + active = False + + @rx.event + def toggle_active(self): + self.active = not self.active + +def tailwind_demo(): + return rx.el.div( + rx.el.button( + "Click me", + on_click=TailwindState.toggle_active, + class_name=( + "px-4 py-2 text-white rounded-md", + rx.cond( + TailwindState.active, + "bg-red-500", + "bg-blue-500", + ), + ), + ), + ) +``` + +## Using Tailwind Classes from the State + +When using Tailwind with Reflex, it's important to understand that class names must be statically defined in your code for Tailwind to properly compile them. If you dynamically generate class names from state variables or functions at runtime, Tailwind won't be able to detect these classes during the build process, resulting in missing styles in your application. + +For example, this won't work correctly because the class names are defined in the state: + +```python demo exec +class TailwindState(rx.State): + active = False + + @rx.var + def button_class(self) -> str: + return "bg-accent" if self.active else "bg-secondary" + + @rx.event + def toggle_active(self): + self.active = not self.active + +def tailwind_demo(): + return rx.el.button( + f"Click me: {TailwindState.active}", + class_name=TailwindState.button_class, + on_click=TailwindState.toggle_active, + ) +``` + +## Using Tailwind with Reflex Core Components + +Reflex core components are built on Radix Themes, which means they come with pre-defined styling. When you apply Tailwind classes to these components, you may encounter styling conflicts or unexpected behavior as the Tailwind styles compete with the built-in Radix styles. + +For the best experience when using Tailwind CSS in your Reflex application, we recommend using the lower-level `rx.el` components. These components don't have pre-applied styles, giving you complete control over styling with Tailwind classes without any conflicts. Check the list of HTML components [here]({library.other.html.path}). + diff --git a/docs/styling/theming.md b/docs/styling/theming.md new file mode 100644 index 00000000000..83697108fe5 --- /dev/null +++ b/docs/styling/theming.md @@ -0,0 +1,206 @@ +```python exec +import reflex as rx +from pcweb.constants import REFLEX_ASSETS_CDN +from pcweb.pages.docs import library +from pcweb.styles.styles import get_code_style_rdx, cell_style +``` + +# Theming + +As of Reflex `v0.4.0`, you can now theme your Reflex applications. The core of our theming system is directly based on the [Radix Themes](https://www.radix-ui.com) library. This allows you to easily change the theme of your application along with providing a default light and dark theme. Themes cause all the components to have a unified color appearance. + +## Overview + +The `Theme` component is used to change the theme of the application. The `Theme` can be set directly in your rx.App. + +```python +app = rx.App( + theme=rx.theme( + appearance="light", has_background=True, radius="large", accent_color="teal" + ) +) +``` + +Here are the props that can be passed to the `rx.theme` component: + +```python eval +rx.table.root( + rx.table.header( + rx.table.row( + rx.table.column_header_cell("Name", class_name="table-header"), + rx.table.column_header_cell("Type", class_name="table-header"), + rx.table.column_header_cell("Description", class_name="table-header"), + ), + ), + rx.table.body( + rx.table.row( + rx.table.row_header_cell(rx.code("has_background", style=get_code_style_rdx("violet"), class_name="code-style")), + rx.table.cell(rx.code("Bool", style=get_code_style_rdx("gray"), class_name="code-style")), + rx.table.cell("Whether to apply the themes background color to the theme node. Defaults to True.", style=cell_style), + ), + rx.table.row( + rx.table.row_header_cell(rx.code("appearance", style=get_code_style_rdx("violet"), class_name="code-style")), + rx.table.cell(rx.code('"inherit" | "light" | "dark"', style=get_code_style_rdx("gray"), class_name="code-style")), + rx.table.cell("The appearance of the theme. Can be 'light' or 'dark'. Defaults to 'light'.", style=cell_style), + ), + rx.table.row( + rx.table.row_header_cell(rx.code("accent_color", style=get_code_style_rdx("violet"), class_name="code-style")), + rx.table.cell(rx.code("Str", style=get_code_style_rdx("gray"), class_name="code-style")), + rx.table.cell("The primary color used for default buttons, typography, backgrounds, etc.", style=cell_style), + ), + rx.table.row( + rx.table.row_header_cell(rx.code("gray_color", style=get_code_style_rdx("violet"), class_name="code-style")), + rx.table.cell(rx.code("Str", style=get_code_style_rdx("gray"), class_name="code-style")), + rx.table.cell("The secondary color used for default buttons, typography, backgrounds, etc.", style=cell_style), + ), + rx.table.row( + rx.table.row_header_cell(rx.code("panel_background", style=get_code_style_rdx("violet"), class_name="code-style")), + rx.table.cell(rx.code('"solid" | "translucent"', style=get_code_style_rdx("gray"), class_name="code-style")), + rx.table.cell('Whether panel backgrounds are translucent: "solid" | "translucent" (default).', style=cell_style), + ), + rx.table.row( + rx.table.row_header_cell(rx.code("radius", style=get_code_style_rdx("violet"), class_name="code-style")), + rx.table.cell(rx.code('"none" | "small" | "medium" | "large" | "full"', style=get_code_style_rdx("gray"))), + rx.table.cell("The radius of the theme. Can be 'small', 'medium', or 'large'. Defaults to 'medium'.", style=cell_style), + ), + rx.table.row( + rx.table.row_header_cell(rx.code("scaling", style=get_code_style_rdx("violet"), class_name="code-style")), + rx.table.cell(rx.code('"90%" | "95%" | "100%" | "105%" | "110%"', style=get_code_style_rdx("gray"), class_name="code-style")), + rx.table.cell("Scale of all theme items.", style=cell_style), + ), + ), + variant="surface", + margin_y="1em", +) + +``` + +Additionally you can modify the theme of your app through using the `Theme Panel` component which can be found in the [Theme Panel docs]({library.other.theme.path}). + + +## Colors + +### Color Scheme + +On a high-level, component `color_scheme` inherits from the color specified in the theme. This means that if you change the theme, the color of the component will also change. Available colors can be found [here](https://www.radix-ui.com/colors). + +You can also specify the `color_scheme` prop. + +```python demo +rx.flex( + rx.button( + "Hello World", + color_scheme="tomato", + ), + rx.button( + "Hello World", + color_scheme="teal", + ), + spacing="2" +) +``` + +### Shades + +Sometime you may want to use a specific shade of a color from the theme. This is recommended vs using a hex color directly as it will automatically change when the theme changes appearance change from light/dark. + + +To access a specific shade of color from the theme, you can use the `rx.color`. When switching to light and dark themes, the color will automatically change. Shades can be accessed by using the color name and the shade number. The shade number ranges from 1 to 12. Additionally, they can have their alpha value set by using the `True` parameter it defaults to `False`. A full list of colors can be found [here](https://www.radix-ui.com/colors). + +```python demo +rx.flex( + rx.button( + "Hello World", + color=rx.color("grass", 1), + background_color=rx.color("grass", 7), + border_color=f"1px solid {rx.color('grass', 1)}", + ), + spacing="2" +) +``` + +```python eval +rx.table.root( + rx.table.header( + rx.table.row( + rx.table.column_header_cell("Name"), + rx.table.column_header_cell("Type"), + rx.table.column_header_cell("Description"), + ), + ), + rx.table.body( + rx.table.row( + rx.table.row_header_cell(rx.code("color", style=get_code_style_rdx("violet"), class_name="code-style")), + rx.table.cell(rx.code("Str", style=get_code_style_rdx("gray"), class_name="code-style")), + rx.table.cell("The color to use. Can be any valid accent color or 'accent' to reference the current theme color.", style=cell_style), + ), + rx.table.row( + rx.table.row_header_cell(rx.code("shade", style=get_code_style_rdx("violet"), class_name="code-style")), + rx.table.cell(rx.link(rx.code('1 - 12', style=get_code_style_rdx("gray"), class_name="code-style"), href="https://www.radix-ui.com/colors")), + rx.table.cell("The shade of the color to use. Defaults to 7.", style=cell_style), + ), + rx.table.row( + rx.table.row_header_cell(rx.code("alpha", style=get_code_style_rdx("violet"), class_name="code-style")), + rx.table.cell(rx.code("Bool", style=get_code_style_rdx("gray"), class_name="code-style")), + rx.table.cell("Whether to use the alpha value of the color. Defaults to False.", style=cell_style), + ) + ), + variant="surface", + margin_y="1em", +) + +``` + +### Regular Colors + +You can also use standard hex, rgb, and rgba colors. + +```python demo +rx.flex( + rx.button( + "Hello World", + color="white", + background_color="#87CEFA", + border="1px solid rgb(176,196,222)", + ), + spacing="2" +) +``` + +## Toggle Appearance + +To toggle between the light and dark mode manually, you can use the `toggle_color_mode` with the desired event trigger of your choice. + +```python + +from reflex.style import toggle_color_mode + + + +def index(): + return rx.button( + "Toggle Color Mode", + on_click=toggle_color_mode, + ) +``` + +## Appearance Conditional Rendering + +To render a different component depending on whether the app is in `light` mode or `dark` mode, you can use the `rx.color_mode_cond` component. The first component will be rendered if the app is in `light` mode and the second component will be rendered if the app is in `dark` mode. + +```python demo +rx.color_mode_cond( + light=rx.image(src=f"{REFLEX_ASSETS_CDN}logos/light/reflex.svg", alt="Reflex Logo light", height="4em"), + dark=rx.image(src=f"{REFLEX_ASSETS_CDN}logos/dark/reflex.svg", alt="Reflex Logo dark", height="4em"), +) +``` + +This can also be applied to props. + +```python demo +rx.button( + "Hello World", + color=rx.color_mode_cond(light="black", dark="white"), + background_color=rx.color_mode_cond(light="white", dark="black"), +) +``` diff --git a/docs/tr/README.md b/docs/tr/README.md deleted file mode 100644 index be888117332..00000000000 --- a/docs/tr/README.md +++ /dev/null @@ -1,248 +0,0 @@ -
    -Reflex Logo -
    - -### **✨ Saf Python'da performanslı, özelleştirilebilir web uygulamaları. Saniyeler içinde dağıtın. ✨** - -[![PyPI version](https://badge.fury.io/py/reflex.svg)](https://badge.fury.io/py/reflex) -![versions](https://img.shields.io/pypi/pyversions/reflex.svg) -[![Documentation](https://img.shields.io/badge/Documentation%20-Introduction%20-%20%23007ec6)](https://reflex.dev/docs/getting-started/introduction) -[![PyPI Downloads](https://static.pepy.tech/badge/reflex)](https://pepy.tech/projects/reflex) -[![Discord](https://img.shields.io/discord/1029853095527727165?color=%237289da&label=Discord)](https://discord.gg/T5WSbC2YtQ) - -
    - ---- - -[English](https://github.com/reflex-dev/reflex/blob/main/README.md) | [简体中文](https://github.com/reflex-dev/reflex/blob/main/docs/zh/zh_cn/README.md) | [繁體中文](https://github.com/reflex-dev/reflex/blob/main/docs/zh/zh_tw/README.md) | [Türkçe](https://github.com/reflex-dev/reflex/blob/main/docs/tr/README.md) | [हिंदी](https://github.com/reflex-dev/reflex/blob/main/docs/in/README.md) | [Português (Brasil)](https://github.com/reflex-dev/reflex/blob/main/docs/pt/pt_br/README.md) | [Italiano](https://github.com/reflex-dev/reflex/blob/main/docs/it/README.md) | [Español](https://github.com/reflex-dev/reflex/blob/main/docs/es/README.md) | [한국어](https://github.com/reflex-dev/reflex/blob/main/docs/kr/README.md) | [日本語](https://github.com/reflex-dev/reflex/blob/main/docs/ja/README.md) | [Deutsch](https://github.com/reflex-dev/reflex/blob/main/docs/de/README.md) | [Persian (پارسی)](https://github.com/reflex-dev/reflex/blob/main/docs/pe/README.md) | [Tiếng Việt](https://github.com/reflex-dev/reflex/blob/main/docs/vi/README.md) - ---- - -# Reflex - -Reflex, saf Python'da tam yığın web uygulamaları oluşturmak için bir kütüphanedir. - -Temel özellikler: - -- **Saf Python** - Uygulamanızın ön uç ve arka uç kısımlarının tamamını Python'da yazın, Javascript öğrenmenize gerek yok. -- **Tam Esneklik** - Reflex ile başlamak kolaydır, ancak karmaşık uygulamalara da ölçeklenebilir. -- **Anında Dağıtım** - Oluşturduktan sonra, uygulamanızı [tek bir komutla](https://reflex.dev/docs/hosting/deploy-quick-start/) dağıtın veya kendi sunucunuzda barındırın. - -Reflex'in perde arkasında nasıl çalıştığını öğrenmek için [mimari sayfamıza](https://reflex.dev/blog/2024-03-21-reflex-architecture/#the-reflex-architecture) göz atın. - -## ⚙️ Kurulum - -Bir terminal açın ve çalıştırın (Python 3.10+ gerekir): - -```bash -pip install reflex -``` - -## 🥳 İlk Uygulamanı Oluştur - -`reflex`'i kurduğunuzda `reflex` komut satırı aracınıda kurmuş olursunuz. - -Kurulumun başarılı olduğunu test etmek için yeni bir proje oluşturun. (`my_app_name`'i proje ismiyle değiştirin.): - -```bash -mkdir my_app_name -cd my_app_name -reflex init -``` - -Bu komut ile birlikte yeni oluşturduğunuz dizinde bir şablon uygulaması oluşturur. - -Uygulamanızı geliştirme modunda başlatabilirsiniz: - -```bash -reflex run -``` - -Uygulamanızın http://localhost:3000 adresinde çalıştığını görmelisiniz. - -Şimdi `my_app_name/my_app_name.py` yolundaki kaynak kodu düzenleyebilirsiniz. Reflex'in hızlı yenileme özelliği vardır, böylece kodunuzu kaydettiğinizde değişikliklerinizi anında görebilirsiniz. - -## 🫧 Örnek Uygulama - -Bir örnek üzerinden gidelim: [DALL·E](https://platform.openai.com/docs/guides/images/image-generation?context=node) kullanarak bir görüntü oluşturma arayüzü oluşturalım. Basit olması açısından, yalnızca [OpenAI API](https://platform.openai.com/docs/api-reference/authentication)'ını kullanıyoruz, ancak bunu yerel olarak çalıştırılan bir ML modeliyle değiştirebilirsiniz. - -  - -
    -A frontend wrapper for DALL·E, shown in the process of generating an image. -
    - -  - -İşte bunu oluşturmak için kodun tamamı. Her şey sadece bir Python dosyasıyla hazırlandı! - -```python -import reflex as rx -import openai - -openai_client = openai.OpenAI() - - -class State(rx.State): - """Uygulama durumu.""" - prompt = "" - image_url = "" - processing = False - complete = False - - def get_image(self): - """Prompt'tan görüntüyü alın.""" - if self.prompt == "": - return rx.window_alert("Prompt Empty") - - self.processing, self.complete = True, False - yield - response = openai_client.images.generate( - prompt=self.prompt, n=1, size="1024x1024" - ) - self.image_url = response.data[0].url - self.processing, self.complete = False, True - - -def index(): - return rx.center( - rx.vstack( - rx.heading("DALL-E", font_size="1.5em"), - rx.input( - placeholder="Enter a prompt..", - on_blur=State.set_prompt, - width="25em", - ), - rx.button( - "Generate Image", - on_click=State.get_image, - width="25em", - loading=State.processing - ), - rx.cond( - State.complete, - rx.image(src=State.image_url, width="20em"), - ), - align="center", - ), - width="100%", - height="100vh", - ) - -# Sayfa ve durumu uygulamaya ekleyin. -app = rx.App() -app.add_page(index, title="Reflex:DALL-E") -``` - -## Daha Detaylı İceleyelim - -
    -DALL-E uygulamasının arka uç ve ön uç kısımları arasındaki farkları açıklama. -
    - -### **Reflex UI** - -UI (Kullanıcı Arayüzü) ile başlayalım. - -```python -def index(): - return rx.center( - ... - ) -``` - -Bu `index` fonkisyonu uygulamanın frontend'ini tanımlar. - -Frontend'i oluşturmak için `center`, `vstack`, `input`, ve `button` gibi farklı bileşenler kullanıyoruz. Karmaşık düzenler oluşturmak için bileşenleri birbirinin içine yerleştirilebiliriz. Ayrıca bunları CSS'nin tüm gücüyle şekillendirmek için anahtar kelime argümanları kullanabilirsiniz. - -Reflex, işinizi kolaylaştırmak için [60'tan fazla dahili bileşen](https://reflex.dev/docs/library) içerir. Aktif olarak yeni bileşen ekliyoruz ve [kendi bileşenlerinizi oluşturmak](https://reflex.dev/docs/wrapping-react/overview/) oldukça kolay. - -### **Durum (State)** - -Reflex arayüzünüzü durumunuzun bir fonksiyonu olarak temsil eder. - -```python -class State(rx.State): - """Uygulama durumu.""" - prompt = "" - image_url = "" - processing = False - complete = False -``` - -Durum (State), bir uygulamadaki değişebilen tüm değişkenleri (vars olarak adlandırılır) ve bunları değiştiren fonksiyonları tanımlar. - -Burada durum `prompt` ve `image_url`inden oluşur. Ayrıca düğmenin ne zaman devre dışı bırakılacağını (görüntü oluşturma sırasında) ve görüntünün ne zaman gösterileceğini belirtmek için `processing` ve `complete` booleanları da vardır. - -### **Olay İşleyicileri (Event Handlers)** - -```python -def get_image(self): - """Prompt'tan görüntüyü alın.""" - if self.prompt == "": - return rx.window_alert("Prompt Empty") - - self.processing, self.complete = True, False - yield - response = openai_client.images.generate( - prompt=self.prompt, n=1, size="1024x1024" - ) - self.image_url = response.data[0].url - self.processing, self.complete = False, True -``` - -Durum içinde, durum değişkenlerini değiştiren olay işleyicileri adı verilen fonksiyonları tanımlarız. Olay işleyicileri, Reflex'te durumu değiştirebilmemizi sağlar. Bir düğmeye tıklamak veya bir metin kutusuna yazı yazmak gibi kullanıcı eylemlerine yanıt olarak çağrılabilirler. Bu eylemlere olay denir. - -DALL·E uygulamamız OpenAI API'ından bu görüntüyü almak için `get_image` adlı bir olay işleyicisine sahiptir. Bir olay işleyicisinin ortasında `yield`ın kullanılması UI'ın güncellenmesini sağlar. Aksi takdirde UI olay işleyicisinin sonunda güncellenecektir. - -### **Yönlendirme (Routing)** - -En sonunda uygulamamızı tanımlıyoruz. - -```python -app = rx.App() -``` - -Uygulamamızın kök dizinine index bileşeninden bir sayfa ekliyoruz. Ayrıca sayfa önizlemesinde/tarayıcı sekmesinde görünecek bir başlık ekliyoruz. - -```python -app.add_page(index, title="DALL-E") -``` - -Daha fazla sayfa ekleyerek çok sayfalı bir uygulama oluşturabilirsiniz. - -## 📑 Kaynaklar - -
    - -📑 [Docs](https://reflex.dev/docs/getting-started/introduction)   |   🗞️ [Blog](https://reflex.dev/blog)   |   📱 [Component Library](https://reflex.dev/docs/library)   |   🖼️ [Templates](https://reflex.dev/templates/)   |   🛸 [Deployment](https://reflex.dev/docs/hosting/deploy-quick-start)   - -
    - -## ✅ Durum - -Reflex, Aralık 2022'de Pynecone adıyla piyasaya sürüldü. - -2025'in başından itibaren, Reflex uygulamaları için en iyi barındırma deneyimini sunmak amacıyla [Reflex Cloud](https://cloud.reflex.dev) hizmete girmiştir. Bunu geliştirmeye ve daha fazla özellik eklemeye devam edeceğiz. - -Reflex'in her hafta yeni sürümleri ve özellikleri geliyor! Güncel kalmak için bu depoyu :star: yıldızlamayı ve :eyes: izlediğinizden emin olun. - -## Katkı - -Her boyuttaki katkıları memnuniyetle karşılıyoruz! Aşağıda Reflex topluluğuna adım atmanın bazı yolları mevcut. - -- **Discord Kanalımıza Katılın**: [Discord'umuz](https://discord.gg/T5WSbC2YtQ), Reflex projeniz hakkında yardım almak ve nasıl katkıda bulunabileceğinizi tartışmak için en iyi yerdir. -- **GitHub Discussions**: Eklemek istediğiniz özellikler veya kafa karıştırıcı, açıklığa kavuşturulması gereken şeyler hakkında konuşmanın harika bir yolu. -- **GitHub Issues**: [Issues](https://github.com/reflex-dev/reflex/issues) hataları bildirmenin mükemmel bir yoludur. Ayrıca mevcut bir sorunu deneyip çözebilir ve bir PR (Pull Requests) gönderebilirsiniz. - -Beceri düzeyiniz veya deneyiminiz ne olursa olsun aktif olarak katkıda bulunacak kişiler arıyoruz. Katkı sağlamak için katkı sağlama rehberimize bakabilirsiniz: [CONTRIBUTING.md](https://github.com/reflex-dev/reflex/blob/main/CONTRIBUTING.md) - -## Hepsi Katkıda Bulunanlar Sayesinde: - - - - - -## Lisans - -Reflex açık kaynaklıdır ve [Apache License 2.0](/LICENSE) altında lisanslıdır. diff --git a/docs/ui/overview.md b/docs/ui/overview.md new file mode 100644 index 00000000000..f59b8ae855d --- /dev/null +++ b/docs/ui/overview.md @@ -0,0 +1,79 @@ +```python exec +from pcweb.pages.docs import components +from pcweb.pages.docs.library import library +import reflex as rx +``` + +# UI Overview + +Components are the building blocks for your app's user interface (UI). They are the visual elements that make up your app, like buttons, text, and images. + +## Component Basics + +Components are made up of children and props. + +```md definition +# Children +* Text or other Reflex components nested inside a component. +* Passed as **positional arguments**. + +# Props +* Attributes that affect the behavior and appearance of a component. +* Passed as **keyword arguments**. +``` + +Let's take a look at the `rx.text` component. + +```python demo +rx.text('Hello World!', color='blue', font_size="1.5em") +``` + +Here `"Hello World!"` is the child text to display, while `color` and `font_size` are props that modify the appearance of the text. + +```md alert success +# Regular Python data types can be passed in as children to components. This is useful for passing in text, numbers, and other simple data types. +``` + +## Another Example + +Now let's take a look at a more complex component, which has other components nested inside it. The `rx.vstack` component is a container that arranges its children vertically with space between them. + +```python demo +rx.vstack( + rx.heading("Sample Form"), + rx.input(placeholder="Name"), + rx.checkbox("Subscribe to Newsletter"), +) +``` + +Some props are specific to a component. For example, the `header` and `content` props of the `rx.accordion.item` component show the heading and accordion content details of the accordion respectively. + +Styling props like `color` are shared across many components. + +```md alert info +# You can find all the props for a component by checking its documentation page in the [component library]({library.path}). +``` + +## Pages + +Reflex apps are organized into pages, each of which maps to a different URL. + +Pages are defined as functions that return a component. By default, the function name will be used as the path, but you can also specify a route explicitly. + +```python +def index(): + return rx.text('Root Page') + + +def about(): + return rx.text('About Page') + + +app = rx.App() +app.add_page(index, route="/") +app.add_page(about, route="/about") +``` + +In this example we add a page called `index` at the root route. +If you `reflex run` the app, you will see the `index` page at `http://localhost:3000`. +Similarly, the `about` page will be available at `http://localhost:3000/about`. diff --git a/docs/utility_methods/exception_handlers.md b/docs/utility_methods/exception_handlers.md new file mode 100644 index 00000000000..80ed6ed51b1 --- /dev/null +++ b/docs/utility_methods/exception_handlers.md @@ -0,0 +1,43 @@ +# Exception handlers + +_Added in v0.5.7_ + +Exceptions handlers are functions that can be assigned to your app to handle exceptions that occur during the application runtime. +They are useful for customizing the response when an error occurs, logging errors, and performing cleanup tasks. + +## Types + +Reflex support two type of exception handlers `frontend_exception_handler` and `backend_exception_handler`. + +They are used to handle exceptions that occur in the `frontend` and `backend` respectively. + +The `frontend` errors are coming from the JavaScript side of the application, while `backend` errors are coming from the the event handlers on the Python side. + +## Register an Exception Handler + +To register an exception handler, assign it to `app.frontend_exception_handler` or `app.backend_exception_handler` to assign a function that will handle the exception. + +The expected signature for an error handler is `def handler(exception: Exception)`. + +```md alert warning +# Only named functions are supported as exception handler. +``` + +## Examples + +```python +import reflex as rx + +def custom_frontend_handler(exception: Exception) -> None: + # My custom logic for frontend errors + print("Frontend Error: " + str(exception)) + +def custom_backend_handler(exception: Exception) -> Optional[rx.event.EventSpec]: + # My custom logic for backend errors + print("Backend Error: " + str(exception)) + +app = rx.App( + frontend_exception_handler = custom_frontend_handler, + backend_exception_handler = custom_backend_handler + ) +``` \ No newline at end of file diff --git a/docs/utility_methods/lifespan_tasks.md b/docs/utility_methods/lifespan_tasks.md new file mode 100644 index 00000000000..60b7ae1cc20 --- /dev/null +++ b/docs/utility_methods/lifespan_tasks.md @@ -0,0 +1,84 @@ +# Lifespan Tasks + +_Added in v0.5.2_ + +Lifespan tasks are coroutines that run when the backend server is running. They +are useful for setting up the initial global state of the app, running periodic +tasks, and cleaning up resources when the server is shut down. + +Lifespan tasks are defined as async coroutines or async contextmanagers. To avoid +blocking the event thread, never use `time.sleep` or perform non-async I/O within +a lifespan task. + +In dev mode, lifespan tasks will stop and restart when a hot-reload occurs. + +## Tasks + +Any async coroutine can be used as a lifespan task. It will be started when the +backend comes up and will run until it returns or is cancelled due to server +shutdown. Long-running tasks should catch `asyncio.CancelledError` to perform +any necessary clean up. + +```python +async def long_running_task(foo, bar): + print(f"Starting \{foo} \{bar} task") + some_api = SomeApi(foo) + try: + while True: + updates = some_api.poll_for_updates() + other_api.push_changes(updates, bar) + await asyncio.sleep(5) # add some polling delay to avoid running too often + except asyncio.CancelledError: + some_api.close() # clean up the API if needed + print("Task was stopped") +``` + +### Register the Task + +To register a lifespan task, use `app.register_lifespan_task(coro_func, **kwargs)`. +Any keyword arguments specified during registration will be passed to the task. + +If the task accepts the special argument, `app`, it will be an instance of the `FastAPI` object +associated with the app. + +```python +app = rx.App() +app.register_lifespan_task(long_running_task, foo=42, bar=os.environ["BAR_PARAM"]) +``` + +## Context Managers + +Lifespan tasks can also be defined as async contextmanagers. This is useful for +setting up and tearing down resources and behaves similarly to the ASGI lifespan +protocol. + +Code up to the first `yield` will run when the backend comes up. As the backend +is shutting down, the code after the `yield` will run to clean up. + +Here is an example borrowed from the FastAPI docs and modified to work with this +interface. + +```python +from contextlib import asynccontextmanager + + +def fake_answer_to_everything_ml_model(x: float): + return x * 42 + + +ml_models = \{} + + +@asynccontextmanager +async def setup_model(app: FastAPI): + # Load the ML model + ml_models["answer_to_everything"] = fake_answer_to_everything_ml_model + yield + # Clean up the ML models and release the resources + ml_models.clear() + +... + +app = rx.App() +app.register_lifespan_task(setup_model) +``` \ No newline at end of file diff --git a/docs/utility_methods/other_methods.md b/docs/utility_methods/other_methods.md new file mode 100644 index 00000000000..fab7dee2c67 --- /dev/null +++ b/docs/utility_methods/other_methods.md @@ -0,0 +1,14 @@ +# Other Methods + +* `reset`: set all Vars to their default value for the given state (including substates). +* `get_value`: returns the value of a Var **without tracking changes to it**. This is useful + for serialization where the tracking wrapper is considered unserializable. +* `dict`: returns all state Vars (and substates) as a dictionary. This is + used internally when a page is first loaded and needs to be "hydrated" and + sent to the client. + +## Special Attributes + +* `dirty_vars`: a set of all Var names that have been modified since the last + time the state was sent to the client. This is used internally to determine + which Vars need to be sent to the client after processing an event. diff --git a/docs/utility_methods/router_attributes.md b/docs/utility_methods/router_attributes.md new file mode 100644 index 00000000000..1789faeb73a --- /dev/null +++ b/docs/utility_methods/router_attributes.md @@ -0,0 +1,116 @@ +```python exec box +import reflex as rx +from pcweb.styles.styles import get_code_style, cell_style + +class RouterState(rx.State): + pass + + +router_data = [ + {"name": "rx.State.router.page.host", "value": RouterState.router.page.host}, + {"name": "rx.State.router.page.path", "value": RouterState.router.page.path}, + {"name": "rx.State.router.page.raw_path", "value": RouterState.router.page.raw_path}, + {"name": "rx.State.router.page.full_path", "value": RouterState.router.page.full_path}, + {"name": "rx.State.router.page.full_raw_path", "value": RouterState.router.page.full_raw_path}, + {"name": "rx.State.router.page.params", "value": RouterState.router.page.params.to_string()}, + {"name": "rx.State.router.session.client_token", "value": RouterState.router.session.client_token}, + {"name": "rx.State.router.session.session_id", "value": RouterState.router.session.session_id}, + {"name": "rx.State.router.session.client_ip", "value": RouterState.router.session.client_ip}, + {"name": "rx.State.router.headers.host", "value": RouterState.router.headers.host}, + {"name": "rx.State.router.headers.origin", "value": RouterState.router.headers.origin}, + {"name": "rx.State.router.headers.upgrade", "value": RouterState.router.headers.upgrade}, + {"name": "rx.State.router.headers.connection", "value": RouterState.router.headers.connection}, + {"name": "rx.State.router.headers.cookie", "value": RouterState.router.headers.cookie}, + {"name": "rx.State.router.headers.pragma", "value": RouterState.router.headers.pragma}, + {"name": "rx.State.router.headers.cache_control", "value": RouterState.router.headers.cache_control}, + {"name": "rx.State.router.headers.user_agent", "value": RouterState.router.headers.user_agent}, + {"name": "rx.State.router.headers.sec_websocket_version", "value": RouterState.router.headers.sec_websocket_version}, + {"name": "rx.State.router.headers.sec_websocket_key", "value": RouterState.router.headers.sec_websocket_key}, + {"name": "rx.State.router.headers.sec_websocket_extensions", "value": RouterState.router.headers.sec_websocket_extensions}, + {"name": "rx.State.router.headers.accept_encoding", "value": RouterState.router.headers.accept_encoding}, + {"name": "rx.State.router.headers.accept_language", "value": RouterState.router.headers.accept_language}, + {"name": "rx.State.router.headers.raw_headers", "value": RouterState.router.headers.raw_headers.to_string()}, + ] + +``` + +# State Utility Methods + +The state object has several methods and attributes that return information +about the current page, session, or state. + +## Router Attributes + +The `self.router` attribute has several sub-attributes that provide various information: + +* `router.page`: data about the current page and route + * `host`: The hostname and port serving the current page (frontend). + * `path`: The path of the current page (for dynamic pages, this will contain the slug) + * `raw_path`: The path of the page displayed in the browser (including params and dynamic values) + * `full_path`: `path` with `host` prefixed + * `full_raw_path`: `raw_path` with `host` prefixed + * `params`: Dictionary of query params associated with the request + +* `router.session`: data about the current session + * `client_token`: UUID associated with the current tab's token. Each tab has a unique token. + * `session_id`: The ID associated with the client's websocket connection. Each tab has a unique session ID. + * `client_ip`: The IP address of the client. Many users may share the same IP address. + +* `router.headers`: headers associated with the websocket connection. These values can only change when the websocket is re-established (for example, during page refresh). + * `host`: The hostname and port serving the websocket (backend). + * `origin`: The origin of the request. + * `upgrade`: The upgrade header for websocket connections. + * `connection`: The connection header. + * `cookie`: The cookie header. + * `pragma`: The pragma header. + * `cache_control`: The cache control header. + * `user_agent`: The user agent string of the client. + * `sec_websocket_version`: The websocket version. + * `sec_websocket_key`: The websocket key. + * `sec_websocket_extensions`: The websocket extensions. + * `accept_encoding`: The accepted encodings. + * `accept_language`: The accepted languages. + * `raw_headers`: A mapping of all HTTP headers as a frozen dictionary. This provides access to any header that was sent with the request, not just the common ones listed above. + +### Example Values on this Page + +```python eval +rx.table.root( + rx.table.header( + rx.table.row( + rx.table.column_header_cell("Name"), + rx.table.column_header_cell("Value"), + ), + ), + rx.table.body( + *[ + rx.table.row( + rx.table.cell(item["name"], style=cell_style), + rx.table.cell(rx.code(item["value"], style=get_code_style("violet"))), + ) + for item in router_data + ] + ), + variant="surface", + margin_y="1em", + ) +``` + +### Accessing Raw Headers + +The `raw_headers` attribute provides access to all HTTP headers as a frozen dictionary. This is useful when you need to access headers that are not explicitly defined in the `HeaderData` class: + +```python box +# Access a specific header +custom_header_value = self.router.headers.raw_headers.get("x-custom-header", "") + +# Example of accessing common headers +user_agent = self.router.headers.raw_headers.get("user-agent", "") +content_type = self.router.headers.raw_headers.get("content-type", "") +authorization = self.router.headers.raw_headers.get("authorization", "") + +# You can also check if a header exists +has_custom_header = "x-custom-header" in self.router.headers.raw_headers +``` + +This is particularly useful for accessing custom headers or when working with specific HTTP headers that are not part of the standard set exposed as direct attributes. diff --git a/docs/vars/base_vars.md b/docs/vars/base_vars.md new file mode 100644 index 00000000000..07b207dc7af --- /dev/null +++ b/docs/vars/base_vars.md @@ -0,0 +1,228 @@ +```python exec +import random +import time + +import reflex as rx + +from pcweb.pages.docs import vars +``` + +# Base Vars + +Vars are any fields in your app that may change over time. A Var is directly +rendered into the frontend of the app. + +Base vars are defined as fields in your State class. + +They can have a preset default value. If you don't provide a default value, you +must provide a type annotation. + +```md alert warning +# State Vars should provide type annotations. + +Reflex relies on type annotations to determine the type of state vars during the compilation process. +``` + +```python demo exec +class TickerState(rx.State): + ticker: str ="AAPL" + price: str = "$150" + + +def ticker_example(): + return rx.center( + rx.vstack( + rx.heading(TickerState.ticker, size="3"), + rx.text(f"Current Price: {TickerState.price}", font_size="md"), + rx.text("Change: 4%", color="green"), + ), + + ) +``` + +In this example `ticker` and `price` are base vars in the app, which can be modified at runtime. + +```md alert warning +# Vars must be JSON serializable. + +Vars are used to communicate between the frontend and backend. They must be primitive Python types, Plotly figures, Pandas dataframes, or [a custom defined type]({vars.custom_vars.path}). +``` + +## Accessing state variables on different pages + +State is just a python class and so can be defined on one page and then imported and used on another. Below we define `TickerState` class on the page `state.py` and then import it and use it on the page `index.py`. + +```python +# state.py + +class TickerState(rx.State): + ticker: str = "AAPL" + price: str = "$150" +``` + +```python +# index.py +from .state import TickerState + +def ticker_example(): + return rx.center( + rx.vstack( + rx.heading(TickerState.ticker, size="3"), + rx.text(f"Current Price: \{TickerState.price}", font_size="md"), + rx.text("Change: 4%", color="green"), + ), + + ) +``` + +## Backend-only Vars + +Any Var in a state class that starts with an underscore (`_`) is considered backend +only and will **not be synchronized with the frontend**. Data associated with a +specific session that is _not directly rendered on the frontend should be stored +in a backend-only var_ to reduce network traffic and improve performance. + +They have the advantage that they don't need to be JSON serializable, however +they must still be pickle-able to be used with redis in prod mode. They are +not directly renderable on the frontend, and **may be used to store sensitive +values that should not be sent to the client**. + +```md alert warning +# Protect auth data and sensitive state in backend-only vars. + +Regular vars and computed vars should **only** be used for rendering the state +of your app in the frontend. Having any type of permissions or authenticated state based on +a regular var presents a security risk as you may assume these have shared control +with the frontend (client) due to default setter methods. + +For improved security, `state_auto_setters=False` may be set in `rxconfig.py` +to prevent the automatic generation of setters for regular vars, however, the +client will still be able to locally modify the contents of frontend vars as +they are presented in the UI. +``` + +For example, a backend-only var is used to store a large data structure which is +then paged to the frontend using cached vars. + +```python demo exec +import numpy as np + + +class BackendVarState(rx.State): + _backend: np.ndarray = np.array([random.randint(0, 100) for _ in range(100)]) + offset: int = 0 + limit: int = 10 + + @rx.var(cache=True) + def page(self) -> list[int]: + return [ + int(x) # explicit cast to int + for x in self._backend[self.offset : self.offset + self.limit] + ] + + @rx.var(cache=True) + def page_number(self) -> int: + return (self.offset // self.limit) + 1 + (1 if self.offset % self.limit else 0) + + @rx.var(cache=True) + def total_pages(self) -> int: + return len(self._backend) // self.limit + (1 if len(self._backend) % self.limit else 0) + + @rx.event + def prev_page(self): + self.offset = max(self.offset - self.limit, 0) + + @rx.event + def next_page(self): + if self.offset + self.limit < len(self._backend): + self.offset += self.limit + + @rx.event + def generate_more(self): + self._backend = np.append(self._backend, [random.randint(0, 100) for _ in range(random.randint(0, 100))]) + + @rx.event + def set_limit(self, value: str): + self.limit = int(value) + +def backend_var_example(): + return rx.vstack( + rx.hstack( + rx.button( + "Prev", + on_click=BackendVarState.prev_page, + ), + rx.text(f"Page {BackendVarState.page_number} / {BackendVarState.total_pages}"), + rx.button( + "Next", + on_click=BackendVarState.next_page, + ), + rx.text("Page Size"), + rx.input( + width="5em", + value=BackendVarState.limit, + on_change=BackendVarState.set_limit, + ), + rx.button("Generate More", on_click=BackendVarState.generate_more), + ), + rx.list( + rx.foreach( + BackendVarState.page, + lambda x, ix: rx.text(f"_backend[{ix + BackendVarState.offset}] = {x}"), + ), + ), + ) +``` + + +## Using rx.field / rx.Field to improve type hinting for vars + +When defining state variables you can use `rx.Field[T]` to annotate the variable's type. Then, you can initialize the variable using `rx.field(default_value)`, where `default_value` is an instance of type `T`. + +This approach makes the variable's type explicit, aiding static analysis tools in type checking. In addition, it shows you what methods are allowed to modify the variable in your frontend code, as they are listed in the type hint. + +Below are two examples: + +```python +import reflex as rx + +app = rx.App() + + +class State(rx.State): + x: rx.Field[bool] = rx.field(False) + + def flip(self): + self.x = not self.x + + +@app.add_page +def index(): + return rx.vstack( + rx.button("Click me", on_click=State.flip), + rx.text(State.x), + rx.text(~State.x), + ) +``` + +Here `State.x`, as it is typed correctly as a `boolean` var, gets better code completion, i.e. here we get options such as `to_string()` or `equals()`. + + +```python +import reflex as rx + +app = rx.App() + + +class State(rx.State): + x: rx.Field[dict[str, list[int]]] = rx.field(default_factory=dict) + + +@app.add_page +def index(): + return rx.vstack( + rx.text(State.x.values()[0][0]), + ) +``` + +Here `State.x`, as it is typed correctly as a `dict` of `str` to `list` of `int` var, gets better code completion, i.e. here we get options such as `contains()`, `keys()`, `values()`, `items()` or `merge()`. \ No newline at end of file diff --git a/docs/vars/computed_vars.md b/docs/vars/computed_vars.md new file mode 100644 index 00000000000..ee711609d10 --- /dev/null +++ b/docs/vars/computed_vars.md @@ -0,0 +1,190 @@ +```python exec +import random +import time +import asyncio + +import reflex as rx +``` + +# Computed Vars + +Computed vars have values derived from other properties on the backend. They are +defined as methods in your State class with the `@rx.var` decorator. + +Try typing in the input box and clicking out. + +```python demo exec id=upper +class UppercaseState(rx.State): + text: str = "hello" + + def set_text(self, value: str): + self.text = value + + @rx.var + def upper_text(self) -> str: + # This will be recomputed whenever `text` changes. + return self.text.upper() + + +def uppercase_example(): + return rx.vstack( + rx.heading(UppercaseState.upper_text), + rx.input(on_blur=UppercaseState.set_text, placeholder="Type here..."), + ) +``` + +Here, `upper_text` is a computed var that always holds the upper case version of `text`. + +We recommend always using type annotations for computed vars. + +## Cached Vars + +By default, all computed vars are cached (`cache=True`). A cached var is only +recomputed when the other state vars it depends on change. This is useful for +expensive computations, but in some cases it may not update when you expect it to. + +To create a computed var that recomputes on every state update regardless of +dependencies, use `@rx.var(cache=False)`. + +Previous versions of Reflex had a `@rx.cached_var` decorator, which is now replaced +by the `cache` argument of `@rx.var` (which defaults to `True`). + +```python demo exec +class CachedVarState(rx.State): + counter_a: int = 0 + counter_b: int = 0 + + @rx.var(cache=False) + def last_touch_time(self) -> str: + # This is updated anytime the state is updated. + return time.strftime("%H:%M:%S") + + @rx.event + def increment_a(self): + self.counter_a += 1 + + @rx.var(cache=True) + def last_counter_a_update(self) -> str: + # This is updated only when `counter_a` changes. + return f"{self.counter_a} at {time.strftime('%H:%M:%S')}" + + @rx.event + def increment_b(self): + self.counter_b += 1 + + @rx.var(cache=True) + def last_counter_b_update(self) -> str: + # This is updated only when `counter_b` changes. + return f"{self.counter_b} at {time.strftime('%H:%M:%S')}" + + +def cached_var_example(): + return rx.vstack( + rx.text(f"State touched at: {CachedVarState.last_touch_time}"), + rx.text(f"Counter A: {CachedVarState.last_counter_a_update}"), + rx.text(f"Counter B: {CachedVarState.last_counter_b_update}"), + rx.hstack( + rx.button("Increment A", on_click=CachedVarState.increment_a), + rx.button("Increment B", on_click=CachedVarState.increment_b), + ), + ) +``` + +In this example `last_touch_time` uses `cache=False` to ensure it updates any +time the state is modified. `last_counter_a_update` is a cached computed var (using +the default `cache=True`) that only depends on `counter_a`, so it only gets recomputed +when `counter_a` changes. Similarly `last_counter_b_update` only depends on `counter_b`, +and thus is updated only when `counter_b` changes. + +## Async Computed Vars + +Async computed vars allow you to use asynchronous operations in your computed vars. +They are defined as async methods in your State class with the same `@rx.var` decorator. +Async computed vars are useful for operations that require asynchronous processing, such as: + +- Fetching data from external APIs +- Database operations +- File I/O operations +- Any other operations that benefit from async/await + +```python demo exec +class AsyncVarState(rx.State): + count: int = 0 + + @rx.var + async def delayed_count(self) -> int: + # Simulate an async operation like an API call + await asyncio.sleep(0.5) + return self.count * 2 + + @rx.event + def increment(self): + self.count += 1 + + +def async_var_example(): + return rx.vstack( + rx.heading("Async Computed Var Example"), + rx.text(f"Count: {AsyncVarState.count}"), + rx.text(f"Delayed count (x2): {AsyncVarState.delayed_count}"), + rx.button("Increment", on_click=AsyncVarState.increment), + spacing="4", + ) +``` + +In this example, `delayed_count` is an async computed var that returns the count multiplied by 2 after a simulated delay. +When the count changes, the async computed var is automatically recomputed. + +### Caching Async Computed Vars + +Just like regular computed vars, async computed vars can also be cached. This is especially +useful for expensive async operations like API calls or database queries. + +```python demo exec +class AsyncCachedVarState(rx.State): + user_id: int = 1 + refresh_trigger: int = 0 + + @rx.var(cache=True) + async def user_data(self) -> dict: + # In a real app, this would be an API call + await asyncio.sleep(1) # Simulate network delay + + # Simulate different user data based on user_id + users = { + 1: {"name": "Alice", "email": "alice@example.com"}, + 2: {"name": "Bob", "email": "bob@example.com"}, + 3: {"name": "Charlie", "email": "charlie@example.com"}, + } + + return users.get(self.user_id, {"name": "Unknown", "email": "unknown"}) + + @rx.event + def change_user(self): + # Cycle through users 1-3 + self.user_id = (self.user_id % 3) + 1 + + @rx.event + def force_refresh(self): + # This will not affect user_data dependencies, but will trigger a state update + self.refresh_trigger += 1 + + +def async_cached_var_example(): + return rx.vstack( + rx.heading("Cached Async Computed Var Example"), + rx.text(f"User ID: {AsyncCachedVarState.user_id}"), + rx.text(f"User Name: {AsyncCachedVarState.user_data['name']}"), + rx.text(f"User Email: {AsyncCachedVarState.user_data['email']}"), + rx.hstack( + rx.button("Change User", on_click=AsyncCachedVarState.change_user), + rx.button("Force Refresh (No Effect)", on_click=AsyncCachedVarState.force_refresh), + ), + rx.text("Note: The cached async var only updates when user_id changes, not when refresh_trigger changes."), + spacing="4", + ) +``` + +In this example, `user_data` is a cached async computed var that simulates fetching user data. +It is only recomputed when `user_id` changes, not when other state variables like `refresh_trigger` change. +This demonstrates how caching works with async computed vars to optimize performance for expensive operations. diff --git a/docs/vars/custom_vars.md b/docs/vars/custom_vars.md new file mode 100644 index 00000000000..4de84b059b1 --- /dev/null +++ b/docs/vars/custom_vars.md @@ -0,0 +1,82 @@ +```python exec +import reflex as rx +import dataclasses +from typing import TypedDict + +from pcweb.pages.docs import vars +``` + +# Custom Vars + +As mentioned in the [vars page]({vars.base_vars.path}), Reflex vars must be JSON serializable. + +This means we can support any Python primitive types, as well as lists, dicts, and tuples. However, you can also create more complex var types using dataclasses (recommended), TypedDict, or Pydantic models. + +## Defining a Type + +In this example, we will create a custom var type for storing translations using a dataclass. + +Once defined, we can use it as a state var, and reference it from within a component. + +```python demo exec +import googletrans +import dataclasses +from typing import TypedDict + +@dataclasses.dataclass +class Translation: + original_text: str + translated_text: str + +class TranslationState(rx.State): + input_text: str = "Hola Mundo" + current_translation: Translation = Translation(original_text="", translated_text="") + + # Explicitly define the setter method + def set_input_text(self, value: str): + self.input_text = value + + @rx.event + def translate(self): + self.current_translation.original_text = self.input_text + self.current_translation.translated_text = googletrans.Translator().translate(self.input_text, dest="en").text + +def translation_example(): + return rx.vstack( + rx.input( + on_blur=TranslationState.set_input_text, + default_value=TranslationState.input_text, + placeholder="Text to translate...", + ), + rx.button("Translate", on_click=TranslationState.translate), + rx.text(TranslationState.current_translation.translated_text), + ) +``` + +## Alternative Approaches + +### Using TypedDict + +You can also use TypedDict for defining custom var types: + +```python +from typing import TypedDict + +class Translation(TypedDict): + original_text: str + translated_text: str +``` + +### Using Pydantic Models + +Pydantic models are another option for complex data structures: + +```python +from pydantic import BaseModel + +class Translation(BaseModel): + original_text: str + translated_text: str +``` + +For complex data structures, dataclasses are recommended as they provide a clean, type-safe way to define custom var types with good IDE support. diff --git a/docs/vars/var-operations.md b/docs/vars/var-operations.md new file mode 100644 index 00000000000..7a56a73e798 --- /dev/null +++ b/docs/vars/var-operations.md @@ -0,0 +1,555 @@ +```python exec +import random +import time + +import numpy as np + +import reflex as rx + +from pcweb.templates.docpage import docpage +``` + +# Var Operations + +Var operations transform the placeholder representation of the value on the +frontend and provide a way to perform basic operations on the Var without having +to define a computed var. + +Within your frontend components, you cannot use arbitrary Python functions on +the state vars. For example, the following code will **not work.** + +```python +class State(rx.State): + number: int + +def index(): + # This will be compiled before runtime, before `State.number` has a known value. + # Since `float` is not a valid var operation, this will throw an error. + rx.text(float(State.number)) +``` + +This is because we compile the frontend to Javascript, but the value of `State.number` +is only known at runtime. + +In this example below we use a var operation to concatenate a `string` with a `var`, meaning we do not have to do in within state as a computed var. + +```python demo exec +coins = ["BTC", "ETH", "LTC", "DOGE"] + +class VarSelectState(rx.State): + selected: str = "DOGE" + + def set_selected(self, value: str): + self.selected = value + +def var_operations_example(): + return rx.vstack( + # Using a var operation to concatenate a string with a var. + rx.heading("I just bought a bunch of " + VarSelectState.selected), + # Using an f-string to interpolate a var. + rx.text(f"{VarSelectState.selected} is going to the moon!"), + rx.select( + coins, + value=VarSelectState.selected, + on_change=VarSelectState.set_selected, + ) + ) +``` + +```md alert success +# Vars support many common operations. + +They can be used for arithmetic, string concatenation, inequalities, indexing, and more. See the [full list of supported operations](/docs/api-reference/var/). +``` + +## Supported Operations + +Var operations allow us to change vars on the front-end without having to create more computed vars on the back-end in the state. + +Some simple examples are the `==` var operator, which is used to check if two vars are equal and the `to_string()` var operator, which is used to convert a var to a string. + +```python demo exec + +fruits = ["Apple", "Banana", "Orange", "Mango"] + +class EqualsState(rx.State): + selected: str = "Apple" + favorite: str = "Banana" + + def set_selected(self, value: str): + self.selected = value + + +def var_equals_example(): + return rx.vstack( + rx.text(EqualsState.favorite.to_string() + " is my favorite fruit!"), + rx.select( + fruits, + value=EqualsState.selected, + on_change=EqualsState.set_selected, + ), + rx.cond( + EqualsState.selected == EqualsState.favorite, + rx.text("The selected fruit is equal to the favorite fruit!"), + rx.text("The selected fruit is not equal to the favorite fruit."), + ), + ) + +``` + +### Negate, Absolute and Length + +The `-` operator is used to get the negative version of the var. The `abs()` operator is used to get the absolute value of the var. The `.length()` operator is used to get the length of a list var. + +```python demo exec +import random + +class OperState(rx.State): + number: int + numbers_seen: list = [] + + @rx.event + def update(self): + self.number = random.randint(-100, 100) + self.numbers_seen.append(self.number) + +def var_operation_example(): + return rx.vstack( + rx.heading(f"The number: {OperState.number}", size="3"), + rx.hstack( + rx.text("Negated:", rx.badge(-OperState.number, variant="soft", color_scheme="green")), + rx.text(f"Absolute:", rx.badge(abs(OperState.number), variant="soft", color_scheme="blue")), + rx.text(f"Numbers seen:", rx.badge(OperState.numbers_seen.length(), variant="soft", color_scheme="red")), + ), + rx.button("Update", on_click=OperState.update), + ) +``` + +### Comparisons and Mathematical Operators + +All of the comparison operators are used as expected in python. These include `==`, `!=`, `>`, `>=`, `<`, `<=`. + +There are operators to add two vars `+`, subtract two vars `-`, multiply two vars `*` and raise a var to a power `pow()`. + +```python demo exec +import random + +class CompState(rx.State): + number_1: int + number_2: int + + @rx.event + def update(self): + self.number_1 = random.randint(-10, 10) + self.number_2 = random.randint(-10, 10) + +def var_comparison_example(): + + return rx.vstack( + rx.table.root( + rx.table.header( + rx.table.row( + rx.table.column_header_cell("Integer 1"), + rx.table.column_header_cell("Integer 2"), + rx.table.column_header_cell("Operation"), + rx.table.column_header_cell("Outcome"), + ), + ), + rx.table.body( + rx.table.row( + rx.table.row_header_cell(CompState.number_1), + rx.table.cell(CompState.number_2), + rx.table.cell("Int 1 == Int 2"), + rx.table.cell((CompState.number_1 == CompState.number_2).to_string()), + ), + rx.table.row( + rx.table.row_header_cell(CompState.number_1), + rx.table.cell(CompState.number_2), + rx.table.cell("Int 1 != Int 2"), + rx.table.cell((CompState.number_1 != CompState.number_2).to_string()), + ), + rx.table.row( + rx.table.row_header_cell(CompState.number_1), + rx.table.cell(CompState.number_2), + rx.table.cell("Int 1 > Int 2"), + rx.table.cell((CompState.number_1 > CompState.number_2).to_string()), + ), + rx.table.row( + rx.table.row_header_cell(CompState.number_1), + rx.table.cell(CompState.number_2), + rx.table.cell("Int 1 >= Int 2"), + rx.table.cell((CompState.number_1 >= CompState.number_2).to_string()), + ), + rx.table.row( + rx.table.row_header_cell(CompState.number_1), + rx.table.cell(CompState.number_2, ), + rx.table.cell("Int 1 < Int 2 "), + rx.table.cell((CompState.number_1 < CompState.number_2).to_string()), + ), + rx.table.row( + rx.table.row_header_cell(CompState.number_1), + rx.table.cell(CompState.number_2), + rx.table.cell("Int 1 <= Int 2"), + rx.table.cell((CompState.number_1 <= CompState.number_2).to_string()), + ), + + rx.table.row( + rx.table.row_header_cell(CompState.number_1), + rx.table.cell(CompState.number_2), + rx.table.cell("Int 1 + Int 2"), + rx.table.cell(f"{(CompState.number_1 + CompState.number_2)}"), + ), + rx.table.row( + rx.table.row_header_cell(CompState.number_1), + rx.table.cell(CompState.number_2), + rx.table.cell("Int 1 - Int 2"), + rx.table.cell(f"{CompState.number_1 - CompState.number_2}"), + ), + rx.table.row( + rx.table.row_header_cell(CompState.number_1), + rx.table.cell(CompState.number_2), + rx.table.cell("Int 1 * Int 2"), + rx.table.cell(f"{CompState.number_1 * CompState.number_2}"), + ), + rx.table.row( + rx.table.row_header_cell(CompState.number_1), + rx.table.cell(CompState.number_2), + rx.table.cell("pow(Int 1, Int2)"), + rx.table.cell(f"{pow(CompState.number_1, CompState.number_2)}"), + ), + ), + width="100%", + ), + rx.button("Update", on_click=CompState.update), + ) +``` + +### True Division, Floor Division and Remainder + +The operator `/` represents true division. The operator `//` represents floor division. The operator `%` represents the remainder of the division. + +```python demo exec +import random + +class DivState(rx.State): + number_1: float = 3.5 + number_2: float = 1.4 + + @rx.event + def update(self): + self.number_1 = round(random.uniform(5.1, 9.9), 2) + self.number_2 = round(random.uniform(0.1, 4.9), 2) + +def var_div_example(): + return rx.vstack( + rx.table.root( + rx.table.header( + rx.table.row( + rx.table.column_header_cell("Integer 1"), + rx.table.column_header_cell("Integer 2"), + rx.table.column_header_cell("Operation"), + rx.table.column_header_cell("Outcome"), + ), + ), + rx.table.body( + rx.table.row( + rx.table.row_header_cell(DivState.number_1), + rx.table.cell(DivState.number_2), + rx.table.cell("Int 1 / Int 2"), + rx.table.cell(f"{DivState.number_1 / DivState.number_2}"), + ), + rx.table.row( + rx.table.row_header_cell(DivState.number_1), + rx.table.cell(DivState.number_2), + rx.table.cell("Int 1 // Int 2"), + rx.table.cell(f"{DivState.number_1 // DivState.number_2}"), + ), + rx.table.row( + rx.table.row_header_cell(DivState.number_1), + rx.table.cell(DivState.number_2), + rx.table.cell("Int 1 % Int 2"), + rx.table.cell(f"{DivState.number_1 % DivState.number_2}"), + ), + ), + width="100%", + ), + rx.button("Update", on_click=DivState.update), + ) +``` + +### And, Or and Not + +In Reflex the `&` operator represents the logical AND when used in the front end. This means that it returns true only when both conditions are true simultaneously. +The `|` operator represents the logical OR when used in the front end. This means that it returns true when either one or both conditions are true. +The `~` operator is used to invert a var. It is used on a var of type `bool` and is equivalent to the `not` operator. + +```python demo exec +import random + +class LogicState(rx.State): + var_1: bool = True + var_2: bool = True + + @rx.event + def update(self): + self.var_1 = random.choice([True, False]) + self.var_2 = random.choice([True, False]) + +def var_logical_example(): + return rx.vstack( + rx.table.root( + rx.table.header( + rx.table.row( + rx.table.column_header_cell("Var 1"), + rx.table.column_header_cell("Var 2"), + rx.table.column_header_cell("Operation"), + rx.table.column_header_cell("Outcome"), + ), + ), + rx.table.body( + rx.table.row( + rx.table.row_header_cell(LogicState.var_1.to_string()), + rx.table.cell(LogicState.var_2.to_string()), + rx.table.cell("Logical AND (&)"), + rx.table.cell((LogicState.var_1 & LogicState.var_2).to_string()), + ), + rx.table.row( + rx.table.row_header_cell(LogicState.var_1.to_string()), + rx.table.cell(LogicState.var_2.to_string()), + rx.table.cell("Logical OR (|)"), + rx.table.cell((LogicState.var_1 | LogicState.var_2).to_string()), + ), + rx.table.row( + rx.table.row_header_cell(LogicState.var_1.to_string()), + rx.table.cell(LogicState.var_2.to_string()), + rx.table.cell("The invert of Var 1 (~)"), + rx.table.cell((~LogicState.var_1).to_string()), + ), + + ), + width="100%", + ), + rx.button("Update", on_click=LogicState.update), + ) +``` + +### Contains, Reverse and Join + +The 'in' operator is not supported for Var types, we must use the `Var.contains()` instead. When we use `contains`, the var must be of type: `dict`, `list`, `tuple` or `str`. +`contains` checks if a var contains the object that we pass to it as an argument. + +We use the `reverse` operation to reverse a list var. The var must be of type `list`. + +Finally we use the `join` operation to join a list var into a string. + +```python demo exec +class ListsState(rx.State): + list_1: list = [1, 2, 3, 4, 6] + list_2: list = [7, 8, 9, 10] + list_3: list = ["p","y","t","h","o","n"] + +def var_list_example(): + return rx.hstack( + rx.vstack( + rx.heading(f"List 1: {ListsState.list_1}", size="3"), + rx.text(f"List 1 Contains 3: {ListsState.list_1.contains(3)}"), + ), + rx.vstack( + rx.heading(f"List 2: {ListsState.list_2}", size="3"), + rx.text(f"Reverse List 2: {ListsState.list_2.reverse()}"), + ), + rx.vstack( + rx.heading(f"List 3: {ListsState.list_3}", size="3"), + rx.text(f"List 3 Joins: {ListsState.list_3.join()}"), + ), + ) +``` + +### Lower, Upper, Split + +The `lower` operator converts a string var to lowercase. The `upper` operator converts a string var to uppercase. The `split` operator splits a string var into a list. + +```python demo exec +class StringState(rx.State): + string_1: str = "PYTHON is FUN" + string_2: str = "react is hard" + + +def var_string_example(): + return rx.hstack( + rx.vstack( + rx.heading(f"List 1: {StringState.string_1}", size="3"), + rx.text(f"List 1 Lower Case: {StringState.string_1.lower()}"), + ), + rx.vstack( + rx.heading(f"List 2: {StringState.string_2}", size="3"), + rx.text(f"List 2 Upper Case: {StringState.string_2.upper()}"), + rx.text(f"Split String 2: {StringState.string_2.split()}"), + ), + ) +``` + +## Get Item (Indexing) + +Indexing is only supported for strings, lists, tuples, dicts, and dataframes. To index into a state var strict type annotations are required. + +```python +class GetItemState1(rx.State): + list_1: list = [50, 10, 20] + +def get_item_error_1(): + return rx.progress(value=GetItemState1.list_1[0]) +``` + +In the code above you would expect to index into the first index of the list_1 state var. In fact the code above throws the error: `Invalid var passed for prop value, expected type , got value of type typing.Any.` This is because the type of the items inside the list have not been clearly defined in the state. To fix this you change the list_1 definition to `list_1: list[int] = [50, 10, 20]` + +```python demo exec +class GetItemState1(rx.State): + list_1: list[int] = [50, 10, 20] + +def get_item_error_1(): + return rx.progress(value=GetItemState1.list_1[0]) +``` + +### Using with Foreach + +Errors frequently occur when using indexing and `foreach`. + +```python +class ProjectsState(rx.State): + projects: List[dict] = [ + { + "technologies": ["Next.js", "Prisma", "Tailwind", "Google Cloud", "Docker", "MySQL"] + }, + { + "technologies": ["Python", "Flask", "Google Cloud", "Docker"] + } + ] + +def get_badge(technology: str) -> rx.Component: + return rx.badge(technology, variant="soft", color_scheme="green") + +def project_item(project: dict): + return rx.box( + rx.hstack( + rx.foreach(project["technologies"], get_badge) + ), + ) + +def failing_projects_example() -> rx.Component: + return rx.box(rx.foreach(ProjectsState.projects, project_item)) +``` + +The code above throws the error `TypeError: Could not foreach over var of type Any. (If you are trying to foreach over a state var, add a type annotation to the var.)` + +We must change `projects: list[dict]` => `projects: list[dict[str, list]]` because while projects is annotated, the item in project["technologies"] is not. + +```python demo exec +class ProjectsState(rx.State): + projects: list[dict[str, list]] = [ + { + "technologies": ["Next.js", "Prisma", "Tailwind", "Google Cloud", "Docker", "MySQL"] + }, + { + "technologies": ["Python", "Flask", "Google Cloud", "Docker"] + } + ] + + +def projects_example() -> rx.Component: + def get_badge(technology: str) -> rx.Component: + return rx.badge(technology, variant="soft", color_scheme="green") + + def project_item(project: dict) -> rx.Component: + + return rx.box( + rx.hstack( + rx.foreach(project["technologies"], get_badge) + ), + ) + return rx.box(rx.foreach(ProjectsState.projects, project_item)) +``` + +The previous example had only a single type for each of the dictionaries `keys` and `values`. For complex multi-type data, you need to use a dataclass, as shown below. + +```python demo exec +import dataclasses + +@dataclasses.dataclass +class ActressType: + actress_name: str + age: int + pages: list[dict[str, str]] + +class MultiDataTypeState(rx.State): + """The app state.""" + actresses: list[ActressType] = [ + ActressType( + actress_name="Ariana Grande", + age=30, + pages=[ + {"url": "arianagrande.com"}, {"url": "https://es.wikipedia.org/wiki/Ariana_Grande"} + ] + ), + ActressType( + actress_name="Gal Gadot", + age=38, + pages=[ + {"url": "http://www.galgadot.com/"}, {"url": "https://es.wikipedia.org/wiki/Gal_Gadot"} + ] + ) + ] + +def actresses_example() -> rx.Component: + def showpage(page: dict[str, str]): + return rx.vstack( + rx.text(page["url"]), + ) + + def showlist(item: ActressType): + return rx.vstack( + rx.hstack( + rx.text(item.actress_name), + rx.text(item.age), + ), + rx.foreach(item.pages, showpage), + ) + return rx.box(rx.foreach(MultiDataTypeState.actresses, showlist)) + +``` + +Setting the type of `actresses` to be `actresses: list[dict[str,str]]` would fail as it cannot be understood that the `value` for the `pages key` is actually a `list`. + +## Combine Multiple Var Operations + +You can also combine multiple var operations together, as seen in the next example. + +```python demo exec +import random + +class VarNumberState(rx.State): + number: int + + @rx.event + def update(self): + self.number = random.randint(0, 100) + +def var_number_example(): + return rx.vstack( + rx.heading(f"The number is {VarNumberState.number}", size="5"), + # Var operations can be composed for more complex expressions. + rx.cond( + VarNumberState.number % 2 == 0, + rx.text("Even", color="green"), + rx.text("Odd", color="red"), + ), + rx.button("Update", on_click=VarNumberState.update), + ) +``` + +We could have made a computed var that returns the parity of `number`, but +it can be simpler just to use a var operation instead. + +Var operations may be generally chained to make compound expressions, however +some complex transformations not supported by var operations must use computed vars +to calculate the value on the backend. diff --git a/docs/vi/README.md b/docs/vi/README.md deleted file mode 100644 index b8bc6637d68..00000000000 --- a/docs/vi/README.md +++ /dev/null @@ -1,255 +0,0 @@ -
    -Reflex Logo -
    - -### **✨ Ứng dụng web hiệu suất cao, tùy chỉnh bằng Python thuần. Deploy trong vài giây. ✨** - -[![PyPI version](https://badge.fury.io/py/reflex.svg)](https://badge.fury.io/py/reflex) -![versions](https://img.shields.io/pypi/pyversions/reflex.svg) -[![Documentation](https://img.shields.io/badge/Documentation%20-Introduction%20-%20%23007ec6)](https://reflex.dev/docs/getting-started/introduction) -[![PyPI Downloads](https://static.pepy.tech/badge/reflex)](https://pepy.tech/projects/reflex) -[![Discord](https://img.shields.io/discord/1029853095527727165?color=%237289da&label=Discord)](https://discord.gg/T5WSbC2YtQ) - -
    - ---- - -[English](https://github.com/reflex-dev/reflex/blob/main/README.md) | [简体中文](https://github.com/reflex-dev/reflex/blob/main/docs/zh/zh_cn/README.md) | [繁體中文](https://github.com/reflex-dev/reflex/blob/main/docs/zh/zh_tw/README.md) | [Türkçe](https://github.com/reflex-dev/reflex/blob/main/docs/tr/README.md) | [हिंदी](https://github.com/reflex-dev/reflex/blob/main/docs/in/README.md) | [Português (Brasil)](https://github.com/reflex-dev/reflex/blob/main/docs/pt/pt_br/README.md) | [Italiano](https://github.com/reflex-dev/reflex/blob/main/docs/it/README.md) | [Español](https://github.com/reflex-dev/reflex/blob/main/docs/es/README.md) | [한국어](https://github.com/reflex-dev/reflex/blob/main/docs/kr/README.md) | [日本語](https://github.com/reflex-dev/reflex/blob/main/docs/ja/README.md) | [Deutsch](https://github.com/reflex-dev/reflex/blob/main/docs/de/README.md) | [Persian (پارسی)](https://github.com/reflex-dev/reflex/blob/main/docs/pe/README.md) | [Tiếng Việt](https://github.com/reflex-dev/reflex/blob/main/docs/vi/README.md) - ---- - -# Reflex - -Reflex là một thư viện để xây dựng ứng dụng web toàn bộ bằng Python thuần. - -Các tính năng chính: - -- **Python thuần tuý** - Viết toàn bộ ứng dụng cả backend và frontend hoàn toàn bằng Python, không cần học JavaScript. -- **Full Flexibility** - Reflex dễ dàng để bắt đầu, nhưng cũng có thể mở rộng lên các ứng dụng phức tạp. -- **Deploy Instantly** - Sau khi xây dựng ứng dụng, bạn có thể triển khai bằng [một dòng lệnh](https://reflex.dev/docs/hosting/deploy-quick-start/) hoặc triển khai trên server của riêng bạn. - -Đọc [bài viết về kiến trúc hệ thống](https://reflex.dev/blog/2024-03-21-reflex-architecture/#the-reflex-architecture) để hiểu rõ các hoạt động của Reflex. - -## ⚙️ Cài đặt - -Mở cửa sổ lệnh và chạy (Yêu cầu Python phiên bản 3.10+): - -```bash -pip install reflex -``` - -## 🥳 Tạo ứng dụng đầu tiên - -Cài đặt `reflex` cũng như cài đặt công cụ dòng lệnh `reflex`. - -Kiểm tra việc cài đặt đã thành công hay chưa bằng cách tạo mới một ứng dụng. (Thay `my_app_name` bằng tên ứng dụng của bạn): - -```bash -mkdir my_app_name -cd my_app_name -reflex init -``` - -Lệnh này tạo ra một ứng dụng mẫu trong một thư mục mới. - -Bạn có thể chạy ứng dụng ở chế độ phát triển. - -```bash -reflex run -``` - -Bạn có thể xem ứng dụng của bạn ở địa chỉ http://localhost:3000. - -Bạn có thể thay đổi mã nguồn ở `my_app_name/my_app_name.py`. Reflex nhanh chóng làm mới và bạn có thể thấy thay đổi trên ứng dụng của bạn ngay lập tức khi bạn lưu file. - -## 🫧 Ứng dụng ví dụ - -Bắt đầu với ví dụ: tạo một ứng dụng tạo ảnh bằng [DALL·E](https://platform.openai.com/docs/guides/images/image-generation?context=node). Để cho đơn giản, chúng ta sẽ sử dụng [OpenAI API](https://platform.openai.com/docs/api-reference/authentication), nhưng bạn có thể sử dụng model của chính bạn được triển khai trên local. - -  - -
    -A frontend wrapper for DALL·E, shown in the process of generating an image. -
    - -  - -Đây là toàn bộ đoạn mã để xây dựng ứng dụng trên. Nó được viết hoàn toàn trong một file Python! - -```python -import reflex as rx -import openai - -openai_client = openai.OpenAI() - - -class State(rx.State): - """The app state.""" - - prompt = "" - image_url = "" - processing = False - complete = False - - def get_image(self): - """Get the image from the prompt.""" - if self.prompt == "": - return rx.window_alert("Prompt Empty") - - self.processing, self.complete = True, False - yield - response = openai_client.images.generate( - prompt=self.prompt, n=1, size="1024x1024" - ) - self.image_url = response.data[0].url - self.processing, self.complete = False, True - - -def index(): - return rx.center( - rx.vstack( - rx.heading("DALL-E", font_size="1.5em"), - rx.input( - placeholder="Enter a prompt..", - on_blur=State.set_prompt, - width="25em", - ), - rx.button( - "Generate Image", - on_click=State.get_image, - width="25em", - loading=State.processing - ), - rx.cond( - State.complete, - rx.image(src=State.image_url, width="20em"), - ), - align="center", - ), - width="100%", - height="100vh", - ) - -# Add state and page to the app. -app = rx.App() -app.add_page(index, title="Reflex:DALL-E") -``` - -## Hãy phân tích chi tiết. - -
    -Explaining the differences between backend and frontend parts of the DALL-E app. -
    - -### **Reflex UI** - -Bắt đầu với giao diện chính. - -```python -def index(): - return rx.center( - ... - ) -``` - -Hàm `index` định nghĩa phần giao diện chính của ứng dụng. - -Chúng tôi sử dụng các component (thành phần) khác nhau như `center`, `vstack`, `input` và `button` để xây dựng giao diện phía trước. -Các component có thể được lồng vào nhau để tạo ra các bố cục phức tạp. Và bạn cũng có thể sử dụng từ khoá `args` để tận dụng đầy đủ sức mạnh của CSS. - -Reflex có đến hơn [60 component được xây dựng sẵn](https://reflex.dev/docs/library) để giúp bạn bắt đầu. Chúng ta có thể tạo ra một component mới khá dễ dàng, thao khảo: [xây dựng component của riêng bạn](https://reflex.dev/docs/wrapping-react/overview/). - -### **State** - -Reflex biểu diễn giao diện bằng các hàm của state (trạng thái). - -```python -class State(rx.State): - """The app state.""" - prompt = "" - image_url = "" - processing = False - complete = False - -``` - -Một state định nghĩa các biến (được gọi là vars) có thể thay đổi trong một ứng dụng và cho phép các hàm có thể thay đổi chúng. - -Tại đây state được cấu thành từ một `prompt` và `image_url`. -Có cũng những biến boolean `processing` và `complete` -để chỉ ra khi nào tắt nút (trong quá trình tạo hình ảnh) -và khi nào hiển thị hình ảnh kết quả. - -### **Event Handlers** - -```python -def get_image(self): - """Get the image from the prompt.""" - if self.prompt == "": - return rx.window_alert("Prompt Empty") - - self.processing, self.complete = True, False - yield - response = openai_client.images.generate( - prompt=self.prompt, n=1, size="1024x1024" - ) - self.image_url = response.data[0].url - self.processing, self.complete = False, True -``` - -Với các state, chúng ta định nghĩa các hàm có thể thay đổi state vars được gọi là event handlers. Event handler là cách chúng ta có thể thay đổi state trong Reflex. Chúng có thể là phản hồi khi người dùng thao tác, chằng hạn khi nhấn vào nút hoặc khi đang nhập trong text box. Các hành động này được gọi là event. - -Ứng dụng DALL·E. của chúng ta có một event handler, `get_image` để lấy hình ảnh từ OpenAI API. Sử dụng từ khoá `yield` in ở giữa event handler để cập nhật giao diện. Hoặc giao diện có thể cập nhật ở cuối event handler. - -### **Routing** - -Cuối cùng, chúng ta định nghĩa một ứng dụng. - -```python -app = rx.App() -``` - -Chúng ta thêm một trang ở đầu ứng dụng bằng index component. Chúng ta cũng thêm tiêu đề của ứng dụng để hiển thị lên trình duyệt. - -```python -app.add_page(index, title="DALL-E") -``` - -Bạn có thể tạo một ứng dụng nhiều trang bằng cách thêm trang. - -## 📑 Tài liệu - -
    - -📑 [Docs](https://reflex.dev/docs/getting-started/introduction)   |   🗞️ [Blog](https://reflex.dev/blog)   |   📱 [Component Library](https://reflex.dev/docs/library)   |   🖼️ [Templates](https://reflex.dev/templates/)   |   🛸 [Deployment](https://reflex.dev/docs/hosting/deploy-quick-start)   - -
    - -## ✅ Status - -Reflex phát hành vào tháng 12/2022 với tên là Pynecone. - -Từ năm 2025, [Reflex Cloud](https://cloud.reflex.dev) đã ra mắt để cung cấp trải nghiệm lưu trữ tốt nhất cho các ứng dụng Reflex. Chúng tôi sẽ tiếp tục phát triển và triển khai thêm nhiều tính năng mới. - -Reflex ra phiên bản mới với các tính năng mới hàng tuần! Hãy :star: star và :eyes: watch repo này để thấy các cập nhật mới nhất. - -## Contributing - -Chúng tôi chào đón mọi đóng góp dù lớn hay nhỏ. Dưới đây là các cách để bắt đầu với cộng đồng Reflex. - -- **Discord**: [Discord](https://discord.gg/T5WSbC2YtQ) của chúng tôi là nơi tốt nhất để nhờ sự giúp đỡ và thảo luận các bạn có thể đóng góp. -- **GitHub Discussions**: Là cách tốt nhất để thảo luận về các tính năng mà bạn có thể đóng góp hoặc những điều bạn chưa rõ. -- **GitHub Issues**: [Issues](https://github.com/reflex-dev/reflex/issues) là nơi tốt nhất để thông báo. Ngoài ra bạn có thể sửa chữa các vấn đề bằng cách tạo PR. - -Chúng tôi luôn sẵn sàng tìm kiếm các contributor, bất kể kinh nghiệm. Để tham gia đóng góp, xin mời xem -[CONTRIBUTING.md](https://github.com/reflex-dev/reflex/blob/main/CONTRIBUTING.md) - -## Xin cảm ơn các Contributors: - - - - - -## License - -Reflex là mã nguồn mở và sử dụng giấy phép [Apache License 2.0](/LICENSE). diff --git a/docs/wrapping-react/custom-code-and-hooks.md b/docs/wrapping-react/custom-code-and-hooks.md new file mode 100644 index 00000000000..21281de75bc --- /dev/null +++ b/docs/wrapping-react/custom-code-and-hooks.md @@ -0,0 +1,108 @@ + +When wrapping a React component, you may need to define custom code or hooks that are specific to the component. This is done by defining the `add_custom_code`or `add_hooks` methods in your component class. + +## Custom Code + +Custom code is any JS code that need to be included in your page, but not necessarily in the component itself. This can include things like CSS styles, JS libraries, or any other code that needs to be included in the page. + +```python +class CustomCodeComponent(MyBaseComponent): + """MyComponent.""" + + def add_custom_code(self) -> list[str]: + """Add custom code to the component.""" + code1 = """const customVariable = "Custom code1";""" + code2 = """console.log(customVariable);""" + + return [code1, code2] +``` + +The above example will render the following JS code in the page: + +```javascript +/* import here */ + +const customVariable = "Custom code1"; +console.log(customVariable); + +/* rest of the page code */ +``` + +## Custom Hooks +Custom hooks are any hooks that need to be included in your component. This can include things like `useEffect`, `useState`, or any other hooks from the library you are wrapping. + +- Simple hooks can be added as strings. +- More complex hooks that need to have special import or be written in a specific order can be added as `rx.Var` with a `VarData` object to specify the position of the hook. + - The `imports` attribute of the `VarData` object can be used to specify any imports that need to be included in the component. + - The `position` attribute of the `VarData` object can be set to `Hooks.HookPosition.PRE_TRIGGER` or `Hooks.HookPosition.POST_TRIGGER` to specify the position of the hook in the component. + +```md alert info +# The `position` attribute is only used for hooks that need to be written in a specific order. +- If an event handler need to refer to a variable defined in a hook, the hook should be written before the event handler. +- If a hook need to refer to the memoized event handler by name, the hook should be written after the event handler. +``` + +```python +from reflex.vars.base import Var, VarData +from reflex.constants import Hooks +from reflex.components.el.elements import Div + +class ComponentWithHooks(Div, MyBaseComponent): + """MyComponent.""" + + def add_hooks(self) -> list[str| Var]: + """Add hooks to the component.""" + hooks = [] + hooks1 = """const customHookVariable = "some value";""" + hooks.append(hooks1) + + # A hook that need to be written before the memoized event handlers. + hooks2 = Var( + """useEffect(() => { + console.log("PreTrigger: " + customHookVariable); + }, []); + """, + _var_data=VarData( + imports=\{"react": ["useEffect"],\}, + position=Hooks.HookPosition.PRE_TRIGGER + ), + ) + hooks.append(hooks2) + + hooks3 = Var( + """useEffect(() => { + console.log("PostTrigger: " + customHookVariable); + }, []); + """, + _var_data=VarData( + imports=\{"react": ["useEffect"],\}, + position=Hooks.HookPosition.POST_TRIGGER + ), + ) + hooks.append(hooks3) + return hooks +``` + +The `ComponentWithHooks` will be rendered in the component in the following way: + +```javascript +export function Div_7178f430b7b371af8a12d8265d65ab9b() { + const customHookVariable = "some value"; + + useEffect(() => { + console.log("PreTrigger: " + customHookVariable); + }, []); + + /* memoized triggers such as on_click, on_change, etc will render here */ + + useEffect(() => { + console.log("PostTrigger: "+ customHookVariable); + }, []); + + return jsx("div", \{\}); +} +``` + +```md alert info +# You can mix custom code and hooks in the same component. Hooks can access a variable defined in the custom code, but custom code cannot access a variable defined in a hook. +``` diff --git a/docs/wrapping-react/example.md b/docs/wrapping-react/example.md new file mode 100644 index 00000000000..5cb34ace0a6 --- /dev/null +++ b/docs/wrapping-react/example.md @@ -0,0 +1,408 @@ +```python exec +import reflex as rx +from typing import Any +``` + +# Complex Example + +In this more complex example we will be wrapping `reactflow` a library for building node based applications like flow charts, diagrams, graphs, etc. + +## Import + +Lets start by importing the library [reactflow](https://www.npmjs.com/package/reactflow). Lets make a separate file called `reactflow.py` and add the following code: + +```python +import reflex as rx +from typing import Any, Dict, List, Union + +class ReactFlowLib(rx.Component): + """A component that wraps a react flow lib.""" + + library = "reactflow" + + def _get_custom_code(self) -> str: + return """import 'reactflow/dist/style.css'; + """ +``` + +Notice we also use the `_get_custom_code` method to import the css file that is needed for the styling of the library. + +## Components + +For this tutorial we will wrap three components from Reactflow: `ReactFlow`, `Background`, and `Controls`. Lets start with the `ReactFlow` component. + +Here we will define the `tag` and the `vars` that we will need to use the component. + +For this tutorial we will define `EventHandler` props `on_nodes_change` and `on_connect`, but you can find all the events that the component triggers in the [reactflow docs](https://reactflow.dev/docs/api/react-flow-props/#onnodeschange). + +```python +import reflex as rx +from typing import Any, Dict, List, Union + +class ReactFlowLib(rx.Component): + ... + +class ReactFlow(ReactFlowLib): + + tag = "ReactFlow" + + nodes: rx.Var[List[Dict[str, Any]]] + + edges: rx.Var[List[Dict[str, Any]]] + + fit_view: rx.Var[bool] + + nodes_draggable: rx.Var[bool] + + nodes_connectable: rx.Var[bool] + + nodes_focusable: rx.Var[bool] + + on_nodes_change: rx.EventHandler[lambda e0: [e0]] + + on_connect: rx.EventHandler[lambda e0: [e0]] +``` + +Now lets add the `Background` and `Controls` components. We will also create the components using the `create` method so that we can use them in our app. + +```python +import reflex as rx +from typing import Any, Dict, List, Union + +class ReactFlowLib(rx.Component): + ... + +class ReactFlow(ReactFlowLib): + ... + +class Background(ReactFlowLib): + + tag = "Background" + + color: rx.Var[str] + + gap: rx.Var[int] + + size: rx.Var[int] + + variant: rx.Var[str] + +class Controls(ReactFlowLib): + + tag = "Controls" + +react_flow = ReactFlow.create +background = Background.create +controls = Controls.create +``` + +## Building the App + +Now that we have our components lets build the app. + +Lets start by defining the initial nodes and edges that we will use in our app. + +```python +import reflex as rx +from .react_flow import react_flow, background, controls +import random +from collections import defaultdict +from typing import Any, Dict, List + + +initial_nodes = [ + \{ + 'id': '1', + 'type': 'input', + 'data': \{'label': '150'}, + 'position': \{'x': 250, 'y': 25}, + }, + \{ + 'id': '2', + 'data': \{'label': '25'}, + 'position': \{'x': 100, 'y': 125}, + }, + \{ + 'id': '3', + 'type': 'output', + 'data': \{'label': '5'}, + 'position': \{'x': 250, 'y': 250}, + }, +] + +initial_edges = [ + \{'id': 'e1-2', 'source': '1', 'target': '2', 'label': '*', 'animated': True}, + \{'id': 'e2-3', 'source': '2', 'target': '3', 'label': '+', 'animated': True}, +] +``` + +Next we will define the state of our app. We have four event handlers: `add_random_node`, `clear_graph`, `on_connect` and `on_nodes_change`. + +The `on_nodes_change` event handler is triggered when a node is selected and dragged. This function is used to update the position of a node during dragging. It takes a single argument `node_changes`, which is a list of dictionaries containing various types of metadata. For updating positions, the function specifically processes changes of type `position`. + +```python +class State(rx.State): + """The app state.""" + nodes: List[Dict[str, Any]] = initial_nodes + edges: List[Dict[str, Any]] = initial_edges + + @rx.event + def add_random_node(self): + new_node_id = f'\{len(self.nodes) + 1\}' + node_type = random.choice(['default']) + # Label is random number + label = new_node_id + x = random.randint(0, 500) + y = random.randint(0, 500) + + new_node = { + 'id': new_node_id, + 'type': node_type, + 'data': \{'label': label}, + 'position': \{'x': x, 'y': y}, + 'draggable': True, + } + self.nodes.append(new_node) + + @rx.event + def clear_graph(self): + self.nodes = [] # Clear the nodes list + self.edges = [] # Clear the edges list + + @rx.event + def on_connect(self, new_edge): + # Iterate over the existing edges + for i, edge in enumerate(self.edges): + # If we find an edge with the same ID as the new edge + if edge["id"] == f"e\{new_edge['source']}-\{new_edge['target']}": + # Delete the existing edge + del self.edges[i] + break + + # Add the new edge + self.edges.append({ + "id": f"e\{new_edge['source']}-\{new_edge['target']}", + "source": new_edge["source"], + "target": new_edge["target"], + "label": random.choice(["+", "-", "*", "/"]), + "animated": True, + }) + + @rx.event + def on_nodes_change(self, node_changes: List[Dict[str, Any]]): + # Receives a list of Nodes in case of events like dragging + map_id_to_new_position = defaultdict(dict) + + # Loop over the changes and store the new position + for change in node_changes: + if change["type"] == "position" and change.get("dragging") == True: + map_id_to_new_position[change["id"]] = change["position"] + + # Loop over the nodes and update the position + for i, node in enumerate(self.nodes): + if node["id"] in map_id_to_new_position: + new_position = map_id_to_new_position[node["id"]] + self.nodes[i]["position"] = new_position +``` + +Now lets define the UI of our app. We will use the `react_flow` component and pass in the `nodes` and `edges` from our state. We will also add the `on_connect` event handler to the `react_flow` component to handle when an edge is connected. + +```python +def index() -> rx.Component: + return rx.vstack( + react_flow( + background(), + controls(), + nodes_draggable=True, + nodes_connectable=True, + on_connect=lambda e0: State.on_connect(e0), + on_nodes_change=lambda e0: State.on_nodes_change(e0), + nodes=State.nodes, + edges=State.edges, + fit_view=True, + ), + rx.hstack( + rx.button("Clear graph", on_click=State.clear_graph, width="100%"), + rx.button("Add node", on_click=State.add_random_node, width="100%"), + width="100%", + ), + height="30em", + width="100%", + ) + + +# Add state and page to the app. +app = rx.App() +app.add_page(index) +``` + +```python exec +import reflex as rx +from typing import Any, Dict, List, Union +from collections import defaultdict +import random + +class ReactFlowLib(rx.Component): + """A component that wraps a react flow lib.""" + + library = "reactflow" + + def _get_custom_code(self) -> str: + return """import 'reactflow/dist/style.css'; + """ + +class ReactFlow(ReactFlowLib): + + tag = "ReactFlow" + + nodes: rx.Var[List[Dict[str, Any]]] + + edges: rx.Var[List[Dict[str, Any]]] + + fit_view: rx.Var[bool] + + nodes_draggable: rx.Var[bool] + + nodes_connectable: rx.Var[bool] + + nodes_focusable: rx.Var[bool] + + on_nodes_change: rx.EventHandler[lambda e0: [e0]] + + on_connect: rx.EventHandler[lambda e0: [e0]] + + +class Background(ReactFlowLib): + + tag = "Background" + + color: rx.Var[str] + + gap: rx.Var[int] + + size: rx.Var[int] + + variant: rx.Var[str] + +class Controls(ReactFlowLib): + + tag = "Controls" + +react_flow = ReactFlow.create +background = Background.create +controls = Controls.create + +initial_nodes = [ + { + 'id': '1', + 'type': 'input', + 'data': {'label': '150'}, + 'position': {'x': 250, 'y': 25}, + }, + { + 'id': '2', + 'data': {'label': '25'}, + 'position': {'x': 100, 'y': 125}, + }, + { + 'id': '3', + 'type': 'output', + 'data': {'label': '5'}, + 'position': {'x': 250, 'y': 250}, + }, +] + +initial_edges = [ + {'id': 'e1-2', 'source': '1', 'target': '2', 'label': '*', 'animated': True}, + {'id': 'e2-3', 'source': '2', 'target': '3', 'label': '+', 'animated': True}, +] + + +class ReactFlowState(rx.State): + """The app state.""" + nodes: List[Dict[str, Any]] = initial_nodes + edges: List[Dict[str, Any]] = initial_edges + + @rx.event + def add_random_node(self): + new_node_id = f'{len(self.nodes) + 1}' + node_type = random.choice(['default']) + # Label is random number + label = new_node_id + x = random.randint(0, 250) + y = random.randint(0, 250) + + new_node = { + 'id': new_node_id, + 'type': node_type, + 'data': {'label': label}, + 'position': {'x': x, 'y': y}, + 'draggable': True, + } + self.nodes.append(new_node) + + @rx.event + def clear_graph(self): + self.nodes = [] # Clear the nodes list + self.edges = [] # Clear the edges list + + @rx.event + def on_connect(self, new_edge): + # Iterate over the existing edges + for i, edge in enumerate(self.edges): + # If we find an edge with the same ID as the new edge + if edge["id"] == f"e{new_edge['source']}-{new_edge['target']}": + # Delete the existing edge + del self.edges[i] + break + + # Add the new edge + self.edges.append({ + "id": f"e{new_edge['source']}-{new_edge['target']}", + "source": new_edge["source"], + "target": new_edge["target"], + "label": random.choice(["+", "-", "*", "/"]), + "animated": True, + }) + + @rx.event + def on_nodes_change(self, node_changes: List[Dict[str, Any]]): + # Receives a list of Nodes in case of events like dragging + map_id_to_new_position = defaultdict(dict) + + # Loop over the changes and store the new position + for change in node_changes: + if change["type"] == "position" and change.get("dragging") == True: + map_id_to_new_position[change["id"]] = change["position"] + + # Loop over the nodes and update the position + for i, node in enumerate(self.nodes): + if node["id"] in map_id_to_new_position: + new_position = map_id_to_new_position[node["id"]] + self.nodes[i]["position"] = new_position +``` + +Here is an example of the app running: + +```python eval +rx.vstack( + react_flow( + background(), + controls(), + nodes_draggable=True, + nodes_connectable=True, + on_connect=lambda e0: ReactFlowState.on_connect(e0), + on_nodes_change=lambda e0: ReactFlowState.on_nodes_change(e0), + nodes=ReactFlowState.nodes, + edges=ReactFlowState.edges, + fit_view=True, + ), + rx.hstack( + rx.button("Clear graph", on_click=ReactFlowState.clear_graph, width="50%"), + rx.button("Add node", on_click=ReactFlowState.add_random_node, width="50%"), + width="100%", + ), + height="30em", + width="100%", + ) +``` diff --git a/docs/wrapping-react/imports-and-styles.md b/docs/wrapping-react/imports-and-styles.md new file mode 100644 index 00000000000..56b3b16c029 --- /dev/null +++ b/docs/wrapping-react/imports-and-styles.md @@ -0,0 +1,51 @@ + +# Styles and Imports + +When wrapping a React component, you may need to define styles and imports that are specific to the component. This is done by defining the `add_styles` and `add_imports` methods in your component class. + +### Imports + +Sometimes, the component you are wrapping will need to import other components or libraries. This is done by defining the `add_imports` method in your component class. +That method should return a dictionary of imports, where the keys are the names of the packages to import and the values are the names of the components or libraries to import. + +Values can be either a string or a list of strings. If the import needs to be aliased, you can use the `ImportVar` object to specify the alias and whether the import should be installed as a dependency. + +```python +from reflex.utils.imports import ImportVar + +class ComponentWithImports(MyBaseComponent): + def add_imports(self): + """Add imports to the component.""" + return { + # If you only have one import, you can use a string. + "my-package1": "my-import1", + # If you have multiple imports, you can pass a list. + "my-package2": ["my-import2"], + # If you need to control the import in a more detailed way, you can use an ImportVar object. + "my-package3": ImportVar(tag="my-import3", alias="my-alias", install=False, is_default=False), + # To import a CSS file, pass the full path to the file, and use an empty string as the key. + "": "my-package-with-css/styles.css", + } +``` + +```md alert info +# The tag and library of the component will be automatically added to the imports. They do not need to be added again in `add_imports`. +``` + +### Styles + +Styles are any CSS styles that need to be included in the component. The style will be added inline to the component, so you can use any CSS styles that are valid in React. + +```python +class StyledComponent(MyBaseComponent): + """MyComponent.""" + + def add_style(self) -> dict[str, Any] | None: + """Add styles to the component.""" + + return rx.Style({ + "backgroundColor": "red", + "color": "white", + "padding": "10px", + }) +``` diff --git a/docs/wrapping-react/library-and-tags.md b/docs/wrapping-react/library-and-tags.md new file mode 100644 index 00000000000..6b13694bb52 --- /dev/null +++ b/docs/wrapping-react/library-and-tags.md @@ -0,0 +1,166 @@ +--- +title: Library and Tags +--- + +```python exec +from pcweb.pages.docs import api_reference +``` + +# Find The Component + +There are two ways to find a component to wrap: +1. Write the component yourself locally. +2. Find a well-maintained React library on [npm](https://www.npmjs.com/) that contains the component you need. + +In both cases, the process of wrapping the component is the same except for the `library` field. + +# Wrapping the Component + +To start wrapping your React component, the first step is to create a new component in your Reflex app. This is done by creating a new class that inherits from `rx.Component` or `rx.NoSSRComponent`. + +See the [API Reference]({api_reference.component.path}) for more details on the `rx.Component` class. + +This is when we will define the most important attributes of the component: +1. **library**: The name of the npm package that contains the component. +2. **tag**: The name of the component to import from the package. +3. **alias**: (Optional) The name of the alias to use for the component. This is useful if multiple component from different package have a name in common. If `alias` is not specified, `tag` will be used. +4. **lib_dependencies**: Any additional libraries needed to use the component. +5. **is_default**: (Optional) If the component is a default export from the module, set this to `True`. Default is `False`. + +Optionally, you can override the default component creation behavior by implementing the `create` class method. Most components won't need this when props are straightforward conversions from Python to JavaScript. However, this is useful when you need to add custom initialization logic, transform props, or handle special cases when the component is created. + +```md alert warning +# When setting the `library` attribute, it is recommended to included a pinned version of the package. Doing so, the package will only change when you intentionally update the version, avoid unexpected breaking changes. +``` + +```python +class MyBaseComponent(rx.Component): + """MyBaseComponent.""" + + # The name of the npm package. + library = "my-library@x.y.z" + + # The name of the component to use from the package. + tag = "MyComponent" + + # Any additional libraries needed to use the component. + lib_dependencies: list[str] = ["package-deps@x.y.z"] + + # The name of the alias to use for the component. + alias = "MyComponentAlias" + + # If the component is a default export from the module, set this to True. + is_default = True/False + + @classmethod + def create(cls, *children, **props): + """Create an instance of MyBaseComponent. + + Args: + *children: The children of the component. + **props: The props of the component. + + Returns: + The component instance. + """ + # Your custom creation logic here + return super().create(*children, **props) + +``` + +# Wrapping a Dynamic Component + +When wrapping some libraries, you may want to use dynamic imports. This is because they may not be compatible with Server-Side Rendering (SSR). + +To handle this in Reflex, subclass `NoSSRComponent` when defining your component. It works the same as `rx.Component`, but it will automatically add the correct custom code for a dynamic import. + +Often times when you see an import something like this: + +```javascript +import dynamic from 'next/dynamic'; + +const MyLibraryComponent = dynamic(() => import('./MyLibraryComponent'), { + ssr: false +}); +``` + +You can wrap it in Reflex like this: + +```python +from reflex.components.component import NoSSRComponent + +class MyLibraryComponent(NoSSRComponent): + """A component that wraps a lib needing dynamic import.""" + + library = "my-library@x.y.z" + + tag="MyLibraryComponent" +``` + +It may not always be clear when a library requires dynamic imports. A few things to keep in mind are if the component is very client side heavy i.e. the view and structure depends on things that are fetched at run time, or if it uses `window` or `document` objects directly it will need to be wrapped as a `NoSSRComponent`. + +Some examples are: + +1. Video and Audio Players +2. Maps +3. Drawing Canvas +4. 3D Graphics +5. QR Scanners +6. Reactflow + +The reason for this is that it does not make sense for your server to render these components as the server does not have access to your camera, it cannot draw on your canvas or render a video from a file. + +In addition, if in the component documentation it mentions nextJS compatibility or server side rendering compatibility, it is a good sign that it requires dynamic imports. + +# Advanced - Parsing a state Var with a JS Function +When wrapping a component, you may need to parse a state var by applying a JS function to it. + +## Define the parsing function + +First you need to define the parsing function by writing it in `add_custom_code`. + +```python + +def add_custom_code(self) -> list[str]: + """Add custom code to the component.""" + # Define the parsing function + return [ + """ + function myParsingFunction(inputProp) { + // Your parsing logic here + return parsedProp; + }""" + ] +``` + +## Apply the parsing function to your props + +Then, you can apply the parsing function to your props in the `create` method. + +```python +from reflex.vars.base import Var +from reflex.vars.function import FunctionStringVar + + ... + @classmethod + def create(cls, *children, **props): + """Create an instance of MyBaseComponent. + + Args: + *children: The children of the component. + **props: The props of the component. + + Returns: + The component instance. + """ + # Apply the parsing function to the props + if (prop_to_parse := props.get("propsToParse")) is not None: + if isinstance(prop_to_parse, Var): + props["propsToParse"] = FunctionStringVar.create("myParsingFunction").call(prop_to_parse) + else: + # This is not a state Var, so you can parse the value directly in python + parsed_prop = python_parsing_function(prop_to_parse) + props["propsToParse"] = parsed_prop + return super().create(*children, **props) + ... +``` \ No newline at end of file diff --git a/docs/wrapping-react/local-packages.md b/docs/wrapping-react/local-packages.md new file mode 100644 index 00000000000..a0ae718cb0b --- /dev/null +++ b/docs/wrapping-react/local-packages.md @@ -0,0 +1,171 @@ +--- +title: Wrapping Local Packages +--- + +```python exec +import reflex as rx +``` + +# Assets + +If a wrapped component depends on assets such as images, scripts, or +stylesheets, these can be kept adjacent to the component code and +included in the final build using the `rx.asset` function. + +`rx.asset` returns a relative path that references the asset in the compiled +output. The target files are copied into a subdirectory of `assets/external` +based on the module where they are initially used. This allows third-party +components to have external assets with the same name without conflicting +with each other. + +For example, if there is an SVG file named `wave.svg` in the same directory as +this component, it can be rendered using `rx.image` and `rx.asset`. + +```python +class Hello(rx.Component): + @classmethod + def create(cls, *children, **props) -> rx.Component: + props.setdefault("align", "center") + return rx.hstack( + rx.image(src=rx.asset("wave.svg", shared=True), width="50px", height="50px"), + rx.heading("Hello ", *children), + **props + ) +``` + + +# Local Components + +You can also wrap components that you have written yourself. For local components (when the code source is directly in the project), we recommend putting it beside the files that is wrapping it. + +If there is a file `hello.jsx` in the same directory as the component with this content: + +```javascript +// /path/to/components/hello.jsx +import React from 'react'; + +export function Hello({name, onGreet}) { + return ( +
    +

    Hello, {name}!

    + +
    + ) +} +``` + +The python app can use the `rx.asset` helper to copy the component source into +the generated frontend, after which the `library` path in the `rx.Component` may +be specified by prefixing `$/public` to that path returned by `rx.asset`. + +```python +import reflex as rx + +hello_path = rx.asset("./hello.jsx", shared=True) +hello_css_path = rx.asset("./hello.css", shared=True) + +class Hello(rx.Component): + # Use an absolute path starting with $/public + library = f"$/public{hello_path}" + + # Define everything else as normal. + tag = "Hello" + + name: rx.Var[str] = rx.Var.create("World") + on_greet: rx.EventHandler[rx.event.passthrough_event_spec(str)] + + # Include any related CSS files with rx.asset to ensure they are copied. + def add_imports(self): + return {"": f"$/public/{hello_css_path}"} +``` + +## Considerations + +When wrapping local components, keep the following in mind: + +1. **File Extensions**: Ensure that the file extensions are correct (e.g., `.jsx` for React components and `.tsx` for TypeScript components). +2. **Asset Management**: Use `rx.asset` with `shared=True` to manage any assets (e.g., images, styles) that the component depends on. +3. **Event Handling**: Define any event handlers (e.g., `on_greet`) as part of the component's API and pass those to the component _from the Reflex app_. Do not attempt to hook into Reflex's event system directly from Javascript. + +## Use Case + +Local components are useful when shimming small pieces of functionality that are +simpler or more performant when implemented directly in Javascript, such as: + +* Spammy events: keys, touch, mouse, scroll -- these are often better processed on the client side. +* Using canvas, graphics or WebGPU +* Working with other Web APIs like storage, screen capture, audio/midi +* Integrating with complex third-party libraries + * For application-specific use, it may be easier to wrap a local component that + provides the needed subset of the library's functionality in a simpler API for use in Reflex. + +# Local Packages + +If the component is part of a local package, available on Github, or +downloadable via a web URL, it can also be wrapped in Reflex. Specify the path +or URL after an `@` following the package name. + +Any local paths are relative to the `.web` folder, so you can use `../` prefix +to reference the Reflex project root. + +Some examples of valid specifiers for a package called +[`@masenf/hello-react`](https://github.com/masenf/hello-react) are: + +* GitHub: `@masenf/hello-react@github:masenf/hello-react` +* URL: `@masenf/hello-react@https://github.com/masenf/hello-react/archive/refs/heads/main.tar.gz` +* Local Archive: `@masenf/hello-react@../hello-react.tgz` +* Local Directory: `@masenf/hello-react@../hello-react` + +It is important that the package name matches the name in `package.json` so +Reflex can generate the correct import statement in the generated javascript +code. + +These package specifiers can be used for `library` or `lib_dependencies`. + +```python demo exec toggle +class GithubComponent(rx.Component): + library = "@masenf/hello-react@github:masenf/hello-react" + tag = "Counter" + + def add_imports(self): + return { + "": ["@masenf/hello-react/dist/style.css"] + } + +def github_component_example(): + return GithubComponent.create() +``` + +Although more complicated, this approach is useful when the local components +have additional dependencies or build steps required to prepare the component +for use. + +Some important notes regarding this approach: + +* The repo or archive must contain a `package.json` file. +* `prepare` or `build` scripts will NOT be executed. The distribution archive, + directory, or repo must already contain the built javascript files (this is common). + +```md alert +# Ensure CSS files are exported in `package.json` + +In addition to exporting the module containing the component, any CSS files +intended to be imported by the wrapped component must also be listed in the +`exports` key of `package.json`. + +```json +{ + // ..., + "exports": { + ".": { + "import": "./dist/index.js", + "require": "./dist/index.umd.cjs" + }, + "./dist/style.css": { + "import": "./dist/style.css", + "require": "./dist/style.css" + } + }, + // ... +} +``` diff --git a/docs/wrapping-react/more-wrapping-examples.md b/docs/wrapping-react/more-wrapping-examples.md new file mode 100644 index 00000000000..3a5638b66fa --- /dev/null +++ b/docs/wrapping-react/more-wrapping-examples.md @@ -0,0 +1,449 @@ +# More React Libraries + + +## AG Charts + +Here we wrap the AG Charts library from the NPM package [ag-charts-react](https://www.npmjs.com/package/ag-charts-react). + +In the react code below we can see the first `2` lines are importing React and ReactDOM, and this can be ignored when wrapping your component. + +We import the `AgCharts` component from the `ag-charts-react` library on line 5. In Reflex this is wrapped by `library = "ag-charts-react"` and `tag = "AgCharts"`. + +Line `7` defines a functional React component, which on line `26` returns `AgCharts` which is similar in the Reflex code to using the `chart` component. + +Line `9` uses the `useState` hook to create a state variable `chartOptions` and its setter function `setChartOptions` (equivalent to the event handler `set_chart_options` in reflex). The initial state variable is of type dict and has two key value pairs `data` and `series`. + +When we see `useState` in React code, it correlates to state variables in your State. As you can see in our Reflex code we have a state variable `chart_options` which is a dictionary, like in our React code. + +Moving to line `26` we see that the `AgCharts` has a prop `options`. In order to use this in Reflex we must wrap this prop. We do this with `options: rx.Var[dict]` in the `AgCharts` component. + +Lines `31` and `32` are rendering the component inside the root element. This can be ignored when we are wrapping a component as it is done in Reflex by creating an `index` function and adding it to the app. + + +---md tabs + +--tab React Code + +```javascript +1 | import React, \{ useState } from 'react'; +2 | import ReactDOM from 'react-dom/client'; +3 | +4 | // React Chart Component +5 | import \{ AgCharts } from 'ag-charts-react'; +6 | +7 | const ChartExample = () => { +8 | // Chart Options: Control & configure the chart +9 | const [chartOptions, setChartOptions] = useState({ +10| // Data: Data to be displayed in the chart +11| data: [ +12| \{ month: 'Jan', avgTemp: 2.3, iceCreamSales: 162000 }, +13| \{ month: 'Mar', avgTemp: 6.3, iceCreamSales: 302000 }, +14| \{ month: 'May', avgTemp: 16.2, iceCreamSales: 800000 }, +15| \{ month: 'Jul', avgTemp: 22.8, iceCreamSales: 1254000 }, +16| \{ month: 'Sep', avgTemp: 14.5, iceCreamSales: 950000 }, +17| \{ month: 'Nov', avgTemp: 8.9, iceCreamSales: 200000 }, +18| ], +19| // Series: Defines which chart type and data to use +20| series: [\{ type: 'bar', xKey: 'month', yKey: 'iceCreamSales' }], +21| }); +22| +23| // React Chart Component +24| return ( +25| // AgCharts component with options passed as prop +26| +27| ); +28| } +29| +30| // Render component inside root element +31| const root = ReactDOM.createRoot(document.getElementById('root')); +32| root.render(); +``` + +-- +--tab Reflex Code + +```python +import reflex as rx + +class AgCharts(rx.Component): + """ A simple line chart component using AG Charts """ + + library = "ag-charts-react" + + tag = "AgCharts" + + options: rx.Var[dict] + + +chart = AgCharts.create + + +class State(rx.State): + """The app state.""" + chart_options: dict = { + "data": [ + \{"month":"Jan", "avgTemp":2.3, "iceCreamSales":162000}, + \{"month":"Mar", "avgTemp":6.3, "iceCreamSales":302000}, + \{"month":"May", "avgTemp":16.2, "iceCreamSales":800000}, + \{"month":"Jul", "avgTemp":22.8, "iceCreamSales":1254000}, + \{"month":"Sep", "avgTemp":14.5, "iceCreamSales":950000}, + \{"month":"Nov", "avgTemp":8.9, "iceCreamSales":200000} + ], + "series": [\{"type":"bar", "xKey":"month", "yKey":"iceCreamSales"}] + } + +def index() -> rx.Component: + return chart( + options=State.chart_options, + ) + +app = rx.App() +app.add_page(index) +``` +-- + +--- + + +## React Leaflet + +```python exec +from pcweb.pages import docs +``` + +In this example we are wrapping the React Leaflet library from the NPM package [react-leaflet](https://www.npmjs.com/package/react-leaflet). + +On line `1` we import the `dynamic` function from Next.js and on line `21` we set `ssr: false`. Lines `4` and `6` use the `dynamic` function to import the `MapContainer` and `TileLayer` components from the `react-leaflet` library. This is used to dynamically import the `MapContainer` and `TileLayer` components from the `react-leaflet` library. This is done in Reflex by using the `NoSSRComponent` class when defining the component. There is more information of when this is needed on the `Dynamic Imports` section of this [page]({docs.wrapping_react.library_and_tags.path}). + +It mentions in the documentation that it is necessary to include the Leaflet CSS file, which is added on line `2` in the React code below. This can be done in Reflex by using the `add_imports` method in the `MapContainer` component. We can add a relative path from within the React library or a full URL to the CSS file. + +Line `4` defines a functional React component, which on line `8` returns the `MapContainer` which is done in the Reflex code using the `map_container` component. + +The `MapContainer` component has props `center`, `zoom`, `scrollWheelZoom`, which we wrap in the `MapContainer` component in the Reflex code. We ignore the `style` prop as it is a reserved name in Reflex. We can use the `rename_props` method to change the name of the prop, as we will see in the React PDF Renderer example, but in this case we just ignore it and add the `width` and `height` props as css in Reflex. + +The `TileLayer` component has a prop `url` which we wrap in the `TileLayer` component in the Reflex code. + +Lines `24` and `25` defines and exports a React functional component named `Home` which returns the `MapComponent` component. This can be ignored in the Reflex code when wrapping the component as we return the `map_container` component in the `index` function. + +---md tabs + +--tab React Code + +```javascript +1 | import dynamic from "next/dynamic"; +2 | import "leaflet/dist/leaflet.css"; +3 | +4 | const MapComponent = dynamic( +5 | () => { +6 | return import("react-leaflet").then((\{ MapContainer, TileLayer }) => { +7 | return () => ( +8 | +14| +17| +18| ); +19| }); +20| }, +21| \{ ssr: false } +22| ); +23| +24| export default function Home() { +25| return ; +26| } +``` + +-- +--tab Reflex Code + +```python +import reflex as rx + +class MapContainer(rx.NoSSRComponent): + + library = "react-leaflet" + + tag = "MapContainer" + + center: rx.Var[list] + + zoom: rx.Var[int] + + scroll_wheel_zoom: rx.Var[bool] + + # Can also pass a url like: https://unpkg.com/leaflet/dist/leaflet.css + def add_imports(self): + return \{"": ["leaflet/dist/leaflet.css"]} + + + +class TileLayer(rx.NoSSRComponent): + + library = "react-leaflet" + + tag = "TileLayer" + + url: rx.Var[str] + + +map_container = MapContainer.create +tile_layer = TileLayer.create + +def index() -> rx.Component: + return map_container( + tile_layer(url="https://\{s}.tile.openstreetmap.org/\{z}/\{x}/\{y}.png"), + center=[51.505, -0.09], + zoom=13, + #scroll_wheel_zoom=True + width="100%", + height="50vh", + ) + + +app = rx.App() +app.add_page(index) + +``` +-- + +--- + + +## React PDF Renderer + +In this example we are wrapping the React renderer for creating PDF files on the browser and server from the NPM package [@react-pdf/renderer](https://www.npmjs.com/package/@react-pdf/renderer). + +This example is similar to the previous examples, and again Dynamic Imports are required for this library. This is done in Reflex by using the `NoSSRComponent` class when defining the component. There is more information on why this is needed on the `Dynamic Imports` section of this [page]({docs.wrapping_react.library_and_tags.path}). + +The main difference with this example is that the `style` prop, used on lines `20`, `21` and `24` in React code, is a reserved name in Reflex so can not be wrapped. A different name must be used when wrapping this prop and then this name must be changed back to the original with the `rename_props` method. In this example we name the prop `theme` in our Reflex code and then change it back to `style` with the `rename_props` method in both the `Page` and `View` components. + + +```md alert info +# List of reserved names in Reflex + +_The style of the component._ + +`style: Style = Style()` + +_A mapping from event triggers to event chains._ + +`event_triggers: Dict[str, Union[EventChain, Var]] = \{}` + +_The alias for the tag._ + +`alias: Optional[str] = None` + +_Whether the import is default or named._ + +`is_default: Optional[bool] = False` + +_A unique key for the component._ + +`key: Any = None` + +_The id for the component._ + +`id: Any = None` + +_The class name for the component._ + +`class_name: Any = None` + +_Special component props._ + +`special_props: List[Var] = []` + +_Whether the component should take the focus once the page is loaded_ + +`autofocus: bool = False` + +_components that cannot be children_ + +`_invalid_children: List[str] = []` + +_only components that are allowed as children_ + +`_valid_children: List[str] = []` + +_only components that are allowed as parent_ + +`_valid_parents: List[str] = []` + +_props to change the name of_ + +`_rename_props: Dict[str, str] = \{}` + +_custom attribute_ + +`custom_attrs: Dict[str, Union[Var, str]] = \{}` + +_When to memoize this component and its children._ + +`_memoization_mode: MemoizationMode = MemoizationMode()` + +_State class associated with this component instance_ + +`State: Optional[Type[reflex.state.State]] = None` +``` + +---md tabs + +--tab React Code + +```javascript +1 | import ReactDOM from 'react-dom'; +2 | import \{ Document, Page, Text, View, StyleSheet, PDFViewer } from '@react-pdf/renderer'; +3 | +4 | // Create styles +5 | const styles = StyleSheet.create({ +6 | page: { +7 | flexDirection: 'row', +8 | backgroundColor: '#E4E4E4', +9 | }, +10| section: { +11| margin: 10, +12| padding: 10, +13| flexGrow: 1, +14| }, +15| }); +16| +17| // Create Document Component +18| const MyDocument = () => ( +19| +20| +21| +22| Section #1 +23| +24| +25| Section #2 +26| +27| +28| +29| ); +30| +31| const App = () => ( +32| +33| +34| +35| ); +36| +37| ReactDOM.render(, document.getElementById('root')); +``` + +-- +--tab Reflex Code + +```python +import reflex as rx + +class Document(rx.Component): + + library = "@react-pdf/renderer" + + tag = "Document" + + +class Page(rx.Component): + + library = "@react-pdf/renderer" + + tag = "Page" + + size: rx.Var[str] + # here we are wrapping style prop but as style is a reserved name in Reflex we must name it something else and then change this name with rename props method + theme: rx.Var[dict] + + _rename_props: dict[str, str] = { + "theme": "style", + } + + +class Text(rx.Component): + + library = "@react-pdf/renderer" + + tag = "Text" + + +class View(rx.Component): + + library = "@react-pdf/renderer" + + tag = "View" + + # here we are wrapping style prop but as style is a reserved name in Reflex we must name it something else and then change this name with rename props method + theme: rx.Var[dict] + + _rename_props: dict[str, str] = { + "theme": "style", + } + + +class StyleSheet(rx.Component): + + library = "@react-pdf/renderer" + + tag = "StyleSheet" + + page: rx.Var[dict] + + section: rx.Var[dict] + + +class PDFViewer(rx.NoSSRComponent): + + library = "@react-pdf/renderer" + + tag = "PDFViewer" + + +document = Document.create +page = Page.create +text = Text.create +view = View.create +style_sheet = StyleSheet.create +pdf_viewer = PDFViewer.create + + +styles = style_sheet({ + "page": { + "flexDirection": 'row', + "backgroundColor": '#E4E4E4', + }, + "section": { + "margin": 10, + "padding": 10, + "flexGrow": 1, + }, +}) + + +def index() -> rx.Component: + return pdf_viewer( + document( + page( + view( + text("Hello, World!"), + theme=styles.section, + ), + view( + text("Hello, 2!"), + theme=styles.section, + ), + size="A4", theme=styles.page), + ), + width="100%", + height="80vh", + ) + +app = rx.App() +app.add_page(index) +``` +-- + +--- \ No newline at end of file diff --git a/docs/wrapping-react/overview.md b/docs/wrapping-react/overview.md new file mode 100644 index 00000000000..63a8478397b --- /dev/null +++ b/docs/wrapping-react/overview.md @@ -0,0 +1,154 @@ +```python exec +import reflex as rx +from typing import Any +from pcweb.components.spline import spline +from pcweb.pages.docs import custom_components +from pcweb import constants +``` + +# Wrapping React + +One of Reflex's most powerful features is the ability to wrap React components and take advantage of the vast ecosystem of React libraries. + +If you want a specific component for your app but Reflex doesn't provide it, there's a good chance it's available as a React component. Search for it on [npm]({constants.NPMJS_URL}), and if it's there, you can use it in your Reflex app. You can also create your own local React components and wrap them in Reflex. + +Once you wrap your component, you [publish it]({custom_components.overview.path}) to the Reflex library so that others can use it. + +## Simple Example + +Simple components that don't have any interaction can be wrapped with just a few lines of code. + +Below we show how to wrap the [Spline]({constants.SPLINE_URL}) library can be used to create 3D scenes and animations. + +```python demo exec +import reflex as rx + +class Spline(rx.Component): + """Spline component.""" + + # The name of the npm package. + library = "@splinetool/react-spline" + + # Any additional libraries needed to use the component. + lib_dependencies: list[str] = ["@splinetool/runtime@1.5.5"] + + # The name of the component to use from the package. + tag = "Spline" + + # Spline is a default export from the module. + is_default = True + + # Any props that the component takes. + scene: rx.Var[str] + +# Convenience function to create the Spline component. +spline = Spline.create + +# Use the Spline component in your app. +def index(): + return spline(scene="https://prod.spline.design/joLpOOYbGL-10EJ4/scene.splinecode") +``` + + +## ColorPicker Example + +Similar to the Spline example we start with defining the library and tag. In this case the library is `react-colorful` and the tag is `HexColorPicker`. + +We also have a var `color` which is the current color of the color picker. + +Since this component has interaction we must specify any event triggers that the component takes. The color picker has a single trigger `on_change` to specify when the color changes. This trigger takes in a single argument `color` which is the new color. + +```python exec +from reflex.components.component import NoSSRComponent + +class ColorPicker(NoSSRComponent): + library = "react-colorful" + tag = "HexColorPicker" + color: rx.Var[str] + on_change: rx.EventHandler[lambda color: [color]] + +color_picker = ColorPicker.create + +ColorPickerState = rx._x.client_state(default="#db114b", var_name="color") +``` + +```python eval +rx.box( + ColorPickerState, + rx.vstack( + rx.heading(ColorPickerState.value, color="white"), + color_picker( + on_change=ColorPickerState.set_value + ), + ), + background_color=ColorPickerState.value, + padding="5em", + border_radius="12px", + margin_bottom="1em", +) +``` + +```python +from reflex.components.component import NoSSRComponent + +class ColorPicker(NoSSRComponent): + library = "react-colorful" + tag = "HexColorPicker" + color: rx.Var[str] + on_change: rx.EventHandler[lambda color: [color]] + +color_picker = ColorPicker.create + +class ColorPickerState(rx.State): + color: str = "#db114b" + +def index(): + return rx.box( + rx.vstack( + rx.heading(ColorPickerState.color, color="white"), + color_picker( + on_change=ColorPickerState.set_color + ), + ), + background_color=ColorPickerState.color, + padding="5em", + border_radius="1em", + ) +``` + +## What Not To Wrap + +There are some libraries on npm that are not do not expose React components and therefore are very hard to wrap with Reflex. + +A library like [spline](https://www.npmjs.com/package/@splinetool/runtime) below is going to be difficult to wrap with Reflex because it does not expose a React component. + +```javascript +import \{ Application } from '@splinetool/runtime'; + +// make sure you have a canvas in the body +const canvas = document.getElementById('canvas3d'); + +// start the application and load the scene +const spline = new Application(canvas); +spline.load('https://prod.spline.design/6Wq1Q7YGyM-iab9i/scene.splinecode'); +``` + +You should look out for JSX, a syntax extension to JavaScript, which has angle brackets `(

    Hello, world!

    )`. If you see JSX, it's likely that the library is a React component and can be wrapped with Reflex. + +If the library does not expose a react component you need to try and find a JS React wrapper for the library, such as [react-spline](https://www.npmjs.com/package/@splinetool/react-spline). + +```javascript +import Spline from '@splinetool/react-spline'; + +export default function App() { + return ( +
    + +
    + ); +} +``` + + + +In the next page, we will go step by step through a more complex example of wrapping a React component. diff --git a/docs/wrapping-react/props.md b/docs/wrapping-react/props.md new file mode 100644 index 00000000000..afc7442d8a4 --- /dev/null +++ b/docs/wrapping-react/props.md @@ -0,0 +1,202 @@ +--- +title: Props - Wrapping React +--- + +# Props + +When wrapping a React component, you want to define the props that will be accepted by the component. +This is done by defining the props and annotating them with a `rx.Var`. + +Broadly, there are three kinds of props you can encounter when wrapping a React component: +1. **Simple Props**: These are props that are passed directly to the component. They can be of any type, including strings, numbers, booleans, and even lists or dictionaries. +2. **Callback Props**: These are props that expect to receive a function. That function will usually be called by the component as a callback. (This is different from event handlers.) +3. **Component Props**: These are props that expect to receive a components themselves. They can be used to create more complex components by composing them together. +4. **Event Handlers**: These are props that expect to receive a function that will be called when an event occurs. They are defined as `rx.EventHandler` with a signature function to define the spec of the event. + +## Simple Props + +Simple props are the most common type of props you will encounter when wrapping a React component. They are passed directly to the component and can be of any type (but most commonly strings, numbers, booleans, and structures). + +For custom types, you can use `TypedDict` to define the structure of the custom types. However, if you need the attributes to be automatically converted to camelCase once compiled in JS, you can use `rx.PropsBase` instead of `TypedDict`. + +```python +class CustomReactType(TypedDict): + """Custom React type.""" + + # Define the structure of the custom type to match the Javascript structure. + attribute1: str + attribute2: bool + attribute3: int + + +class CustomReactType2(rx.PropsBase): + """Custom React type.""" + + # Define the structure of the custom type to match the Javascript structure. + attr_foo: str # will be attrFoo in JS + attr_bar: bool # will be attrBar in JS + attr_baz: int # will be attrBaz in JS + +class SimplePropsComponent(MyBaseComponent): + """MyComponent.""" + + # Type the props according the component documentation. + + # props annotated as `string` in javascript + prop1: rx.Var[str] + + # props annotated as `number` in javascript + prop2: rx.Var[int] + + # props annotated as `boolean` in javascript + prop3: rx.Var[bool] + + # props annotated as `string[]` in javascript + prop4: rx.Var[list[str]] + + # props annotated as `CustomReactType` in javascript + props5: rx.Var[CustomReactType] + + # props annotated as `CustomReactType2` in javascript + props6: rx.Var[CustomReactType2] + + # Sometimes a props will accept multiple types. You can use `|` to specify the types. + # props annotated as `string | boolean` in javascript + props7: rx.Var[str | bool] +``` + +## Callback Props + +Callback props are used to handle events or to pass data back to the parent component. They are defined as `rx.Var` with a type of `FunctionVar` or `Callable`. + +```python +from typing import Callable +from reflex.vars.function import FunctionVar + +class CallbackPropsComponent(MyBaseComponent): + """MyComponent.""" + + # A callback prop that takes a single argument. + callback_props: rx.Var[Callable] +``` + +## Component Props +Some components will occasionally accept other components as props, usually annotated as `ReactNode`. In Reflex, these are defined as `rx.Component`. + +```python +class ComponentPropsComponent(MyBaseComponent): + """MyComponent.""" + + # A prop that takes a component as an argument. + component_props: rx.Var[rx.Component] +``` + +## Event Handlers +Event handlers are props that expect to receive a function that will be called when an event occurs. They are defined as `rx.EventHandler` with a signature function to define the spec of the event. + +```python +from reflex.vars.event_handler import EventHandler +from reflex.vars.function import FunctionVar +from reflex.vars.object import ObjectVar + +class InputEventType(TypedDict): + """Input event type.""" + + # Define the structure of the input event. + foo: str + bar: int + +class OutputEventType(TypedDict): + """Output event type.""" + + # Define the structure of the output event. + baz: str + qux: int + + +def custom_spec1(event: ObjectVar[InputEventType]) -> tuple[str, int]: + """Custom event spec using ObjectVar with custom type as input and tuple as output.""" + return ( + event.foo.to(str), + event.bar.to(int), + ) + +def custom_spec2(event: ObjectVar[dict]) -> tuple[Var[OutputEventType]]: + """Custom event spec using ObjectVar with dict as input and custom type as output.""" + return Var.create( + { + "baz": event["foo"], + "qux": event["bar"], + }, + ).to(OutputEventType) + +class EventHandlerComponent(MyBaseComponent): + """MyComponent.""" + + # An event handler that take no argument. + on_event: rx.EventHandler[rx.event.no_args_event_spec] + + # An event handler that takes a single string argument. + on_event_with_arg: rx.EventHandler[rx.event.passthrough_event_spec(str)] + + # An event handler specialized for input events, accessing event.target.value from the event. + on_input_change: rx.EventHandler[rx.event.input_event] + + # An event handler specialized for key events, accessing event.key from the event and provided modifiers (ctrl, alt, shift, meta). + on_key_down: rx.EventHandler[rx.event.key_event] + + # An event handler that takes a custom spec. (Event handler must expect a tuple of two values [str and int]) + on_custom_event: rx.EventHandler[custom_spec1] + + # Another event handler that takes a custom spec. (Event handler must expect a tuple of one value, being a OutputEventType) + on_custom_event2: rx.EventHandler[custom_spec2] +``` + +```md alert info +# Custom event specs have a few use case where they are particularly useful. If the event returns non-serializable data, you can filter them out so the event can be sent to the backend. You can also use them to transform the data before sending it to the backend. +``` + +### Emulating Event Handler Behavior Outside a Component + +In some instances, you may need to replicate the special behavior applied to +event handlers from outside of a component context. For example if the component +to be wrapped requires event callbacks passed in a dictionary, this can be +achieved by directly instantiating an `EventChain`. + +A real-world example of this is the `onEvents` prop of +[`echarts-for-react`](https://www.npmjs.com/package/echarts-for-react) library, +which, unlike a normal event handler, expects a mapping of event handlers like: + +```javascript + +``` + +To achieve this in Reflex, you can create an explicit `EventChain` for each +event handler: + +```python +@classmethod +def create(cls, *children, **props): + on_events = props.pop("on_events", {}) + + event_chains = {} + for event_name, handler in on_events.items(): + # Convert the EventHandler/EventSpec/lambda to an EventChain + event_chains[event_name] = rx.EventChain.create( + handler, + args_spec=rx.event.no_args_event_spec, + key=event_name, + ) + if on_events: + props["on_events"] = event_chains + + # Create the component instance + return super().create(*children, **props) +``` \ No newline at end of file diff --git a/docs/wrapping-react/serializers.md b/docs/wrapping-react/serializers.md new file mode 100644 index 00000000000..4bb1abcffd0 --- /dev/null +++ b/docs/wrapping-react/serializers.md @@ -0,0 +1,44 @@ +--- +title: Serializers +--- + +# Serializers + +Vars can be any type that can be serialized to JSON. This includes primitive types like strings, numbers, and booleans, as well as more complex types like lists, dictionaries, and dataframes. + +In case you need to serialize a more complex type, you can use the `serializer` decorator to convert the type to a primitive type that can be stored in the state. Just define a method that takes the complex type as an argument and returns a primitive type. We use type annotations to determine the type that you want to serialize. + +For example, the Plotly component serializes a plotly figure into a JSON string that can be stored in the state. + +```python +import json +import reflex as rx +from plotly.graph_objects import Figure +from plotly.io import to_json + +# Use the serializer decorator to convert the figure to a JSON string. +# Specify the type of the argument as an annotation. +@rx.serializer +def serialize_figure(figure: Figure) -> list: + # Use Plotly's to_json method to convert the figure to a JSON string. + return json.loads(to_json(figure))["data"] +``` + +We can then define a var of this type as a prop in our component. + +```python +import reflex as rx +from plotly.graph_objects import Figure + +class Plotly(rx.Component): + """Display a plotly graph.""" + library = "react-plotly.js@2.6.0" + lib_dependencies: List[str] = ["plotly.js@2.22.0"] + + tag = "Plot" + + is_default = True + + # Since a serialize is defined now, we can use the Figure type directly. + data: rx.Var[Figure] +``` diff --git a/docs/wrapping-react/step-by-step.md b/docs/wrapping-react/step-by-step.md new file mode 100644 index 00000000000..e69de29bb2d diff --git a/docs/zh/zh_cn/README.md b/docs/zh/zh_cn/README.md deleted file mode 100644 index e3da05f034f..00000000000 --- a/docs/zh/zh_cn/README.md +++ /dev/null @@ -1,251 +0,0 @@ -
    -Reflex Logo -
    - -### **✨ 使用 Python 创建高效且可自定义的网页应用程序,几秒钟内即可部署.✨** - -[![PyPI version](https://badge.fury.io/py/reflex.svg)](https://badge.fury.io/py/reflex) -![versions](https://img.shields.io/pypi/pyversions/reflex.svg) -[![Documentaiton](https://img.shields.io/badge/Documentation%20-Introduction%20-%20%23007ec6)](https://reflex.dev/docs/getting-started/introduction) -[![PyPI Downloads](https://static.pepy.tech/badge/reflex)](https://pepy.tech/projects/reflex) -[![Discord](https://img.shields.io/discord/1029853095527727165?color=%237289da&label=Discord)](https://discord.gg/T5WSbC2YtQ) - -
    - ---- - -[English](https://github.com/reflex-dev/reflex/blob/main/README.md) | [简体中文](https://github.com/reflex-dev/reflex/blob/main/docs/zh/zh_cn/README.md) | [繁體中文](https://github.com/reflex-dev/reflex/blob/main/docs/zh/zh_tw/README.md) | [Türkçe](https://github.com/reflex-dev/reflex/blob/main/docs/tr/README.md) | [हिंदी](https://github.com/reflex-dev/reflex/blob/main/docs/in/README.md) | [Português (Brasil)](https://github.com/reflex-dev/reflex/blob/main/docs/pt/pt_br/README.md) | [Italiano](https://github.com/reflex-dev/reflex/blob/main/docs/it/README.md) | [Español](https://github.com/reflex-dev/reflex/blob/main/docs/es/README.md) | [한국어](https://github.com/reflex-dev/reflex/blob/main/docs/kr/README.md) | [日本語](https://github.com/reflex-dev/reflex/blob/main/docs/ja/README.md) | [Deutsch](https://github.com/reflex-dev/reflex/blob/main/docs/de/README.md) | [Persian (پارسی)](https://github.com/reflex-dev/reflex/blob/main/docs/pe/README.md) | [Tiếng Việt](https://github.com/reflex-dev/reflex/blob/main/docs/vi/README.md) - ---- - -# Reflex - -Reflex 是一个使用纯 Python 构建全栈 web 应用的库。 - -关键特性: - -- **纯 Python** - 前端、后端开发全都使用 Python,不需要学习 Javascript。 -- **完整的灵活性** - Reflex 很容易上手, 并且也可以扩展到复杂的应用程序。 -- **立即部署** - 构建后,使用[单个命令](https://reflex.dev/docs/hosting/deploy-quick-start/)就能部署应用程序;或者也可以将其托管在您自己的服务器上。 - -请参阅我们的[架构页](https://reflex.dev/blog/2024-03-21-reflex-architecture/#the-reflex-architecture)了解 Reflex 如何工作。 - -## ⚙️ 安装 - -打开一个终端并且运行(要求 Python3.10+): - -```bash -pip install reflex -``` - -## 🥳 创建您的第一个应用程序 - -安装 Reflex 的同时也会安装 `reflex` 命令行工具. - -通过创建一个新项目来测试是否安装成功(请把 my_app_name 替代为您的项目名字): - -```bash -mkdir my_app_name -cd my_app_name -reflex init -``` - -这段命令会在新文件夹初始化一个应用程序模板. - -您可以在开发者模式下运行这个应用程序: - -```bash -reflex run -``` - -您可以看到您的应用程序运行在 http://localhost:3000. - -现在您可以在以下位置修改代码 `my_app_name/my_app_name.py`,Reflex 拥有快速刷新(fast refresh),所以您可以在保存代码后马上看到更改. - -## 🫧 范例 - -让我们来看一个例子: 创建一个使用 [DALL·E](https://platform.openai.com/docs/guides/images/image-generation?context=node) 进行图像生成的图形界面.为了保持范例简单,我们只使用 OpenAI API,但是您可以将其替换成本地端的 ML 模型. - -  - -
    -DALL·E的前端界面, 展示了图片生成的进程 -
    - -  - -这是这个范例的完整代码,只需要一个 Python 文件就可以完成! - -```python -import reflex as rx -import openai - -openai_client = openai.OpenAI() - - -class State(rx.State): - """The app state.""" - - prompt = "" - image_url = "" - processing = False - complete = False - - def get_image(self): - """Get the image from the prompt.""" - if self.prompt == "": - return rx.window_alert("Prompt Empty") - - self.processing, self.complete = True, False - yield - response = openai_client.images.generate( - prompt=self.prompt, n=1, size="1024x1024" - ) - self.image_url = response.data[0].url - self.processing, self.complete = False, True - - -def index(): - return rx.center( - rx.vstack( - rx.heading("DALL-E", font_size="1.5em"), - rx.input( - placeholder="Enter a prompt..", - on_blur=State.set_prompt, - width="25em", - ), - rx.button( - "Generate Image", - on_click=State.get_image, - width="25em", - loading=State.processing - ), - rx.cond( - State.complete, - rx.image(src=State.image_url, width="20em"), - ), - align="center", - ), - width="100%", - height="100vh", - ) - -# Add state and page to the app. -app = rx.App() -app.add_page(index, title="Reflex:DALL-E") -``` - -## 让我们分解以上步骤. - -
    -解释 DALL-E app 的前端和后端部分的区别。 -
    - -### **Reflex UI** - -让我们从 UI 开始. - -```python -def index(): - return rx.center( - ... - ) -``` - -这个 `index` 函数定义了应用程序的前端. - -我们用不同的组件比如 `center`, `vstack`, `input`, 和 `button` 来创建前端, 组件之间可以相互嵌入,来创建复杂的布局. -并且您可以使用关键字参数来使用 CSS 的全部功能. - -Reflex 拥有 [60+ 个内置组件](https://reflex.dev/docs/library) 来帮助您开始创建应用程序. 我们正在积极添加组件, 但是您也可以容易的 [创建自己的组件](https://reflex.dev/docs/wrapping-react/overview/). - -### **State** - -Reflex 用 State 来渲染您的 UI. - -```python -class State(rx.State): - """The app state.""" - prompt = "" - image_url = "" - processing = False - complete = False - -``` - -State 定义了所有可能会发生变化的变量(称为 vars)以及能够改变这些变量的函数. - -在这个范例中,State 由 `prompt` 和 `image_url` 组成.此外,State 还包含有两个布尔值 `processing` 和 `complete`,用于指示何时显示循环进度指示器和图像. - -### **Event Handlers** - -```python -def get_image(self): - """Get the image from the prompt.""" - if self.prompt == "": - return rx.window_alert("Prompt Empty") - - self.processing, self.complete = True, False - yield - response = openai_client.images.generate( - prompt=self.prompt, n=1, size="1024x1024" - ) - self.image_url = response.data[0].url - self.processing, self.complete = False, True -``` - -在 State 中,我们定义了称为事件处理器(event handlers)的函数,用于改变状态变量(state vars).在 Reflex 中,事件处理器是我们可以修改状态的方式.它们可以作为对用户操作的响应而被调用,例如点击一个按钮或在文本框中输入.这些操作被称为事件. - -我们的 DALL·E 应用有一个事件处理器,名为 `get_image`,它用于从 OpenAI API 获取图像.在事件处理器中使用 `yield` 将导致 UI 进行更新.否则,UI 将在事件处理器结束时进行更新. - -### **Routing** - -最后,定义我们的应用程序. - -```python -app = rx.App() -``` - -我们添加从应用程序根目录到 index 组件的路由.我们还添加了一个在页面预览或浏览器标签中显示的标题. - -```python -app.add_page(index, title="DALL-E") -``` - -您可以通过增加更多页面来创建一个多页面的应用. - -## 📑 资源 - -
    - -📑 [文档](https://reflex.dev/docs/getting-started/introduction)   |   🗞️ [日志](https://reflex.dev/blog)   |   📱 [组件库](https://reflex.dev/docs/library)   |   🖼️ [模板](https://reflex.dev/templates/)   |   🛸 [部署](https://reflex.dev/docs/hosting/deploy-quick-start)   - -
    - -## ✅ Reflex 的状态 - -Reflex 于 2022 年 12 月以 Pynecone 的名称推出. - -从 2025 年开始,[Reflex Cloud](https://cloud.reflex.dev)已经推出,为 Reflex 应用提供最佳的托管体验。我们将继续开发并实现更多功能。 - -Reflex 每周都有新功能和发布新版本! 确保您按下 :star: 收藏和 :eyes: 关注 这个 仓库来确保知道最新信息. - -## 贡献 - -我们欢迎任何大小的贡献,以下是几个好的方法来加入 Reflex 社群. - -- **加入我们的 Discord**: 我们的 [Discord](https://discord.gg/T5WSbC2YtQ) 是帮助您加入 Reflex 项目和讨论或贡献最棒的地方. -- **GitHub Discussions**: 一个来讨论您想要添加的功能或是需要澄清的事情的好地方. -- **GitHub Issues**: [Issues](https://github.com/reflex-dev/reflex/issues)是报告错误的绝佳地方,另外您可以试着解决一些现有 issue 并提交 PR. - -我们正在积极寻找贡献者,无关您的技能或经验水平. 若要贡献,请查看[CONTRIBUTING.md](https://github.com/reflex-dev/reflex/blob/main/CONTRIBUTING.md) - -## 感谢我们所有的贡献者: - - - - - -## 授权 - -Reflex 是一个开源项目,使用 [Apache License 2.0](/LICENSE) 授权. diff --git a/docs/zh/zh_tw/README.md b/docs/zh/zh_tw/README.md deleted file mode 100644 index 606e483ee09..00000000000 --- a/docs/zh/zh_tw/README.md +++ /dev/null @@ -1,251 +0,0 @@ -
    -Reflex Logo -
    - -**✨ 使用 Python 建立高效且可自訂的網頁應用程式,幾秒鐘內即可部署。✨** - -[![PyPI version](https://badge.fury.io/py/reflex.svg)](https://badge.fury.io/py/reflex) -![versions](https://img.shields.io/pypi/pyversions/reflex.svg) -[![Documentaiton](https://img.shields.io/badge/Documentation%20-Introduction%20-%20%23007ec6)](https://reflex.dev/docs/getting-started/introduction) -[![PyPI Downloads](https://static.pepy.tech/badge/reflex)](https://pepy.tech/projects/reflex) -[![Discord](https://img.shields.io/discord/1029853095527727165?color=%237289da&label=Discord)](https://discord.gg/T5WSbC2YtQ) - -
    - ---- - -[English](https://github.com/reflex-dev/reflex/blob/main/README.md) | [简体中文](https://github.com/reflex-dev/reflex/blob/main/docs/zh/zh_cn/README.md) | [繁體中文](https://github.com/reflex-dev/reflex/blob/main/docs/zh/zh_tw/README.md) | [Türkçe](https://github.com/reflex-dev/reflex/blob/main/docs/tr/README.md) | [हिंदी](https://github.com/reflex-dev/reflex/blob/main/docs/in/README.md) | [Português (Brasil)](https://github.com/reflex-dev/reflex/blob/main/docs/pt/pt_br/README.md) | [Italiano](https://github.com/reflex-dev/reflex/blob/main/docs/it/README.md) | [Español](https://github.com/reflex-dev/reflex/blob/main/docs/es/README.md) | [한국어](https://github.com/reflex-dev/reflex/blob/main/docs/kr/README.md) | [日本語](https://github.com/reflex-dev/reflex/blob/main/docs/ja/README.md) | [Deutsch](https://github.com/reflex-dev/reflex/blob/main/docs/de/README.md) | [Persian (پارسی)](https://github.com/reflex-dev/reflex/blob/main/docs/pe/README.md) | [Tiếng Việt](https://github.com/reflex-dev/reflex/blob/main/docs/vi/README.md) - ---- - -# Reflex - -Reflex 是一個可以用純 Python 構建全端網頁應用程式的函式庫。 - -主要特色: - -- **純 Python** - 您可以用 Python 撰寫應用程式的前端和後端,無需學習 Javascript。 -- **完全靈活性** - Reflex 易於上手,但也可以擴展到複雜的應用程式。 -- **立即部署** - 構建後,只需使用[單一指令](https://reflex.dev/docs/hosting/deploy-quick-start/)即可部署您的應用程式,或在您自己的伺服器上託管。 - -請參閱我們的[架構頁面](https://reflex.dev/blog/2024-03-21-reflex-architecture/#the-reflex-architecture)了解 Reflex 如何在底層運作。 - -## ⚙️ 安裝 - -開啟一個終端機並且執行 (需要 Python 3.10+): - -```bash -pip install reflex -``` - -## 🥳 建立你的第一個應用程式 - -安裝 Reflex 的同時也會安裝 `reflex` 命令行工具。 - -通過創建一個新專案來測試是否安裝成功。(把 my_app_name 作為新專案名稱): - -```bash -mkdir my_app_name -cd my_app_name -reflex init -``` - -此命令會初始化一個應用程式模板在你的新資料夾中。 - -你可以在開發者模式運行這個應用程式: - -```bash -reflex run -``` - -你可以看到你的應用程式運行在 http://localhost:3000。 - -現在在以下位置修改原始碼 `my_app_name/my_app_name.py`,Reflex 擁有快速刷新功能,存儲程式碼後便可立即看到改變。 - -## 🫧 範例應用程式 - -讓我們來看一個例子: 建立一個使用 DALL·E 的圖形使用者介面,為了保持範例簡單,我們只呼叫 OpenAI API,而這部份可以置換掉,改為執行成本地端的 ML 模型。 - -  - -
    -A frontend wrapper for DALL·E, shown in the process of generating an image. -
    - -  - -下方為該應用之完整程式碼,這一切都只需要一個 Python 檔案就能作到! - -```python -import reflex as rx -import openai - -openai_client = openai.OpenAI() - - -class State(rx.State): - """應用程式狀態""" - prompt = "" - image_url = "" - processing = False - complete = False - - def get_image(self): - """透過提示詞取得圖片""" - if self.prompt == "": - return rx.window_alert("Prompt Empty") - - self.processing, self.complete = True, False - yield - response = openai_client.images.generate( - prompt=self.prompt, n=1, size="1024x1024" - ) - self.image_url = response.data[0].url - self.processing, self.complete = False, True - - -def index(): - return rx.center( - rx.vstack( - rx.heading("DALL-E", font_size="1.5em"), - rx.input( - placeholder="Enter a prompt..", - on_blur=State.set_prompt, - width="25em", - ), - rx.button( - "Generate Image", - on_click=State.get_image, - width="25em", - loading=State.processing - ), - rx.cond( - State.complete, - rx.image(src=State.image_url, width="20em"), - ), - align="center", - ), - width="100%", - height="100vh", - ) - -# 把狀態跟頁面添加到應用程式。 -app = rx.App() -app.add_page(index, title="Reflex:DALL-E") -``` - -## 讓我們來拆解一下。 - -
    -解釋 DALL-E app 的前端和後端部分的區別。 -
    - -### **Reflex 使用者介面** - -讓我們從使用介面開始。 - -```python -def index(): - return rx.center( - ... - ) -``` - -這個 `index` 函式定義了應用程式的前端. - -我們用不同的元件像是 `center`, `vstack`, `input`, 和 `button` 來建立前端,元件之間可互相套入以建立出複雜的版面配置。並且您可使用關鍵字引數 _keyword args_ 運行 CSS 全部功能來設計這些元件們的樣式。 - -Reflex 擁有 [60+ 內建元件](https://reflex.dev/docs/library) 來幫助你開始建立應用程式。我們正積極添加元件,你也可以簡單地 [創建自己所屬的元件](https://reflex.dev/docs/wrapping-react/overview/)。 - -### **應用程式狀態** - -Reflex 使用應用程式狀態中的函式來渲染你的 UI。 - -```python -class State(rx.State): - """應用程式狀態""" - prompt = "" - image_url = "" - processing = False - complete = False - -``` - -應用程式狀態定義了應用程式中所有可以更改的變數及變更他們的函式 (稱為 vars)。 - -這裡的狀態由 `prompt` 和 `image_url`組成, 以及布林變數 `processing` 和 `complete` 來指示何時顯示進度條及圖片。 - -### **事件處理程序** - -```python -def get_image(self): - """透過提示詞取得圖片""" - if self.prompt == "": - return rx.window_alert("Prompt Empty") - - self.processing, self.complete = True, False - yield - response = openai_client.images.generate( - prompt=self.prompt, n=1, size="1024x1024" - ) - self.image_url = response.data[0].url - self.processing, self.complete = False, True -``` - -在應用程式狀態中,我們定義稱之為事件處理程序的函式來改變其 vars. 事件處理程序是我們用來改變 Reflex 應用程式狀態的方法。 - -當使用者動作被響應時,對應的事件處理程序就會被呼叫。點擊按鈕或是文字框輸入都是使用者動作,它們被稱之為事件。 - -我們的 DALL·E. 應用程式有一個事件處理程序 `get_image`,它透過 Open AI API 取得圖片。在事件處理程序中使用 `yield` 將讓使用者介面中途更新,若不使用的話,使用介面只能在事件處理程序結束時才更新。 - -### **路由** - -最後,我們定義我們的應用程式 app。 - -```python -app = rx.App() -``` - -添加從應用程式根目錄(root of the app) 到 index 元件的路由。 我們也添加了一個標題將會顯示在 預覽/瀏覽 分頁。 - -```python -app.add_page(index, title="DALL-E") -``` - -你可以添加更多頁面至路由藉此來建立多頁面應用程式(multi-page app) - -## 📑 資源 - -
    - -📑 [Docs](https://reflex.dev/docs/getting-started/introduction)   |   🗞️ [Blog](https://reflex.dev/blog)   |   📱 [Component Library](https://reflex.dev/docs/library)   |   🖼️ [Templates](https://reflex.dev/templates/)   |   🛸 [Deployment](https://reflex.dev/docs/hosting/deploy-quick-start)   - -
    - -## ✅ 產品狀態 - -Reflex 在 2022 年 12 月以 Pynecone 的名字推出。 - -自 2025 年起,[Reflex Cloud](https://cloud.reflex.dev) 已推出,為 Reflex 應用程式提供最佳的託管體驗。我們將繼續開發並實施更多功能。 - -Reflex 每周都有新功能和釋出新版本! 確保你按下 :star: 和 :eyes: watch 這個 repository 來確保知道最新資訊。 - -## 貢獻 - -我們歡迎任何大小的貢獻,以下是一些加入 Reflex 社群的好方法。 - -- **加入我們的 Discord**: 我們的 [Discord](https://discord.gg/T5WSbC2YtQ) 是獲取 Reflex 專案幫助和討論如何貢獻的最佳地方。 -- **GitHub Discussions**: 這是一個討論您想新增的功能或對於一些困惑/需要澄清事項的好方法。 -- **GitHub Issues**: 在 [Issues](https://github.com/reflex-dev/reflex/issues) 頁面報告錯誤是一個絕佳的方式。此外,您也可以嘗試解決現有 Issue 並提交 PR。 - -我們積極尋找貢獻者,不論您的技能水平或經驗如何。要貢獻,請查看 [CONTRIBUTING.md](https://github.com/reflex-dev/reflex/blob/main/CONTRIBUTING.md) - -## 感謝所有貢獻者: - - - - - -## 授權 - -Reflex 是一個開源專案且使用 [Apache License 2.0](/LICENSE) 授權。 From c505144cac66aa68ed331e3af3a21504f6cabc00 Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Mon, 23 Mar 2026 18:48:27 -0700 Subject: [PATCH 002/113] write the py filers --- docs/__init__.py | 1 + docs/api-reference/browser_storage.md | 23 +- docs/api-reference/plugins.md | 16 +- docs/api-reference/utils.md | 14 +- docs/api-reference/var_system.md | 2 +- docs/assets/upload_and_download_files.md | 2 - docs/components/conditional_rendering.md | 2 - docs/components/props.md | 2 - docs/components/rendering_iterables.md | 15 +- docs/database/overview.md | 6 +- docs/database/queries.md | 19 +- docs/database/relationships.md | 6 +- docs/enterprise/ag_grid/cell-selection.md | 6 +- docs/enterprise/ag_grid/column-defs.md | 4 +- docs/enterprise/ag_grid/index.md | 9 +- docs/enterprise/ag_grid/model-wrapper.md | 4 +- docs/enterprise/ag_grid/pivot-mode.md | 2 - docs/enterprise/ag_grid/theme.md | 2 +- docs/enterprise/ag_grid/value-transformers.md | 3 +- docs/enterprise/built-with-reflex.md | 2 +- docs/enterprise/components.md | 10 +- docs/enterprise/drag-and-drop.md | 4 + docs/enterprise/mantine/collapse.md | 2 +- docs/enterprise/mantine/combobox.md | 1 - docs/enterprise/mantine/index.md | 2 +- docs/enterprise/mantine/loading-overlay.md | 1 + docs/enterprise/mantine/number-formatter.md | 1 + docs/enterprise/mantine/pill.md | 2 +- .../mantine/semi-circle-progress.md | 1 + docs/enterprise/mantine/timeline.md | 3 +- docs/enterprise/map/index.md | 14 +- docs/enterprise/overview.md | 29 ++- docs/enterprise/react_flow/examples.md | 1 - docs/enterprise/react_flow/interactivity.md | 1 - docs/enterprise/react_flow/theming.md | 11 +- docs/enterprise/single-port-proxy.md | 2 +- docs/events/background_events.md | 10 +- docs/events/decentralized_event_handlers.md | 3 + docs/events/events_overview.md | 4 +- docs/getting_started/__init__.py | 1 + docs/getting_started/basics.md | 4 +- docs/getting_started/chat_tutorial_style.py | 3 +- docs/getting_started/chat_tutorial_utils.py | 15 +- docs/getting_started/chatapp_tutorial.md | 20 +- docs/getting_started/dashboard_tutorial.md | 97 +++----- docs/getting_started/installation.md | 24 +- docs/getting_started/introduction.md | 8 +- docs/getting_started/project-structure.md | 4 +- docs/hosting/adding-members.md | 10 +- docs/hosting/app-management.md | 8 +- docs/hosting/billing.md | 14 +- docs/hosting/compute.md | 74 +++--- docs/hosting/config_file.md | 40 ++-- docs/hosting/custom-domains.md | 10 +- docs/hosting/databricks.md | 14 +- docs/hosting/deploy-quick-start.md | 8 +- docs/hosting/deploy-with-github-actions.md | 11 +- docs/hosting/logs.md | 6 +- docs/hosting/machine-types.md | 5 +- docs/hosting/regions.md | 10 +- docs/hosting/secrets-environment-vars.md | 8 +- docs/hosting/self-hosting.md | 3 +- docs/hosting/tokens.md | 1 - docs/library/data-display/avatar.md | 5 +- docs/library/data-display/badge.md | 5 +- docs/library/data-display/callout-ll.md | 7 +- docs/library/data-display/callout.md | 21 +- docs/library/data-display/code_block.md | 2 +- docs/library/data-display/data_list.md | 60 ++--- docs/library/data-display/icon.md | 7 +- docs/library/data-display/list.md | 8 +- docs/library/data-display/moment.md | 7 +- docs/library/data-display/progress.md | 7 +- docs/library/data-display/scroll_area.md | 52 ++-- docs/library/data-display/spinner.md | 4 +- docs/library/disclosure/segmented_control.md | 4 +- docs/library/dynamic-rendering/auto_scroll.md | 2 +- docs/library/forms/input-ll.md | 5 +- docs/library/forms/select.md | 1 - docs/library/forms/upload.md | 6 +- docs/library/graphing/charts/composedchart.md | 12 +- docs/library/graphing/charts/errorbar.md | 4 +- .../library/graphing/charts/radialbarchart.md | 7 +- docs/library/graphing/general/brush.md | 3 +- .../library/graphing/general/cartesiangrid.md | 9 +- docs/library/graphing/general/label.md | 12 +- docs/library/graphing/general/legend.md | 18 +- docs/library/graphing/general/reference.md | 16 +- docs/library/graphing/general/tooltip.md | 14 +- docs/library/graphing/other-charts/pyplot.md | 4 +- docs/library/layout/card.md | 2 +- docs/library/layout/center.md | 2 +- docs/library/layout/flex.md | 10 +- docs/library/layout/fragment.md | 7 +- docs/library/layout/inset.md | 17 +- docs/library/layout/separator.md | 5 +- docs/library/layout/spacer.md | 2 +- docs/library/layout/stack.md | 18 +- docs/library/media/audio.md | 3 +- docs/library/media/image.md | 4 +- docs/library/media/video.md | 4 +- docs/library/other/html.md | 222 +++++++++--------- docs/library/other/html_embed.md | 5 +- docs/library/other/memo.md | 16 +- docs/library/other/script.md | 8 +- docs/library/other/skeleton.md | 2 +- docs/library/other/theme.md | 40 ++-- docs/library/overlay/alert_dialog.md | 9 +- .../tables-and-data-grids/data_editor.md | 32 +-- .../tables-and-data-grids/data_table.md | 11 +- docs/library/tables-and-data-grids/table.md | 5 +- docs/library/typography/blockquote.md | 2 +- docs/library/typography/code.md | 2 +- docs/library/typography/em.md | 2 +- docs/library/typography/heading.md | 8 +- docs/library/typography/kbd.md | 2 +- docs/library/typography/link.md | 6 +- docs/library/typography/markdown.md | 3 +- docs/library/typography/quote.md | 2 +- docs/library/typography/strong.md | 2 +- docs/library/typography/text.md | 12 +- docs/pages/overview.md | 3 +- docs/recipes/auth/signup_form.md | 2 +- docs/recipes/content/forms.md | 2 +- docs/recipes/content/multi_column_row.md | 2 +- docs/recipes/content/stats.md | 2 +- docs/recipes/content/top_banner.md | 2 +- docs/recipes/layout/navbar.md | 2 - docs/recipes/others/checkboxes.md | 4 +- docs/recipes/others/pricing_cards.md | 1 - docs/state_structure/overview.md | 1 + docs/state_structure/shared_state.md | 2 +- docs/styling/common-props.md | 12 +- docs/styling/custom-stylesheets.md | 20 +- docs/styling/layout.md | 5 +- docs/styling/overview.md | 4 +- docs/styling/tailwind.md | 21 +- docs/styling/theming.md | 2 - docs/ui/overview.md | 10 +- docs/utility_methods/exception_handlers.md | 2 +- docs/utility_methods/lifespan_tasks.md | 2 +- docs/utility_methods/other_methods.md | 10 +- docs/utility_methods/router_attributes.md | 56 ++--- docs/vars/base_vars.md | 6 +- docs/vars/computed_vars.md | 10 +- docs/wrapping-react/custom-code-and-hooks.md | 9 +- docs/wrapping-react/imports-and-styles.md | 1 - docs/wrapping-react/library-and-tags.md | 31 +-- docs/wrapping-react/local-packages.md | 36 +-- docs/wrapping-react/more-wrapping-examples.md | 62 +++-- docs/wrapping-react/overview.md | 11 +- docs/wrapping-react/props.md | 33 +-- 152 files changed, 871 insertions(+), 902 deletions(-) create mode 100644 docs/getting_started/__init__.py diff --git a/docs/__init__.py b/docs/__init__.py index e69de29bb2d..10ffd1f365c 100644 --- a/docs/__init__.py +++ b/docs/__init__.py @@ -0,0 +1 @@ +"""Reflex documentation.""" diff --git a/docs/api-reference/browser_storage.md b/docs/api-reference/browser_storage.md index 525a502ef9c..04374f9a4aa 100644 --- a/docs/api-reference/browser_storage.md +++ b/docs/api-reference/browser_storage.md @@ -300,32 +300,35 @@ def app_settings_example(): Here's a comparison of the different client-side storage options in Reflex: -| Feature | rx.Cookie | rx.LocalStorage | rx.SessionStorage | -|---------|-----------|----------------|------------------| -| Persistence | Until cookie expires | Until explicitly deleted | Until browser/tab is closed | -| Storage Limit | ~4KB | ~5MB | ~5MB | -| Sent with Requests | Yes | No | No | -| Accessibility | Server & Client | Client Only | Client Only | -| Expiration | Configurable | Never | End of session | -| Scope | Configurable (domain, path) | Origin (domain) | Tab/Window | -| Syncing Across Tabs | No | Yes (with sync=True) | No | -| Use Case | Authentication, Server-side state | User preferences, App state | Temporary session data | +| Feature | rx.Cookie | rx.LocalStorage | rx.SessionStorage | +| ------------------- | --------------------------------- | --------------------------- | --------------------------- | +| Persistence | Until cookie expires | Until explicitly deleted | Until browser/tab is closed | +| Storage Limit | ~4KB | ~5MB | ~5MB | +| Sent with Requests | Yes | No | No | +| Accessibility | Server & Client | Client Only | Client Only | +| Expiration | Configurable | Never | End of session | +| Scope | Configurable (domain, path) | Origin (domain) | Tab/Window | +| Syncing Across Tabs | No | Yes (with sync=True) | No | +| Use Case | Authentication, Server-side state | User preferences, App state | Temporary session data | # When to Use Each Storage Type ## Use rx.Cookie When: + - You need the data to be accessible on the server side (cookies are sent with HTTP requests) - You're handling user authentication - You need fine-grained control over expiration and scope - You need to limit the data to specific paths in your app ## Use rx.LocalStorage When: + - You need to store larger amounts of data (up to ~5MB) - You want the data to persist indefinitely (until explicitly deleted) - You need to share data between different tabs/windows of your app - You want to store user preferences that should be remembered across browser sessions ## Use rx.SessionStorage When: + - You need temporary data that should be cleared when the browser/tab is closed - You want to isolate data to a specific tab/window - You're storing sensitive information that shouldn't persist after the session ends diff --git a/docs/api-reference/plugins.md b/docs/api-reference/plugins.md index 7170cd114b9..b05263ef359 100644 --- a/docs/api-reference/plugins.md +++ b/docs/api-reference/plugins.md @@ -55,6 +55,7 @@ def about(): ``` The sitemap configuration supports the following options: + - `loc`: Custom URL for the page (required for dynamic routes) - `lastmod`: Last modification date (datetime object) - `changefreq`: How frequently the page changes (`"always"`, `"hourly"`, `"daily"`, `"weekly"`, `"monthly"`, `"yearly"`, `"never"`) @@ -176,7 +177,6 @@ config = rx.Config( ) ``` - ## Plugin Architecture All plugins inherit from the base `Plugin` class and can implement several lifecycle methods: @@ -186,19 +186,19 @@ class Plugin: def get_frontend_development_dependencies(self, **context) -> list[str]: """Get NPM packages required by the plugin for development.""" return [] - + def get_frontend_dependencies(self, **context) -> list[str]: """Get NPM packages required by the plugin.""" return [] - + def get_static_assets(self, **context) -> Sequence[tuple[Path, str | bytes]]: """Get static assets required by the plugin.""" return [] - + def get_stylesheet_paths(self, **context) -> Sequence[str]: """Get paths to stylesheets required by the plugin.""" return [] - + def pre_compile(self, **context) -> None: """Called before compilation to perform custom tasks.""" pass @@ -215,14 +215,14 @@ from pathlib import Path class CustomPlugin(Plugin): def get_frontend_dependencies(self, **context): return ["my-custom-package@1.0.0"] - + def pre_compile(self, **context): # Custom logic before compilation print("Running custom plugin logic...") - + # Add a custom task context["add_save_task"](self.create_custom_file) - + def create_custom_file(self): return "public/custom.txt", "Custom content" ``` diff --git a/docs/api-reference/utils.md b/docs/api-reference/utils.md index 48beb020dc2..6a42a54b8c9 100644 --- a/docs/api-reference/utils.md +++ b/docs/api-reference/utils.md @@ -54,7 +54,7 @@ class TaskInfo: class RunInThreadState(rx.State): tasks: list[TaskInfo] = [] - + @rx.event(background=True) async def run_quick_task(self): """Run a quick task that completes within the timeout.""" @@ -62,7 +62,7 @@ class RunInThreadState(rx.State): task_ix = len(self.tasks) self.tasks.append(TaskInfo(status="Running quick task...")) task_info = self.tasks[task_ix] - + try: result = await rx.run_in_thread(quick_blocking_function) async with self: @@ -72,7 +72,7 @@ class RunInThreadState(rx.State): async with self: task_info.result = f"Error: {str(e)}" task_info.status = "Failed" - + @rx.event(background=True) async def run_slow_task(self): """Run a slow task that exceeds the timeout.""" @@ -80,7 +80,7 @@ class RunInThreadState(rx.State): task_ix = len(self.tasks) self.tasks.append(TaskInfo(status="Running slow task...")) task_info = self.tasks[task_ix] - + try: # Run with a timeout of 1 second (not enough time) result = await asyncio.wait_for( @@ -151,17 +151,17 @@ import time class FileProcessingState(rx.State): progress: str = "Ready" - + @rx.event(background=True) async def process_large_file(self): async with self: self.progress = "Processing file..." - + def process_file(): # Simulate processing a large file time.sleep(5) return "File processed successfully!" - + # Save the result to a local variable to avoid blocking the event loop. result = await rx.run_in_thread(process_file) async with self: diff --git a/docs/api-reference/var_system.md b/docs/api-reference/var_system.md index 39482420e03..10c2b84de2b 100644 --- a/docs/api-reference/var_system.md +++ b/docs/api-reference/var_system.md @@ -15,7 +15,7 @@ rx.cond( ) ``` - The conditional to roughly the following in Javascript: +The conditional to roughly the following in Javascript: ```js state.threshold >= 50 ? "Pass" : "Fail"; diff --git a/docs/assets/upload_and_download_files.md b/docs/assets/upload_and_download_files.md index 75911965434..646c48ff45f 100644 --- a/docs/assets/upload_and_download_files.md +++ b/docs/assets/upload_and_download_files.md @@ -70,8 +70,6 @@ rx.table.root( ) ``` - - For more information about assets, see the [Assets Overview](/docs/assets/overview/). ## Download diff --git a/docs/components/conditional_rendering.md b/docs/components/conditional_rendering.md index 5df4111a40c..9def2313da3 100644 --- a/docs/components/conditional_rendering.md +++ b/docs/components/conditional_rendering.md @@ -68,12 +68,10 @@ def cond_prop(): ) ``` - ## Var Operations You can use [var operations]({docs.vars.var_operations.path}) with the `cond` component for more complex conditions. See the full [cond reference]({library.dynamic_rendering.cond.path}) for more details. - ## Multiple Conditional Statements The [`rx.match`]({library.dynamic_rendering.match.path}) component in Reflex provides a powerful alternative to`rx.cond` for handling multiple conditional statements and structural pattern matching. This component allows you to handle multiple conditions and their associated components in a cleaner and more readable way compared to nested `rx.cond` structures. diff --git a/docs/components/props.md b/docs/components/props.md index 1c3d4217546..9c53a92cfe9 100644 --- a/docs/components/props.md +++ b/docs/components/props.md @@ -23,7 +23,6 @@ rx.image( Check the docs for the component you are using to see what props are available and how they affect the component (see the `rx.image` [reference]({docs.library.media.image.path}#api-reference) page for example). - ## Common Props Components support many standard HTML properties as props. For example: the HTML [id]({"https://www.w3schools.com/html/html_id.asp"}) property is exposed directly as the prop `id`. The HTML [className]({"https://www.w3schools.com/jsref/prop_html_classname.asp"}) property is exposed as the prop `class_name` (note the Pythonic snake_casing!). @@ -56,7 +55,6 @@ rx.button( See the [styling docs]({docs.styling.overview.path}) to learn more about customizing the appearance of your app. - ## Binding Props to State ```md alert warning diff --git a/docs/components/rendering_iterables.md b/docs/components/rendering_iterables.md index 89564d6b341..0c434cb8a54 100644 --- a/docs/components/rendering_iterables.md +++ b/docs/components/rendering_iterables.md @@ -56,10 +56,12 @@ The first argument of the `rx.foreach` function is the state var that you want t ```md definition # Regular For Loop -* Use when iterating over constants. + +- Use when iterating over constants. # Foreach -* Use when iterating over state vars. + +- Use when iterating over state vars. ``` The above example could have been written using a regular Python `for` loop, since the data is constant. @@ -98,7 +100,7 @@ def dynamic_buttons_foreach(): ## Render Function -The function to render each item can be defined either as a separate function or as a lambda function. In the example below, we define the function `colored_box` separately and pass it to the `rx.foreach` function. +The function to render each item can be defined either as a separate function or as a lambda function. In the example below, we define the function `colored_box` separately and pass it to the `rx.foreach` function. ```python demo exec class IterState2(rx.State): @@ -191,6 +193,7 @@ def dict_foreach(): ```md alert warning # Dict Type Annotation. + It is essential to provide the correct full type annotation for the dictionary in the state definition (e.g., `dict[str, str]` instead of `dict`) to ensure `rx.foreach` works as expected. Proper typing allows Reflex to infer and validate the structure of the data during rendering. ``` @@ -214,7 +217,7 @@ def get_badge(technology: rx.Var[str]) -> rx.Component: def project_item(project: rx.Var[dict[str, list]]) -> rx.Component: return rx.box( - rx.hstack( + rx.hstack( rx.foreach(project["technologies"], get_badge) ), ) @@ -277,7 +280,7 @@ class ToDoListItem: class ForeachCondState(rx.State): to_do_list: list[ToDoListItem] = [ - ToDoListItem(item_name="Space suit", is_packed=True), + ToDoListItem(item_name="Space suit", is_packed=True), ToDoListItem(item_name="Helmet", is_packed=True), ToDoListItem(item_name="Back Pack", is_packed=False), ] @@ -285,7 +288,7 @@ class ForeachCondState(rx.State): def render_item(item: rx.Var[ToDoListItem]): return rx.cond( - item.is_packed, + item.is_packed, rx.list.item(item.item_name + ' ✔'), rx.list.item(item.item_name), ) diff --git a/docs/database/overview.md b/docs/database/overview.md index 49a4ef4b328..830d24a6716 100644 --- a/docs/database/overview.md +++ b/docs/database/overview.md @@ -3,7 +3,7 @@ Reflex uses [sqlmodel](https://sqlmodel.tiangolo.com) to provide a built-in ORM wrapping SQLAlchemy. The examples on this page refer specifically to how Reflex uses various tools to -expose an integrated database interface. Only basic use cases will be covered +expose an integrated database interface. Only basic use cases will be covered below, but you can refer to the [sqlmodel tutorial](https://sqlmodel.tiangolo.com/tutorial/select/) for more examples and information, just replace `SQLModel` with `rx.Model` and @@ -46,7 +46,7 @@ that it is a table. class User(rx.Model, table=True): username: str email: str - password: str + password: str ``` ## Migrations @@ -60,7 +60,7 @@ to initialize alembic and create a migration script with the current schema. After making changes to the schema, use `reflex db makemigrations --message 'something changed'` to generate a script in the `alembic/versions` directory that will update the -database schema. It is recommended that generated scripts be inspected before applying them. +database schema. It is recommended that generated scripts be inspected before applying them. Bear in mind that your newest models will not be detected by the `reflex db makemigrations` command unless imported and used somewhere within the application. diff --git a/docs/database/queries.md b/docs/database/queries.md index 0a69c99f200..61267afb6d4 100644 --- a/docs/database/queries.md +++ b/docs/database/queries.md @@ -54,7 +54,7 @@ database or persist an existing object. class AddUser(rx.State): username: str email: str - + @rx.event def add_user(self): with rx.session() as session: @@ -112,7 +112,7 @@ ensure all fields are up to date before exiting the session. ```python class AddUserForm(rx.State): user: User | None = None - + @rx.event def add_user(self, form_data: dict[str, Any]): with rx.session() as session: @@ -134,7 +134,7 @@ may either be created or updated accordingly. ```python class AddUserForm(rx.State): ... - + @rx.event def update_user(self, form_data: dict[str, Any]): if self.user is None: @@ -156,7 +156,7 @@ necessary for particularly complex queries, or when using database-specific features. SQLModel exposes the `session.execute()` method that can be used to execute raw -SQL strings. If parameter binding is needed, the query may be wrapped in +SQL strings. If parameter binding is needed, the query may be wrapped in [`sqlalchemy.text`](https://docs.sqlalchemy.org/en/14/core/sqlelement.html#sqlalchemy.sql.expression.text), which allows colon-prefix names to be used as placeholders. @@ -205,7 +205,7 @@ import reflex as rx class AsyncUserState(rx.State): users: list[User] = [] - + @rx.event(background=True) async def get_users_async(self): async with rx.asession() as asession: @@ -240,7 +240,7 @@ To add a new record to the database asynchronously: class AsyncAddUser(rx.State): username: str email: str - + @rx.event(background=True) async def add_user(self): async with rx.asession() as asession: @@ -295,7 +295,7 @@ Similar to the regular session, you can refresh an object to ensure all fields a ```python class AsyncAddUserForm(rx.State): user: User | None = None - + @rx.event(background=True) async def add_user(self, form_data: dict[str, str]): async with rx.asession() as asession: @@ -313,7 +313,7 @@ You can also execute raw SQL asynchronously: ```python class AsyncRawSQL(rx.State): users: list[list] = [] - + @rx.event(background=True) async def insert_user_raw(self, username, email): async with rx.asession() as asession: @@ -325,7 +325,7 @@ class AsyncRawSQL(rx.State): dict(username=username, email=email), ) await asession.commit() - + @rx.event(background=True) async def get_raw_users(self): async with rx.asession() as asession: @@ -336,6 +336,7 @@ class AsyncRawSQL(rx.State): ```md alert info # Important Notes for Async Database Operations + - Always use the `@rx.event(background=True)` decorator for async event handlers - Most operations against the `asession` must be awaited, including `commit()`, `execute()`, `refresh()`, and `delete()` - The `add()` method does not need to be awaited diff --git a/docs/database/relationships.md b/docs/database/relationships.md index 9b06daeeb44..93bfb53c5aa 100644 --- a/docs/database/relationships.md +++ b/docs/database/relationships.md @@ -85,12 +85,12 @@ inefficient when accessing many linked objects for serialization purposes. There are several alternative loading mechanisms available that can be set on the relationship object or when performing the query. -* "joined" or `joinload` - generates a single query to load all related objects +- "joined" or `joinload` - generates a single query to load all related objects at once. -* "subquery" or `subqueryload` - generates a single query to load all related +- "subquery" or `subqueryload` - generates a single query to load all related objects at once, but uses a subquery to do the join, instead of a join in the main query. -* "selectin" or `selectinload` - emits a second (or more) SELECT statement which +- "selectin" or `selectinload` - emits a second (or more) SELECT statement which assembles the primary key identifiers of the parent objects into an IN clause, so that all members of related collections / scalar references are loaded at once by primary key diff --git a/docs/enterprise/ag_grid/cell-selection.md b/docs/enterprise/ag_grid/cell-selection.md index c428d46fd03..15effee2066 100644 --- a/docs/enterprise/ag_grid/cell-selection.md +++ b/docs/enterprise/ag_grid/cell-selection.md @@ -89,7 +89,7 @@ class AdvancedSelectionState(rx.State): def handle_selection(self, ranges: list[dict], started: bool, finished: bool): if finished and ranges: total_cells = sum( - (r.get("endRow", 0) - r.get("startRow", 0) + 1) * + (r.get("endRow", 0) - r.get("startRow", 0) + 1) * len(r.get("columns", [])) for r in ranges ) @@ -164,10 +164,10 @@ class FillHandleState(rx.State): field = data.get("colId", "") new_value = data.get("newValue", "") old_value = data.get("oldValue", "") - + change_msg = f"Row {row_index + 1}, {field}: '{old_value}' → '{new_value}'" self.change_log = [change_msg] + self.change_log[:9] # Keep last 10 changes - + # Update the data if 0 <= row_index < len(self.data): self.data[row_index][field] = new_value diff --git a/docs/enterprise/ag_grid/column-defs.md b/docs/enterprise/ag_grid/column-defs.md index a1dea018114..0823dda0fff 100644 --- a/docs/enterprise/ag_grid/column-defs.md +++ b/docs/enterprise/ag_grid/column-defs.md @@ -17,6 +17,7 @@ AgGrid allows you to define the columns of your grid, passed to the prop `column ``` Here we define a grid with 3 columns: + ```python column_defs = [ {"field": "direction"}, @@ -26,10 +27,11 @@ column_defs = [ ``` To set default properties for all your columns, you can define `default_col_def` in your grid: + ```python default_col_def = { "sortable": True, "filter": True, "resizable": True, } -``` \ No newline at end of file +``` diff --git a/docs/enterprise/ag_grid/index.md b/docs/enterprise/ag_grid/index.md index a6ed8c25b48..322b79269d4 100644 --- a/docs/enterprise/ag_grid/index.md +++ b/docs/enterprise/ag_grid/index.md @@ -525,8 +525,6 @@ The following props are available for `column_defs` as well as many others that - `cell_editor`: `AGEditors | str | None`: Provide your own cell editor component for this column's cells. (Check out the Editing section of this page for more information) - `cell_editor_params`: `dict[str, list[Any]] | None`: Params to be passed to the cellEditor component. - - ## Functionality you need is not available/working in Reflex All AGGrid options found in this [documentation](https://www.ag-grid.com/react-data-grid/reference/) are mapped in rxe.ag_grid, but some features might not have been fully tested, due to the sheer number of existing features in the underlying AG Grid library. @@ -582,20 +580,20 @@ The react code for the `select_all()` event handler is `selectAll = (source?: Se To use this in Reflex as you can see, it should be called in snake case rather than camel case. The `void` means it doesn't return anything. The `source?` indicates that it takes an optional `source` argument. - -```md alert info +````md alert info # Another way to use the AG Grid API + It is also possible to use the AG Grid API directly with the event trigger (`on_click`) of the component. This removes the need to create a variable `my_api`. This is shown in the example below. It is necessary to use the `id` of the `ag_grid` component that is to be referenced. ```python rx.button("Select all", on_click=rxe.ag_grid.api(id="ag_grid_basic_row_selection").select_all()), ``` +```` ### More examples The following example lets a user [export the data as a csv](https://www.ag-grid.com/javascript-data-grid/grid-api/#reference-export-exportDataAsCsv) and [adjust the size of columns to fit the available horizontal space](https://www.ag-grid.com/javascript-data-grid/grid-api/#reference-columnSizing-sizeColumnsToFit). (Try resizing the screen and then clicking the resize columns button) - ```python demo exec import reflex as rx import reflex_enterprise as rxe @@ -636,7 +634,6 @@ The react code for both of these is shown below. The key point to see is that bo `sizeColumnsToFit = (paramsOrGridWidth?: ISizeColumnsToFitParams | number) => void;` - ### Example with a Return Value This example shows how to get the data from `ag_grid` as a [csv on the backend](https://www.ag-grid.com/javascript-data-grid/grid-api/#reference-export-getDataAsCsv). The data that was passed to the backend is then displayed as a toast with the data. diff --git a/docs/enterprise/ag_grid/model-wrapper.md b/docs/enterprise/ag_grid/model-wrapper.md index 4615ee87296..27983ef587d 100644 --- a/docs/enterprise/ag_grid/model-wrapper.md +++ b/docs/enterprise/ag_grid/model-wrapper.md @@ -31,6 +31,7 @@ class MyCustomWrapper(rxe.ModelWrapper[MyModel]): ``` In the custom model wrapper, you can override the following methods: + - `_get_columns_defs` - `_get_data` - `_row_count` @@ -38,7 +39,6 @@ In the custom model wrapper, you can override the following methods: to modify how the model wrapper will behave. - ## SSRM Model Wrapper The SSRM model wrapper, used with `rxe.model_wrapper_ssrm`, is a version of the model wrapper that allows you to use the ServerSideRowModel of AgGrid. @@ -61,4 +61,4 @@ class MyCustomSSRMWrapper(rxe.ModelWrapperSSRM[MyModel]): pass ``` -The overridable methods are the same as the standard model wrapper. \ No newline at end of file +The overridable methods are the same as the standard model wrapper. diff --git a/docs/enterprise/ag_grid/pivot-mode.md b/docs/enterprise/ag_grid/pivot-mode.md index f2f9b863526..8ec4ed58878 100644 --- a/docs/enterprise/ag_grid/pivot-mode.md +++ b/docs/enterprise/ag_grid/pivot-mode.md @@ -126,5 +126,3 @@ def sandbox_page(): ``` 📊 **Dataset source:** [wind_dataset.csv](https://raw.githubusercontent.com/plotly/datasets/master/wind_dataset.csv) - - diff --git a/docs/enterprise/ag_grid/theme.md b/docs/enterprise/ag_grid/theme.md index f88fa392453..e3444b14ee3 100644 --- a/docs/enterprise/ag_grid/theme.md +++ b/docs/enterprise/ag_grid/theme.md @@ -60,4 +60,4 @@ def ag_grid_simple_themes(): ) ``` -📊 **Dataset source:** [gapminder2007.csv](https://raw.githubusercontent.com/plotly/datasets/master/gapminder2007.csv) \ No newline at end of file +📊 **Dataset source:** [gapminder2007.csv](https://raw.githubusercontent.com/plotly/datasets/master/gapminder2007.csv) diff --git a/docs/enterprise/ag_grid/value-transformers.md b/docs/enterprise/ag_grid/value-transformers.md index c9de70ebbc2..75bfd1ff61d 100644 --- a/docs/enterprise/ag_grid/value-transformers.md +++ b/docs/enterprise/ag_grid/value-transformers.md @@ -13,6 +13,7 @@ from pcweb.pages.docs import enterprise AgGrid allow you to apply transformers based on the column of your grid. This allow you to perform operations on the data before displaying it on the grid, without having to pre-process the data on the backend, reducing the load on your application. TOC: + - [Value Getter](#value-getter) - [Value Formatter](#value-formatter) @@ -72,5 +73,3 @@ def ag_grid_value_formatter(): width="100%", ) ``` - - diff --git a/docs/enterprise/built-with-reflex.md b/docs/enterprise/built-with-reflex.md index 8fb0f60b950..85917a81fa2 100644 --- a/docs/enterprise/built-with-reflex.md +++ b/docs/enterprise/built-with-reflex.md @@ -14,4 +14,4 @@ import reflex_enterprise as rxe config = rxe.Config( show_built_with_reflex=False, # Requires paid tier ) -``` \ No newline at end of file +``` diff --git a/docs/enterprise/components.md b/docs/enterprise/components.md index 3e82efae420..01d2b049cdc 100644 --- a/docs/enterprise/components.md +++ b/docs/enterprise/components.md @@ -25,7 +25,7 @@ def enterprise_component_grid(): ] }, { - "title": "AG Chart", + "title": "AG Chart", "description": "Interactive charts and data visualization", "link": enterprise.ag_chart.path, "components": [ @@ -34,7 +34,7 @@ def enterprise_component_grid(): }, { "title": "Interactive Components", - "description": "Drag-and-drop and mapping functionality", + "description": "Drag-and-drop and mapping functionality", "link": enterprise.drag_and_drop.path, "components": [ ("Drag and Drop", enterprise.drag_and_drop.path), @@ -64,7 +64,7 @@ def enterprise_component_grid(): ] } ] - + cards = [] for section in sections: cards.append( @@ -97,7 +97,7 @@ def enterprise_component_grid(): class_name="flex flex-col border border-slate-5 rounded-xl bg-slate-2 shadow-large overflow-hidden", ) ) - + return rx.box( *cards, class_name="grid grid-cols-1 lg:grid-cols-2 gap-6", @@ -116,4 +116,4 @@ text_comp_2(text="Advanced UI components and features to enhance your Reflex app ```python eval component_grid -``` \ No newline at end of file +``` diff --git a/docs/enterprise/drag-and-drop.md b/docs/enterprise/drag-and-drop.md index 2bf8913d38b..cf38a732f64 100644 --- a/docs/enterprise/drag-and-drop.md +++ b/docs/enterprise/drag-and-drop.md @@ -322,6 +322,7 @@ def dynamic_list_example(): The `rxe.dnd.draggable` component makes any element draggable: **Key Properties:** + - `type`: String identifier for drag type matching - `item`: Data object passed to drop handlers - `on_end`: Called when drag operation ends @@ -331,6 +332,7 @@ The `rxe.dnd.draggable` component makes any element draggable: The `rxe.dnd.drop_target` component creates areas that accept draggable items: **Key Properties:** + - `accept`: List of drag types this target accepts - `on_drop`: Called when item is dropped - `on_hover`: Called when item hovers over target @@ -340,9 +342,11 @@ The `rxe.dnd.drop_target` component creates areas that accept draggable items: Access real-time drag/drop state: **Draggable Parameters (`rxe.dnd.Draggable.collected_params`):** + - `is_dragging`: Boolean indicating if item is being dragged **Drop Target Parameters (`rxe.dnd.DropTarget.collected_params`):** + - `is_over`: Boolean indicating if draggable is hovering - `can_drop`: Boolean indicating if drop is allowed diff --git a/docs/enterprise/mantine/collapse.md b/docs/enterprise/mantine/collapse.md index 7ed25026163..de0ca1073d7 100644 --- a/docs/enterprise/mantine/collapse.md +++ b/docs/enterprise/mantine/collapse.md @@ -29,7 +29,7 @@ def collapse_example(): description="Click the button to toggle the collapse.", ), rx.button( - "Toggle Collapse", + "Toggle Collapse", on_click=lambda: CollapseState.toggle_collapse, ), ) diff --git a/docs/enterprise/mantine/combobox.md b/docs/enterprise/mantine/combobox.md index d212a9f1c1e..c95238634cc 100644 --- a/docs/enterprise/mantine/combobox.md +++ b/docs/enterprise/mantine/combobox.md @@ -33,4 +33,3 @@ def combobox_page(): placeholder="Select a value", ) ``` - diff --git a/docs/enterprise/mantine/index.md b/docs/enterprise/mantine/index.md index 58d48822d84..28aa4e0de7e 100644 --- a/docs/enterprise/mantine/index.md +++ b/docs/enterprise/mantine/index.md @@ -8,6 +8,7 @@ order: 4 Mantine is a React component library that provides a set of high-quality components and hooks for building modern web applications. It is designed to be flexible, customizable, and easy to use, making it a popular choice among developers. Some of those components have been integrated into Reflex Enterprise, allowing you to use them in your Reflex applications. The following components are available: + - JsonInput - Autocomplete - ComboBox @@ -23,4 +24,3 @@ Some of those components have been integrated into Reflex Enterprise, allowing y - Spoiler - Timeline - Collapse - diff --git a/docs/enterprise/mantine/loading-overlay.md b/docs/enterprise/mantine/loading-overlay.md index 8aebe7cdd3f..30409d63e2e 100644 --- a/docs/enterprise/mantine/loading-overlay.md +++ b/docs/enterprise/mantine/loading-overlay.md @@ -3,6 +3,7 @@ title: Loading Overlay --- # Loading Overlay component + `rxe.mantine.loading_overlay` is a component that displays a loading overlay on top of its children. It is useful for indicating that a process is ongoing and prevents user interaction with the underlying content. ```python demo exec diff --git a/docs/enterprise/mantine/number-formatter.md b/docs/enterprise/mantine/number-formatter.md index d2833125d75..dea1bdf4dbf 100644 --- a/docs/enterprise/mantine/number-formatter.md +++ b/docs/enterprise/mantine/number-formatter.md @@ -3,6 +3,7 @@ title: Number Formatter --- # Number Formatter component + `rxe.mantine.number_formatter` is a component for formatting numbers in a user-friendly way. It allows you to specify the format, precision, and other options for displaying numbers. ```python demo exec diff --git a/docs/enterprise/mantine/pill.md b/docs/enterprise/mantine/pill.md index e401685eb02..95c5a5884c9 100644 --- a/docs/enterprise/mantine/pill.md +++ b/docs/enterprise/mantine/pill.md @@ -30,6 +30,7 @@ def pill_page(): ``` ## Pill Group + `rxe.mantine.pill.group` allows grouping multiple `rxe.mantine.pill` components together, with a predefined layout. ```python demo exec @@ -44,7 +45,6 @@ def pill_group_page(): ) ``` - # PillsInput `rxe.mantine.pills_input` is a wrapping of the mantine component [PillsInput](https://mantine.dev/core/pills-input/). It is an utility component that can be used to display a list of tags or labels. It can be used in various contexts, such as in a form or as a standalone component. diff --git a/docs/enterprise/mantine/semi-circle-progress.md b/docs/enterprise/mantine/semi-circle-progress.md index 3f7f1f5df03..89425c0594f 100644 --- a/docs/enterprise/mantine/semi-circle-progress.md +++ b/docs/enterprise/mantine/semi-circle-progress.md @@ -3,6 +3,7 @@ title: Semi Circle Progress --- # Semi Circle Progress component + `rxe.mantine.semi_circle_progress` is a component for displaying progress in a semi-circular format. It is useful for visualizing completion percentages or other metrics in a compact and visually appealing way. ```python demo exec diff --git a/docs/enterprise/mantine/timeline.md b/docs/enterprise/mantine/timeline.md index 32843a35b6e..441f1d98fc0 100644 --- a/docs/enterprise/mantine/timeline.md +++ b/docs/enterprise/mantine/timeline.md @@ -3,6 +3,7 @@ title: Timeline --- # Timeline component + `rxe.mantine.timeline` is a component for displaying a sequence of events or milestones in a linear format. It is useful for visualizing progress, history, or any sequential information. ```python demo exec @@ -28,7 +29,7 @@ def timeline_example(): bullet_size=24, line_width=2, color="blue", - + ) ) ``` diff --git a/docs/enterprise/map/index.md b/docs/enterprise/map/index.md index 20af518807e..fffcf502ac7 100644 --- a/docs/enterprise/map/index.md +++ b/docs/enterprise/map/index.md @@ -14,6 +14,7 @@ The map components in Reflex Enterprise provide interactive mapping capabilities ```md alert info # All map components are built using Leaflet and react-leaflet, providing a familiar and powerful mapping experience. + For advanced Leaflet features, refer to the [Leaflet documentation](https://leafletjs.com/reference.html). ``` @@ -71,6 +72,7 @@ rxe.map( ``` **Key Properties:** + - `center`: Initial map center coordinates - `zoom`: Initial zoom level (0-18+ depending on tile provider) - `bounds`: Alternative to center/zoom, fits map to bounds @@ -87,7 +89,6 @@ rxe.map.tile_layer( ) ``` - ### Markers Add point markers to specific locations: @@ -327,6 +328,7 @@ def map_api_example(): ### Common API Methods **View Control:** + - `fly_to(latlng, zoom, options)` - Smooth animated movement to location - `set_view(latlng, zoom, options)` - Instant movement to location - `set_zoom(zoom)` - Change zoom level @@ -334,16 +336,19 @@ def map_api_example(): - `fit_bounds(bounds, options)` - Fit map to specific bounds **Location Services:** + - `locate(options)` - Get user's current location - `stop_locate()` - Stop location tracking **Information Retrieval:** + - `get_center(callback)` - Get current map center - `get_zoom(callback)` - Get current zoom level - `get_bounds(callback)` - Get current map bounds - `get_size(callback)` - Get map container size **Layer Management:** + - `add_layer(layer)` - Add a layer to the map - `remove_layer(layer)` - Remove a layer from the map - `has_layer(layer)` - Check if layer exists on map @@ -352,12 +357,14 @@ def map_api_example(): ```md alert info # The Map API provides access to the complete Leaflet map API. Any method available on a Leaflet map instance can be called through the MapAPI instance. + Function names are automatically converted from snake_case (Python) to camelCase (JavaScript). ``` This means you can use any method from the [Leaflet Map documentation](https://leafletjs.com/reference.html#map). For example: **Python (snake_case) → JavaScript (camelCase):** + - `map_api.pan_to(latlng)` → `map.panTo(latlng)` - `map_api.set_max_bounds(bounds)` → `map.setMaxBounds(bounds)` - `map_api.get_pixel_bounds()` → `map.getPixelBounds()` @@ -505,6 +512,7 @@ class CallbackMapState(rx.State): The map components support a comprehensive set of events: **Map Events:** + - `on_click`, `on_dblclick` - Mouse click events - `on_zoom`, `on_zoom_start`, `on_zoom_end` - Zoom events - `on_move`, `on_move_start`, `on_move_end` - Pan events @@ -512,12 +520,15 @@ The map components support a comprehensive set of events: - `on_load`, `on_unload` - Map lifecycle **Location Events:** + - `on_locationfound`, `on_locationerror` - Geolocation **Layer Events:** + - `on_layeradd`, `on_layerremove` - Layer management **Popup Events:** + - `on_popupopen`, `on_popupclose` - Popup lifecycle - `on_tooltipopen`, `on_tooltipclose` - Tooltip lifecycle @@ -550,7 +561,6 @@ def dynamic_markers(): ) ``` - ## Best Practices 1. **Always include attribution** for tile providers diff --git a/docs/enterprise/overview.md b/docs/enterprise/overview.md index fb58a235ea5..38e8867b421 100644 --- a/docs/enterprise/overview.md +++ b/docs/enterprise/overview.md @@ -19,6 +19,7 @@ Reflex Enterprise is a package containing paid features built on top of Reflex. ```md alert info # Despite being an enterprise package, free users can use the components from this package. A badge "Built with Reflex" will be shown in the bottom right corner of the app. + For more information on the badge, visit [Built with Reflex]({enterprise.built_with_reflex.path}). ``` @@ -43,16 +44,16 @@ categories_data = [ "count": 2, "components": [ { - "feature": "show_built_with_reflex", - "description": "Toggle the 'Built with Reflex' badge in your app", - "cloud_tier": "Enterprise", + "feature": "show_built_with_reflex", + "description": "Toggle the 'Built with Reflex' badge in your app", + "cloud_tier": "Enterprise", "self_hosted_tier": "Enterprise", "link": "/docs/enterprise/built-with-reflex", }, { - "feature": "use_single_port", - "description": "Enable single-port deployment by proxying backend to frontend", - "cloud_tier": "Free", + "feature": "use_single_port", + "description": "Enable single-port deployment by proxying backend to frontend", + "cloud_tier": "Free", "self_hosted_tier": "Free", "link": "/docs/enterprise/single-port-proxy", }, @@ -78,7 +79,7 @@ categories_data = [ "link": "/docs/enterprise/ag-chart", }, ] - }, + }, { "category": "Interactive Components", "description": "Interactive UI features including drag-and-drop and mapping", @@ -219,8 +220,8 @@ if rxe is not None: grid = rxe.ag_grid( column_defs=[ { - "field": "category", - "header_name": "Category", + "field": "category", + "header_name": "Category", "cell_renderer": "agGroupCellRenderer", "suppress_menu": True, "width": 220, @@ -241,9 +242,9 @@ if rxe is not None: "detail_grid_options": { "column_defs": [ { - "field": "feature", - "header_name": "Component/Feature", - "cell_renderer": custom_link_renderer, + "field": "feature", + "header_name": "Component/Feature", + "cell_renderer": custom_link_renderer, "width": 250 }, {"field": "description", "header_name": "Description", "width": 350}, @@ -280,16 +281,18 @@ Using `rxe.App` as your `app` is required to use any of the components provided ### In the main file Instead of the usual `rx.App()` to create your app, use the following: + ```python import reflex_enterprise as rxe app = rxe.App() ``` ### In rxconfig.py + ```python import reflex_enterprise as rxe config = rxe.Config( app_name="MyApp", ... # you can pass all rx.Config arguments as well as the one specific to rxe.Config ) -``` \ No newline at end of file +``` diff --git a/docs/enterprise/react_flow/examples.md b/docs/enterprise/react_flow/examples.md index 72d4a0e76aa..edbfe836fe5 100644 --- a/docs/enterprise/react_flow/examples.md +++ b/docs/enterprise/react_flow/examples.md @@ -6,7 +6,6 @@ This section showcases examples of interactive flow components built with Reflex In this example, we demonstrate how to dynamically add nodes to a flow when a connection is dropped onto the canvas. When the user drops a connection, a new node is created at the drop point, and an edge is added between the source node and the new node. - ```python demo exec import reflex as rx diff --git a/docs/enterprise/react_flow/interactivity.md b/docs/enterprise/react_flow/interactivity.md index 52691360839..05eccee3882 100644 --- a/docs/enterprise/react_flow/interactivity.md +++ b/docs/enterprise/react_flow/interactivity.md @@ -41,7 +41,6 @@ def set_edges(self, edges: list[Edge]): - set_edges updates edges when they are modified or deleted. - ## Render the Interactive Flow Finally, we render the flow using **rxe.flow**, passing in the state and event handlers. Additional UI features include zoom/pan controls, a background grid, and a mini-map for navigation. diff --git a/docs/enterprise/react_flow/theming.md b/docs/enterprise/react_flow/theming.md index 3aa8a1d2fa1..41361caac7f 100644 --- a/docs/enterprise/react_flow/theming.md +++ b/docs/enterprise/react_flow/theming.md @@ -10,8 +10,9 @@ The Flow component uses CSS variables for theming. You can override these variab .react-flow { --xy-background-color: #f7f9fb; --xy-node-border-default: 1px solid #ededed; - --xy-node-boxshadow-default: 0px 3.54px 4.55px 0px #00000005, - 0px 3.54px 4.55px 0px #0000000d, 0px 0.51px 1.01px 0px #0000001a; + --xy-node-boxshadow-default: + 0px 3.54px 4.55px 0px #00000005, 0px 3.54px 4.55px 0px #0000000d, + 0px 0.51px 1.01px 0px #0000001a; --xy-node-border-radius-default: 8px; --xy-handle-background-color-default: #ffffff; --xy-handle-border-color-default: #aaaaaa; @@ -67,8 +68,8 @@ node = { ```css /* In your CSS file */ .my-custom-node { - background-color: #ffcc00; - border: 2px solid #ff9900; - border-radius: 10px; + background-color: #ffcc00; + border: 2px solid #ff9900; + border-radius: 10px; } ``` diff --git a/docs/enterprise/single-port-proxy.md b/docs/enterprise/single-port-proxy.md index 078b9a703ba..91c4a18b494 100644 --- a/docs/enterprise/single-port-proxy.md +++ b/docs/enterprise/single-port-proxy.md @@ -12,4 +12,4 @@ config = rxe.Config( ) ``` -This allows your application to run on a single port, which is useful for deployment scenarios where you can only expose one port. \ No newline at end of file +This allows your application to run on a single port, which is useful for deployment scenarios where you can only expose one port. diff --git a/docs/events/background_events.md b/docs/events/background_events.md index f8d0d38874c..04d381cb7d0 100644 --- a/docs/events/background_events.md +++ b/docs/events/background_events.md @@ -14,6 +14,7 @@ A background task is defined by decorating an async `State` method with ```md alert warning # `@rx.event(background=True)` used to be called `@rx.background`. + In Reflex version 0.6.5 and later, the `@rx.background` decorator has been renamed to `@rx.event(background=True)`. ``` @@ -120,10 +121,10 @@ class State(rx.State): while True: if self.router.session.client_token not in app.event_namespace.token_to_sid: print("WebSocket connection closed or user navigated away. Stopping background task.") - break - + break + print("Running background task...") - await asyncio.sleep(2) + await asyncio.sleep(2) @rx.page(on_load=State.loop_function) @@ -146,9 +147,6 @@ the circumstances that are undesirable. In the example above, the `_n_tasks` backend var is used to control whether `my_task` will enter the increment loop, or exit early. - - - ## Background Task Limitations Background tasks mostly work like normal `EventHandler` methods, with certain exceptions: diff --git a/docs/events/decentralized_event_handlers.md b/docs/events/decentralized_event_handlers.md index 2ac8fcb747a..c7a20029cd0 100644 --- a/docs/events/decentralized_event_handlers.md +++ b/docs/events/decentralized_event_handlers.md @@ -9,6 +9,7 @@ import reflex as rx Decentralized event handlers allow you to define event handlers outside of state classes, providing more flexible code organization. This feature was introduced in Reflex v0.7.10 and enables a more modular approach to event handling. With decentralized event handlers, you can: + - Organize event handlers by feature rather than by state class - Separate UI logic from state management - Create more maintainable and scalable applications @@ -41,6 +42,7 @@ def decentralized_event_example(): ``` In this example: + 1. We define a `MyState` class with a `count` variable 2. We create a decentralized event handler `increment` that takes a `MyState` instance as its first parameter 3. We use the event handler in buttons, passing different amounts to increment by @@ -74,6 +76,7 @@ rx.button("Increment", on_click=increment(5)) ``` Key differences: + - Traditional event handlers use `self` to reference the state instance - Decentralized event handlers explicitly take a state instance as the first parameter - Both approaches use the same syntax for triggering events in components diff --git a/docs/events/events_overview.md b/docs/events/events_overview.md index e67aa3bf5ef..fdea0c283f6 100644 --- a/docs/events/events_overview.md +++ b/docs/events/events_overview.md @@ -11,10 +11,10 @@ Events are composed of two parts: Event Triggers and Event Handlers. - **Events Handlers** are how the State of a Reflex application is updated. They are triggered by user interactions with the UI, such as clicking a button or hovering over an element. Events can also be triggered by the page loading or by other events. - **Event triggers** are component props that create an event to be sent to an event handler. -Each component supports a set of events triggers. They are described in each [component's documentation]({library.path}) in the event trigger section. + Each component supports a set of events triggers. They are described in each [component's documentation]({library.path}) in the event trigger section. +## Example -## Example Lets take a look at an example below. Try mousing over the heading to change the word. ```python demo exec diff --git a/docs/getting_started/__init__.py b/docs/getting_started/__init__.py new file mode 100644 index 00000000000..0cdc5e99ec8 --- /dev/null +++ b/docs/getting_started/__init__.py @@ -0,0 +1 @@ +"""Getting started docs.""" diff --git a/docs/getting_started/basics.md b/docs/getting_started/basics.md index baf4d69b420..b05d88ce87e 100644 --- a/docs/getting_started/basics.md +++ b/docs/getting_started/basics.md @@ -165,11 +165,11 @@ def counter_increment(): ) ``` -When an event trigger is activated, the event handler is called, which updates the state. The UI is automatically re-rendered to reflect the new state. - +When an event trigger is activated, the event handler is called, which updates the state. The UI is automatically re-rendered to reflect the new state. ```md alert info # What is the `@rx.event` decorator? + Adding the `@rx.event` decorator above the event handler is strongly recommended. This decorator enables proper static type checking, which ensures event handlers receive the correct number and types of arguments. This was introduced in Reflex version 0.6.5. ``` diff --git a/docs/getting_started/chat_tutorial_style.py b/docs/getting_started/chat_tutorial_style.py index fe8ff74d610..758a089d987 100644 --- a/docs/getting_started/chat_tutorial_style.py +++ b/docs/getting_started/chat_tutorial_style.py @@ -1,4 +1,5 @@ -# Common styles for questions and answers. +"""Common styles for questions and answers.""" + import reflex as rx shadow = "rgba(0, 0, 0, 0.15) 0px 2px 8px" diff --git a/docs/getting_started/chat_tutorial_utils.py b/docs/getting_started/chat_tutorial_utils.py index 9b3f27988c8..4bf4a7d1859 100644 --- a/docs/getting_started/chat_tutorial_utils.py +++ b/docs/getting_started/chat_tutorial_utils.py @@ -1,14 +1,19 @@ +"""Utility classes for the chat app tutorial.""" + from __future__ import annotations import os -import openai +import openai # pyright: ignore[reportMissingImports] + import reflex as rx OPENAI_API_KEY = os.environ.get("OPENAI_API_KEY") class ChatappState(rx.State): + """State for the chat app tutorial.""" + # The current question being asked. question: str @@ -16,23 +21,29 @@ class ChatappState(rx.State): chat_history: list[tuple[str, str]] def set_question(self, q: str): + """Set the current question.""" self.question = q def set_question1(self, q: str): + """Set the current question (variant 1).""" self.question = q def set_question2(self, q: str): + """Set the current question (variant 2).""" self.question = q def set_question3(self, q: str): + """Set the current question (variant 3).""" self.question = q def answer(self) -> None: + """Answer the question with a static response.""" # Our chatbot is not very smart right now... answer = "I don't know!" self.chat_history.append((self.question, answer)) def answer2(self) -> None: + """Answer the question and clear the input.""" # Our chatbot is not very smart right now... answer = "I don't know!" self.chat_history.append((self.question, answer)) @@ -40,6 +51,7 @@ def answer2(self) -> None: self.question = "" async def answer3(self): + """Answer with a streaming static response.""" import asyncio # Our chatbot is not very smart right now... @@ -57,6 +69,7 @@ async def answer3(self): yield async def answer4(self): + """Answer using the OpenAI API with streaming.""" # Our chatbot has some brains now! client = openai.AsyncOpenAI(api_key=OPENAI_API_KEY) session = await client.chat.completions.create( diff --git a/docs/getting_started/chatapp_tutorial.md b/docs/getting_started/chatapp_tutorial.md index fc2c0e36fd2..ebb8ca4d22c 100644 --- a/docs/getting_started/chatapp_tutorial.md +++ b/docs/getting_started/chatapp_tutorial.md @@ -37,9 +37,6 @@ In this tutorial you'll learn how to: 3. Use state to add interactivity to your app. 4. Deploy your app to share with others. - - - ## Setting up Your Project ```md video https://youtube.com/embed/ITOZkzjtjUA?start=175&end=445 @@ -64,7 +61,6 @@ Now, we will install Reflex and create a new project. This will create a new dir > **Note:** When prompted to select a template, choose option 0 for a blank project. - ```bash chatapp $ pip install reflex chatapp $ reflex init @@ -77,6 +73,7 @@ assets chatapp rxconfig.py venv ```python eval rx.box(height="20px") ``` + You can run the template app to make sure everything is working. ```bash @@ -97,9 +94,6 @@ Reflex also starts the backend server which handles all the state management and Now that we have our project set up, in the next section we will start building our app! - - - ## Basic Frontend Let's start with defining the frontend for our chat app. In Reflex, the frontend can be broken down into independent, reusable components. See the [components docs]({components.props.path}) for more information. @@ -360,11 +354,6 @@ app.add_page(index) The app is looking good, but it's not very useful yet! In the next section, we will add some functionality to the app. - - - - - ## State Now let’s make the chat app interactive by adding state. The state is where we define all the variables that can change in the app and all the functions that can modify them. You can learn more about state in the [state docs]({state.overview.path}). @@ -555,8 +544,6 @@ async def answer(self): In the next section, we will finish our chatbot by adding AI! - - ## Final App We will use OpenAI's API to give our chatbot some intelligence. @@ -565,6 +552,7 @@ We will use OpenAI's API to give our chatbot some intelligence. First, ensure you have an active OpenAI subscription. Next, install the latest openai package: + ```bash pip install --upgrade openai ``` @@ -593,7 +581,6 @@ Making your chatbot intelligent requires connecting to a language model API. Thi 2. Next, when a prompt is ready, the user can choose to submit it by clicking the `Ask` button which in turn triggers the `State.answer` method inside our `state.py` file. 3. Finally, if the method is triggered, the `prompt` is sent via a request to OpenAI client and returns an answer that we can trim and use to update the chat history! - ```python # chatapp.py def action_bar() -> rx.Component: @@ -723,7 +710,6 @@ app = rx.App() app.add_page(index) ``` - The `state.py` file: ```python @@ -764,7 +750,6 @@ class State(rx.State): yield ``` - The `style.py` file: ```python @@ -797,7 +782,6 @@ input_style = dict(border_width="1px", padding="0.5em", box_shadow=shadow, width button_style = dict(background_color=rx.color("accent", 10), box_shadow=shadow) ``` - ### Next Steps Congratulations! You have built your first chatbot. From here, you can read through the rest of the documentations to learn about Reflex in more detail. The best way to learn is to build something, so try to build your own app using this as a starting point! diff --git a/docs/getting_started/dashboard_tutorial.md b/docs/getting_started/dashboard_tutorial.md index 7866cd40912..77ac09fd1b5 100644 --- a/docs/getting_started/dashboard_tutorial.md +++ b/docs/getting_started/dashboard_tutorial.md @@ -5,11 +5,10 @@ from pcweb.pages import docs # Tutorial: Data Dashboard -During this tutorial you will build a small data dashboard, where you can input data and it will be rendered in table and a graph. This tutorial does not assume any existing Reflex knowledge, but we do recommend checking out the quick [Basics Guide]({docs.getting_started.basics.path}) first. +During this tutorial you will build a small data dashboard, where you can input data and it will be rendered in table and a graph. This tutorial does not assume any existing Reflex knowledge, but we do recommend checking out the quick [Basics Guide]({docs.getting_started.basics.path}) first. The techniques you’ll learn in the tutorial are fundamental to building any Reflex app, and fully understanding it will give you a deep understanding of Reflex. - This tutorial is divided into several sections: - **Setup for the Tutorial**: A starting point to follow the tutorial @@ -25,7 +24,6 @@ In this tutorial, you are building an interactive data dashboard with Reflex. You can see what the finished app and code will look like here: - ```python exec from collections import Counter @@ -51,12 +49,12 @@ class State5(rx.State): f"User {form_data['name']} has been added.", position="bottom-right", ) - + def transform_data(self): """Transform user gender group data into a format suitable for visualization in graphs.""" # Count users of each gender group gender_counts = Counter(user.gender for user in self.users) - + # Transform into list of dict so it can be used in the graph self.users_for_graph = [ { @@ -65,7 +63,7 @@ class State5(rx.State): } for gender_group, count in gender_counts.items() ] - + def show_user5(user: User): """Show a user in a table row.""" @@ -199,12 +197,12 @@ class State(rx.State): def add_user(self, form_data: dict): self.users.append(User(**form_data)) self.transform_data() - + def transform_data(self): """Transform user gender group data into a format suitable for visualization in graphs.""" # Count users of each gender group gender_counts = Counter(user.gender for user in self.users) - + # Transform into list of dict so it can be used in the graph self.users_for_graph = [ { @@ -213,7 +211,7 @@ class State(rx.State): } for gender_group, count in gender_counts.items() ] - + def show_user(user: User): """Show a user in a table row.""" @@ -340,14 +338,12 @@ app.add_page( Don't worry if you don't understand the code above, in this tutorial we are going to walk you through the whole thing step by step. - ## Setup for the tutorial Check out the [installation docs]({docs.getting_started.installation.path}) to get Reflex set up on your machine. Follow these to create a folder called `dashboard_tutorial`, which you will `cd` into and `pip install reflex`. We will choose template `0` when we run `reflex init` to get the blank template. Finally run `reflex run` to start the app and confirm everything is set up correctly. - ## Overview Now that you’re set up, let’s get an overview of Reflex! @@ -360,7 +356,7 @@ There is also an `assets` folder where static files such as images and styleshee Most importantly there is a folder also called `dashboard_tutorial` which contains all the code for your app. Inside of this folder there is a file named `dashboard_tutorial.py`. To begin this tutorial we will delete all the code in this file so that we can start from scratch and explain every step as we go. -The first thing we need to do is import `reflex`. Once we have done this we can create a component, which is a reusable piece of user interface code. Components are used to render, manage, and update the UI elements in your application. +The first thing we need to do is import `reflex`. Once we have done this we can create a component, which is a reusable piece of user interface code. Components are used to render, manage, and update the UI elements in your application. Let's look at the example below. Here we have a function called `index` that returns a `text` component (an in-built Reflex UI component) that displays the text "Hello World!". @@ -380,7 +376,7 @@ app.add_page(index) This code will render a page with the text "Hello World!" when you run your app like below: ```python eval -rx.text("Hello World!", +rx.text("Hello World!", border_width="2px", border_radius="10px", padding="1em" @@ -447,7 +443,7 @@ def index() -> rx.Component: ) ``` -Components in Reflex have `props`, which can be used to customize the component and are passed in as keyword arguments to the component function. +Components in Reflex have `props`, which can be used to customize the component and are passed in as keyword arguments to the component function. The `rx.table.root` component has for example the `variant` and `size` props, which customize the table as seen below. @@ -515,7 +511,6 @@ This is where `State` comes in. `State` is a Python class that stores variables To define a state class, subclass `rx.State` and define fields that store the state of your app. The state variables (vars) should have a type annotation, and can be initialized with a default value. Check out the [basics]({docs.getting_started.basics.path}) section for a simple example of how state works. - In the example below we define a `State` class called `State` that has a variable called `users` that is a list of lists of strings. Each list in the `users` list represents a user and contains their name, email and gender. ```python @@ -530,7 +525,8 @@ To iterate over a state var that is a list, we use the [`rx.foreach`]({docs.comp ```md alert info # Why can we not just splat this in a `for` loop -You might be wondering why a `foreach` is even needed to render this state variable and why we cannot just splat a `for` loop. Check out this [documentation]({docs.getting_started.basics.path}#compile-time-vs.-runtime-(important)) to learn why. + +You might be wondering why a `foreach` is even needed to render this state variable and why we cannot just splat a `for` loop. Check out this [documentation](<{docs.getting_started.basics.path}#compile-time-vs.-runtime-(important)>) to learn why. ``` Here the render function is `show_user` which takes in a single user and returns a `table.row` component that displays the users name, email and gender. @@ -573,7 +569,6 @@ rx.table.root( ) ``` - ```python class State(rx.State): users: list[list[str]] = [ @@ -612,13 +607,13 @@ As you can see the output above looks the same as before, except now the data is ### Using a proper class structure for our data -So far our data has been defined in a list of lists, where the data is accessed by index i.e. `user[0]`, `user[1]`. This is not very maintainable as our app gets bigger. +So far our data has been defined in a list of lists, where the data is accessed by index i.e. `user[0]`, `user[1]`. This is not very maintainable as our app gets bigger. A better way to structure our data in Reflex is to use a class to represent a user. This way we can access the data using attributes i.e. `user.name`, `user.email`. In Reflex when we create these classes to showcase our data, the class must inherit from `rx.Base`. -`rx.Base` is also necessary if we want to have a state var that is an iterable with different types. For example if we wanted to have `age` as an `int` we would have to use `rx.base` as we could not do this with a state var defined as `list[list[str]]`. +`rx.Base` is also necessary if we want to have a state var that is an iterable with different types. For example if we wanted to have `age` as an `int` we would have to use `rx.base` as we could not do this with a state var defined as `list[list[str]]`. The `show_user` render function is also updated to access the data by named attributes, instead of indexing. @@ -668,7 +663,6 @@ rx.table.root( ) ``` - ```python class User(rx.Base): """The user model.""" @@ -711,11 +705,9 @@ def index() -> rx.Component: ) ``` - Next let's add a form to the app so we can add new users to the table. - -## Using a Form to Add Data +## Using a Form to Add Data We build a form using `rx.form`, which takes several components such as `rx.input` and `rx.select`, which represent the form fields that allow you to add information to submit with the form. Check out the [form]({docs.library.forms.form.path}) docs for more information on form components. @@ -742,7 +734,6 @@ rx.form( This form is all very compact as you can see from the example, so we need to add some styling to make it look better. We can do this by adding a `vstack` component around the form fields. The `vstack` component stacks the form fields vertically. Check out the [layout]({docs.styling.layout.path}) docs for more information on how to layout your app. - ```python demo rx.form( rx.vstack( @@ -762,18 +753,17 @@ rx.form( ) ``` -Now you have probably realised that we have all the form fields, but we have no way to submit the form. We can add a submit button to the form by adding a `rx.button` component to the `vstack` component. The `rx.button` component takes in the text that is displayed on the button and the `type` prop which is the type of button. The `type` prop is set to `submit` so that the form is submitted when the button is clicked. +Now you have probably realised that we have all the form fields, but we have no way to submit the form. We can add a submit button to the form by adding a `rx.button` component to the `vstack` component. The `rx.button` component takes in the text that is displayed on the button and the `type` prop which is the type of button. The `type` prop is set to `submit` so that the form is submitted when the button is clicked. In addition to this we need a way to update the `users` state variable when the form is submitted. All state changes are handled through functions in the state class, called [event handlers]({docs.events.events_overview.path}). Components have special props called event triggers, such as `on_submit`, that can be used to make components interactive. Event triggers connect components to event handlers, which update the state. Different event triggers expect the event handler that you hook them up to, to take in different arguments (and some do not take in any arguments). -The `on_submit` event trigger of `rx.form` is hooked up to the `add_user` event handler that is defined in the `State` class. This event trigger expects to pass a `dict`, containing the form data, to the event handler that it is hooked up to. The `add_user` event handler takes in the form data as a dictionary and appends it to the `users` state variable. - +The `on_submit` event trigger of `rx.form` is hooked up to the `add_user` event handler that is defined in the `State` class. This event trigger expects to pass a `dict`, containing the form data, to the event handler that it is hooked up to. The `add_user` event handler takes in the form data as a dictionary and appends it to the `users` state variable. ```python class State(rx.State): - + ... def add_user(self, form_data: dict): @@ -806,7 +796,6 @@ Finally we must add the new `form()` component we have defined to the `index()` Below is the full code for the app so far. If you try this form out you will see that you can add new users to the table by filling out the form and clicking the submit button. The form data will also appear as a toast (a small window in the corner of the page) on the screen when submitted. - ```python exec class State3(rx.State): users: list[User] = [ @@ -816,7 +805,7 @@ class State3(rx.State): def add_user(self, form_data: dict): self.users.append(User(**form_data)) - + return rx.toast.info( f"User has been added: {form_data}.", @@ -940,7 +929,6 @@ def index() -> rx.Component: ) ``` - ### Putting the Form in an Overlay In Reflex, we like to make the user interaction as intuitive as possible. Placing the form we just constructed in an overlay creates a focused interaction by dimming the background, and ensures a cleaner layout when you have multiple action points such as editing and deleting as well. @@ -1035,7 +1023,6 @@ rx.dialog.root( At this point we have an app that allows you to add users to a table by filling out a form. The form is placed in a dialog that can be opened by clicking the "Add User" button. We change the name of the component from `form` to `add_customer_button` and update this in our `index` component. The full app so far and code are below. - ```python exec def add_customer_button() -> rx.Component: return rx.dialog.root( @@ -1136,7 +1123,7 @@ class State(rx.State): def add_user(self, form_data: dict): self.users.append(User(**form_data)) - + def show_user(user: User): """Show a person in a table row.""" @@ -1223,16 +1210,14 @@ def index() -> rx.Component: ) ``` - ## Plotting Data in a Graph -The last part of this tutorial is to plot the user data in a graph. We will use Reflex's built-in graphing library recharts to plot the number of users of each gender. +The last part of this tutorial is to plot the user data in a graph. We will use Reflex's built-in graphing library recharts to plot the number of users of each gender. ### Transforming the data for the graph The graphing components in Reflex expect to take in a list of dictionaries. Each dictionary represents a data point on the graph and contains the x and y values. We will create a new event handler in the state called `transform_data` to transform the user data into the format that the graphing components expect. We must also create a new state variable called `users_for_graph` to store the transformed data, which will be used to render the graph. - ```python from collections import Counter @@ -1243,12 +1228,12 @@ class State(rx.State): def add_user(self, form_data: dict): self.users.append(User(**form_data)) self.transform_data() - + def transform_data(self): """Transform user gender group data into a format suitable for visualization in graphs.""" # Count users of each gender group gender_counts = Counter(user.gender for user in self.users) - + # Transform into list of dict so it can be used in the graph self.users_for_graph = [ { @@ -1259,7 +1244,7 @@ class State(rx.State): ] ``` -As we can see above the `transform_data` event handler uses the `Counter` class from the `collections` module to count the number of users of each gender. We then create a list of dictionaries from this which we set to the state var `users_for_graph`. +As we can see above the `transform_data` event handler uses the `Counter` class from the `collections` module to count the number of users of each gender. We then create a list of dictionaries from this which we set to the state var `users_for_graph`. Finally we can see that whenever we add a new user through submitting the form and running the `add_user` event handler, we call the `transform_data` event handler to update the `users_for_graph` state variable. @@ -1305,12 +1290,12 @@ class State4(rx.State): f"User {form_data['name']} has been added.", position="bottom-right", ) - + def transform_data(self): """Transform user gender group data into a format suitable for visualization in graphs.""" # Count users of each gender group gender_counts = Counter(user.gender for user in self.users) - + # Transform into list of dict so it can be used in the graph self.users_for_graph = [ { @@ -1429,12 +1414,12 @@ class State(rx.State): def add_user(self, form_data: dict): self.users.append(User(**form_data)) self.transform_data() - + def transform_data(self): """Transform user gender group data into a format suitable for visualization in graphs.""" # Count users of each gender group gender_counts = Counter(user.gender for user in self.users) - + # Transform into list of dict so it can be used in the graph self.users_for_graph = [ { @@ -1443,7 +1428,7 @@ class State(rx.State): } for gender_group, count in gender_counts.items() ] - + def show_user(user: User): """Show a person in a table row.""" @@ -1547,16 +1532,15 @@ def index() -> rx.Component: One thing you may have noticed about your app is that the graph does not appear initially when you run the app, and that you must add a user to the table for it to first appear. This occurs because the `transform_data` event handler is only called when a user is added to the table. In the next section we will explore a solution to this. - ## Final Cleanup ### Revisiting app.add_page -At the beginning of this tutorial we mentioned that the `app.add_page` function is required for every Reflex app. This function is used to add a component to a page. +At the beginning of this tutorial we mentioned that the `app.add_page` function is required for every Reflex app. This function is used to add a component to a page. The `app.add_page` currently looks like this `app.add_page(index)`. We could change the route that the page renders on by setting the `route` prop such as `route="/custom-route"`, this would change the route to `http://localhost:3000/custom-route` for this page. -We can also set a `title` to be shown in the browser tab and a `description` as shown in search results. +We can also set a `title` to be shown in the browser tab and a `description` as shown in search results. To solve the problem we had above about our graph not loading when the page loads, we can use `on_load` inside of `app.add_page` to call the `transform_data` event handler when the page loads. This would look like `on_load=State.transform_data`. Below see what our `app.add_page` would look like with some of the changes above added. @@ -1600,7 +1584,7 @@ app.add_page( At the beginning of the tutorial we also mentioned that we defined our app using `app=rx.App()`. We can also pass in some props to the `rx.App` component to customize the app. -The most important one is `theme` which allows you to customize the look and feel of the app. The `theme` prop takes in an `rx.theme` component which has several props that can be set. +The most important one is `theme` which allows you to customize the look and feel of the app. The `theme` prop takes in an `rx.theme` component which has several props that can be set. The `radius` prop sets the global radius value for the app that is inherited by all components that have a `radius` prop. It can be overwritten locally for a specific component by manually setting the `radius` prop. @@ -1618,8 +1602,6 @@ app = rx.App( Unfortunately in this tutorial here we cannot actually apply this to the live example on the page, but if you copy and paste the code below into a reflex app locally you can see it in action. - - ## Conclusion Finally let's make some final styling updates to our app. We will add some hover styling to the table rows and center the table inside the `show_user` with `style=\{"_hover": \{"bg": rx.color("gray", 3)}}, align="center"`. @@ -1658,7 +1640,6 @@ rx.vstack( ) ``` - ```python import reflex as rx from collections import Counter @@ -1681,12 +1662,12 @@ class State(rx.State): def add_user(self, form_data: dict): self.users.append(User(**form_data)) self.transform_data() - + def transform_data(self): """Transform user gender group data into a format suitable for visualization in graphs.""" # Count users of each gender group gender_counts = Counter(user.gender for user in self.users) - + # Transform into list of dict so it can be used in the graph self.users_for_graph = [ { @@ -1695,7 +1676,7 @@ class State(rx.State): } for gender_group, count in gender_counts.items() ] - + def show_user(user: User): """Show a user in a table row.""" @@ -1820,21 +1801,19 @@ app.add_page( ) ``` -And that is it for your first dashboard tutorial. In this tutorial we have created +And that is it for your first dashboard tutorial. In this tutorial we have created - a table to display user data - a form to add new users to the table - a dialog to showcase the form - a graph to visualize the user data -In addition to the above we have we have +In addition to the above we have we have - explored state to allow you to show dynamic data that changes over time - explored events to allow you to make your app interactive and respond to user actions - added styling to the app to make it look better - - ## Advanced Section (Hooking this up to a Database) -Coming Soon! \ No newline at end of file +Coming Soon! diff --git a/docs/getting_started/installation.md b/docs/getting_started/installation.md index b412c738a61..edb63660d60 100644 --- a/docs/getting_started/installation.md +++ b/docs/getting_started/installation.md @@ -10,29 +10,26 @@ default_url = "http://localhost:3000" Reflex requires Python 3.10+. - ```md video https://youtube.com/embed/ITOZkzjtjUA?start=758&end=1206 # Video: Installation ``` - ## Virtual Environment We **highly recommend** creating a virtual environment for your project. [uv]({constants.UV_URL}) is the recommended modern option. [venv]({constants.VENV_URL}), [conda]({constants.CONDA_URL}) and [poetry]({constants.POETRY_URL}) are some alternatives. - # Install Reflex on your system ---md tabs --tab macOS/Linux + ## Install on macOS/Linux We will go with [uv]({constants.UV_URL}) here. - ### Prerequisites #### Install uv @@ -46,11 +43,10 @@ After installation, restart your terminal or run `source ~/.bashrc` (or `source Alternatively, install via [Homebrew, PyPI, or other methods](https://docs.astral.sh/uv/getting-started/installation/). **macOS (Apple Silicon) users:** Install [Rosetta 2](https://support.apple.com/en-us/HT211861). Run this command: - -`/usr/sbin/softwareupdate --install-rosetta --agree-to-license` +`/usr/sbin/softwareupdate --install-rosetta --agree-to-license` -### Create the project directory +### Create the project directory Replace `{app_name}` with your project name. Switch to the new directory. @@ -77,9 +73,9 @@ uv add reflex uv run reflex init ``` - -- --tab Windows + ## Install on Windows For Windows users, we recommend using [Windows Subsystem for Linux (WSL)](https://learn.microsoft.com/en-us/windows/wsl/about) for optimal performance. @@ -102,7 +98,7 @@ After installation, restart your terminal (PowerShell or Command Prompt). Alternatively, install via [WinGet, Scoop, or other methods](https://docs.astral.sh/uv/getting-started/installation/). -### Create the project directory +### Create the project directory Replace `{app_name}` with your project name. Switch to the new directory. @@ -131,13 +127,14 @@ uv run reflex init ```md alert warning # Error `Install Failed - You are missing a DLL required to run bun.exe` Windows + Bun requires runtime components of Visual C++ libraries to run on Windows. This issue is fixed by installing [Microsoft Visual C++ 2015 Redistributable](https://www.microsoft.com/en-us/download/details.aspx?id=53840). ``` + -- --- - Running `uv run reflex init` will return the option to start with a blank Reflex app, premade templates built by the Reflex team, or to try our [AI builder]({constants.REFLEX_BUILD_URL}). ```bash @@ -147,11 +144,10 @@ Get started with a template: (0) A blank Reflex app. (1) Premade templates built by the Reflex team. (2) Try our AI builder. -Which template would you like to use? (0): +Which template would you like to use? (0): ``` -From here select an option. - +From here select an option. ## Run the App @@ -169,4 +165,4 @@ Reflex prints logs to the terminal. To increase log verbosity to help with debug uv run reflex run --loglevel debug ``` -Reflex will *hot reload* any code changes in real time when running in development mode. Your code edits will show up on [http://localhost:3000](http://localhost:3000) automatically. +Reflex will _hot reload_ any code changes in real time when running in development mode. Your code edits will show up on [http://localhost:3000](http://localhost:3000) automatically. diff --git a/docs/getting_started/introduction.md b/docs/getting_started/introduction.md index f4e852cde4e..3e14c84dee3 100644 --- a/docs/getting_started/introduction.md +++ b/docs/getting_started/introduction.md @@ -341,10 +341,10 @@ By continuing with our documentation, you will learn how to build awesome apps w For a glimpse of the possibilities, check out these resources: -* For a more real-world example, check out either the [dashboard tutorial]({getting_started.dashboard_tutorial.path}) or the [chatapp tutorial]({getting_started.chatapp_tutorial.path}). -* Check out our open-source [templates]({getting_started.open_source_templates.path})! -* We have an AI Builder that can generate full Reflex apps or help with your existing app! Check it out at [Reflex Build]({constants.REFLEX_BUILD_URL})! -* Deploy your app with a single command using [Reflex Cloud]({hosting.deploy_quick_start.path})! +- For a more real-world example, check out either the [dashboard tutorial]({getting_started.dashboard_tutorial.path}) or the [chatapp tutorial]({getting_started.chatapp_tutorial.path}). +- Check out our open-source [templates]({getting_started.open_source_templates.path})! +- We have an AI Builder that can generate full Reflex apps or help with your existing app! Check it out at [Reflex Build]({constants.REFLEX_BUILD_URL})! +- Deploy your app with a single command using [Reflex Cloud]({hosting.deploy_quick_start.path})! If you want to learn more about how Reflex works, check out the [How Reflex Works]({advanced_onboarding.how_reflex_works.path}) section. diff --git a/docs/getting_started/project-structure.md b/docs/getting_started/project-structure.md index f2ec8da416c..b47e278f568 100644 --- a/docs/getting_started/project-structure.md +++ b/docs/getting_started/project-structure.md @@ -48,7 +48,9 @@ For example, if you save an image to `assets/image.png` you can display it from ```python rx.image(src=f"{REFLEX_ASSETS_CDN}other/image.png") ``` + j + ## Main Project Initializing your project creates a directory with the same name as your app. This is where you will write your app's logic. @@ -68,4 +70,4 @@ config = rx.Config( ) ``` -We will discuss project structure and configuration in more detail in the [advanced project structure]({advanced_onboarding.code_structure.path}) documentation. \ No newline at end of file +We will discuss project structure and configuration in more detail in the [advanced project structure]({advanced_onboarding.code_structure.path}) documentation. diff --git a/docs/hosting/adding-members.md b/docs/hosting/adding-members.md index 9e27e8dd5c4..76712312313 100644 --- a/docs/hosting/adding-members.md +++ b/docs/hosting/adding-members.md @@ -10,11 +10,9 @@ A project is a collection of applications (apps / websites). Every project has its own billing page that are accessible to Admins. - - ## Adding Team Members -To see the team members of a project click on the `Members` tab in the Cloud UI on the project page. +To see the team members of a project click on the `Members` tab in the Cloud UI on the project page. If you are a User you have the ability to create, deploy and delete apps, but you do not have the power to add or delete users from that project. You must be an Admin for that. @@ -29,11 +27,11 @@ rx.box(height="20px") ``` ```md alert warning -# Currently a User must already have logged in once before they can be added to a project. +# Currently a User must already have logged in once before they can be added to a project. + At this time a User must be logged in to be added to a project. In future there will be automatic email invites sent to add new users who have never logged in before. ``` - ## Other project settings -Clicking on the `Settings` tab in the Cloud UI on the project page allows a user to change the `project name`, check the `project id` and, if the project is not your default project, delete the project. \ No newline at end of file +Clicking on the `Settings` tab in the Cloud UI on the project page allows a user to change the `project name`, check the `project id` and, if the project is not your default project, delete the project. diff --git a/docs/hosting/app-management.md b/docs/hosting/app-management.md index c05fbbf33f8..c7c19ba94c9 100644 --- a/docs/hosting/app-management.md +++ b/docs/hosting/app-management.md @@ -2,20 +2,19 @@ import reflex as rx from pcweb.constants import REFLEX_ASSETS_CDN from reflex_image_zoom import image_zoom -from pcweb.pages.docs import hosting +from pcweb.pages.docs import hosting from pcweb.pages import docs from pcweb.styles.styles import get_code_style, cell_style ``` # App -In Reflex Cloud an "app" (or "application" or "website") refers to a web application built using the Reflex framework, which can be deployed and managed within the Cloud platform. +In Reflex Cloud an "app" (or "application" or "website") refers to a web application built using the Reflex framework, which can be deployed and managed within the Cloud platform. You can deploy an app using the `reflex deploy` command. There are many actions you can take in the Cloud UI to manage your app. Below are some of the most common actions you may want to take. - ## Stopping an App To stop an app follow the arrow in the image below and press on the `Stop app` button. Pausing an app will stop it from running and will not be accessible to users until you resume it. In addition, this will stop you being billed for your app. @@ -26,6 +25,7 @@ image_zoom(rx.image(src=f"{REFLEX_ASSETS_CDN}other/stopping_app.webp", padding_b ```md alert info # CLI Command to stop an app + `reflex cloud apps stop [OPTIONS] [APP_ID]` ``` @@ -47,10 +47,10 @@ Here there is a `Delete app` button. Pressing this button will delete the app an ```md alert info # CLI Command to delete an app + `reflex cloud apps delete [OPTIONS] [APP_ID]` ``` - ## Other app settings Clicking on the `Settings` tab in the Cloud UI on the app page also allows a user to change the `app name`, change the `app description` and check the `app id`. diff --git a/docs/hosting/billing.md b/docs/hosting/billing.md index 2cbb6d9e77f..0d5abb07034 100644 --- a/docs/hosting/billing.md +++ b/docs/hosting/billing.md @@ -2,24 +2,24 @@ import reflex as rx from reflex_image_zoom import image_zoom from pcweb.pages.pricing.calculator import compute_table_base -from pcweb.pages.docs import hosting +from pcweb.pages.docs import hosting ``` -## Overview +## Overview -Billing for Reflex Cloud is monthly per project. Project owners and admins are able to view and manage the billing page. +Billing for Reflex Cloud is monthly per project. Project owners and admins are able to view and manage the billing page. -The billing for a project is comprised of two parts - number of `seats` and `compute`. +The billing for a project is comprised of two parts - number of `seats` and `compute`. ## Seats -Projects on a paid plan can invite collaborators to join their project. +Projects on a paid plan can invite collaborators to join their project. -Each additional collaborator is considered a `seat` and is charged on a flat monthly rate. Project owners and admins can manage permissions and roles for each seat in the settings tab on the project page. +Each additional collaborator is considered a `seat` and is charged on a flat monthly rate. Project owners and admins can manage permissions and roles for each seat in the settings tab on the project page. ## Compute -Reflex Cloud is billed on a per second basis so you only pay for when your app is being used by your end users. When your app is idle, you are not charged. +Reflex Cloud is billed on a per second basis so you only pay for when your app is being used by your end users. When your app is idle, you are not charged. For more information on compute pricing, please see the [compute]({hosting.compute.path}) page. diff --git a/docs/hosting/compute.md b/docs/hosting/compute.md index cae2fee5399..92196d9b76b 100644 --- a/docs/hosting/compute.md +++ b/docs/hosting/compute.md @@ -6,13 +6,14 @@ from pcweb.pages.pricing.calculator import compute_table_base ## Compute Usage -Reflex Cloud is billed on a per second basis so you only pay for when your app is being used by your end users. When your app is idle, you are not charged. +Reflex Cloud is billed on a per second basis so you only pay for when your app is being used by your end users. When your app is idle, you are not charged. This allows you to deploy your app on larger sizes and multiple regions without worrying about paying for idle compute. We bill on a per second basis so you only pay for the compute you use. By default your app stays alive for 5 minutes after the no users are connected. After this time your app will be considered idle and you will not be charged. Start up times usually take less than 1 second for you apps to come back online. #### Warm vs Cold Start + - Apps below `c2m2` are considered warm starts and are usually less than 1 second. - If your app is larger than `c2m2` it will be a cold start which takes around 15 seconds. If you want to avoid this you can reserve a machine. @@ -24,7 +25,7 @@ compute_table_base() ## Reserved Machines (Coming Soon) -If you expect your apps to be continuously receiving users, you may want to reserve a machine instead of having us manage your compute. +If you expect your apps to be continuously receiving users, you may want to reserve a machine instead of having us manage your compute. This will be a flat monthly rate for the machine. @@ -34,10 +35,8 @@ To monitor your projects usage, you can go to the billing tab in the Reflex Clou Here you can see the current billing and usage for your project. - ## Real Life Examples of compute charges on the paid tiers - ```md alert # Single Application - Single Region @@ -64,8 +63,6 @@ Anna's total cost for compute would be `$2.49` for the month. However, since pai `$0.00 dollars` ``` - - ```md alert # Single Application - Multi Region @@ -79,7 +76,7 @@ Bob created a social media application and decided to host it on Reflex Cloud. B **Maths:** -`5 regions * 0.5 hours * 30 days = 75 compute hours` +`5 regions * 0.5 hours * 30 days = 75 compute hours` `75 * 0.083 = 6.23` @@ -92,9 +89,6 @@ Bob would owe `$6.23` for this month. However since Bob is a paid user they rece `$0.00 dollars` ``` - - - ```md alert # Single Growing Application - Multi Region @@ -103,13 +97,14 @@ Charlie, a small startup founder, built a finance tracking app that allows users If users access the app on average for **16 hours per week** in each region, how much would Charlie pay? **Facts:** -- **Machine size:** `c1m2` (1 CPU, 2 GB Memory) - `$0.157` per hour -- **Regions:** `4` + +- **Machine size:** `c1m2` (1 CPU, 2 GB Memory) - `$0.157` per hour +- **Regions:** `4` - **Avg usage per week per region:** `16 Hours` **Maths:** -`4 regions * 16 hours * 4 weeks = 256 compute hours` +`4 regions * 16 hours * 4 weeks = 256 compute hours` `256 * 0.157 = 40.19` @@ -120,27 +115,24 @@ Charlie would owe `$40.19` for this month. However since Charlie is a paid user **Charge for compute:** `$30.19 dollars` - ``` - - ```md alert # Single Application High-Performance App - Single Region -David, an **AI enthusiast**, developed a **real-time image enhancement tool** that allows photographers to upscale and enhance their images using machine learning. Since his app requires more processing power, he deployed it on a **`c2m2` machine**, which offers increased CPU and memory to handle the intensive AI workloads. +David, an **AI enthusiast**, developed a **real-time image enhancement tool** that allows photographers to upscale and enhance their images using machine learning. Since his app requires more processing power, he deployed it on a **`c2m2` machine**, which offers increased CPU and memory to handle the intensive AI workloads. With users accessing the app **2 hours per day** over a **30-day month**, how much would David pay? **Facts:** -- **Machine size:** `c2m2` (2 CPU, 2 GB Memory) - `$0.166` per hour -- **Regions:** `1` -- **Avg usage per day:** `2 Hours` +- **Machine size:** `c2m2` (2 CPU, 2 GB Memory) - `$0.166` per hour +- **Regions:** `1` +- **Avg usage per day:** `2 Hours` **Maths:** -`1 region * 2 hours * 30 days = 60 compute hours` +`1 region * 2 hours * 30 days = 60 compute hours` `60 * 0.166 = 9.96` @@ -151,27 +143,24 @@ David would owe `$9.96` for this month. However since David is a paid user they **Charge for compute:** `$0.00 dollars` - ``` - ```md alert -# Single Fast Scaling App - Multiple Regions - -Emily, a **productivity app developer**, built a **real-time team collaboration tool** that helps remote teams manage tasks and communicate efficiently. With users spread across multiple locations, she needed **low-latency performance** to ensure a seamless experience. To achieve this, Emily deployed her app using a `c1m1` machine in **three regions**. +# Single Fast Scaling App - Multiple Regions -With users actively using the app **6 hours per day in each region** over a **30-day month**, how much would Emily pay? +Emily, a **productivity app developer**, built a **real-time team collaboration tool** that helps remote teams manage tasks and communicate efficiently. With users spread across multiple locations, she needed **low-latency performance** to ensure a seamless experience. To achieve this, Emily deployed her app using a `c1m1` machine in **three regions**. +With users actively using the app **6 hours per day in each region** over a **30-day month**, how much would Emily pay? **Facts:** -- **Machine size:** `c1m1` (1 CPU, 1 GB Memory) - `$0.083` per hour -- **Regions:** `3` -- **Avg usage per day per region:** `6 Hours` +- **Machine size:** `c1m1` (1 CPU, 1 GB Memory) - `$0.083` per hour +- **Regions:** `3` +- **Avg usage per day per region:** `6 Hours` **Maths:** -`3 regions * 6 hours * 30 days = 540 compute hours` +`3 regions * 6 hours * 30 days = 540 compute hours` `540 * 0.083 = 44.82` @@ -182,37 +171,34 @@ Emily would owe `$44.82` for this month. However since Emily is a paid user they **Charge for compute:** `$34.82 dollars` - ``` - ```md alert -# Multiple Apps - Multiple Regions +# Multiple Apps - Multiple Regions Fred, a **freelance developer**, built a **portfolio of web applications** that cater to different clients across the globe. He has built **5 apps** where **4 apps** have a small amount of traffic with an average of **0.5 hours a day** and **1 app** that has a high amount of traffic with an average of **6 hours** a day. He has deployed the 4 small traffic apps on a `c1m1` machine in **1 region** each and the high traffic app on a `c1m1` machine in **2 regions**. How much would Fred pay? - **Facts for 4 small traffic apps:** -- **Machine size:** `c1m1` (1 CPU, 1 GB Memory) - `$0.083` per hour -- **Regions:** `1` -- **Avg usage per day per region:** `0.5 Hours` +- **Machine size:** `c1m1` (1 CPU, 1 GB Memory) - `$0.083` per hour +- **Regions:** `1` +- **Avg usage per day per region:** `0.5 Hours` **Facts for 1 large traffic app:** -- **Machine size:** `c1m1` (1 CPU, 1 GB Memory) - `$0.083` per hour -- **Regions:** `2` -- **Avg usage per day per region:** `6 Hours` +- **Machine size:** `c1m1` (1 CPU, 1 GB Memory) - `$0.083` per hour +- **Regions:** `2` +- **Avg usage per day per region:** `6 Hours` **Maths:** 4 small traffic apps: -`4 apps * 1 region * 0.5 hours * 30 days = 60 compute hours` +`4 apps * 1 region * 0.5 hours * 30 days = 60 compute hours` 1 large traffic apps: -`2 regions * 6 hours * 30 days = 360 compute hours` +`2 regions * 6 hours * 30 days = 360 compute hours` Total compute hours = `60 + 360 = 420 compute hours` @@ -227,4 +213,4 @@ Fred would owe `$34.86` for this month. However since Fred is a paid user they r `$24.82 dollars` ``` -One thing that is important to note is that in the hypothetical example where you have `50 people` using your app `continuously for 24 hours` or if you have `1 person` using your app `continuously for 24 hours`, you `will be charged the same amount` as the charge is based on the amount of time your app up and not the number of users using your app. In `both these examples` your `app is up for 24 hours` and therefore you will be `charged for 24 hours of compute`. \ No newline at end of file +One thing that is important to note is that in the hypothetical example where you have `50 people` using your app `continuously for 24 hours` or if you have `1 person` using your app `continuously for 24 hours`, you `will be charged the same amount` as the charge is based on the amount of time your app up and not the number of users using your app. In `both these examples` your `app is up for 24 hours` and therefore you will be `charged for 24 hours of compute`. diff --git a/docs/hosting/config_file.md b/docs/hosting/config_file.md index b56c90da526..1a0a78c8bbc 100644 --- a/docs/hosting/config_file.md +++ b/docs/hosting/config_file.md @@ -23,22 +23,22 @@ The `cloud.yml` file uses YAML format and supports the following structure. **Al ```yaml # Basic deployment settings -name: my-app-prod # Optional: defaults to project folder name -description: 'Production deployment' # Optional: empty by default -projectname: my-client-project # Optional: defaults to personal project +name: my-app-prod # Optional: defaults to project folder name +description: "Production deployment" # Optional: empty by default +projectname: my-client-project # Optional: defaults to personal project # Infrastructure settings -regions: # Optional: defaults to sjc: 1 - sjc: 1 # San Jose (# of machines) - lhr: 2 # London (# of machines) -vmtype: c2m2 # Optional: defaults to c1m1 +regions: # Optional: defaults to sjc: 1 + sjc: 1 # San Jose (# of machines) + lhr: 2 # London (# of machines) +vmtype: c2m2 # Optional: defaults to c1m1 # Custom domain and environment -hostname: myapp # Optional: myapp.reflex.dev -envfile: .env.production # Optional: defaults to .env +hostname: myapp # Optional: myapp.reflex.dev +envfile: .env.production # Optional: defaults to .env # Additional dependencies -packages: # Optional: empty by default +packages: # Optional: empty by default - procps ``` @@ -92,10 +92,11 @@ For details of specific sections click the links in the table. Organize deployments using projects: ```yaml -projectname: client-alpha # Groups related deployments +projectname: client-alpha # Groups related deployments ``` You can also specify a project uuid instead of name: + ```yaml project: 12345678-1234-1234-1234-1234567890ab ``` @@ -108,9 +109,9 @@ Install additional system packages your application requires. Package names are ```yaml packages: - - procps=2.0.32-1 # Version pinning is optional - - imagemagick - - ffmpeg + - procps=2.0.32-1 # Version pinning is optional + - imagemagick + - ffmpeg ``` ### Include SQLite @@ -127,6 +128,7 @@ This is not persistent and will be lost on restart. It is recommended to use a d Deployment strategy: Available strategies: + - `immediate`: [Default] Deploy immediately - `rolling`: Deploy in a rolling manner - `bluegreen`: Deploy in a blue-green manner @@ -139,17 +141,19 @@ strategy: immediate ## Multi-Environment Setup **Development (`cloud-dev.yml`):** + ```yaml name: myapp-dev -description: 'Development environment' +description: "Development environment" vmtype: c1m1 envfile: .env.development ``` **Staging (`cloud-staging.yml`):** + ```yaml name: myapp-staging -description: 'Staging environment' +description: "Staging environment" regions: sjc: 1 vmtype: c2m2 @@ -157,9 +161,10 @@ envfile: .env.staging ``` **Production (`cloud-prod.yml`):** + ```yaml name: myapp-production -description: 'Production environment' +description: "Production environment" regions: sjc: 2 lhr: 1 @@ -178,4 +183,3 @@ reflex deploy reflex deploy --config cloud-prod.yml reflex deploy --config cloud-staging.yml ``` - diff --git a/docs/hosting/custom-domains.md b/docs/hosting/custom-domains.md index 73c8961d62b..7f78a984aa9 100644 --- a/docs/hosting/custom-domains.md +++ b/docs/hosting/custom-domains.md @@ -6,16 +6,14 @@ from reflex_image_zoom import image_zoom # Custom Domains - -With the Enterprise tier of Reflex Cloud you can use your own custom domain to host your app. +With the Enterprise tier of Reflex Cloud you can use your own custom domain to host your app. ## Prerequisites -You must purchase a domain from a domain registrar such as GoDaddy, Cloudflare, Namecheap, or AWS. +You must purchase a domain from a domain registrar such as GoDaddy, Cloudflare, Namecheap, or AWS. For this tutorial we will use GoDaddy and the example domain `tomgotsman.us`. - ## Steps Once you have purchased your domain, you can add it to your Reflex Cloud app by following these steps: @@ -50,11 +48,13 @@ image_zoom(rx.image(src=f"{REFLEX_ASSETS_CDN}other/custom-domains-DNS-after.webp ```md alert warning # It may alert you that this record will resolve on ######.tomgotsman.us.tomgotsman.us. + If this happens ensure that you select to only have the record resolve on ######.tomgotsman.us. ``` ```md alert warning # Your domain provider may not support an Apex CNAME record, in this case just use an A record. + ![Image showing failed CNAME record](/custom-domains-CNAME-fail.png) ``` @@ -72,4 +72,4 @@ image_zoom(rx.image(src=f"{REFLEX_ASSETS_CDN}other/custom-domains-success.webp") rx.box(height="20px") ``` -6 - Now redeploy your app using the `reflex deploy` command and your app should now be live on your custom domain! \ No newline at end of file +6 - Now redeploy your app using the `reflex deploy` command and your app should now be live on your custom domain! diff --git a/docs/hosting/databricks.md b/docs/hosting/databricks.md index 67a4967d18a..e7207d1c6e3 100644 --- a/docs/hosting/databricks.md +++ b/docs/hosting/databricks.md @@ -30,14 +30,8 @@ This guide walks you through deploying a Reflex web application on Databricks us Create a new file called `app.yaml` directly in Databricks (not in GitHub): ```yaml -command: [ - "reflex", - "run", - "--env", - "prod", - "--backend-port", - "$DATABRICKS_APP_PORT" -] +command: + ["reflex", "run", "--env", "prod", "--backend-port", "$DATABRICKS_APP_PORT"] env: - name: "HOME" @@ -57,7 +51,7 @@ env: ### Obtain Required Tokens 1. **Reflex Access Token** - - Visit [Reflex Cloud Tokens]({constants.REFLEX_CLOUD_URL.rstrip("/")}/tokens/) + - Visit [Reflex Cloud Tokens](<{constants.REFLEX_CLOUD_URL.rstrip("/")}/tokens/>) - Navigate to Account Settings → Tokens - Create a new token and copy the value - Replace `your-token-here` in the configuration @@ -92,7 +86,7 @@ app = rxe.App( ``` ```md alert info -# Also add `reflex-enterprise` and `asgiproxy` to your `requirements.txt` file. +# Also add `reflex-enterprise` and `asgiproxy` to your `requirements.txt` file. ``` ## Step 4: Create Databricks App diff --git a/docs/hosting/deploy-quick-start.md b/docs/hosting/deploy-quick-start.md index ade0fc99a6f..cd2c7495f2b 100644 --- a/docs/hosting/deploy-quick-start.md +++ b/docs/hosting/deploy-quick-start.md @@ -21,7 +21,6 @@ Reflex’s hosting service makes it easy to deploy your apps without worrying ab 2. This tutorial assumes you have successfully `reflex init` and `reflex run` your app. 3. Also make sure you have a `requirements.txt` file at the top level app directory that contains all your python dependencies! (To create a `requirements.txt` file, run `pip freeze > requirements.txt`.) - ### Authentication First run the command below to login / signup to your Reflex Cloud account: (command line) @@ -56,35 +55,34 @@ In your project directory (where you would normally run `reflex run`) paste this The command is by default interactive. It asks you a few questions for information required for the deployment. - 1. The first question will compare your `requirements.txt` to your python environment and if they are different then it will ask you if you want to update your `requirements.txt` or to continue with the current one. If they are identical this question will not appear. To create a `requirements.txt` file, run `pip freeze > requirements.txt`. 2. The second question will search for a deployed app with the name of your current app, if it does not find one then it will ask if you wish to proceed in deploying your new app. 3. The third question is optional and will ask you for an app description. - That’s it! You should receive some feedback on the progress of your deployment and in a few minutes your app should be up. 🎉 For detailed information about the deploy command and its options, see the [Deploy API Reference]({docs.cloud.deploy.path}) and the [CLI Reference](https://reflex.dev/docs/api-reference/cli/). - ```md alert info # Once your code is uploaded, the hosting service will start the deployment. After a complete upload, exiting from the command **does not** affect the deployment process. The command prints a message when you can safely close it without affecting the deployment. ``` If you go back to the Cloud UI you should be able to see your deployed app and other useful app information. - ```md alert info # Setup a Cloud Config File + To create a `config.yml` file for your app to set your app configuration check out the [Cloud Config Docs]({docs.hosting.config_file.path}). ``` ```md alert info # Moving around the Cloud UI + To go back, i.e. from an app to a project or from a project to your list of projects you just click the `REFLEX logo` in the top left corner of the page. ``` ```md alert info # All flag values are saved between runs + All your flag values, i.e. environment variables or regions or tokens, are saved between runs. This means that if you run a command and you pass a flag value, the next time you run the same command the flag value will be the same as the last time you ran it. This means you should only set the flag values again if you want to change them. ``` diff --git a/docs/hosting/deploy-with-github-actions.md b/docs/hosting/deploy-with-github-actions.md index 55ff68ad7a1..603ff7b0324 100644 --- a/docs/hosting/deploy-with-github-actions.md +++ b/docs/hosting/deploy-with-github-actions.md @@ -46,12 +46,15 @@ This GitHub Action simplifies the deployment of Reflex applications to Reflex Cl ``` **Features:** + - Deploy Reflex apps directly from your GitHub repository to Reflex Cloud. - Supports subdirectory-based app structures. - Securely uses authentication tokens via GitHub Secrets. ## Usage + ### Add the Action to Your Workflow + Create a `.github/workflows/deploy.yml` file in your repository and add the following: ```yaml @@ -77,19 +80,17 @@ jobs: ``` ### Set Up Your Secrets -Store your Reflex authentication token securely in your repository's secrets: +Store your Reflex authentication token securely in your repository's secrets: 1. Go to your GitHub repository. 2. Navigate to Settings > Secrets and variables > Actions > New repository secret. -3. Create new secrets for `REFLEX_AUTH_TOKEN` and `REFLEX_PROJECT_ID`. +3. Create new secrets for `REFLEX_AUTH_TOKEN` and `REFLEX_PROJECT_ID`. -(Create a `REFLEX_AUTH_TOKEN` in the tokens tab of your UI, check out these [docs]({docs.hosting.tokens.path}#tokens). +(Create a `REFLEX_AUTH_TOKEN` in the tokens tab of your UI, check out these [docs]({docs.hosting.tokens.path}#tokens). The `REFLEX_PROJECT_ID` can be found in the UI when you click on the How to deploy button on the top right when inside a project and copy the ID after the `--project` flag.) - - ### Inputs ```python eval diff --git a/docs/hosting/logs.md b/docs/hosting/logs.md index 1ab4144959e..39f90eb3a20 100644 --- a/docs/hosting/logs.md +++ b/docs/hosting/logs.md @@ -2,7 +2,7 @@ import reflex as rx from pcweb.constants import REFLEX_ASSETS_CDN from reflex_image_zoom import image_zoom -from pcweb.pages.docs import hosting +from pcweb.pages.docs import hosting from pcweb.pages import docs from pcweb.styles.styles import get_code_style, cell_style ``` @@ -17,6 +17,7 @@ image_zoom(rx.image(src=f"{REFLEX_ASSETS_CDN}other/view_logs.webp", padding_bott ```md alert info # CLI Command to view logs + `reflex cloud apps logs [OPTIONS] [APP_ID]` ``` @@ -36,6 +37,7 @@ image_zoom(rx.image(src=f"{REFLEX_ASSETS_CDN}other/view_deployment_logs_2.webp", ```md alert info # CLI Command to view deployment history + `reflex cloud apps history [OPTIONS] [APP_ID]` ``` @@ -43,4 +45,4 @@ This brings you to the page below where you can view the deployment logs of your ```python eval image_zoom(rx.image(src=f"{REFLEX_ASSETS_CDN}other/view_deployment_logs_3.webp")) -``` \ No newline at end of file +``` diff --git a/docs/hosting/machine-types.md b/docs/hosting/machine-types.md index e4056f5eb78..14e77e72584 100644 --- a/docs/hosting/machine-types.md +++ b/docs/hosting/machine-types.md @@ -2,14 +2,13 @@ import reflex as rx from pcweb.constants import REFLEX_ASSETS_CDN from reflex_image_zoom import image_zoom -from pcweb.pages.docs import hosting +from pcweb.pages.docs import hosting from pcweb.pages import docs from pcweb.styles.styles import get_code_style, cell_style ``` ## Machine Types - To scale your app you can choose different VMTypes. VMTypes are different configurations of CPU and RAM. To scale your VM in the Cloud UI, click on the `Settings` tab in the Cloud UI on the app page, and then click on the `Scale` tab as shown below. Clicking on the `Change VM` button will allow you to scale your app. @@ -32,4 +31,4 @@ To set which VMType to use when deploying your app you can pass the `--vmtype` f reflex deploy --project f88b1574-f101-####-####-5f########## --vmtype c2m4 ``` -This will deploy your app with the `c2m4` VMType, giving your app 2 CPU cores and 4 GB of RAM. \ No newline at end of file +This will deploy your app with the `c2m4` VMType, giving your app 2 CPU cores and 4 GB of RAM. diff --git a/docs/hosting/regions.md b/docs/hosting/regions.md index e4283214147..c6e1de0d7bc 100644 --- a/docs/hosting/regions.md +++ b/docs/hosting/regions.md @@ -2,7 +2,7 @@ import reflex as rx from reflex_image_zoom import image_zoom from pcweb.constants import REFLEX_ASSETS_CDN, REFLEX_CLOUD_URL -from pcweb.pages.docs import hosting +from pcweb.pages.docs import hosting from pcweb.pages import docs from pcweb.styles.styles import get_code_style, cell_style @@ -54,7 +54,7 @@ COUNTRIES_CODES = { ## Regions -To scale your app you can choose different regions. Regions are different locations around the world where your app can be deployed. +To scale your app you can choose different regions. Regions are different locations around the world where your app can be deployed. To scale your app to multiple regions in the Cloud UI, click on the `Settings` tab in the Cloud UI on the app page, and then click on the `Regions` tab as shown below. Clicking on the `Add new region` button will allow you to scale your app to multiple regions. @@ -124,7 +124,6 @@ reflex deploy --project f88b1574-f101-####-####-5f########## --region sjc --regi By default all apps are deloyed in `sjc` if no other regions are given. If you wish to deploy in another region or several regions you can pass the `--region` flag (`-r` also works) with the region code. Check out all the regions that we can deploy to below: - ## Config File To create a `config.yml` file for your app run the command below: @@ -137,7 +136,7 @@ This will create a yaml file similar to the one below where you can edit the app ```yaml name: medo -description: '' +description: "" regions: sjc: 1 lhr: 2 @@ -146,6 +145,5 @@ hostname: null envfile: .env project: null packages: -- procps + - procps ``` - diff --git a/docs/hosting/secrets-environment-vars.md b/docs/hosting/secrets-environment-vars.md index 14b7af5b023..84fb719a375 100644 --- a/docs/hosting/secrets-environment-vars.md +++ b/docs/hosting/secrets-environment-vars.md @@ -6,7 +6,6 @@ from reflex_image_zoom import image_zoom # Secrets (Environment Variables) - ## Adding Secrets through the CLI Below is an example of how to use an environment variable file. You can pass the `--envfile` flag with the path to the env file. For example: @@ -17,20 +16,19 @@ reflex deploy --project f88b1574-f101-####-####-5f########## --envfile .env In this example the path to the file is `.env`. - If you prefer to pass the environment variables manually below is deployment command example: ```bash reflex deploy --project f88b1574-f101-####-####-5f########## --env OPENAI_API_KEY=sk-proj-vD4i9t6U############################ ``` -They are passed after the `--env` flag as key value pairs. +They are passed after the `--env` flag as key value pairs. To pass multiple environment variables, you can repeat the `--env` tag. i.e. `reflex deploy --project f88b1574-f101-####-####-5f########## --env KEY1=VALUE1 --env KEY2=VALUE`. The `--envfile` flag will override any envs set manually. - ```md alert info # More information on Environment Variables + Environment variables are encrypted and safely stored. We recommend that backend API keys or secrets are entered as `envs`. Make sure to enter the `envs` without any quotation marks. We do not show the values of them in any CLI commands, only their names (or keys). You access the values of `envs` by referencing `os.environ` with their names as keys in your app's backend. For example, if you set an env `ASYNC_DB_URL`, you are able to access it by `os.environ["ASYNC_DB_URL"]`. Some Python libraries automatically look for certain environment variables. For example, `OPENAI_API_KEY` for the `openai` python client. The `boto3` client credentials can be configured by setting `AWS_ACCESS_KEY_ID`,`AWS_SECRET_ACCESS_KEY`. This information is typically available in the documentation of the Python packages you use. @@ -52,4 +50,4 @@ image_zoom(rx.image(src=f"{REFLEX_ASSETS_CDN}other/environment_variables_2.webp" From here you can add or edit your environment variables. You will need to restart your app for these changes to take effect. -This functionality in the UI can be disabled by an admin of the project. \ No newline at end of file +This functionality in the UI can be disabled by an admin of the project. diff --git a/docs/hosting/self-hosting.md b/docs/hosting/self-hosting.md index 2e687a11970..f974914c226 100644 --- a/docs/hosting/self-hosting.md +++ b/docs/hosting/self-hosting.md @@ -37,12 +37,13 @@ Then run your app in production mode: reflex run --env prod ``` -Production mode creates an optimized build of your app. By default, the static +Production mode creates an optimized build of your app. By default, the static frontend of the app (HTML, Javascript, CSS) will be exposed on port `3000` and the backend (event handlers) will be listening on port `8000`. ```md alert warning # Reverse Proxy and Websockets + Because the backend uses websockets, some reverse proxy servers, like [nginx](https://nginx.org/en/docs/http/websocket.html) or [apache](https://httpd.apache.org/docs/2.4/mod/mod_proxy.html#protoupgrade), must be configured to pass the `Upgrade` header to allow backend connectivity. ``` diff --git a/docs/hosting/tokens.md b/docs/hosting/tokens.md index b7a6442f785..7c65428acb3 100644 --- a/docs/hosting/tokens.md +++ b/docs/hosting/tokens.md @@ -16,7 +16,6 @@ image_zoom(rx.image(src=f"{REFLEX_ASSETS_CDN}other/hosting_tokens_1.webp", alt=" Clicking `Account Settings` will redirect you to both the `Settings` and `Tokens` dashboards. Click the `Tokens` tab at the top to access your tokens or create new ones. - ```python eval image_zoom(rx.image(src=f"{REFLEX_ASSETS_CDN}other/hosting_tokens_2.webp", alt="Adding tokens to Reflex Cloud", padding="1em 0em")) ``` diff --git a/docs/library/data-display/avatar.md b/docs/library/data-display/avatar.md index 9e7f71b8525..725ac8f8328 100644 --- a/docs/library/data-display/avatar.md +++ b/docs/library/data-display/avatar.md @@ -1,9 +1,10 @@ --- components: - - rx.avatar + - rx.avatar Avatar: | - lambda **props: rx.hstack(rx.avatar(src=f"{REFLEX_ASSETS_CDN}other/logo.jpg", **props), rx.avatar(fallback="RX", **props), spacing="3") + lambda **props: rx.hstack(rx.avatar(src=f"{REFLEX_ASSETS_CDN}other/logo.jpg", **props), rx.avatar(fallback="RX", **props), spacing="3") --- + # Avatar ```python exec diff --git a/docs/library/data-display/badge.md b/docs/library/data-display/badge.md index 9ee9225e77f..24b8113ed39 100644 --- a/docs/library/data-display/badge.md +++ b/docs/library/data-display/badge.md @@ -1,10 +1,11 @@ --- components: - - rx.badge + - rx.badge Badge: | - lambda **props: rx.badge("Basic Badge", **props) + lambda **props: rx.badge("Basic Badge", **props) --- + # Badge ```python exec diff --git a/docs/library/data-display/callout-ll.md b/docs/library/data-display/callout-ll.md index c4b9ef22285..8cb58950e1d 100644 --- a/docs/library/data-display/callout-ll.md +++ b/docs/library/data-display/callout-ll.md @@ -1,11 +1,10 @@ --- components: - - rx.callout.root - - rx.callout.icon - - rx.callout.text + - rx.callout.root + - rx.callout.icon + - rx.callout.text --- - ```python exec import reflex as rx ``` diff --git a/docs/library/data-display/callout.md b/docs/library/data-display/callout.md index 81f84904632..ebd286004e4 100644 --- a/docs/library/data-display/callout.md +++ b/docs/library/data-display/callout.md @@ -1,22 +1,21 @@ --- components: - - rx.callout - - rx.callout.root - - rx.callout.icon - - rx.callout.text + - rx.callout + - rx.callout.root + - rx.callout.icon + - rx.callout.text Callout: | - lambda **props: rx.callout("Basic Callout", icon="search", **props) + lambda **props: rx.callout("Basic Callout", icon="search", **props) CalloutRoot: | - lambda **props: rx.callout.root( - rx.callout.icon(rx.icon(tag="info")), - rx.callout.text("You will need admin privileges to install and access this application."), - **props - ) + lambda **props: rx.callout.root( + rx.callout.icon(rx.icon(tag="info")), + rx.callout.text("You will need admin privileges to install and access this application."), + **props + ) --- - ```python exec import reflex as rx from pcweb.pages import docs diff --git a/docs/library/data-display/code_block.md b/docs/library/data-display/code_block.md index e19f551f7aa..d7e299aa2d9 100644 --- a/docs/library/data-display/code_block.md +++ b/docs/library/data-display/code_block.md @@ -1,6 +1,6 @@ --- components: - - rx.code_block + - rx.code_block --- ```python exec diff --git a/docs/library/data-display/data_list.md b/docs/library/data-display/data_list.md index ebb45f5098b..cd866a00f2c 100644 --- a/docs/library/data-display/data_list.md +++ b/docs/library/data-display/data_list.md @@ -1,38 +1,38 @@ --- components: - - rx.data_list.root - - rx.data_list.item - - rx.data_list.label - - rx.data_list.value + - rx.data_list.root + - rx.data_list.item + - rx.data_list.label + - rx.data_list.value DataListRoot: | - lambda **props: rx.data_list.root( - rx.foreach( - [["Status", "Authorized"], ["ID", "U-474747"], ["Name", "Developer Success"], ["Email", "foo@reflex.dev"]], - lambda item: rx.data_list.item(rx.data_list.label(item[0]), rx.data_list.value(item[1])), - ), - **props, - ) + lambda **props: rx.data_list.root( + rx.foreach( + [["Status", "Authorized"], ["ID", "U-474747"], ["Name", "Developer Success"], ["Email", "foo@reflex.dev"]], + lambda item: rx.data_list.item(rx.data_list.label(item[0]), rx.data_list.value(item[1])), + ), + **props, + ) DataListItem: | - lambda **props: rx.data_list.root( - rx.foreach( - [["Status", "Authorized"], ["ID", "U-474747"], ["Name", "Developer Success"], ["Email", "foo@reflex.dev"]], - lambda item: rx.data_list.item(rx.data_list.label(item[0]), rx.data_list.value(item[1]), **props), - ), - ) + lambda **props: rx.data_list.root( + rx.foreach( + [["Status", "Authorized"], ["ID", "U-474747"], ["Name", "Developer Success"], ["Email", "foo@reflex.dev"]], + lambda item: rx.data_list.item(rx.data_list.label(item[0]), rx.data_list.value(item[1]), **props), + ), + ) DataListLabel: | - lambda **props: rx.data_list.root( - rx.foreach( - [["Status", "Authorized"], ["ID", "U-474747"], ["Name", "Developer Success"], ["Email", "foo@reflex.dev"]], - lambda item: rx.data_list.item(rx.data_list.label(item[0], **props), rx.data_list.value(item[1])), - ), - ) + lambda **props: rx.data_list.root( + rx.foreach( + [["Status", "Authorized"], ["ID", "U-474747"], ["Name", "Developer Success"], ["Email", "foo@reflex.dev"]], + lambda item: rx.data_list.item(rx.data_list.label(item[0], **props), rx.data_list.value(item[1])), + ), + ) DataListValue: | - lambda **props: rx.data_list.root( - rx.foreach( - [["Status", "Authorized"], ["ID", "U-474747"], ["Name", "Developer Success"], ["Email", "foo@reflex.dev"]], - lambda item: rx.data_list.item(rx.data_list.label(item[0]), rx.data_list.value(item[1], **props)), - ), - ) + lambda **props: rx.data_list.root( + rx.foreach( + [["Status", "Authorized"], ["ID", "U-474747"], ["Name", "Developer Success"], ["Email", "foo@reflex.dev"]], + lambda item: rx.data_list.item(rx.data_list.label(item[0]), rx.data_list.value(item[1], **props)), + ), + ) --- ```python exec @@ -88,4 +88,4 @@ rx.card( ), ), ), -``` \ No newline at end of file +``` diff --git a/docs/library/data-display/icon.md b/docs/library/data-display/icon.md index f2f31a908dd..d2190117c6d 100644 --- a/docs/library/data-display/icon.md +++ b/docs/library/data-display/icon.md @@ -1,6 +1,6 @@ --- components: - - rx.lucide.Icon + - rx.lucide.Icon --- ```python exec @@ -12,7 +12,6 @@ from pcweb.components.icons.lucide.lucide import lucide_icons The Icon component is used to display an icon from a library of icons. This implementation is based on the [Lucide Icons](https://lucide.dev/icons) where you can find a list of all available icons. - ## Icons List ```python eval @@ -69,7 +68,7 @@ Reflex also supports using dynamic values directly as the `tag` prop in `rx.icon ```python exec class DynamicIconState(rx.State): current_icon: str = "heart" - + def change_icon(self): icons = ["heart", "star", "bell", "calendar", "settings"] import random @@ -108,7 +107,6 @@ rx.flex( ) ``` - ### Size ```python demo @@ -176,7 +174,6 @@ rx.flex( ) ``` - ## Final Example Icons can be used as child components of many other components. For example, adding a magnifying glass icon to a search bar. diff --git a/docs/library/data-display/list.md b/docs/library/data-display/list.md index 941abf9f4ef..b5dfcf8de72 100644 --- a/docs/library/data-display/list.md +++ b/docs/library/data-display/list.md @@ -1,8 +1,8 @@ --- components: - - rx.list.item - - rx.list.ordered - - rx.list.unordered + - rx.list.item + - rx.list.ordered + - rx.list.unordered --- ```python exec @@ -23,7 +23,7 @@ rx.list.unordered( ) ``` - `list.ordered` has numbers to display the list items. +`list.ordered` has numbers to display the list items. ```python demo rx.list.ordered( diff --git a/docs/library/data-display/moment.md b/docs/library/data-display/moment.md index 10ac603b5d8..a6eeb294620 100644 --- a/docs/library/data-display/moment.md +++ b/docs/library/data-display/moment.md @@ -1,15 +1,13 @@ --- components: - - rx.moment - + - rx.moment --- # Moment Displaying date and relative time to now sometimes can be more complicated than necessary. -To make it easy, Reflex is wrapping [react-moment](https://www.npmjs.com/package/react-moment) under `rx.moment`. - +To make it easy, Reflex is wrapping [react-moment](https://www.npmjs.com/package/react-moment) under `rx.moment`. ```python exec import reflex as rx @@ -58,6 +56,7 @@ rx.moment(MomentState.date_now, from_now=True) ```python demo rx.moment(MomentState.date_now, to_now=True) ``` + You can also set a duration (in milliseconds) with `from_now_during` where the date will display as relative, then after that, it will be displayed as defined in `format`. ```python demo diff --git a/docs/library/data-display/progress.md b/docs/library/data-display/progress.md index a5df0dd478d..aa3a1cdd691 100644 --- a/docs/library/data-display/progress.md +++ b/docs/library/data-display/progress.md @@ -1,9 +1,9 @@ --- components: - - rx.progress + - rx.progress Progress: | - lambda **props: rx.progress(value=50, **props) + lambda **props: rx.progress(value=50, **props) --- # Progress @@ -13,6 +13,7 @@ Progress is used to display the progress status for a task that takes a long tim ```python exec import reflex as rx ``` + ## Basic Example `rx.progress` expects the `value` prop to set the progress value. @@ -47,7 +48,7 @@ class ProgressState(rx.State): def live_progress(): return rx.hstack( - rx.progress(value=ProgressState.value), + rx.progress(value=ProgressState.value), rx.button("Start", on_click=ProgressState.start_progress), width="50%" ) diff --git a/docs/library/data-display/scroll_area.md b/docs/library/data-display/scroll_area.md index 17327eae412..5c42be41b9b 100644 --- a/docs/library/data-display/scroll_area.md +++ b/docs/library/data-display/scroll_area.md @@ -1,29 +1,27 @@ --- components: - - rx.scroll_area + - rx.scroll_area ScrollArea: | - lambda **props: rx.scroll_area( - rx.flex( - rx.text( - """Three fundamental aspects of typography are legibility, readability, and aesthetics. Although in a non-technical sense "legible" and "readable"are often used synonymously, typographically they are separate but related concepts.""", - size="5", - ), - rx.text( - """Legibility describes how easily individual characters can be distinguished from one another. It is described by Walter Tracy as "the quality of being decipherable and recognisable". For instance, if a "b" and an "h", or a "3" and an "8", are difficult to distinguish at small sizes, this is a problem of legibility.""", - size="5", - ), - direction="column", - spacing="4", - height="100px", - width="50%", - ), - **props - ) - + lambda **props: rx.scroll_area( + rx.flex( + rx.text( + """Three fundamental aspects of typography are legibility, readability, and aesthetics. Although in a non-technical sense "legible" and "readable"are often used synonymously, typographically they are separate but related concepts.""", + size="5", + ), + rx.text( + """Legibility describes how easily individual characters can be distinguished from one another. It is described by Walter Tracy as "the quality of being decipherable and recognisable". For instance, if a "b" and an "h", or a "3" and an "8", are difficult to distinguish at small sizes, this is a problem of legibility.""", + size="5", + ), + direction="column", + spacing="4", + height="100px", + width="50%", + ), + **props + ) --- - ```python exec import random import reflex as rx @@ -97,7 +95,7 @@ rx.grid( ), type="always", scrollbars="vertical", - style={"height": 150}, + style={"height": 150}, ), rx.scroll_area( rx.flex( @@ -120,7 +118,7 @@ rx.grid( ), type="always", scrollbars="horizontal", - style={"height": 150}, + style={"height": 150}, ), rx.scroll_area( rx.flex( @@ -143,7 +141,7 @@ rx.grid( ), type="always", scrollbars="both", - style={"height": 150}, + style={"height": 150}, ), columns="3", spacing="2", @@ -179,7 +177,7 @@ rx.grid( ), type="auto", scrollbars="vertical", - style={"height": 150}, + style={"height": 150}, ), rx.scroll_area( rx.flex( @@ -196,7 +194,7 @@ rx.grid( ), type="always", scrollbars="vertical", - style={"height": 150}, + style={"height": 150}, ), rx.scroll_area( rx.flex( @@ -213,7 +211,7 @@ rx.grid( ), type="scroll", scrollbars="vertical", - style={"height": 150}, + style={"height": 150}, ), rx.scroll_area( rx.flex( @@ -230,7 +228,7 @@ rx.grid( ), type="hover", scrollbars="vertical", - style={"height": 150}, + style={"height": 150}, ), columns="4", spacing="2", diff --git a/docs/library/data-display/spinner.md b/docs/library/data-display/spinner.md index 90850f3f45c..686a076d06b 100644 --- a/docs/library/data-display/spinner.md +++ b/docs/library/data-display/spinner.md @@ -1,6 +1,6 @@ --- components: - - rx.spinner + - rx.spinner --- # Spinner @@ -52,5 +52,3 @@ rx.button( disabled=True ) ``` - - diff --git a/docs/library/disclosure/segmented_control.md b/docs/library/disclosure/segmented_control.md index f67748abdd6..9740c556d21 100644 --- a/docs/library/disclosure/segmented_control.md +++ b/docs/library/disclosure/segmented_control.md @@ -1,7 +1,7 @@ --- components: - - rx.segmented_control.root - - rx.segmented_control.item + - rx.segmented_control.root + - rx.segmented_control.item --- ```python exec diff --git a/docs/library/dynamic-rendering/auto_scroll.md b/docs/library/dynamic-rendering/auto_scroll.md index 4e6ade214fe..a2294ca539c 100644 --- a/docs/library/dynamic-rendering/auto_scroll.md +++ b/docs/library/dynamic-rendering/auto_scroll.md @@ -13,7 +13,7 @@ import reflex as rx class AutoScrollState(rx.State): messages: list[str] = ["Initial message"] - + def add_message(self): self.messages.append(f"New message #{len(self.messages) + 1}") diff --git a/docs/library/forms/input-ll.md b/docs/library/forms/input-ll.md index c62c5486020..1604614d44b 100644 --- a/docs/library/forms/input-ll.md +++ b/docs/library/forms/input-ll.md @@ -1,7 +1,7 @@ --- components: - - rx.input - - rx.input.slot + - rx.input + - rx.input.slot --- ```python exec @@ -12,7 +12,6 @@ import reflex as rx A text field is an input field that users can type into. This component uses Radix's [text field](https://www.radix-ui.com/themes/docs/components/text-field) component. - ## Overview The TextField component is used to capture user input and can include an optional slot for buttons and icons. It is based on the
    element and supports common margin props. diff --git a/docs/library/forms/select.md b/docs/library/forms/select.md index 78e5206bf99..b37e7104880 100644 --- a/docs/library/forms/select.md +++ b/docs/library/forms/select.md @@ -178,7 +178,6 @@ def select_form_example(): ) ``` - ### Using Select within a Drawer component If using within a [Drawer](/docs/library/overlay/drawer) component, set the `position` prop to `"popper"` to ensure the select menu is displayed correctly. diff --git a/docs/library/forms/upload.md b/docs/library/forms/upload.md index b87391253fa..767122e5e4c 100644 --- a/docs/library/forms/upload.md +++ b/docs/library/forms/upload.md @@ -16,7 +16,6 @@ from pcweb.constants import REFLEX_ASSETS_CDN Reflex makes it simple to add file upload functionality to your app. You can let users select files, store them on your server, and display or process them as needed. Below is a minimal example that demonstrates how to upload files, save them to disk, and display uploaded images using application state. - ## Basic File Upload Example You can let users upload files and keep track of them in your app’s state. The example below allows users to upload files, saves them using the backend, and then displays the uploaded files as images. @@ -59,18 +58,21 @@ To upload the file(s), you need to bind an event handler and pass the special Reflex provides two key functions for handling uploaded files: ### rx.get_upload_dir() + - **Purpose**: Returns a `Path` object pointing to the server-side directory where uploaded files should be saved - **Usage**: Used in backend event handlers to determine where to save uploaded files - **Default Location**: `./uploaded_files` (can be customized via `REFLEX_UPLOADED_FILES_DIR` environment variable) - **Type**: Returns `pathlib.Path` ### rx.get_upload_url(filename) + - **Purpose**: Returns the URL string that can be used in frontend components to access uploaded files - **Usage**: Used in frontend components (like `rx.image`, `rx.video`) to display uploaded files - **URL Format**: `/_upload/filename` - **Type**: Returns `str` ### Key Differences + - **rx.get_upload_dir()** -> Backend file path for saving files - **rx.get_upload_url()** -> Frontend URL for displaying files @@ -280,7 +282,6 @@ def index(): ) ``` - ### Customizing the Upload In the example below, the upload component accepts a maximum number of 5 files of specific types. @@ -443,6 +444,7 @@ your_project/ ``` The files are automatically served at: + - `/_upload/image1.png` ← `rx.get_upload_url("image1.png")` - `/_upload/document.pdf` ← `rx.get_upload_url("document.pdf")` - `/_upload/video.mp4` ← `rx.get_upload_url("video.mp4")` diff --git a/docs/library/graphing/charts/composedchart.md b/docs/library/graphing/charts/composedchart.md index f69ed43145e..a2475f2becc 100644 --- a/docs/library/graphing/charts/composedchart.md +++ b/docs/library/graphing/charts/composedchart.md @@ -1,19 +1,19 @@ --- components: - - rx.recharts.ComposedChart + - rx.recharts.ComposedChart --- ```python exec import reflex as rx from pcweb.pages.docs import library ``` + # Composed Chart A `composed_chart` is a higher-level component chart that is composed of multiple charts, where other charts are the children of the `composed_chart`. The charts are placed on top of each other in the order they are provided in the `composed_chart` function. - ```md alert info -# To learn more about individual charts, checkout: **[area_chart]({library.graphing.charts.areachart.path})**, **[line_chart]({library.graphing.charts.linechart.path})**, or **[bar_chart]({library.graphing.charts.barchart.path})**. +# To learn more about individual charts, checkout: **[area_chart]({library.graphing.charts.areachart.path})**, **[line_chart]({library.graphing.charts.linechart.path})**, or **[bar_chart]({library.graphing.charts.barchart.path})**. ``` ```python demo graphing @@ -68,7 +68,7 @@ def composed(): data_key="uv", stroke="#8884d8", fill="#8884d8" - ), + ), rx.recharts.bar( data_key="amt", bar_size=20, @@ -78,8 +78,8 @@ def composed(): data_key="pv", type_="monotone", stroke="#ff7300" - ), - rx.recharts.x_axis(data_key="name"), + ), + rx.recharts.x_axis(data_key="name"), rx.recharts.y_axis(), rx.recharts.cartesian_grid(stroke_dasharray="3 3"), rx.recharts.graphing_tooltip(), diff --git a/docs/library/graphing/charts/errorbar.md b/docs/library/graphing/charts/errorbar.md index 2e4b3bdd3db..f488a2c7bab 100644 --- a/docs/library/graphing/charts/errorbar.md +++ b/docs/library/graphing/charts/errorbar.md @@ -1,6 +1,6 @@ --- components: - - rx.recharts.ErrorBar + - rx.recharts.ErrorBar --- ```python exec @@ -93,7 +93,7 @@ def error(): data=data, fill="#8884d8", name="A"), - rx.recharts.x_axis(data_key="x", name="x", type_="number"), + rx.recharts.x_axis(data_key="x", name="x", type_="number"), rx.recharts.y_axis(data_key="y", name="y", type_="number"), width = "100%", height = 300, diff --git a/docs/library/graphing/charts/radialbarchart.md b/docs/library/graphing/charts/radialbarchart.md index 136105967d6..b9c6b93656e 100644 --- a/docs/library/graphing/charts/radialbarchart.md +++ b/docs/library/graphing/charts/radialbarchart.md @@ -1,6 +1,6 @@ --- components: - - rx.recharts.RadialBarChart + - rx.recharts.RadialBarChart --- # Radial Bar Chart @@ -8,6 +8,7 @@ components: ```python exec import reflex as rx ``` + ## Simple Example This example demonstrates how to use a `radial_bar_chart` with a `radial_bar`. The `radial_bar_chart` takes in `data` and then the `radial_bar` takes in a `data_key`. A radial bar chart is a circular visualization where data categories are represented by bars extending outward from a central point, with the length of each bar proportional to its value. @@ -41,7 +42,7 @@ def radial_bar_simple(): ## Advanced Example -The `start_angle` and `end_angle` define the circular arc over which the bars are distributed, while `inner_radius` and `outer_radius` determine the radial extent of the bars from the center. +The `start_angle` and `end_angle` define the circular arc over which the bars are distributed, while `inner_radius` and `outer_radius` determine the radial extent of the bars from the center. ```python demo graphing @@ -106,4 +107,4 @@ def radial_bar_advanced(): width="100%", height=300, ) -``` \ No newline at end of file +``` diff --git a/docs/library/graphing/general/brush.md b/docs/library/graphing/general/brush.md index 62fcc666087..9ac8ec6840a 100644 --- a/docs/library/graphing/general/brush.md +++ b/docs/library/graphing/general/brush.md @@ -1,6 +1,6 @@ --- components: - - rx.recharts.Brush + - rx.recharts.Brush --- # Brush @@ -8,6 +8,7 @@ components: ```python exec import reflex as rx ``` + ## Simple Example The brush component allows us to view charts that have a large number of data points. To view and analyze them efficiently, the brush provides a slider with two handles that helps the viewer to select some range of data points to be displayed. diff --git a/docs/library/graphing/general/cartesiangrid.md b/docs/library/graphing/general/cartesiangrid.md index 75e474a38bf..9e52a772f7a 100644 --- a/docs/library/graphing/general/cartesiangrid.md +++ b/docs/library/graphing/general/cartesiangrid.md @@ -1,7 +1,7 @@ --- components: - - rx.recharts.CartesianGrid - # - rx.recharts.CartesianAxis + - rx.recharts.CartesianGrid + # - rx.recharts.CartesianAxis --- ```python exec @@ -16,8 +16,8 @@ The Cartesian Grid is a component in Recharts that provides a visual reference f The `stroke_dasharray` prop in Recharts is used to create dashed or dotted lines for various chart elements like lines, axes, or grids. It's based on the SVG stroke-dasharray attribute. The `stroke_dasharray` prop accepts a comma-separated string of numbers that define a repeating pattern of dashes and gaps along the length of the stroke. -- `stroke_dasharray="5,5"`: creates a line with 5-pixel dashes and 5-pixel gaps -- `stroke_dasharray="10,5,5,5"`: creates a more complex pattern with 10-pixel dashes, 5-pixel gaps, 5-pixel dashes, and 5-pixel gaps +- `stroke_dasharray="5,5"`: creates a line with 5-pixel dashes and 5-pixel gaps +- `stroke_dasharray="10,5,5,5"`: creates a more complex pattern with 10-pixel dashes, 5-pixel gaps, 5-pixel dashes, and 5-pixel gaps Here's a simple example using it on a Line component: @@ -156,6 +156,7 @@ def cgrid_hidden(): The `horizontal_points` and `vertical_points` props allow you to specify custom grid lines on the chart, offering fine-grained control over the grid's appearance. These props accept arrays of numbers, where each number represents a pixel offset: + - For `horizontal_points`, the offset is measured from the top edge of the chart - For `vertical_points`, the offset is measured from the left edge of the chart diff --git a/docs/library/graphing/general/label.md b/docs/library/graphing/general/label.md index b6b73132986..0ea0caeb7ac 100644 --- a/docs/library/graphing/general/label.md +++ b/docs/library/graphing/general/label.md @@ -1,7 +1,7 @@ --- components: - - rx.recharts.Label - - rx.recharts.LabelList + - rx.recharts.Label + - rx.recharts.LabelList --- # Label @@ -161,13 +161,13 @@ def label_list(): data_key="uv", stroke="#8884d8", fill="#8884d8" - ), + ), rx.recharts.bar( rx.recharts.label_list(data_key="pv", position="top"), data_key="pv", stroke="#82ca9d", - fill="#82ca9d" - ), + fill="#82ca9d" + ), rx.recharts.x_axis( data_key="name" ), @@ -178,5 +178,3 @@ def label_list(): height = 300, ) ``` - - diff --git a/docs/library/graphing/general/legend.md b/docs/library/graphing/general/legend.md index edf1db283af..0f60447905c 100644 --- a/docs/library/graphing/general/legend.md +++ b/docs/library/graphing/general/legend.md @@ -1,6 +1,6 @@ --- components: - - rx.recharts.Legend + - rx.recharts.Legend --- # Legend @@ -65,7 +65,7 @@ def legend_simple(): data_key="uv", stroke="#8884d8", fill="#8884d8" - ), + ), rx.recharts.bar( data_key="amt", bar_size=20, @@ -75,8 +75,8 @@ def legend_simple(): data_key="pv", type_="monotone", stroke="#ff7300" - ), - rx.recharts.x_axis(data_key="name"), + ), + rx.recharts.x_axis(data_key="name"), rx.recharts.y_axis(), rx.recharts.legend(), data=data, @@ -141,18 +141,18 @@ def legend_props(): data_key="pv", type_="monotone", stroke=rx.color("accent", 7), - ), + ), rx.recharts.line( data_key="amt", type_="monotone", stroke=rx.color("green", 7), - ), + ), rx.recharts.line( data_key="uv", type_="monotone", stroke=rx.color("red", 7), - ), - rx.recharts.x_axis(data_key="name"), + ), + rx.recharts.x_axis(data_key="name"), rx.recharts.y_axis(), rx.recharts.legend( width=60, @@ -168,4 +168,4 @@ def legend_props(): height=300, ) -``` \ No newline at end of file +``` diff --git a/docs/library/graphing/general/reference.md b/docs/library/graphing/general/reference.md index 3721b3b8c2a..a470d282bc6 100644 --- a/docs/library/graphing/general/reference.md +++ b/docs/library/graphing/general/reference.md @@ -1,8 +1,8 @@ --- components: - - rx.recharts.ReferenceLine - - rx.recharts.ReferenceDot - - rx.recharts.ReferenceArea + - rx.recharts.ReferenceLine + - rx.recharts.ReferenceDot + - rx.recharts.ReferenceArea --- # Reference @@ -98,10 +98,10 @@ def reference(): fill="#8884d8", name="A"), rx.recharts.reference_area(x1= 150, x2=180, y1=150, y2=300, fill="#8884d8", fill_opacity=0.3), - rx.recharts.x_axis(data_key="x", name="x", type_="number"), + rx.recharts.x_axis(data_key="x", name="x", type_="number"), rx.recharts.y_axis(data_key="y", name="y", type_="number"), rx.recharts.graphing_tooltip(), - width = "100%", + width = "100%", height = 300, ) ``` @@ -222,8 +222,8 @@ data_3 = [ def reference_dot(): return rx.recharts.scatter_chart( rx.recharts.scatter( - data=data_3, - fill=rx.color("accent", 9), + data=data_3, + fill=rx.color("accent", 9), name="A", ), rx.recharts.x_axis( @@ -255,4 +255,4 @@ def reference_dot(): width = "100%", ) -``` \ No newline at end of file +``` diff --git a/docs/library/graphing/general/tooltip.md b/docs/library/graphing/general/tooltip.md index 33025831c96..c89037bd6f7 100644 --- a/docs/library/graphing/general/tooltip.md +++ b/docs/library/graphing/general/tooltip.md @@ -1,6 +1,6 @@ --- components: - - rx.recharts.GraphingTooltip + - rx.recharts.GraphingTooltip --- # Tooltip @@ -63,7 +63,7 @@ def tooltip_simple(): data_key="uv", stroke="#8884d8", fill="#8884d8" - ), + ), rx.recharts.bar( data_key="amt", bar_size=20, @@ -73,8 +73,8 @@ def tooltip_simple(): data_key="pv", type_="monotone", stroke="#ff7300" - ), - rx.recharts.x_axis(data_key="name"), + ), + rx.recharts.x_axis(data_key="name"), rx.recharts.y_axis(), rx.recharts.cartesian_grid(stroke_dasharray="3 3"), rx.recharts.graphing_tooltip(), @@ -153,13 +153,13 @@ def tooltip_custom_styling(): view_box = {"width" : 675, " height" : 300 }, allow_escape_view_box={"x": True, "y": False}, wrapper_style={ - "backgroundColor": rx.color("accent", 3), + "backgroundColor": rx.color("accent", 3), "borderRadius": "8px", "padding": "10px", }, content_style={ - "backgroundColor": rx.color("accent", 4), - "borderRadius": "4px", + "backgroundColor": rx.color("accent", 4), + "borderRadius": "4px", "padding": "8px", }, position = {"x" : 600, "y" : 0}, diff --git a/docs/library/graphing/other-charts/pyplot.md b/docs/library/graphing/other-charts/pyplot.md index 701366d2591..5e57071bf4d 100644 --- a/docs/library/graphing/other-charts/pyplot.md +++ b/docs/library/graphing/other-charts/pyplot.md @@ -110,12 +110,12 @@ class PyplotState(rx.State): def set_num_points(self, num_points: list[int | float]): self.num_points = int(num_points[0]) yield PyplotState.randomize() - + @rx.var def fig_light(self) -> Figure: fig = create_plot("light", self.plot_data, self.scale) return fig - + @rx.var def fig_dark(self) -> Figure: fig = create_plot("dark", self.plot_data, self.scale) diff --git a/docs/library/layout/card.md b/docs/library/layout/card.md index 7f82ade7da7..78d70efaad2 100644 --- a/docs/library/layout/card.md +++ b/docs/library/layout/card.md @@ -3,7 +3,7 @@ components: - rx.card Card: | - lambda **props: rx.card("Basic Card ", **props) + lambda **props: rx.card("Basic Card ", **props) --- ```python exec diff --git a/docs/library/layout/center.md b/docs/library/layout/center.md index b52702f20cb..3d422553680 100644 --- a/docs/library/layout/center.md +++ b/docs/library/layout/center.md @@ -1,6 +1,6 @@ --- components: - - rx.center + - rx.center --- ```python exec diff --git a/docs/library/layout/flex.md b/docs/library/layout/flex.md index 7abb0546199..181cc5317ae 100644 --- a/docs/library/layout/flex.md +++ b/docs/library/layout/flex.md @@ -10,8 +10,8 @@ import reflex as rx # Flex The Flex component is used to make [flexbox layouts](https://developer.mozilla.org/en-US/docs/Learn/CSS/CSS_layout/Flexbox). -It makes it simple to arrange child components in horizontal or vertical directions, apply wrapping, -justify and align content, and automatically size components based on available space, making it +It makes it simple to arrange child components in horizontal or vertical directions, apply wrapping, +justify and align content, and automatically size components based on available space, making it ideal for building responsive layouts. By default, children are arranged horizontally (`direction="row"`) without wrapping. @@ -65,8 +65,8 @@ rx.flex( Two props control how children are aligned within the Flex component: -* `align` controls how children are aligned along the cross axis (vertical for `row` and horizontal for `column`). -* `justify` controls how children are aligned along the main axis (horizontal for `row` and vertical for `column`). +- `align` controls how children are aligned along the cross axis (vertical for `row` and horizontal for `column`). +- `justify` controls how children are aligned along the main axis (horizontal for `row` and vertical for `column`). The following example visually demonstrates the effect of these props with different `wrap` and `direction` values. @@ -166,7 +166,7 @@ When `flex_shrink="0"`, the box will not shrink to less than the `flex_basis`. These props are used when creating flexible responsive layouts. Move the slider below and see how adjusting the width of the flex container -affects the computed sizes of the flex items based on the props that are set. +affects the computed sizes of the flex items based on the props that are set. ```python demo exec class FlexGrowShrinkState(rx.State): diff --git a/docs/library/layout/fragment.md b/docs/library/layout/fragment.md index 66b3e301fce..99f3ff2fc19 100644 --- a/docs/library/layout/fragment.md +++ b/docs/library/layout/fragment.md @@ -1,6 +1,6 @@ --- components: - - rx.fragment + - rx.fragment --- # Fragment @@ -16,12 +16,11 @@ Refer to the React docs at [React/Fragment]({constants.FRAGMENT_COMPONENT_INFO_U ```python demo rx.fragment( - rx.text("Component1"), + rx.text("Component1"), rx.text("Component2") ) ``` - ```md video https://youtube.com/embed/ITOZkzjtjUA?start=3196&end=3340 # Video: Fragment -``` \ No newline at end of file +``` diff --git a/docs/library/layout/inset.md b/docs/library/layout/inset.md index 09df465f714..d70aa9fea74 100644 --- a/docs/library/layout/inset.md +++ b/docs/library/layout/inset.md @@ -1,16 +1,15 @@ --- components: - - rx.inset + - rx.inset Inset: | - lambda **props: rx.card( - rx.inset( - rx.image(src=f"{REFLEX_ASSETS_CDN}other/reflex_banner.png", height="auto"), - **props, - ), - width="500px", - ) - + lambda **props: rx.card( + rx.inset( + rx.image(src=f"{REFLEX_ASSETS_CDN}other/reflex_banner.png", height="auto"), + **props, + ), + width="500px", + ) --- ```python exec diff --git a/docs/library/layout/separator.md b/docs/library/layout/separator.md index 816382d233e..e1d4e443399 100644 --- a/docs/library/layout/separator.md +++ b/docs/library/layout/separator.md @@ -1,9 +1,8 @@ --- components: - - rx.separator + - rx.separator Separator: | - lambda **props: rx.separator(**props) - + lambda **props: rx.separator(**props) --- ```python exec diff --git a/docs/library/layout/spacer.md b/docs/library/layout/spacer.md index 730076159ce..0d9d1b01723 100644 --- a/docs/library/layout/spacer.md +++ b/docs/library/layout/spacer.md @@ -1,6 +1,6 @@ --- components: - - rx.spacer + - rx.spacer --- ```python exec diff --git a/docs/library/layout/stack.md b/docs/library/layout/stack.md index d00160bdf4b..04fdde0b292 100644 --- a/docs/library/layout/stack.md +++ b/docs/library/layout/stack.md @@ -1,15 +1,15 @@ --- components: - - rx.stack - - rx.hstack - - rx.vstack + - rx.stack + - rx.hstack + - rx.vstack Stack: | - lambda **props: rx.stack( - rx.card("Card 1", size="2"), rx.card("Card 2", size="2"), rx.card("Card 3", size="2"), - width="100%", - height="20vh", - **props, - ) + lambda **props: rx.stack( + rx.card("Card 1", size="2"), rx.card("Card 2", size="2"), rx.card("Card 3", size="2"), + width="100%", + height="20vh", + **props, + ) --- ```python exec diff --git a/docs/library/media/audio.md b/docs/library/media/audio.md index e842fe162ed..d3f8e8ebeaf 100644 --- a/docs/library/media/audio.md +++ b/docs/library/media/audio.md @@ -1,6 +1,6 @@ --- components: - - rx.audio + - rx.audio --- # Audio @@ -24,5 +24,6 @@ If we had a local file in the `assets` folder named `test.mp3` we could set `src ```md alert info # How to let your user upload an audio file + To let a user upload an audio file to your app check out the [upload docs]({library.forms.upload.path}). ``` diff --git a/docs/library/media/image.md b/docs/library/media/image.md index 55d99f703c5..5640f1c230a 100644 --- a/docs/library/media/image.md +++ b/docs/library/media/image.md @@ -1,6 +1,6 @@ --- components: - - rx.image + - rx.image --- ```python exec @@ -54,10 +54,12 @@ def image_pil_example(): ```md alert info # rx.image only accepts URLs and Pillow Images + A cv2 image must be converted to a PIL image to be passed directly to `rx.image` as a State variable, or saved to the `assets` folder and then passed to the `rx.image` component. ``` ```md alert info # How to let your user upload an image + To let a user upload an image to your app check out the [upload docs]({library.forms.upload.path}). ``` diff --git a/docs/library/media/video.md b/docs/library/media/video.md index 328e44f8133..668e00ba1d9 100644 --- a/docs/library/media/video.md +++ b/docs/library/media/video.md @@ -1,6 +1,6 @@ --- components: - - rx.video + - rx.video --- # Video @@ -22,8 +22,8 @@ rx.video( If we had a local file in the `assets` folder named `test.mp4` we could set `url="/test.mp4"` to view the video. - ```md alert info # How to let your user upload a video + To let a user upload a video to your app check out the [upload docs]({library.forms.upload.path}). ``` diff --git a/docs/library/other/html.md b/docs/library/other/html.md index 3fc169fb5cb..f74ec858c9d 100644 --- a/docs/library/other/html.md +++ b/docs/library/other/html.md @@ -1,119 +1,119 @@ --- components: - - rx.el.a - - rx.el.abbr - - rx.el.address - - rx.el.area - - rx.el.article - - rx.el.aside - - rx.el.audio - - rx.el.b - - rx.el.bdi - - rx.el.bdo - - rx.el.blockquote - - rx.el.body - - rx.el.br - - rx.el.button - - rx.el.canvas - - rx.el.caption - - rx.el.cite - - rx.el.code - - rx.el.col - - rx.el.colgroup - - rx.el.data - - rx.el.dd - - rx.el.Del - - rx.el.details - - rx.el.dfn - - rx.el.dialog - - rx.el.div - - rx.el.dl - - rx.el.dt - - rx.el.em - - rx.el.embed - - rx.el.fieldset - - rx.el.figcaption - - rx.el.footer - - rx.el.form - - rx.el.h1 - - rx.el.h2 - - rx.el.h3 - - rx.el.h4 - - rx.el.h5 - - rx.el.h6 - - rx.el.head - - rx.el.header - - rx.el.hr - - rx.el.html - - rx.el.i - - rx.el.iframe - - rx.el.img - - rx.el.input - - rx.el.ins - - rx.el.kbd - - rx.el.label - - rx.el.legend - - rx.el.li - - rx.el.link - - rx.el.main - - rx.el.mark - - rx.el.math - - rx.el.meta - - rx.el.meter - - rx.el.nav - - rx.el.noscript - - rx.el.object - - rx.el.ol - - rx.el.optgroup - - rx.el.option - - rx.el.output - - rx.el.p - - rx.el.picture - - rx.el.portal - - rx.el.pre - - rx.el.progress - - rx.el.q - - rx.el.rp - - rx.el.rt - - rx.el.ruby - - rx.el.s - - rx.el.samp - - rx.el.script - - rx.el.section - - rx.el.select - - rx.el.small - - rx.el.source - - rx.el.span - - rx.el.strong - - rx.el.sub - - rx.el.sup - - rx.el.svg.circle - - rx.el.svg.defs - - rx.el.svg.linear_gradient - - rx.el.svg.polygon - - rx.el.svg.path - - rx.el.svg.rect - - rx.el.svg.stop - - rx.el.table - - rx.el.tbody - - rx.el.td - - rx.el.template - - rx.el.textarea - - rx.el.tfoot - - rx.el.th - - rx.el.thead - - rx.el.time - - rx.el.title - - rx.el.tr - - rx.el.track - - rx.el.u - - rx.el.ul - - rx.el.video - - rx.el.wbr + - rx.el.a + - rx.el.abbr + - rx.el.address + - rx.el.area + - rx.el.article + - rx.el.aside + - rx.el.audio + - rx.el.b + - rx.el.bdi + - rx.el.bdo + - rx.el.blockquote + - rx.el.body + - rx.el.br + - rx.el.button + - rx.el.canvas + - rx.el.caption + - rx.el.cite + - rx.el.code + - rx.el.col + - rx.el.colgroup + - rx.el.data + - rx.el.dd + - rx.el.Del + - rx.el.details + - rx.el.dfn + - rx.el.dialog + - rx.el.div + - rx.el.dl + - rx.el.dt + - rx.el.em + - rx.el.embed + - rx.el.fieldset + - rx.el.figcaption + - rx.el.footer + - rx.el.form + - rx.el.h1 + - rx.el.h2 + - rx.el.h3 + - rx.el.h4 + - rx.el.h5 + - rx.el.h6 + - rx.el.head + - rx.el.header + - rx.el.hr + - rx.el.html + - rx.el.i + - rx.el.iframe + - rx.el.img + - rx.el.input + - rx.el.ins + - rx.el.kbd + - rx.el.label + - rx.el.legend + - rx.el.li + - rx.el.link + - rx.el.main + - rx.el.mark + - rx.el.math + - rx.el.meta + - rx.el.meter + - rx.el.nav + - rx.el.noscript + - rx.el.object + - rx.el.ol + - rx.el.optgroup + - rx.el.option + - rx.el.output + - rx.el.p + - rx.el.picture + - rx.el.portal + - rx.el.pre + - rx.el.progress + - rx.el.q + - rx.el.rp + - rx.el.rt + - rx.el.ruby + - rx.el.s + - rx.el.samp + - rx.el.script + - rx.el.section + - rx.el.select + - rx.el.small + - rx.el.source + - rx.el.span + - rx.el.strong + - rx.el.sub + - rx.el.sup + - rx.el.svg.circle + - rx.el.svg.defs + - rx.el.svg.linear_gradient + - rx.el.svg.polygon + - rx.el.svg.path + - rx.el.svg.rect + - rx.el.svg.stop + - rx.el.table + - rx.el.tbody + - rx.el.td + - rx.el.template + - rx.el.textarea + - rx.el.tfoot + - rx.el.th + - rx.el.thead + - rx.el.time + - rx.el.title + - rx.el.tr + - rx.el.track + - rx.el.u + - rx.el.ul + - rx.el.video + - rx.el.wbr --- # HTML Reflex also provides a set of HTML elements that can be used to create web pages. These elements are the same as the HTML elements that are used in web development. These elements come unstyled bhy default. You can style them using style props or tailwindcss classes. -The following is a list of the HTML elements that are available in Reflex: \ No newline at end of file +The following is a list of the HTML elements that are available in Reflex: diff --git a/docs/library/other/html_embed.md b/docs/library/other/html_embed.md index dfa79f90fde..f3ff0916823 100644 --- a/docs/library/other/html_embed.md +++ b/docs/library/other/html_embed.md @@ -1,6 +1,6 @@ --- components: - - rx.html + - rx.html --- ```python exec @@ -27,7 +27,8 @@ rx.vstack( ```md alert # Missing Styles? -Reflex uses Radix-UI and tailwind for styling, both of which reset default styles for headings. + +Reflex uses Radix-UI and tailwind for styling, both of which reset default styles for headings. If you are using the html component and want pretty default styles, consider setting `class_name='prose'`, adding `@tailwindcss/typography` package to `frontend_packages` and enabling it via `tailwind` config in `rxconfig.py`. See the [Tailwind docs]({styling.overview.path}) for an example of adding this plugin. ``` diff --git a/docs/library/other/memo.md b/docs/library/other/memo.md index 2fe0798d174..83ee4306bfd 100644 --- a/docs/library/other/memo.md +++ b/docs/library/other/memo.md @@ -22,7 +22,7 @@ When you wrap a component function with `@rx.memo`, the component will only re-r # Define a state class to track count class DemoState(rx.State): count: int = 0 - + @rx.event def increment(self): self.count += 1 @@ -61,7 +61,7 @@ You can also use `rx.memo` with components that have event handlers: # Define a state class to track clicks class ButtonState(rx.State): clicks: int = 0 - + @rx.event def increment(self): self.clicks += 1 @@ -76,7 +76,7 @@ def index(): return rx.vstack( rx.text("Clicks: 0"), # This will update with state.clicks my_button( - text="Click me", + text="Click me", on_click=ButtonState.increment ), spacing="4", @@ -92,11 +92,11 @@ When used with state variables, memoized components will only re-render when the class AppState(rx.State): name: str = "World" count: int = 0 - + @rx.event def increment(self): self.count += 1 - + @rx.event def set_name(self, name: str): self.name = name @@ -113,7 +113,7 @@ def index(): rx.text("Count: 0"), # Will display the count rx.button("Increment Count", on_click=AppState.increment), rx.input( - placeholder="Enter your name", + placeholder="Enter your name", on_change=AppState.set_name, value="World", # Will be bound to AppState.name ), @@ -129,7 +129,7 @@ You can also pass arguments to event handlers in memoized components: # Define a state class to track messages class MessageState(rx.State): message: str = "" - + @rx.event def set_message(self, text: str): self.message = text @@ -156,10 +156,12 @@ def index(): ## Performance Considerations Use `rx.memo` for: + - Components with expensive rendering logic - Components that render the same result given the same props - Components that re-render too often due to parent component updates Avoid using `rx.memo` for: + - Simple components where the memoization overhead might exceed the performance gain - Components that almost always receive different props on re-render diff --git a/docs/library/other/script.md b/docs/library/other/script.md index 8d9356e7c41..8065ad34c67 100644 --- a/docs/library/other/script.md +++ b/docs/library/other/script.md @@ -1,6 +1,6 @@ --- components: - - rx.script + - rx.script --- ```python exec @@ -34,5 +34,9 @@ rx.script(src="//gc.zgo.at/count.js", custom_attrs=\{"data-goatcounter": "https: This code renders to something like the following to enable stat counting with a third party service. ```jsx - + ``` diff --git a/docs/library/other/skeleton.md b/docs/library/other/skeleton.md index 46c6a08f6e7..1f6c1927f0f 100644 --- a/docs/library/other/skeleton.md +++ b/docs/library/other/skeleton.md @@ -1,7 +1,7 @@ --- description: Skeleton, a loading placeholder component for content that is not yet available. components: - - rx.skeleton + - rx.skeleton --- ```python exec diff --git a/docs/library/other/theme.md b/docs/library/other/theme.md index 3ba92af8783..3683b2cc061 100644 --- a/docs/library/other/theme.md +++ b/docs/library/other/theme.md @@ -1,31 +1,31 @@ --- - components: - - rx.theme - - rx.theme_panel +components: + - rx.theme + - rx.theme_panel --- # Theme - The `Theme` component is used to change the theme of the application. The `Theme` can be set directly in the rx.App. +The `Theme` component is used to change the theme of the application. The `Theme` can be set directly in the rx.App. - ```python - app = rx.App( - theme=rx.theme( - appearance="light", has_background=True, radius="large", accent_color="teal" - ) - ) - ``` +```python +app = rx.App( + theme=rx.theme( + appearance="light", has_background=True, radius="large", accent_color="teal" + ) +) +``` - # Theme Panel +# Theme Panel - The `ThemePanel` component is a container for the `Theme` component. It provides a way to change the theme of the application. +The `ThemePanel` component is a container for the `Theme` component. It provides a way to change the theme of the application. - ```python - rx.theme_panel() - ``` +```python +rx.theme_panel() +``` - The theme panel is closed by default. You can set it open `default_open=True`. +The theme panel is closed by default. You can set it open `default_open=True`. - ```python - rx.theme_panel(default_open=True) - ``` +```python +rx.theme_panel(default_open=True) +``` diff --git a/docs/library/overlay/alert_dialog.md b/docs/library/overlay/alert_dialog.md index 6316a6ee743..7b4abb8b6fd 100644 --- a/docs/library/overlay/alert_dialog.md +++ b/docs/library/overlay/alert_dialog.md @@ -278,7 +278,6 @@ def alert_dialog2(): ) ``` - ## Form Submission to a Database from an Alert Dialog This example adds new users to a database from an alert dialog using a form. @@ -287,12 +286,12 @@ This example adds new users to a database from an alert dialog using a form. 2. The `add_user_to_db` method adds a new user to the database, checking for existing emails. 3. On form submission, it calls the `add_user_to_db` method. 4. The UI component has: + - A button to open an alert dialog - An alert dialog containing a form to add a new user - Input fields for name and email - Submit and Cancel buttons - ```python demo exec class User1(rx.Model, table=True): """The user model.""" @@ -300,7 +299,7 @@ class User1(rx.Model, table=True): email: str class State(rx.State): - + current_user: User1 = User1() @rx.event @@ -314,7 +313,7 @@ class State(rx.State): # return rx.window_alert("User with this email already exists") # session.add(User1(**self.current_user)) # session.commit() - + return rx.toast.info(f"User {self.current_user['name']} has been added.", position="bottom-right") @@ -357,7 +356,7 @@ def index() -> rx.Component: ), direction="column", spacing="4", - ), + ), on_submit=State.add_user_to_db, reset_on_submit=False, ), diff --git a/docs/library/tables-and-data-grids/data_editor.md b/docs/library/tables-and-data-grids/data_editor.md index 5c98c858c3f..c64eac81b1b 100644 --- a/docs/library/tables-and-data-grids/data_editor.md +++ b/docs/library/tables-and-data-grids/data_editor.md @@ -1,6 +1,6 @@ --- components: - - rx.data_editor + - rx.data_editor --- # Data Editor @@ -20,7 +20,7 @@ columns: list[dict[str, str]] = [ { "title":"Value", "type": "int", - }, + }, { "title":"Activated", "type": "bool", @@ -73,7 +73,7 @@ columns: list[dict[str, str]] = [ { "title":"Value", "type": "int", - }, + }, { "title":"Activated", "type": "bool", @@ -154,20 +154,20 @@ class DataEditorState_HP(rx.State): data = [ ["1", "Harry James Potter", "31 July 1980", True, "Gryffindor", "11' Holly phoenix feather", "Stag", "Half-blood"], ["2", "Ronald Bilius Weasley", "1 March 1980", True,"Gryffindor", "12' Ash unicorn tail hair", "Jack Russell terrier", "Pure-blood"], - ["3", "Hermione Jean Granger", "19 September, 1979", True, "Gryffindor", "10¾' vine wood dragon heartstring", "Otter", "Muggle-born"], - ["4", "Albus Percival Wulfric Brian Dumbledore", "Late August 1881", True, "Gryffindor", "15' Elder Thestral tail hair core", "Phoenix", "Half-blood"], - ["5", "Rubeus Hagrid", "6 December 1928", False, "Gryffindor", "16' Oak unknown core", "None", "Part-Human (Half-giant)"], - ["6", "Fred Weasley", "1 April, 1978", True, "Gryffindor", "Unknown", "Unknown", "Pure-blood"], + ["3", "Hermione Jean Granger", "19 September, 1979", True, "Gryffindor", "10¾' vine wood dragon heartstring", "Otter", "Muggle-born"], + ["4", "Albus Percival Wulfric Brian Dumbledore", "Late August 1881", True, "Gryffindor", "15' Elder Thestral tail hair core", "Phoenix", "Half-blood"], + ["5", "Rubeus Hagrid", "6 December 1928", False, "Gryffindor", "16' Oak unknown core", "None", "Part-Human (Half-giant)"], + ["6", "Fred Weasley", "1 April, 1978", True, "Gryffindor", "Unknown", "Unknown", "Pure-blood"], ] def click_cell(self, pos): col, row = pos yield self.get_clicked_data(pos) - + def get_clicked_data(self, pos) -> str: self.clicked_data = f"Cell clicked: {pos}" - + ``` Here we define a State, as shown below, that allows us to print the location of the cell as a heading when we click on it, using the `on_cell_clicked` `event trigger`. Check out all the other `event triggers` that you can use with datatable at the bottom of this page. We also define a `group` with a label `Data`. This groups all the columns with this `group` label under a larger group `Data` as seen in the table below. @@ -186,12 +186,12 @@ rx.data_editor( ```python class DataEditorState_HP(rx.State): - + clicked_data: str = "Cell clicked: " cols: list[Any] = [ { - "title": "Title", + "title": "Title", "type": "str" }, { @@ -239,17 +239,17 @@ class DataEditorState_HP(rx.State): data = [ ["1", "Harry James Potter", "31 July 1980", True, "Gryffindor", "11' Holly phoenix feather", "Stag", "Half-blood"], ["2", "Ronald Bilius Weasley", "1 March 1980", True,"Gryffindor", "12' Ash unicorn tail hair", "Jack Russell terrier", "Pure-blood"], - ["3", "Hermione Jean Granger", "19 September, 1979", True, "Gryffindor", "10¾' vine wood dragon heartstring", "Otter", "Muggle-born"], - ["4", "Albus Percival Wulfric Brian Dumbledore", "Late August 1881", True, "Gryffindor", "15' Elder Thestral tail hair core", "Phoenix", "Half-blood"], - ["5", "Rubeus Hagrid", "6 December 1928", False, "Gryffindor", "16' Oak unknown core", "None", "Part-Human (Half-giant)"], - ["6", "Fred Weasley", "1 April, 1978", True, "Gryffindor", "Unknown", "Unknown", "Pure-blood"], + ["3", "Hermione Jean Granger", "19 September, 1979", True, "Gryffindor", "10¾' vine wood dragon heartstring", "Otter", "Muggle-born"], + ["4", "Albus Percival Wulfric Brian Dumbledore", "Late August 1881", True, "Gryffindor", "15' Elder Thestral tail hair core", "Phoenix", "Half-blood"], + ["5", "Rubeus Hagrid", "6 December 1928", False, "Gryffindor", "16' Oak unknown core", "None", "Part-Human (Half-giant)"], + ["6", "Fred Weasley", "1 April, 1978", True, "Gryffindor", "Unknown", "Unknown", "Pure-blood"], ] def click_cell(self, pos): col, row = pos yield self.get_clicked_data(pos) - + def get_clicked_data(self, pos) -> str: self.clicked_data = f"Cell clicked: \{pos}" diff --git a/docs/library/tables-and-data-grids/data_table.md b/docs/library/tables-and-data-grids/data_table.md index 539ef559259..d8ef4c3f94c 100644 --- a/docs/library/tables-and-data-grids/data_table.md +++ b/docs/library/tables-and-data-grids/data_table.md @@ -1,6 +1,6 @@ --- components: - - rx.data_table + - rx.data_table --- ```python exec @@ -19,7 +19,6 @@ We will also add a search, pagination, sorting to the data_table to make it more If you want to [add, edit or remove data]({library.tables_and_data_grids.table.path}) in your app or deal with anything but static data then the [`rx.table`]({library.tables_and_data_grids.table.path}) might be a better fit for your use case. - ```python demo box rx.data_table( data=[ @@ -50,7 +49,7 @@ rx.data_table( pagination= True, search= True, sort= True, -) +) ``` 📊 **Dataset source:** [nba.csv](https://media.geeksforgeeks.org/wp-content/uploads/nba.csv) @@ -64,10 +63,10 @@ class State(rx.State): ["Christiano", "Ronaldo", "Al-Nasir"] ] columns: List[str] = ["First Name", "Last Name"] - -def index(): + +def index(): return rx.data_table( data=State.data, columns=State.columns, - ) + ) ``` diff --git a/docs/library/tables-and-data-grids/table.md b/docs/library/tables-and-data-grids/table.md index 8e2509100af..140ae333674 100644 --- a/docs/library/tables-and-data-grids/table.md +++ b/docs/library/tables-and-data-grids/table.md @@ -230,9 +230,11 @@ class Person: email: str group: str ``` + ## Sorting and Filtering (Searching) In this example we show two approaches to sort and filter data: + 1. Using SQL-like operations for database-backed models (simulated) 2. Using Python operations for in-memory data @@ -242,7 +244,6 @@ Both approaches use the same UI components: `rx.select` for sorting and `rx.inpu For database-backed models, we typically use SQL queries with `select`, `where`, and `order_by`. In this example, we'll simulate this behavior with mock data. - ```python demo exec # Simulating database operations with mock data class DatabaseTableState(rx.State): @@ -755,7 +756,6 @@ def loading_data_table_example2(): ``` - ## Pagination Pagination is an important part of database management, especially when working with large datasets. It helps in enabling efficient data retrieval by breaking down results into manageable loads. @@ -959,6 +959,7 @@ def loading_data_table_example3(): ) ``` + ## More advanced examples The real power of the `rx.table` comes where you are able to visualise, add and edit data live in your app. Check out these apps and code to see how this is done: app: https://customer-data-app.reflex.run code: https://github.com/reflex-dev/templates/tree/main/customer_data_app and code: https://github.com/reflex-dev/templates/tree/main/sales. diff --git a/docs/library/typography/blockquote.md b/docs/library/typography/blockquote.md index 5070c1d0029..33b79ac34cc 100644 --- a/docs/library/typography/blockquote.md +++ b/docs/library/typography/blockquote.md @@ -1,6 +1,6 @@ --- components: - - rx.blockquote + - rx.blockquote --- ```python exec diff --git a/docs/library/typography/code.md b/docs/library/typography/code.md index e823a2cdf4a..8db6cf613b2 100644 --- a/docs/library/typography/code.md +++ b/docs/library/typography/code.md @@ -1,6 +1,6 @@ --- components: - - rx.code + - rx.code --- ```python exec diff --git a/docs/library/typography/em.md b/docs/library/typography/em.md index 2fd67aeb76e..a977335333c 100644 --- a/docs/library/typography/em.md +++ b/docs/library/typography/em.md @@ -1,6 +1,6 @@ --- components: - - rx.text.em + - rx.text.em --- ```python exec diff --git a/docs/library/typography/heading.md b/docs/library/typography/heading.md index 44cd6c3f8d0..7d0a3737328 100644 --- a/docs/library/typography/heading.md +++ b/docs/library/typography/heading.md @@ -1,6 +1,6 @@ --- components: - - rx.heading + - rx.heading --- ```python exec @@ -24,7 +24,7 @@ rx.flex( rx.heading("Level 3", as_="h3"), direction="column", spacing="3", -) +) ``` ## Size @@ -107,14 +107,14 @@ rx.flex( rx.box( rx.heading("Without trim", margin_bottom="4px", size="3",), rx.text("The goal of typography is to relate font size, line height, and line width in a proportional way that maximizes beauty and makes reading easier and more pleasant."), - style={"background": "var(--gray-a2)", + style={"background": "var(--gray-a2)", "border": "1px dashed var(--gray-a7)",}, padding="16px", ), rx.box( rx.heading("With trim", margin_bottom="4px", size="3", trim="start"), rx.text("The goal of typography is to relate font size, line height, and line width in a proportional way that maximizes beauty and makes reading easier and more pleasant."), - style={"background": "var(--gray-a2)", + style={"background": "var(--gray-a2)", "border": "1px dashed var(--gray-a7)",}, padding="16px", ), diff --git a/docs/library/typography/kbd.md b/docs/library/typography/kbd.md index c37771268ec..e20078b75dd 100644 --- a/docs/library/typography/kbd.md +++ b/docs/library/typography/kbd.md @@ -1,6 +1,6 @@ --- components: - - rx.text.kbd + - rx.text.kbd --- ```python exec diff --git a/docs/library/typography/link.md b/docs/library/typography/link.md index a05087f8447..8f4b0a73d90 100644 --- a/docs/library/typography/link.md +++ b/docs/library/typography/link.md @@ -1,6 +1,6 @@ --- components: - - rx.link + - rx.link --- ```python exec @@ -22,7 +22,6 @@ You can also provide local links to other pages in your project without writing rx.link("Example", href="/docs/library",) ``` - The `link` component can be used to wrap other components to make them link to other pages. ```python demo @@ -42,7 +41,8 @@ rx.link("Example", href="/docs/library/typography/link#example") ``` ```md alert info -# Redirecting the user using State +# Redirecting the user using State + It is also possible to redirect the user to a new path within the application, using `rx.redirect()`. Check out the docs [here]({api_reference.special_events.path}). ``` diff --git a/docs/library/typography/markdown.md b/docs/library/typography/markdown.md index b308c15f03a..19f19a07725 100644 --- a/docs/library/typography/markdown.md +++ b/docs/library/typography/markdown.md @@ -1,6 +1,6 @@ --- components: - - rx.markdown + - rx.markdown --- ```python exec @@ -69,6 +69,7 @@ rx.markdown( Plugins can be used to extend the functionality of the markdown renderer. By default Reflex uses the following plugins: + - `remark-gfm` for Github Flavored Markdown support (`use_gfm`). - `remark-math` and `rehype-katex` for math equation support (`use_math`, `use_katex`). - `rehype-unwrap-images` to remove paragraph tags around images (`use_unwrap_images`). diff --git a/docs/library/typography/quote.md b/docs/library/typography/quote.md index 15290f5fc85..aefedc8abc5 100644 --- a/docs/library/typography/quote.md +++ b/docs/library/typography/quote.md @@ -1,6 +1,6 @@ --- components: - - rx.text.quote + - rx.text.quote --- ```python exec diff --git a/docs/library/typography/strong.md b/docs/library/typography/strong.md index 85731243ea6..7ebfc9132df 100644 --- a/docs/library/typography/strong.md +++ b/docs/library/typography/strong.md @@ -1,6 +1,6 @@ --- components: - - rx.text.strong + - rx.text.strong --- ```python exec diff --git a/docs/library/typography/text.md b/docs/library/typography/text.md index bede02dd0f4..73f9e42df98 100644 --- a/docs/library/typography/text.md +++ b/docs/library/typography/text.md @@ -1,8 +1,7 @@ --- components: - - rx.text - - rx.text.em - + - rx.text + - rx.text.em --- ```python exec @@ -27,7 +26,7 @@ rx.flex( rx.text("This is a ", rx.text.strong("span"), " element.", as_="span"), direction="column", spacing="3", -) +) ``` ## Size @@ -112,14 +111,14 @@ rx.flex( rx.box( rx.heading("Without trim", margin_bottom="4px", size="3",), rx.text("The goal of typography is to relate font size, line height, and line width in a proportional way that maximizes beauty and makes reading easier and more pleasant."), - style={"background": "var(--gray-a2)", + style={"background": "var(--gray-a2)", "border": "1px dashed var(--gray-a7)",}, padding="16px", ), rx.box( rx.heading("With trim", margin_bottom="4px", size="3", trim="start"), rx.text("The goal of typography is to relate font size, line height, and line width in a proportional way that maximizes beauty and makes reading easier and more pleasant."), - style={"background": "var(--gray-a2)", + style={"background": "var(--gray-a2)", "border": "1px dashed var(--gray-a7)",}, padding="16px", ), @@ -176,6 +175,7 @@ rx.text( ``` ## Preformmatting + By Default, the browser renders multiple white spaces into one. To preserve whitespace, use the `white_space = "pre"` css prop. ```python demo diff --git a/docs/pages/overview.md b/docs/pages/overview.md index 2a28e8b4ddc..ac19bdd74d2 100644 --- a/docs/pages/overview.md +++ b/docs/pages/overview.md @@ -182,7 +182,7 @@ meta = [ ] app = rx.App() -""" +""" ) @@ -217,7 +217,6 @@ for [dynamic pages]({docs.pages.dynamic_routing.path}) this will contain the slu To get the actual URL displayed in the browser, use `router.page.raw_path`. This will contain all query parameters and dynamic path segments. - In the above example, `current_page_route` will contain the route pattern (e.g., `/posts/[id]`), while `current_page_url` will contain the actual URL (e.g., `/posts/123`). diff --git a/docs/recipes/auth/signup_form.md b/docs/recipes/auth/signup_form.md index c45ae731a7e..90f8fb3c0f0 100644 --- a/docs/recipes/auth/signup_form.md +++ b/docs/recipes/auth/signup_form.md @@ -6,6 +6,7 @@ from pcweb.constants import REFLEX_ASSETS_CDN # Sign up Form The sign up form is a common component in web applications. It allows users to create an account and access the application's features. This page provides a few examples of sign up forms that you can use in your application. + ## Default ```python demo exec toggle @@ -113,7 +114,6 @@ def signup_default_icons() -> rx.Component: ## Third-party auth - ```python demo exec toggle def signup_single_thirdparty() -> rx.Component: return rx.card( diff --git a/docs/recipes/content/forms.md b/docs/recipes/content/forms.md index 57b6162aaf6..0b4a076af0f 100644 --- a/docs/recipes/content/forms.md +++ b/docs/recipes/content/forms.md @@ -170,4 +170,4 @@ def contact_form() -> rx.Component: ), size="3", ) -``` \ No newline at end of file +``` diff --git a/docs/recipes/content/multi_column_row.md b/docs/recipes/content/multi_column_row.md index 71d6041df3d..891da8cc606 100644 --- a/docs/recipes/content/multi_column_row.md +++ b/docs/recipes/content/multi_column_row.md @@ -61,4 +61,4 @@ rx.flex( height="600px", width="100%", ) -``` \ No newline at end of file +``` diff --git a/docs/recipes/content/stats.md b/docs/recipes/content/stats.md index 676e2e9a132..b4d038835fa 100644 --- a/docs/recipes/content/stats.md +++ b/docs/recipes/content/stats.md @@ -104,4 +104,4 @@ def stats_2(stat_name: str = "Orders", value: int = 6500, prev_value: int = 1200 width="100%", max_width="21rem", ) -``` \ No newline at end of file +``` diff --git a/docs/recipes/content/top_banner.md b/docs/recipes/content/top_banner.md index 8dd0797048d..b4e2c0b2855 100644 --- a/docs/recipes/content/top_banner.md +++ b/docs/recipes/content/top_banner.md @@ -268,4 +268,4 @@ class TopBannerNewsletter(rx.ComponentState): ) top_banner_newsletter = TopBannerNewsletter.create -``` \ No newline at end of file +``` diff --git a/docs/recipes/layout/navbar.md b/docs/recipes/layout/navbar.md index 84a7f29e3f8..5ca3acde41a 100644 --- a/docs/recipes/layout/navbar.md +++ b/docs/recipes/layout/navbar.md @@ -11,7 +11,6 @@ It typically provides links or buttons to the main sections of a website or appl Navigation bars are useful for web apps because they provide a consistent and intuitive way for users to navigate through the app. Having a clear and consistent navigation structure can greatly improve the user experience by making it easy for users to find the information they need and access the different features of the app. - ```md video https://youtube.com/embed/ITOZkzjtjUA?start=2365&end=2627 # Video: Example of Using the Navbar Recipe ``` @@ -70,7 +69,6 @@ def navbar() -> rx.Component: ) ``` - ## Dropdown ```python demo exec toggle diff --git a/docs/recipes/others/checkboxes.md b/docs/recipes/others/checkboxes.md index 5d6392fa2bd..308211f95a5 100644 --- a/docs/recipes/others/checkboxes.md +++ b/docs/recipes/others/checkboxes.md @@ -18,7 +18,7 @@ Additionally, the limit that prevent the user from checking more boxes than allo ```python class CBoxeState(rx.State): - + choices: dict[str, bool] = \{k: False for k in ["Choice A", "Choice B", "Choice C"]} _check_limit = 2 @@ -52,7 +52,7 @@ def render_checkboxes(values, limit, handler): def index() -> rx.Component: - + return rx.center( rx.vstack( rx.text("Make your choices (2 max):"), diff --git a/docs/recipes/others/pricing_cards.md b/docs/recipes/others/pricing_cards.md index 0efe8b9580b..0d88ae4824c 100644 --- a/docs/recipes/others/pricing_cards.md +++ b/docs/recipes/others/pricing_cards.md @@ -196,4 +196,3 @@ def pricing_cards() -> rx.Component: align_items="center", ) ``` - diff --git a/docs/state_structure/overview.md b/docs/state_structure/overview.md index 9c5bdf02e0d..53060cbc907 100644 --- a/docs/state_structure/overview.md +++ b/docs/state_structure/overview.md @@ -207,6 +207,7 @@ def var_value_example(): ``` In this example: + 1. We have two separate states: `CounterState` which manages a counter, and `DisplayState` which displays information 2. When you click "Increment", it calls `CounterState.increment()` to increase the counter value 3. When you click "Show Count", it calls `DisplayState.show_count()` which uses `get_var_value` to retrieve just the count value from `CounterState` without loading the entire state diff --git a/docs/state_structure/shared_state.md b/docs/state_structure/shared_state.md index c8586cd1bc3..4666b16f4d1 100644 --- a/docs/state_structure/shared_state.md +++ b/docs/state_structure/shared_state.md @@ -20,7 +20,7 @@ A token can be any string that uniquely identifies a group of clients that shoul ``` ```md alert warning -# Linked token cannot contain underscore (_) characters. +# Linked token cannot contain underscore (\_) characters. Underscore characters are currently used as an internal delimiter for tokens and will raise an exception if used for linked states. diff --git a/docs/styling/common-props.md b/docs/styling/common-props.md index f1fb70c2faa..75491d265c9 100644 --- a/docs/styling/common-props.md +++ b/docs/styling/common-props.md @@ -167,7 +167,7 @@ props = { "values": ["auto", "1", "5", "200"], "link": "https://developer.mozilla.org/en-US/docs/Web/CSS/z-index", }, - + } @@ -184,18 +184,18 @@ def show_props(key, props_dict): ), href=prop_details["link"], is_external=True, - ), + ), justify="start",), rx.table.cell(prop_details["description"], justify="start", style=cell_style), rx.table.cell(rx.hstack(*[rx.code(value, style=get_code_style("violet")) for value in prop_details["values"]], flex_wrap="wrap"), justify="start",), justify="center", align="center", - + ) ``` -Any [CSS](https://developer.mozilla.org/en-US/docs/Web/CSS) prop can be used in a component in Reflex. This is a short list of the most commonly used props. To see all CSS props that can be used check out this [documentation](https://developer.mozilla.org/en-US/docs/Web/CSS). +Any [CSS](https://developer.mozilla.org/en-US/docs/Web/CSS) prop can be used in a component in Reflex. This is a short list of the most commonly used props. To see all CSS props that can be used check out this [documentation](https://developer.mozilla.org/en-US/docs/Web/CSS). Hyphens in CSS property names may be replaced by underscores to use as valid python identifiers, i.e. the CSS prop `z-index` would be used as `z_index` in Reflex. @@ -209,7 +209,7 @@ rx.table.root( rx.table.column_header_cell( "Description", justify="center", - + ), rx.table.column_header_cell( "Potential Values", @@ -224,4 +224,4 @@ rx.table.root( padding_x="0", size="1", ) -``` \ No newline at end of file +``` diff --git a/docs/styling/custom-stylesheets.md b/docs/styling/custom-stylesheets.md index b57f5426fd6..01213c6d27d 100644 --- a/docs/styling/custom-stylesheets.md +++ b/docs/styling/custom-stylesheets.md @@ -29,19 +29,19 @@ app = rx.App( ```md alert warning # Always use a leading slash (/) when referencing files in the assets directory. + Without a leading slash the path is considered relative to the current page route and may not work for routes containing more than one path component, like `/blog/my-cool-post`. ``` - ## Styling with CSS You can use CSS variables directly in your Reflex app by passing them alongside the appropriae props. Create a `style.css` file inside the `assets` folder with the following lines: ```css :root { - --primary-color: blue; - --accent-color: green; + --primary-color: blue; + --accent-color: green; } ``` @@ -99,11 +99,11 @@ $padding: 16px; .container { background-color: $primary-color; padding: $padding; - + .button { background-color: $secondary-color; padding: $padding / 2; - + &:hover { opacity: 0.8; } @@ -167,14 +167,14 @@ Now we have the font ready, let's create the stylesheet `myfont.css`. ```css @font-face { - font-family: MyFont; - src: url("/fonts/MyFont.otf") format("opentype"); + font-family: MyFont; + src: url("/fonts/MyFont.otf") format("opentype"); } @font-face { - font-family: MyFont; - font-weight: bold; - src: url("/fonts/MyFont.otf") format("opentype"); + font-family: MyFont; + font-weight: bold; + src: url("/fonts/MyFont.otf") format("opentype"); } ``` diff --git a/docs/styling/layout.md b/docs/styling/layout.md index 84282b14f83..e3e29f2c302 100644 --- a/docs/styling/layout.md +++ b/docs/styling/layout.md @@ -16,7 +16,6 @@ Layout components such as `rx.flex`, `rx.container`, `rx.box`, etc. are used to **When to use:** Use `rx.box` when you need to apply specific styles or constraints to a part of your interface. - ```python demo rx.box( rx.box( @@ -90,7 +89,6 @@ The `rx.flex` component is used to create a flexible box layout, inspired by [CS **When to use:** Use `rx.flex` when you need a responsive layout that adjusts the size and position of child components dynamically. - ```python demo rx.flex( rx.card("Card 1"), @@ -101,7 +99,6 @@ rx.flex( ) ``` - ## Grid `rx.grid` components are used to create complex responsive layouts based on a grid system, similar to [CSS Grid Layout](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_grid_layout). @@ -152,4 +149,4 @@ rx.box( background_color="var(--gray-3)", width="100%", ) -``` \ No newline at end of file +``` diff --git a/docs/styling/overview.md b/docs/styling/overview.md index 137f7f1bc67..b247d3ef11a 100644 --- a/docs/styling/overview.md +++ b/docs/styling/overview.md @@ -15,6 +15,7 @@ There are three main ways to add style to your app and they take precedence in t ```md alert success # Style keys can be any valid CSS property name. + To be consistent with Python standards, you can specify keys in `snake_case`. ``` @@ -70,9 +71,9 @@ app = rx.App(style=style) Using style dictionaries like this, you can easily create a consistent theme for your app. - ```md alert warning # Watch out for underscores in class names and IDs + Reflex automatically converts `snake_case` identifiers into `camelCase` format when applying styles. To ensure consistency, it is recommended to use the dash character or camelCase identifiers in your own class names and IDs. To style third-party libraries relying on underscore class names, an external stylesheet should be used. See [custom stylesheets]({styling.custom_stylesheets.path}) for how to reference external CSS files. ``` @@ -155,7 +156,6 @@ rx.box( The style dictionaries are applied in the order they are passed in. This means that styles defined later will override styles defined earlier. - ## Theming As of Reflex 'v0.4.0', you can now theme your Reflex web apps. To learn more checkout the [Theme docs]({styling.theming.path}). diff --git a/docs/styling/tailwind.md b/docs/styling/tailwind.md index fe47f6a450b..fcbcfa227e7 100644 --- a/docs/styling/tailwind.md +++ b/docs/styling/tailwind.md @@ -48,13 +48,14 @@ config = rx.Config( ) ``` -```md alert info +```````md alert info ## Migration from Legacy Configuration If you're currently using the legacy `tailwind` configuration parameter, you should migrate to using the plugin system: **Old approach (legacy):** -``````python + +```python config = rx.Config( app_name="my_app", tailwind={ @@ -62,10 +63,12 @@ config = rx.Config( "theme": {"extend": {"colors": {"primary": "#3b82f6"}}}, }, ) -`````` +``` +``````` **New approach (plugin-based):** -``````python + +```python tailwind_config = { "plugins": ["@tailwindcss/typography"], "theme": {"extend": {"colors": {"primary": "#3b82f6"}}}, @@ -77,9 +80,10 @@ config = rx.Config( rx.plugins.TailwindV4Plugin(tailwind_config), ], ) -`````` ``` +```` + ### Choosing Between Tailwind Versions Reflex supports both Tailwind CSS v3 and v4: @@ -90,7 +94,7 @@ Reflex supports both Tailwind CSS v3 and v4: ```python # For Tailwind CSS v4 (recommended for new projects) config = rx.Config( - app_name="myapp", + app_name="myapp", plugins=[rx.plugins.TailwindV4Plugin()], ) @@ -99,7 +103,7 @@ config = rx.Config( app_name="myapp", plugins=[rx.plugins.TailwindV3Plugin()], ) -``` +```` All Tailwind configuration options are supported. @@ -140,7 +144,7 @@ Begin by creating a CSS file inside your `assets` folder. Inside the CSS file, i We define a couple of custom CSS variables (`--background` and `--foreground`) that will be used throughout your app for styling. These variables can be dynamically updated based on the theme. -Tailwind defaults to light mode, but to handle dark mode, you can define a separate set of CSS variables under the `.dark` class. +Tailwind defaults to light mode, but to handle dark mode, you can define a separate set of CSS variables under the `.dark` class. Tailwind Directives (`@tailwind base`, `@tailwind components`, `@tailwind utilities`): These are essential Tailwind CSS imports that enable the default base styles, components, and utility classes. @@ -253,4 +257,3 @@ def tailwind_demo(): Reflex core components are built on Radix Themes, which means they come with pre-defined styling. When you apply Tailwind classes to these components, you may encounter styling conflicts or unexpected behavior as the Tailwind styles compete with the built-in Radix styles. For the best experience when using Tailwind CSS in your Reflex application, we recommend using the lower-level `rx.el` components. These components don't have pre-applied styles, giving you complete control over styling with Tailwind classes without any conflicts. Check the list of HTML components [here]({library.other.html.path}). - diff --git a/docs/styling/theming.md b/docs/styling/theming.md index 83697108fe5..78493c5de7c 100644 --- a/docs/styling/theming.md +++ b/docs/styling/theming.md @@ -77,7 +77,6 @@ rx.table.root( Additionally you can modify the theme of your app through using the `Theme Panel` component which can be found in the [Theme Panel docs]({library.other.theme.path}). - ## Colors ### Color Scheme @@ -104,7 +103,6 @@ rx.flex( Sometime you may want to use a specific shade of a color from the theme. This is recommended vs using a hex color directly as it will automatically change when the theme changes appearance change from light/dark. - To access a specific shade of color from the theme, you can use the `rx.color`. When switching to light and dark themes, the color will automatically change. Shades can be accessed by using the color name and the shade number. The shade number ranges from 1 to 12. Additionally, they can have their alpha value set by using the `True` parameter it defaults to `False`. A full list of colors can be found [here](https://www.radix-ui.com/colors). ```python demo diff --git a/docs/ui/overview.md b/docs/ui/overview.md index f59b8ae855d..f6b34f49037 100644 --- a/docs/ui/overview.md +++ b/docs/ui/overview.md @@ -14,12 +14,14 @@ Components are made up of children and props. ```md definition # Children -* Text or other Reflex components nested inside a component. -* Passed as **positional arguments**. + +- Text or other Reflex components nested inside a component. +- Passed as **positional arguments**. # Props -* Attributes that affect the behavior and appearance of a component. -* Passed as **keyword arguments**. + +- Attributes that affect the behavior and appearance of a component. +- Passed as **keyword arguments**. ``` Let's take a look at the `rx.text` component. diff --git a/docs/utility_methods/exception_handlers.md b/docs/utility_methods/exception_handlers.md index 80ed6ed51b1..b0b62f2f532 100644 --- a/docs/utility_methods/exception_handlers.md +++ b/docs/utility_methods/exception_handlers.md @@ -40,4 +40,4 @@ app = rx.App( frontend_exception_handler = custom_frontend_handler, backend_exception_handler = custom_backend_handler ) -``` \ No newline at end of file +``` diff --git a/docs/utility_methods/lifespan_tasks.md b/docs/utility_methods/lifespan_tasks.md index 60b7ae1cc20..bf5f84eb8ad 100644 --- a/docs/utility_methods/lifespan_tasks.md +++ b/docs/utility_methods/lifespan_tasks.md @@ -81,4 +81,4 @@ async def setup_model(app: FastAPI): app = rx.App() app.register_lifespan_task(setup_model) -``` \ No newline at end of file +``` diff --git a/docs/utility_methods/other_methods.md b/docs/utility_methods/other_methods.md index fab7dee2c67..e821faccf32 100644 --- a/docs/utility_methods/other_methods.md +++ b/docs/utility_methods/other_methods.md @@ -1,14 +1,14 @@ # Other Methods -* `reset`: set all Vars to their default value for the given state (including substates). -* `get_value`: returns the value of a Var **without tracking changes to it**. This is useful - for serialization where the tracking wrapper is considered unserializable. -* `dict`: returns all state Vars (and substates) as a dictionary. This is +- `reset`: set all Vars to their default value for the given state (including substates). +- `get_value`: returns the value of a Var **without tracking changes to it**. This is useful + for serialization where the tracking wrapper is considered unserializable. +- `dict`: returns all state Vars (and substates) as a dictionary. This is used internally when a page is first loaded and needs to be "hydrated" and sent to the client. ## Special Attributes -* `dirty_vars`: a set of all Var names that have been modified since the last +- `dirty_vars`: a set of all Var names that have been modified since the last time the state was sent to the client. This is used internally to determine which Vars need to be sent to the client after processing an event. diff --git a/docs/utility_methods/router_attributes.md b/docs/utility_methods/router_attributes.md index 1789faeb73a..fc00dbdb059 100644 --- a/docs/utility_methods/router_attributes.md +++ b/docs/utility_methods/router_attributes.md @@ -43,34 +43,34 @@ about the current page, session, or state. The `self.router` attribute has several sub-attributes that provide various information: -* `router.page`: data about the current page and route - * `host`: The hostname and port serving the current page (frontend). - * `path`: The path of the current page (for dynamic pages, this will contain the slug) - * `raw_path`: The path of the page displayed in the browser (including params and dynamic values) - * `full_path`: `path` with `host` prefixed - * `full_raw_path`: `raw_path` with `host` prefixed - * `params`: Dictionary of query params associated with the request - -* `router.session`: data about the current session - * `client_token`: UUID associated with the current tab's token. Each tab has a unique token. - * `session_id`: The ID associated with the client's websocket connection. Each tab has a unique session ID. - * `client_ip`: The IP address of the client. Many users may share the same IP address. - -* `router.headers`: headers associated with the websocket connection. These values can only change when the websocket is re-established (for example, during page refresh). - * `host`: The hostname and port serving the websocket (backend). - * `origin`: The origin of the request. - * `upgrade`: The upgrade header for websocket connections. - * `connection`: The connection header. - * `cookie`: The cookie header. - * `pragma`: The pragma header. - * `cache_control`: The cache control header. - * `user_agent`: The user agent string of the client. - * `sec_websocket_version`: The websocket version. - * `sec_websocket_key`: The websocket key. - * `sec_websocket_extensions`: The websocket extensions. - * `accept_encoding`: The accepted encodings. - * `accept_language`: The accepted languages. - * `raw_headers`: A mapping of all HTTP headers as a frozen dictionary. This provides access to any header that was sent with the request, not just the common ones listed above. +- `router.page`: data about the current page and route + - `host`: The hostname and port serving the current page (frontend). + - `path`: The path of the current page (for dynamic pages, this will contain the slug) + - `raw_path`: The path of the page displayed in the browser (including params and dynamic values) + - `full_path`: `path` with `host` prefixed + - `full_raw_path`: `raw_path` with `host` prefixed + - `params`: Dictionary of query params associated with the request + +- `router.session`: data about the current session + - `client_token`: UUID associated with the current tab's token. Each tab has a unique token. + - `session_id`: The ID associated with the client's websocket connection. Each tab has a unique session ID. + - `client_ip`: The IP address of the client. Many users may share the same IP address. + +- `router.headers`: headers associated with the websocket connection. These values can only change when the websocket is re-established (for example, during page refresh). + - `host`: The hostname and port serving the websocket (backend). + - `origin`: The origin of the request. + - `upgrade`: The upgrade header for websocket connections. + - `connection`: The connection header. + - `cookie`: The cookie header. + - `pragma`: The pragma header. + - `cache_control`: The cache control header. + - `user_agent`: The user agent string of the client. + - `sec_websocket_version`: The websocket version. + - `sec_websocket_key`: The websocket key. + - `sec_websocket_extensions`: The websocket extensions. + - `accept_encoding`: The accepted encodings. + - `accept_language`: The accepted languages. + - `raw_headers`: A mapping of all HTTP headers as a frozen dictionary. This provides access to any header that was sent with the request, not just the common ones listed above. ### Example Values on this Page diff --git a/docs/vars/base_vars.md b/docs/vars/base_vars.md index 07b207dc7af..b75bb8e161c 100644 --- a/docs/vars/base_vars.md +++ b/docs/vars/base_vars.md @@ -174,10 +174,9 @@ def backend_var_example(): ) ``` - ## Using rx.field / rx.Field to improve type hinting for vars -When defining state variables you can use `rx.Field[T]` to annotate the variable's type. Then, you can initialize the variable using `rx.field(default_value)`, where `default_value` is an instance of type `T`. +When defining state variables you can use `rx.Field[T]` to annotate the variable's type. Then, you can initialize the variable using `rx.field(default_value)`, where `default_value` is an instance of type `T`. This approach makes the variable's type explicit, aiding static analysis tools in type checking. In addition, it shows you what methods are allowed to modify the variable in your frontend code, as they are listed in the type hint. @@ -207,7 +206,6 @@ def index(): Here `State.x`, as it is typed correctly as a `boolean` var, gets better code completion, i.e. here we get options such as `to_string()` or `equals()`. - ```python import reflex as rx @@ -225,4 +223,4 @@ def index(): ) ``` -Here `State.x`, as it is typed correctly as a `dict` of `str` to `list` of `int` var, gets better code completion, i.e. here we get options such as `contains()`, `keys()`, `values()`, `items()` or `merge()`. \ No newline at end of file +Here `State.x`, as it is typed correctly as a `dict` of `str` to `list` of `int` var, gets better code completion, i.e. here we get options such as `contains()`, `keys()`, `values()`, `items()` or `merge()`. diff --git a/docs/vars/computed_vars.md b/docs/vars/computed_vars.md index ee711609d10..ce1bef92d13 100644 --- a/docs/vars/computed_vars.md +++ b/docs/vars/computed_vars.md @@ -39,11 +39,11 @@ We recommend always using type annotations for computed vars. ## Cached Vars -By default, all computed vars are cached (`cache=True`). A cached var is only -recomputed when the other state vars it depends on change. This is useful for +By default, all computed vars are cached (`cache=True`). A cached var is only +recomputed when the other state vars it depends on change. This is useful for expensive computations, but in some cases it may not update when you expect it to. -To create a computed var that recomputes on every state update regardless of +To create a computed var that recomputes on every state update regardless of dependencies, use `@rx.var(cache=False)`. Previous versions of Reflex had a `@rx.cached_var` decorator, which is now replaced @@ -92,8 +92,8 @@ def cached_var_example(): In this example `last_touch_time` uses `cache=False` to ensure it updates any time the state is modified. `last_counter_a_update` is a cached computed var (using -the default `cache=True`) that only depends on `counter_a`, so it only gets recomputed -when `counter_a` changes. Similarly `last_counter_b_update` only depends on `counter_b`, +the default `cache=True`) that only depends on `counter_a`, so it only gets recomputed +when `counter_a` changes. Similarly `last_counter_b_update` only depends on `counter_b`, and thus is updated only when `counter_b` changes. ## Async Computed Vars diff --git a/docs/wrapping-react/custom-code-and-hooks.md b/docs/wrapping-react/custom-code-and-hooks.md index 21281de75bc..91b39412afe 100644 --- a/docs/wrapping-react/custom-code-and-hooks.md +++ b/docs/wrapping-react/custom-code-and-hooks.md @@ -1,4 +1,3 @@ - When wrapping a React component, you may need to define custom code or hooks that are specific to the component. This is done by defining the `add_custom_code`or `add_hooks` methods in your component class. ## Custom Code @@ -29,15 +28,17 @@ console.log(customVariable); ``` ## Custom Hooks + Custom hooks are any hooks that need to be included in your component. This can include things like `useEffect`, `useState`, or any other hooks from the library you are wrapping. - Simple hooks can be added as strings. - More complex hooks that need to have special import or be written in a specific order can be added as `rx.Var` with a `VarData` object to specify the position of the hook. - - The `imports` attribute of the `VarData` object can be used to specify any imports that need to be included in the component. - - The `position` attribute of the `VarData` object can be set to `Hooks.HookPosition.PRE_TRIGGER` or `Hooks.HookPosition.POST_TRIGGER` to specify the position of the hook in the component. + - The `imports` attribute of the `VarData` object can be used to specify any imports that need to be included in the component. + - The `position` attribute of the `VarData` object can be set to `Hooks.HookPosition.PRE_TRIGGER` or `Hooks.HookPosition.POST_TRIGGER` to specify the position of the hook in the component. ```md alert info -# The `position` attribute is only used for hooks that need to be written in a specific order. +# The `position` attribute is only used for hooks that need to be written in a specific order. + - If an event handler need to refer to a variable defined in a hook, the hook should be written before the event handler. - If a hook need to refer to the memoized event handler by name, the hook should be written after the event handler. ``` diff --git a/docs/wrapping-react/imports-and-styles.md b/docs/wrapping-react/imports-and-styles.md index 56b3b16c029..eb3648cf85c 100644 --- a/docs/wrapping-react/imports-and-styles.md +++ b/docs/wrapping-react/imports-and-styles.md @@ -1,4 +1,3 @@ - # Styles and Imports When wrapping a React component, you may need to define styles and imports that are specific to the component. This is done by defining the `add_styles` and `add_imports` methods in your component class. diff --git a/docs/wrapping-react/library-and-tags.md b/docs/wrapping-react/library-and-tags.md index 6b13694bb52..75e2d45935c 100644 --- a/docs/wrapping-react/library-and-tags.md +++ b/docs/wrapping-react/library-and-tags.md @@ -9,6 +9,7 @@ from pcweb.pages.docs import api_reference # Find The Component There are two ways to find a component to wrap: + 1. Write the component yourself locally. 2. Find a well-maintained React library on [npm](https://www.npmjs.com/) that contains the component you need. @@ -16,11 +17,12 @@ In both cases, the process of wrapping the component is the same except for the # Wrapping the Component -To start wrapping your React component, the first step is to create a new component in your Reflex app. This is done by creating a new class that inherits from `rx.Component` or `rx.NoSSRComponent`. +To start wrapping your React component, the first step is to create a new component in your Reflex app. This is done by creating a new class that inherits from `rx.Component` or `rx.NoSSRComponent`. See the [API Reference]({api_reference.component.path}) for more details on the `rx.Component` class. This is when we will define the most important attributes of the component: + 1. **library**: The name of the npm package that contains the component. 2. **tag**: The name of the component to import from the package. 3. **alias**: (Optional) The name of the alias to use for the component. This is useful if multiple component from different package have a name in common. If `alias` is not specified, `tag` will be used. @@ -55,11 +57,11 @@ class MyBaseComponent(rx.Component): @classmethod def create(cls, *children, **props): """Create an instance of MyBaseComponent. - + Args: *children: The children of the component. **props: The props of the component. - + Returns: The component instance. """ @@ -68,7 +70,7 @@ class MyBaseComponent(rx.Component): ``` -# Wrapping a Dynamic Component +# Wrapping a Dynamic Component When wrapping some libraries, you may want to use dynamic imports. This is because they may not be compatible with Server-Side Rendering (SSR). @@ -77,10 +79,10 @@ To handle this in Reflex, subclass `NoSSRComponent` when defining your component Often times when you see an import something like this: ```javascript -import dynamic from 'next/dynamic'; +import dynamic from "next/dynamic"; -const MyLibraryComponent = dynamic(() => import('./MyLibraryComponent'), { - ssr: false +const MyLibraryComponent = dynamic(() => import("./MyLibraryComponent"), { + ssr: false, }); ``` @@ -97,7 +99,7 @@ class MyLibraryComponent(NoSSRComponent): tag="MyLibraryComponent" ``` -It may not always be clear when a library requires dynamic imports. A few things to keep in mind are if the component is very client side heavy i.e. the view and structure depends on things that are fetched at run time, or if it uses `window` or `document` objects directly it will need to be wrapped as a `NoSSRComponent`. +It may not always be clear when a library requires dynamic imports. A few things to keep in mind are if the component is very client side heavy i.e. the view and structure depends on things that are fetched at run time, or if it uses `window` or `document` objects directly it will need to be wrapped as a `NoSSRComponent`. Some examples are: @@ -108,12 +110,13 @@ Some examples are: 5. QR Scanners 6. Reactflow -The reason for this is that it does not make sense for your server to render these components as the server does not have access to your camera, it cannot draw on your canvas or render a video from a file. +The reason for this is that it does not make sense for your server to render these components as the server does not have access to your camera, it cannot draw on your canvas or render a video from a file. In addition, if in the component documentation it mentions nextJS compatibility or server side rendering compatibility, it is a good sign that it requires dynamic imports. # Advanced - Parsing a state Var with a JS Function -When wrapping a component, you may need to parse a state var by applying a JS function to it. + +When wrapping a component, you may need to parse a state var by applying a JS function to it. ## Define the parsing function @@ -135,7 +138,7 @@ def add_custom_code(self) -> list[str]: ## Apply the parsing function to your props -Then, you can apply the parsing function to your props in the `create` method. +Then, you can apply the parsing function to your props in the `create` method. ```python from reflex.vars.base import Var @@ -145,11 +148,11 @@ from reflex.vars.function import FunctionStringVar @classmethod def create(cls, *children, **props): """Create an instance of MyBaseComponent. - + Args: *children: The children of the component. **props: The props of the component. - + Returns: The component instance. """ @@ -163,4 +166,4 @@ from reflex.vars.function import FunctionStringVar props["propsToParse"] = parsed_prop return super().create(*children, **props) ... -``` \ No newline at end of file +``` diff --git a/docs/wrapping-react/local-packages.md b/docs/wrapping-react/local-packages.md index a0ae718cb0b..d7555c10517 100644 --- a/docs/wrapping-react/local-packages.md +++ b/docs/wrapping-react/local-packages.md @@ -33,7 +33,6 @@ class Hello(rx.Component): ) ``` - # Local Components You can also wrap components that you have written yourself. For local components (when the code source is directly in the project), we recommend putting it beside the files that is wrapping it. @@ -42,15 +41,15 @@ If there is a file `hello.jsx` in the same directory as the component with this ```javascript // /path/to/components/hello.jsx -import React from 'react'; +import React from "react"; -export function Hello({name, onGreet}) { +export function Hello({ name, onGreet }) { return (

    Hello, {name}!

    - ) + ); } ``` @@ -92,11 +91,11 @@ When wrapping local components, keep the following in mind: Local components are useful when shimming small pieces of functionality that are simpler or more performant when implemented directly in Javascript, such as: -* Spammy events: keys, touch, mouse, scroll -- these are often better processed on the client side. -* Using canvas, graphics or WebGPU -* Working with other Web APIs like storage, screen capture, audio/midi -* Integrating with complex third-party libraries - * For application-specific use, it may be easier to wrap a local component that +- Spammy events: keys, touch, mouse, scroll -- these are often better processed on the client side. +- Using canvas, graphics or WebGPU +- Working with other Web APIs like storage, screen capture, audio/midi +- Integrating with complex third-party libraries + - For application-specific use, it may be easier to wrap a local component that provides the needed subset of the library's functionality in a simpler API for use in Reflex. # Local Packages @@ -108,13 +107,13 @@ or URL after an `@` following the package name. Any local paths are relative to the `.web` folder, so you can use `../` prefix to reference the Reflex project root. -Some examples of valid specifiers for a package called +Some examples of valid specifiers for a package called [`@masenf/hello-react`](https://github.com/masenf/hello-react) are: -* GitHub: `@masenf/hello-react@github:masenf/hello-react` -* URL: `@masenf/hello-react@https://github.com/masenf/hello-react/archive/refs/heads/main.tar.gz` -* Local Archive: `@masenf/hello-react@../hello-react.tgz` -* Local Directory: `@masenf/hello-react@../hello-react` +- GitHub: `@masenf/hello-react@github:masenf/hello-react` +- URL: `@masenf/hello-react@https://github.com/masenf/hello-react/archive/refs/heads/main.tar.gz` +- Local Archive: `@masenf/hello-react@../hello-react.tgz` +- Local Directory: `@masenf/hello-react@../hello-react` It is important that the package name matches the name in `package.json` so Reflex can generate the correct import statement in the generated javascript @@ -142,11 +141,11 @@ for use. Some important notes regarding this approach: -* The repo or archive must contain a `package.json` file. -* `prepare` or `build` scripts will NOT be executed. The distribution archive, +- The repo or archive must contain a `package.json` file. +- `prepare` or `build` scripts will NOT be executed. The distribution archive, directory, or repo must already contain the built javascript files (this is common). -```md alert +````md alert # Ensure CSS files are exported in `package.json` In addition to exporting the module containing the component, any CSS files @@ -165,7 +164,8 @@ intended to be imported by the wrapped component must also be listed in the "import": "./dist/style.css", "require": "./dist/style.css" } - }, + } // ... } ``` +```` diff --git a/docs/wrapping-react/more-wrapping-examples.md b/docs/wrapping-react/more-wrapping-examples.md index 3a5638b66fa..8fc11d855d8 100644 --- a/docs/wrapping-react/more-wrapping-examples.md +++ b/docs/wrapping-react/more-wrapping-examples.md @@ -1,9 +1,8 @@ -# More React Libraries - +# More React Libraries ## AG Charts -Here we wrap the AG Charts library from the NPM package [ag-charts-react](https://www.npmjs.com/package/ag-charts-react). +Here we wrap the AG Charts library from the NPM package [ag-charts-react](https://www.npmjs.com/package/ag-charts-react). In the react code below we can see the first `2` lines are importing React and ReactDOM, and this can be ignored when wrapping your component. @@ -11,26 +10,25 @@ We import the `AgCharts` component from the `ag-charts-react` library on line 5. Line `7` defines a functional React component, which on line `26` returns `AgCharts` which is similar in the Reflex code to using the `chart` component. -Line `9` uses the `useState` hook to create a state variable `chartOptions` and its setter function `setChartOptions` (equivalent to the event handler `set_chart_options` in reflex). The initial state variable is of type dict and has two key value pairs `data` and `series`. +Line `9` uses the `useState` hook to create a state variable `chartOptions` and its setter function `setChartOptions` (equivalent to the event handler `set_chart_options` in reflex). The initial state variable is of type dict and has two key value pairs `data` and `series`. When we see `useState` in React code, it correlates to state variables in your State. As you can see in our Reflex code we have a state variable `chart_options` which is a dictionary, like in our React code. -Moving to line `26` we see that the `AgCharts` has a prop `options`. In order to use this in Reflex we must wrap this prop. We do this with `options: rx.Var[dict]` in the `AgCharts` component. +Moving to line `26` we see that the `AgCharts` has a prop `options`. In order to use this in Reflex we must wrap this prop. We do this with `options: rx.Var[dict]` in the `AgCharts` component. Lines `31` and `32` are rendering the component inside the root element. This can be ignored when we are wrapping a component as it is done in Reflex by creating an `index` function and adding it to the app. - ---md tabs ---tab React Code +--tab React Code ```javascript 1 | import React, \{ useState } from 'react'; 2 | import ReactDOM from 'react-dom/client'; -3 | +3 | 4 | // React Chart Component 5 | import \{ AgCharts } from 'ag-charts-react'; -6 | +6 | 7 | const ChartExample = () => { 8 | // Chart Options: Control & configure the chart 9 | const [chartOptions, setChartOptions] = useState({ @@ -46,14 +44,14 @@ Lines `31` and `32` are rendering the component inside the root element. This ca 19| // Series: Defines which chart type and data to use 20| series: [\{ type: 'bar', xKey: 'month', yKey: 'iceCreamSales' }], 21| }); -22| +22| 23| // React Chart Component 24| return ( 25| // AgCharts component with options passed as prop 26| 27| ); 28| } -29| +29| 30| // Render component inside root element 31| const root = ReactDOM.createRoot(document.getElementById('root')); 32| root.render(); @@ -69,7 +67,7 @@ class AgCharts(rx.Component): """ A simple line chart component using AG Charts """ library = "ag-charts-react" - + tag = "AgCharts" options: rx.Var[dict] @@ -100,11 +98,11 @@ def index() -> rx.Component: app = rx.App() app.add_page(index) ``` + -- --- - ## React Leaflet ```python exec @@ -127,12 +125,12 @@ Lines `24` and `25` defines and exports a React functional component named `Home ---md tabs ---tab React Code +--tab React Code ```javascript 1 | import dynamic from "next/dynamic"; 2 | import "leaflet/dist/leaflet.css"; -3 | +3 | 4 | const MapComponent = dynamic( 5 | () => { 6 | return import("react-leaflet").then((\{ MapContainer, TileLayer }) => { @@ -161,7 +159,7 @@ Lines `24` and `25` defines and exports a React functional component named `Home -- --tab Reflex Code -```python +```python import reflex as rx class MapContainer(rx.NoSSRComponent): @@ -176,7 +174,7 @@ class MapContainer(rx.NoSSRComponent): scroll_wheel_zoom: rx.Var[bool] - # Can also pass a url like: https://unpkg.com/leaflet/dist/leaflet.css + # Can also pass a url like: https://unpkg.com/leaflet/dist/leaflet.css def add_imports(self): return \{"": ["leaflet/dist/leaflet.css"]} @@ -197,7 +195,7 @@ tile_layer = TileLayer.create def index() -> rx.Component: return map_container( tile_layer(url="https://\{s}.tile.openstreetmap.org/\{z}/\{x}/\{y}.png"), - center=[51.505, -0.09], + center=[51.505, -0.09], zoom=13, #scroll_wheel_zoom=True width="100%", @@ -209,11 +207,11 @@ app = rx.App() app.add_page(index) ``` + -- --- - ## React PDF Renderer In this example we are wrapping the React renderer for creating PDF files on the browser and server from the NPM package [@react-pdf/renderer](https://www.npmjs.com/package/@react-pdf/renderer). @@ -222,7 +220,6 @@ This example is similar to the previous examples, and again Dynamic Imports are The main difference with this example is that the `style` prop, used on lines `20`, `21` and `24` in React code, is a reserved name in Reflex so can not be wrapped. A different name must be used when wrapping this prop and then this name must be changed back to the original with the `rename_props` method. In this example we name the prop `theme` in our Reflex code and then change it back to `style` with the `rename_props` method in both the `Page` and `View` components. - ```md alert info # List of reserved names in Reflex @@ -293,7 +290,7 @@ _State class associated with this component instance_ ---md tabs ---tab React Code +--tab React Code ```javascript 1 | import ReactDOM from 'react-dom'; @@ -325,13 +322,13 @@ _State class associated with this component instance_ 27| 28| 29| ); -30| +30| 31| const App = () => ( 32| 33| 34| 35| ); -36| +36| 37| ReactDOM.render(, document.getElementById('root')); ``` @@ -342,14 +339,14 @@ _State class associated with this component instance_ import reflex as rx class Document(rx.Component): - + library = "@react-pdf/renderer" tag = "Document" - + class Page(rx.Component): - + library = "@react-pdf/renderer" tag = "Page" @@ -364,14 +361,14 @@ class Page(rx.Component): class Text(rx.Component): - + library = "@react-pdf/renderer" tag = "Text" class View(rx.Component): - + library = "@react-pdf/renderer" tag = "View" @@ -385,7 +382,7 @@ class View(rx.Component): class StyleSheet(rx.Component): - + library = "@react-pdf/renderer" tag = "StyleSheet" @@ -396,7 +393,7 @@ class StyleSheet(rx.Component): class PDFViewer(rx.NoSSRComponent): - + library = "@react-pdf/renderer" tag = "PDFViewer" @@ -424,7 +421,7 @@ styles = style_sheet({ def index() -> rx.Component: - return pdf_viewer( + return pdf_viewer( document( page( view( @@ -444,6 +441,7 @@ def index() -> rx.Component: app = rx.App() app.add_page(index) ``` + -- ---- \ No newline at end of file +--- diff --git a/docs/wrapping-react/overview.md b/docs/wrapping-react/overview.md index 63a8478397b..bb7f4d3a93d 100644 --- a/docs/wrapping-react/overview.md +++ b/docs/wrapping-react/overview.md @@ -16,7 +16,7 @@ Once you wrap your component, you [publish it]({custom_components.overview.path} ## Simple Example -Simple components that don't have any interaction can be wrapped with just a few lines of code. +Simple components that don't have any interaction can be wrapped with just a few lines of code. Below we show how to wrap the [Spline]({constants.SPLINE_URL}) library can be used to create 3D scenes and animations. @@ -49,7 +49,6 @@ def index(): return spline(scene="https://prod.spline.design/joLpOOYbGL-10EJ4/scene.splinecode") ``` - ## ColorPicker Example Similar to the Spline example we start with defining the library and tag. In this case the library is `react-colorful` and the tag is `HexColorPicker`. @@ -118,7 +117,7 @@ def index(): ## What Not To Wrap -There are some libraries on npm that are not do not expose React components and therefore are very hard to wrap with Reflex. +There are some libraries on npm that are not do not expose React components and therefore are very hard to wrap with Reflex. A library like [spline](https://www.npmjs.com/package/@splinetool/runtime) below is going to be difficult to wrap with Reflex because it does not expose a React component. @@ -133,12 +132,12 @@ const spline = new Application(canvas); spline.load('https://prod.spline.design/6Wq1Q7YGyM-iab9i/scene.splinecode'); ``` -You should look out for JSX, a syntax extension to JavaScript, which has angle brackets `(

    Hello, world!

    )`. If you see JSX, it's likely that the library is a React component and can be wrapped with Reflex. +You should look out for JSX, a syntax extension to JavaScript, which has angle brackets `(

    Hello, world!

    )`. If you see JSX, it's likely that the library is a React component and can be wrapped with Reflex. If the library does not expose a react component you need to try and find a JS React wrapper for the library, such as [react-spline](https://www.npmjs.com/package/@splinetool/react-spline). ```javascript -import Spline from '@splinetool/react-spline'; +import Spline from "@splinetool/react-spline"; export default function App() { return ( @@ -149,6 +148,4 @@ export default function App() { } ``` - - In the next page, we will go step by step through a more complex example of wrapping a React component. diff --git a/docs/wrapping-react/props.md b/docs/wrapping-react/props.md index afc7442d8a4..207fa0f1fd1 100644 --- a/docs/wrapping-react/props.md +++ b/docs/wrapping-react/props.md @@ -1,5 +1,5 @@ --- -title: Props - Wrapping React +title: Props - Wrapping React --- # Props @@ -8,6 +8,7 @@ When wrapping a React component, you want to define the props that will be accep This is done by defining the props and annotating them with a `rx.Var`. Broadly, there are three kinds of props you can encounter when wrapping a React component: + 1. **Simple Props**: These are props that are passed directly to the component. They can be of any type, including strings, numbers, booleans, and even lists or dictionaries. 2. **Callback Props**: These are props that expect to receive a function. That function will usually be called by the component as a callback. (This is different from event handlers.) 3. **Component Props**: These are props that expect to receive a components themselves. They can be used to create more complex components by composing them together. @@ -41,28 +42,28 @@ class SimplePropsComponent(MyBaseComponent): """MyComponent.""" # Type the props according the component documentation. - + # props annotated as `string` in javascript - prop1: rx.Var[str] - + prop1: rx.Var[str] + # props annotated as `number` in javascript prop2: rx.Var[int] - + # props annotated as `boolean` in javascript - prop3: rx.Var[bool] - + prop3: rx.Var[bool] + # props annotated as `string[]` in javascript - prop4: rx.Var[list[str]] - + prop4: rx.Var[list[str]] + # props annotated as `CustomReactType` in javascript - props5: rx.Var[CustomReactType] + props5: rx.Var[CustomReactType] # props annotated as `CustomReactType2` in javascript props6: rx.Var[CustomReactType2] # Sometimes a props will accept multiple types. You can use `|` to specify the types. # props annotated as `string | boolean` in javascript - props7: rx.Var[str | bool] + props7: rx.Var[str | bool] ``` ## Callback Props @@ -81,6 +82,7 @@ class CallbackPropsComponent(MyBaseComponent): ``` ## Component Props + Some components will occasionally accept other components as props, usually annotated as `ReactNode`. In Reflex, these are defined as `rx.Component`. ```python @@ -92,6 +94,7 @@ class ComponentPropsComponent(MyBaseComponent): ``` ## Event Handlers + Event handlers are props that expect to receive a function that will be called when an event occurs. They are defined as `rx.EventHandler` with a signature function to define the spec of the event. ```python @@ -170,10 +173,10 @@ which, unlike a normal event handler, expects a mapping of event handlers like: ```javascript ``` @@ -199,4 +202,4 @@ def create(cls, *children, **props): # Create the component instance return super().create(*children, **props) -``` \ No newline at end of file +``` From 1cfab216c35418ee7da676b5e6c0b69a7b86c980 Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Tue, 24 Mar 2026 13:00:59 -0700 Subject: [PATCH 003/113] delete enterprise and hosting docs --- docs/enterprise/ag_chart.md | 30 - docs/enterprise/ag_grid/aligned-grids.md | 65 -- docs/enterprise/ag_grid/cell-selection.md | 288 -------- docs/enterprise/ag_grid/column-defs.md | 37 - docs/enterprise/ag_grid/index.md | 678 ------------------ docs/enterprise/ag_grid/model-wrapper.md | 64 -- docs/enterprise/ag_grid/pivot-mode.md | 128 ---- docs/enterprise/ag_grid/theme.md | 63 -- docs/enterprise/ag_grid/value-transformers.md | 75 -- docs/enterprise/built-with-reflex.md | 17 - docs/enterprise/components.md | 119 --- docs/enterprise/drag-and-drop.md | 586 --------------- docs/enterprise/mantine/autocomplete.md | 22 - docs/enterprise/mantine/collapse.md | 36 - docs/enterprise/mantine/combobox.md | 35 - docs/enterprise/mantine/index.md | 26 - docs/enterprise/mantine/json-input.md | 35 - docs/enterprise/mantine/loading-overlay.md | 33 - docs/enterprise/mantine/multi-select.md | 31 - docs/enterprise/mantine/number-formatter.md | 28 - docs/enterprise/mantine/pill.md | 94 --- docs/enterprise/mantine/ring-progress.md | 31 - .../mantine/semi-circle-progress.md | 29 - docs/enterprise/mantine/spoiler.md | 22 - docs/enterprise/mantine/tags-input.md | 55 -- docs/enterprise/mantine/timeline.md | 35 - docs/enterprise/mantine/tree.md | 31 - docs/enterprise/map/index.md | 575 --------------- docs/enterprise/overview.md | 298 -------- docs/enterprise/react_flow/basic_flow.md | 96 --- docs/enterprise/react_flow/components.md | 92 --- docs/enterprise/react_flow/edges.md | 217 ------ docs/enterprise/react_flow/examples.md | 234 ------ docs/enterprise/react_flow/hooks.md | 33 - docs/enterprise/react_flow/interactivity.md | 70 -- docs/enterprise/react_flow/nodes.md | 277 ------- docs/enterprise/react_flow/overview.md | 23 - docs/enterprise/react_flow/theming.md | 75 -- docs/enterprise/react_flow/utils.md | 29 - docs/enterprise/single-port-proxy.md | 15 - docs/hosting/adding-members.md | 37 - docs/hosting/app-management.md | 58 -- docs/hosting/billing.md | 32 - docs/hosting/compute.md | 216 ------ docs/hosting/config_file.md | 185 ----- docs/hosting/custom-domains.md | 75 -- docs/hosting/databricks.md | 203 ------ docs/hosting/deploy-quick-start.md | 88 --- docs/hosting/deploy-with-github-actions.md | 119 --- docs/hosting/logs.md | 48 -- docs/hosting/machine-types.md | 34 - docs/hosting/regions.md | 149 ---- docs/hosting/secrets-environment-vars.md | 53 -- docs/hosting/self-hosting.md | 122 ---- docs/hosting/tokens.md | 21 - 55 files changed, 6167 deletions(-) delete mode 100644 docs/enterprise/ag_chart.md delete mode 100644 docs/enterprise/ag_grid/aligned-grids.md delete mode 100644 docs/enterprise/ag_grid/cell-selection.md delete mode 100644 docs/enterprise/ag_grid/column-defs.md delete mode 100644 docs/enterprise/ag_grid/index.md delete mode 100644 docs/enterprise/ag_grid/model-wrapper.md delete mode 100644 docs/enterprise/ag_grid/pivot-mode.md delete mode 100644 docs/enterprise/ag_grid/theme.md delete mode 100644 docs/enterprise/ag_grid/value-transformers.md delete mode 100644 docs/enterprise/built-with-reflex.md delete mode 100644 docs/enterprise/components.md delete mode 100644 docs/enterprise/drag-and-drop.md delete mode 100644 docs/enterprise/mantine/autocomplete.md delete mode 100644 docs/enterprise/mantine/collapse.md delete mode 100644 docs/enterprise/mantine/combobox.md delete mode 100644 docs/enterprise/mantine/index.md delete mode 100644 docs/enterprise/mantine/json-input.md delete mode 100644 docs/enterprise/mantine/loading-overlay.md delete mode 100644 docs/enterprise/mantine/multi-select.md delete mode 100644 docs/enterprise/mantine/number-formatter.md delete mode 100644 docs/enterprise/mantine/pill.md delete mode 100644 docs/enterprise/mantine/ring-progress.md delete mode 100644 docs/enterprise/mantine/semi-circle-progress.md delete mode 100644 docs/enterprise/mantine/spoiler.md delete mode 100644 docs/enterprise/mantine/tags-input.md delete mode 100644 docs/enterprise/mantine/timeline.md delete mode 100644 docs/enterprise/mantine/tree.md delete mode 100644 docs/enterprise/map/index.md delete mode 100644 docs/enterprise/overview.md delete mode 100644 docs/enterprise/react_flow/basic_flow.md delete mode 100644 docs/enterprise/react_flow/components.md delete mode 100644 docs/enterprise/react_flow/edges.md delete mode 100644 docs/enterprise/react_flow/examples.md delete mode 100644 docs/enterprise/react_flow/hooks.md delete mode 100644 docs/enterprise/react_flow/interactivity.md delete mode 100644 docs/enterprise/react_flow/nodes.md delete mode 100644 docs/enterprise/react_flow/overview.md delete mode 100644 docs/enterprise/react_flow/theming.md delete mode 100644 docs/enterprise/react_flow/utils.md delete mode 100644 docs/enterprise/single-port-proxy.md delete mode 100644 docs/hosting/adding-members.md delete mode 100644 docs/hosting/app-management.md delete mode 100644 docs/hosting/billing.md delete mode 100644 docs/hosting/compute.md delete mode 100644 docs/hosting/config_file.md delete mode 100644 docs/hosting/custom-domains.md delete mode 100644 docs/hosting/databricks.md delete mode 100644 docs/hosting/deploy-quick-start.md delete mode 100644 docs/hosting/deploy-with-github-actions.md delete mode 100644 docs/hosting/logs.md delete mode 100644 docs/hosting/machine-types.md delete mode 100644 docs/hosting/regions.md delete mode 100644 docs/hosting/secrets-environment-vars.md delete mode 100644 docs/hosting/self-hosting.md delete mode 100644 docs/hosting/tokens.md diff --git a/docs/enterprise/ag_chart.md b/docs/enterprise/ag_chart.md deleted file mode 100644 index 30fdc4b53ee..00000000000 --- a/docs/enterprise/ag_chart.md +++ /dev/null @@ -1,30 +0,0 @@ -# AG Chart - -AG Chart is a powerful charting library that provides interactive charts and data visualization components for enterprise applications. - -```python demo exec -import reflex as rx -import reflex_enterprise as rxe - -def basic_chart(): - return rxe.ag_chart( - options={ - "data": [ - {"month": "Jan", "value": 10}, - {"month": "Feb", "value": 20}, - {"month": "Mar", "value": 15}, - ], - "series": [ - { - "type": "line", - "xKey": "month", - "yKey": "value", - } - ], - }, - width="100%", - height="400px", - ) -``` - -For more detailed documentation, see the [AG Chart Documentation](https://charts.ag-grid.com/). diff --git a/docs/enterprise/ag_grid/aligned-grids.md b/docs/enterprise/ag_grid/aligned-grids.md deleted file mode 100644 index da63bef7f63..00000000000 --- a/docs/enterprise/ag_grid/aligned-grids.md +++ /dev/null @@ -1,65 +0,0 @@ ---- -title: Aligned Grids ---- - -AgGrid provides a way to align multiple grids together. This is useful when you want to display related data in a synchronized manner. - -You can do so through the `aligned_grids` prop. This prop takes a list of grid IDs that you want to align. - -```python demo exec -import pandas as pd -import reflex as rx -import reflex_enterprise as rxe - -# Olympic winners data (originally from https://www.ag-grid.com/example-assets/olympic-winners.json) -df = pd.read_json("data/olympic-winners.json") - -row_data = df.to_dict("records") - -column_defs = [ - {"field": "athlete"}, - {"field": "age"}, - {"field": "country"}, - {"field": "year"}, - {"field": "sport"}, - { - "header_name": "Medals", - "children": [ - { - "field": "total", - "column_group_show": "closed", - "col_id": "total", - "value_getter": "params.data.gold + params.data.silver + params.data.bronze", - "width": 100, - }, - {"field": "gold", "column_group_show": "open", "width": 100}, - {"field": "silver", "column_group_show": "open", "width": 100}, - {"field": "bronze", "column_group_show": "open", "width": 100}, - ], - }, -] - -def aligned_grids_page(): - """Aligned grids demo.""" - return rx.el.div( - rxe.ag_grid( - id="grid1", - column_defs=column_defs, - row_data=row_data, - aligned_grids=["grid2"], - width="100%", - ), rxe.ag_grid( - id="grid2", - column_defs=column_defs, - row_data=row_data, - aligned_grids=["grid1"], - width="100%", - ), - class_name="flex flex-col gap-y-6 w-full" - ) - -``` - -```md alert warning -# The pivot functionality does not work with aligned grids. This is because pivoting data changes the columns, which would make the aligned grids incompatible, as they are no longer sharing the same set of columns. -``` diff --git a/docs/enterprise/ag_grid/cell-selection.md b/docs/enterprise/ag_grid/cell-selection.md deleted file mode 100644 index 15effee2066..00000000000 --- a/docs/enterprise/ag_grid/cell-selection.md +++ /dev/null @@ -1,288 +0,0 @@ ---- -title: "Cell Selection" -order: 8 ---- - -```python exec -from pcweb.pages.docs import enterprise -``` - -# Cell Selection - -AG Grid provides powerful cell selection capabilities that allow users to select individual cells or ranges of cells. This feature is essential for data manipulation, copying, and advanced interactions like fill handle operations. - -## Range Selection - -To enable cell selection in your AG Grid, set the `cell_selection` prop to `True`. This automatically enables both single cell selection and range selection capabilities. - -### Basic Selection Example - -```python demo exec -import reflex as rx -import reflex_enterprise as rxe -import pandas as pd - -class CellSelectionState(rx.State): - data: list[dict] = [] - - @rx.event - def load_data(self): - df = pd.read_json("https://www.ag-grid.com/example-assets/olympic-winners.json") - self.data = df.head(10).to_dict("records") - - @rx.event - def echo_selection(self, ranges: list[dict], started: bool, finished: bool): - if finished: - yield rx.toast(f"Selected ranges: {ranges}") - -column_defs = [ - {"field": "athlete", "width": 150}, - {"field": "age", "width": 90}, - {"field": "country", "width": 120}, - {"field": "year", "width": 90}, - {"field": "sport", "width": 120}, - {"field": "gold", "width": 100}, - {"field": "silver", "width": 100}, - {"field": "bronze", "width": 100}, -] - -def basic_cell_selection(): - return rx.vstack( - rx.text("Click and drag to select cells. Selection info will appear in a toast.", size="2"), - rxe.ag_grid( - id="basic_cell_selection_grid", - column_defs=column_defs, - row_data=CellSelectionState.data, - cell_selection=True, - on_cell_selection_changed=CellSelectionState.echo_selection, - width="100%", - height="400px", - ), - on_mount=CellSelectionState.load_data, - width="100%", - ) -``` - -### Advanced Selection Event Handling - -For more sophisticated selection handling, you can process the selection ranges to calculate detailed information: - -```python demo exec -import reflex as rx -import reflex_enterprise as rxe -import pandas as pd - -class AdvancedSelectionState(rx.State): - data: list[dict] = [] - - @rx.event - def load_data(self): - df = pd.DataFrame({ - "name": ["Alice", "Bob", "Charlie", "Diana", "Eve"], - "score": [85, 92, 78, 96, 88], - "grade": ["B", "A", "C", "A", "B"], - "attempts": [3, 2, 4, 1, 3] - }) - self.data = df.to_dict("records") - - @rx.event - def handle_selection(self, ranges: list[dict], started: bool, finished: bool): - if finished and ranges: - total_cells = sum( - (r.get("endRow", 0) - r.get("startRow", 0) + 1) * - len(r.get("columns", [])) - for r in ranges - ) - yield rx.toast(f"Selected {total_cells} cells across {len(ranges)} ranges") - -editable_column_defs = [ - {"field": "name", "width": 120}, - {"field": "score", "width": 100, "editable": True}, - {"field": "grade", "width": 100, "editable": True}, - {"field": "attempts", "width": 120, "editable": True}, -] - -def advanced_selection_example(): - return rx.vstack( - rx.text("Select ranges of cells. Try selecting multiple ranges by holding Ctrl/Cmd.", size="2"), - rxe.ag_grid( - id="advanced_selection_grid", - column_defs=editable_column_defs, - row_data=AdvancedSelectionState.data, - cell_selection=True, - on_cell_selection_changed=AdvancedSelectionState.handle_selection, - width="100%", - height="300px", - ), - on_mount=AdvancedSelectionState.load_data, - width="100%", - ) -``` - -## Fill Handle - -The fill handle is a powerful feature that allows users to quickly fill cells by dragging from a selected cell or range. When enabled, a small square appears at the bottom-right corner of the selection that users can drag to fill adjacent cells. - -### Enabling Fill Handle - -To enable the fill handle, configure the `cell_selection` prop with a dictionary containing the handle configuration: - -```python -cell_selection={ - "handle": { - "mode": "fill", # Enable fill handle - } -} -``` - -### Fill Handle Events - -When using the fill handle, it will trigger `on_cell_value_changed` for each cell receiving a fill value. This allows your backend to handle the data changes appropriately. - -```python demo exec -import reflex as rx -import reflex_enterprise as rxe -import pandas as pd - -class FillHandleState(rx.State): - data: list[dict] = [] - change_log: list[str] = [] - - @rx.event - def load_data(self): - df = pd.DataFrame({ - "item": ["Apple", "Banana", "Cherry", "Date", "Elderberry"], - "quantity": [10, 15, 8, 12, 20], - "price": [1.50, 0.75, 2.00, 3.00, 4.50], - "total": [15.00, 11.25, 16.00, 36.00, 90.00] - }) - self.data = df.to_dict("records") - - @rx.event - def handle_cell_change(self, data: dict): - row_index = data.get("rowIndex", 0) - field = data.get("colId", "") - new_value = data.get("newValue", "") - old_value = data.get("oldValue", "") - - change_msg = f"Row {row_index + 1}, {field}: '{old_value}' → '{new_value}'" - self.change_log = [change_msg] + self.change_log[:9] # Keep last 10 changes - - # Update the data - if 0 <= row_index < len(self.data): - self.data[row_index][field] = new_value - -fill_column_defs = [ - {"field": "item", "width": 120}, - {"field": "quantity", "width": 100, "editable": True, "type": "numericColumn"}, - {"field": "price", "width": 100, "editable": True, "type": "numericColumn"}, - {"field": "total", "width": 100, "editable": True, "type": "numericColumn"}, -] - -def fill_handle_example(): - return rx.vstack( - rx.text("Select a cell and drag the fill handle (small square at bottom-right) to fill adjacent cells.", size="2"), - rxe.ag_grid( - id="fill_handle_grid", - column_defs=fill_column_defs, - row_data=FillHandleState.data, - cell_selection={ - "handle": { - "mode": "fill", # Enable fill handle - } - }, - on_cell_value_changed=FillHandleState.handle_cell_change, - width="100%", - height="300px", - ), - rx.divider(), - rx.text("Recent Changes:", weight="bold", size="3"), - rx.cond( - FillHandleState.change_log, - rx.vstack( - rx.foreach( - FillHandleState.change_log, - lambda change: rx.text(change, size="1", color="gray") - ), - spacing="1", - ), - rx.text("No changes yet", size="2", color="gray") - ), - on_mount=FillHandleState.load_data, - width="100%", - spacing="4", - ) -``` - -## Advanced Configuration Options - -You can further customize cell selection behavior using additional configuration options: - -```python demo exec -import reflex as rx -import reflex_enterprise as rxe -import pandas as pd - -class ConfigurationState(rx.State): - data: list[dict] = [] - - @rx.event - def load_data(self): - df = pd.DataFrame({ - "id": range(1, 8), - "name": ["Product A", "Product B", "Product C", "Product D", "Product E", "Product F", "Product G"], - "category": ["Electronics", "Clothing", "Electronics", "Books", "Clothing", "Electronics", "Books"], - "price": [299.99, 49.99, 199.99, 24.99, 79.99, 399.99, 19.99], - "stock": [15, 32, 8, 45, 23, 12, 67] - }) - self.data = df.to_dict("records") - -configuration_column_defs = [ - {"field": "id", "width": 80}, - {"field": "name", "width": 150, "editable": True}, - {"field": "category", "width": 120}, - {"field": "price", "width": 100, "editable": True, "type": "numericColumn"}, - {"field": "stock", "width": 100, "editable": True, "type": "numericColumn"}, -] - -def configuration_example(): - return rx.vstack( - rx.text("Cell selection with additional configuration options", size="2"), - rxe.ag_grid( - id="configuration_grid", - column_defs=configuration_column_defs, - row_data=ConfigurationState.data, - cell_selection={ - "handle": { - "mode": "fill", - } - }, - enable_cell_text_selection=True, # Allow text selection within cells - suppress_cell_focus=False, # Allow cell focus - width="100%", - height="350px", - ), - on_mount=ConfigurationState.load_data, - width="100%", - ) -``` - -## Key Features - -- **Cell Selection**: Enable with `cell_selection=True` for both single cell and range selection capabilities -- **Fill Handle**: Configure with `cell_selection={"handle": {"mode": "fill"}}` for drag-to-fill functionality -- **Event Handling**: Use `on_cell_selection_changed` to respond to selection changes -- **Value Changes**: Use `on_cell_value_changed` to handle individual cell edits and fill operations -- **Text Selection**: Enable `enable_cell_text_selection=True` to allow text selection within cells - -## Best Practices - -1. **Use cell_selection configuration**: Both single cell and range selection are automatically enabled with `cell_selection=True`, providing all necessary selection capabilities for fill operations. - -2. **Handle cell value changes**: When using fill handle, implement `on_cell_value_changed` to process the data updates in your backend. - -3. **Provide user feedback**: Use toasts or other UI elements to confirm selection actions and data changes. - -4. **Consider performance**: For large datasets, be mindful of the performance impact of frequent cell value change events. - -5. **Validate fill operations**: Implement validation logic in your `on_cell_value_changed` handler to ensure data integrity. diff --git a/docs/enterprise/ag_grid/column-defs.md b/docs/enterprise/ag_grid/column-defs.md deleted file mode 100644 index 0823dda0fff..00000000000 --- a/docs/enterprise/ag_grid/column-defs.md +++ /dev/null @@ -1,37 +0,0 @@ ---- -order: 1 ---- - -```python exec -from pcweb.pages.docs import enterprise -``` - -# Column Definitions - -## Basic Columns - -AgGrid allows you to define the columns of your grid, passed to the prop `column_defs`. Each dictionary represents a column. - -```md alert warning -# If you are converting from other AG Grid implementation, we also support camelCase for the name of the properties. -``` - -Here we define a grid with 3 columns: - -```python -column_defs = [ - {"field": "direction"}, - {"field": "strength"}, - {"field": "frequency"}, -] -``` - -To set default properties for all your columns, you can define `default_col_def` in your grid: - -```python -default_col_def = { - "sortable": True, - "filter": True, - "resizable": True, -} -``` diff --git a/docs/enterprise/ag_grid/index.md b/docs/enterprise/ag_grid/index.md deleted file mode 100644 index 322b79269d4..00000000000 --- a/docs/enterprise/ag_grid/index.md +++ /dev/null @@ -1,678 +0,0 @@ ---- -title: "AgGrid Overview" -order: 3 ---- - -```python exec -from pcweb.pages.docs import enterprise -``` - -# AG Grid - -AG Grid is a powerful, feature-rich data grid component that brings enterprise-grade table functionality to your Reflex applications. With support for sorting, filtering, pagination, row selection, and much more, AG Grid transforms how you display and interact with tabular data. - -[Explore the full AG Grid showcase and examples](https://aggrid.reflex.run/) - -## Your First Reflex AG Grid - -A basic Reflex AG Grid contains column definitions `column_defs`, which define the columns to be displayed in the grid, and `row_data`, which contains the data to be displayed in the grid. - -Each grid also requires a unique `id`, which is needed to uniquely identify the Ag-Grid instance on the page. If you have multiple grids on the same page, each grid must have a unique `id` so that it can be correctly rendered and managed. - -```python demo exec -import reflex as rx -import reflex_enterprise as rxe -import pandas as pd - - -df = pd.read_csv("data/wind_dataset.csv") - -column_defs = [ - {"field": "direction"}, - {"field": "strength"}, - {"field": "frequency"}, -] - -def ag_grid_simple(): - return rxe.ag_grid( - id="ag_grid_basic_1", - row_data=df.to_dict("records"), - column_defs=column_defs, - width="100%", - ) -``` - -📊 **Dataset source:** [wind_dataset.csv](https://raw.githubusercontent.com/plotly/datasets/master/wind_dataset.csv) - -The format of the data passed to the `row_data` prop is a list of dictionaries. Each dictionary represents a row in the grid as seen below. - -```python -[ - \{"direction": "N", "strength": "0-1", "frequency": 0.5\}, - \{"direction": "NNE", "strength": "0-1", "frequency": 0.6\}, - \{"direction": "NE", "strength": "0-1", "frequency": 0.5\}, -] -``` - -The previous example showed the `column_defs` written out in full. You can also extract the required information from the dataframe's column names: - -```python demo exec -import reflex as rx -import reflex_enterprise as rxe -import pandas as pd - -df = pd.read_csv("data/wind_dataset.csv") - - -def ag_grid_simple_2(): - return rxe.ag_grid( - id="ag_grid_basic_2", - row_data=df.to_dict("records"), - column_defs=[{"field": i} for i in df.columns], - width="100%", - height="40vh", - ) -``` - -📊 **Dataset source:** [wind_dataset.csv](https://raw.githubusercontent.com/plotly/datasets/master/wind_dataset.csv) - -## Headers - -In the above example, the first letter of the field names provided are capitalized when displaying the header name. You can customize the header names by providing a `header_name` key in the column definition. In this example, the `header_name` is customized for the second and third columns. - -```python demo exec -import reflex as rx -import reflex_enterprise as rxe -import pandas as pd - - -df = pd.read_csv("data/gapminder2007.csv") - -column_defs = [ - {"field": "country"}, - {"field": "pop", "headerName": "Population"}, - {"field": "lifeExp", "headerName": "Life Expectancy"}, -] - -def ag_grid_simple_headers(): - return rxe.ag_grid( - id="ag_grid_basic_headers", - row_data=df.to_dict("records"), - column_defs=column_defs, - width="100%", - height="40vh", - ) -``` - -📊 **Dataset source:** [gapminder2007.csv](https://raw.githubusercontent.com/plotly/datasets/master/gapminder2007.csv) - -## Column Filtering - -Allow a user to filter a column by setting the `filter` key to `True` in the column definition. In this example we enable filtering for the first and last columns. - -```python demo exec -import reflex as rx -import reflex_enterprise as rxe -import pandas as pd - - -df = pd.read_csv("data/gapminder2007.csv") - -column_defs = [ - {"field": "country", "headerName": "Country", "filter": True}, - {"field": "pop", "headerName": "Population"}, - {"field": "lifeExp", "headerName": "Life Expectancy", "filter": True}, -] - -def ag_grid_simple_column_filtering(): - return rxe.ag_grid( - id="ag_grid_basic_column_filtering", - row_data=df.to_dict("records"), - column_defs=column_defs, - width="100%", - height="40vh", - ) -``` - -### Filter Types - -You can set `filter=True` to enable the default filter for a column. - -You can also set the filter type using the `filter` key. The following filter types are available: `ag_grid.filters.date`, `ag_grid.filters.number` and `ag_grid.filters.text`. These ensure that the input you enter to the filter is of the correct type. - -(`ag_grid.filters.set` and `ag_grid.filters.multi` are available with AG Grid Enterprise) - -```python demo exec -import reflex as rx -import reflex_enterprise as rxe -import pandas as pd - - -df = pd.read_csv("data/GanttChart-updated.csv") - -column_defs = [ - {"field": "Task", "filter": True}, - {"field": "Start", "filter": rxe.ag_grid.filters.date}, - {"field": "Duration", "filter": rxe.ag_grid.filters.number}, - {"field": "Resource", "filter": rxe.ag_grid.filters.text}, -] - -def ag_grid_simple_column_filtering(): - return rxe.ag_grid( - id="ag_grid_basic_column_filtering", - row_data=df.to_dict("records"), - column_defs=column_defs, - width="100%", - height="40vh", - ) -``` - -📊 **Dataset source:** [GanttChart-updated.csv](https://raw.githubusercontent.com/plotly/datasets/master/GanttChart-updated.csv) - -## Row Sorting - -By default, the rows can be sorted by any column by clicking on the column header. You can disable sorting of the rows for a column by setting the `sortable` key to `False` in the column definition. - -In this example, we disable sorting for the first column. - -```python demo exec -import reflex as rx -import reflex_enterprise as rxe -import pandas as pd - - -df = pd.read_csv("data/gapminder2007.csv") - -column_defs = [ - {"field": "country", "sortable": False}, - {"field": "pop", "headerName": "Population"}, - {"field": "lifeExp", "headerName": "Life Expectancy"}, -] - -def ag_grid_simple_row_sorting(): - return rxe.ag_grid( - id="ag_grid_basic_row_sorting", - row_data=df.to_dict("records"), - column_defs=column_defs, - width="100%", - height="40vh", - ) -``` - -📊 **Dataset source:** [gapminder2007.csv](https://raw.githubusercontent.com/plotly/datasets/master/gapminder2007.csv) - -## Row Selection - -Row Selection is enabled using the `row_selection` attribute. Setting it to `multiple` allows users to select multiple rows at a time. You can use the `checkbox_selection` column definition attribute to render checkboxes for selection. - -```python demo exec -import reflex as rx -import reflex_enterprise as rxe -import pandas as pd - - -df = pd.read_csv("data/gapminder2007.csv") - -column_defs = [ - {"field": "country", "checkboxSelection": True}, - {"field": "pop", "headerName": "Population"}, - {"field": "continent"}, -] - -def ag_grid_simple_row_selection(): - return rxe.ag_grid( - id="ag_grid_basic_row_selection", - row_data=df.to_dict("records"), - column_defs=column_defs, - row_selection={"mode":"multiple"}, - width="100%", - height="40vh", - ) -``` - -📊 **Dataset source:** [gapminder2007.csv](https://raw.githubusercontent.com/plotly/datasets/master/gapminder2007.csv) - -## Editing - -Enable Editing by setting the `editable` attribute to `True`. The cell editor is inferred from the cell data type. Set the cell editor type using the `cell_editor` attribute. - -There are 7 provided cell editors in AG Grid: - -1. `ag_grid.editors.text` -2. `ag_grid.editors.large_text` -3. `ag_grid.editors.select` -4. `ag_grid.editors.rich_select` -5. `ag_grid.editors.number` -6. `ag_grid.editors.date` -7. `ag_grid.editors.checkbox` - -In this example, we enable editing for the second and third columns. The second column uses the `number` cell editor, and the third column uses the `select` cell editor. - -The `on_cell_value_changed` event trigger is linked to the `cell_value_changed` event handler in the state. This event handler is called whenever a cell value is changed and changes the value of the backend var `_data_df` and the state var `data`. - -```python -import reflex as rx -import reflex_enterprise as rxe -import pandas as pd - -class AGGridEditingState(rx.State): - data: list[dict] = [] - _data_df: pd.DataFrame - - @rx.event - def load_data(self): - self._data_df = pd.read_csv("data/gapminder2007.csv") - self.data = self._data_df.to_dict("records") - - @rx.event - def cell_value_changed(self, row, col_field, new_value): - self._data_df.at[row, col_field] = new_value - self.data = self._data_df.to_dict("records") - yield rx.toast(f"Cell value changed, Row: {row}, Column: {col_field}, New Value: {new_value}") - - -column_defs = [ - \{"field": "country"\}, - \{"field": "pop", "headerName": "Population", "editable": True, "cellEditor": rxe.ag_grid.editors.number\}, - \{"field": "continent", "editable": True, "cellEditor": rxe.ag_grid.editors.select, "cellEditorParams": \{"values": ['Asia', 'Europe', 'Africa', 'Americas', 'Oceania']\}\}, -] - -def ag_grid_simple_editing(): - return rxe.ag_grid( - id="ag_grid_basic_editing", - row_data=AGGridEditingState.data, - column_defs=column_defs, - on_cell_value_changed=AGGridEditingState.cell_value_changed, - on_mount=AGGridEditingState.load_data, - width="100%", - height="40vh", - ) -``` - -## Pagination - -By default, the grid uses a vertical scroll. You can reduce the amount of scrolling required by adding pagination. To add pagination, set `pagination=True`. You can set the `pagination_page_size` to the number of rows per page and `pagination_page_size_selector` to a list of options for the user to select from. - -```python demo exec -import reflex as rx -import reflex_enterprise as rxe -import pandas as pd - -df = pd.read_csv("data/gapminder2007.csv") - -column_defs = [ - {"field": "country"}, - {"field": "pop", "headerName": "Population"}, - {"field": "lifeExp", "headerName": "Life Expectancy"}, -] - -def ag_grid_simple_pagination(): - return rxe.ag_grid( - id="ag_grid_basic_pagination", - row_data=df.to_dict("records"), - column_defs=column_defs, - pagination=True, - pagination_page_size=10, - pagination_page_size_selector=[10, 40, 100], - width="100%", - height="40vh", - ) -``` - -📊 **Dataset source:** [gapminder2007.csv](https://raw.githubusercontent.com/plotly/datasets/master/gapminder2007.csv) - -## AG Grid with State - -### Putting Data in State - -Assuming you want to make any edit to your data, you can put the data in State. This allows you to update the grid based on user input. Whenever the `data` var is updated, the grid will be re-rendered with the new data. - -```python -from typing import Any -import reflex as rx -import reflex_enterprise as rxe -import pandas as pd - -class AGGridState2(rx.State): - data: list[dict] = [] - - @rx.event - def load_data(self): - _df = pd.read_csv("data/gapminder2007.csv") - self.data = _df.to_dict("records") - -column_defs = [ - \{"field": "country"\}, - \{"field": "pop", "headerName": "Population"\}, - \{"field": "continent"\}, -] - -def ag_grid_state_2(): - return rxe.ag_grid( - id="ag_grid_state_2", - row_data=AGGridState2.data, - column_defs=column_defs, - on_mount=AGGridState2.load_data, - width="100%", - height="40vh", - ) -``` - -### Updating the Grid with State - -You can use State to update the grid based on a users input. In this example, we update the `column_defs` of the grid when a user clicks a button. - -```python -import reflex as rx -import reflex_enterprise as rxe -import pandas as pd - -class AgGridState(rx.State): - """The app state.""" - all_columns: list = [] - - two_columns: list = [] - column_defs: list = all_columns - n_clicks = 0 - - @rx.event - def init_columns(self): - self.all_columns = [ - \{"field": "country"\}, - \{"field": "pop"\}, - \{"field": "continent"\}, - \{"field": "lifeExp"\}, - \{"field": "gdpPercap"\}, - ] - self.two_columns = [ - \{"field": "country"\}, - \{"field": "pop"\}, - ] - self.column_defs = self.all_columns - - @rx.event - def update_columns(self): - self.n_clicks += 1 - if self.n_clicks % 2 != 0: - self.column_defs = self.two_columns - else: - self.column_defs = self.all_columns - - -df = pd.read_csv("data/gapminder2007.csv") - - -def ag_grid_simple_with_state(): - return rx.box( - rx.button("Toggle Columns", on_click=AgGridState.update_columns), - rxe.ag_grid( - id="ag_grid_basic_with_state", - row_data=df.to_dict("records"), - column_defs=AgGridState.column_defs, - on_mount=AgGridState.init_columns, - width="100%", - height="40vh", - ), - width="100%", - ) -``` - -📊 **Dataset source:** [gapminder2007.csv](https://raw.githubusercontent.com/plotly/datasets/master/gapminder2007.csv) - -## AG Grid with Data from a Database - -In this example, we will use a database to store the data. The data is loaded from a csv file and inserted into the database when the page is loaded using the `insert_dataframe_to_db` event handler. - -The data is then fetched from the database and displayed in the grid using the `data` computed var. - -When a cell value is changed, the data is updated in the database using the `cell_value_changed` event handler. - -```python -import reflex as rx -import reflex_enterprise as rxe -import pandas as pd -from sqlmodel import select - -class Country(rx.Model, table=True): - country: str - population: int - continent: str - - -class AGGridDatabaseState(rx.State): - - countries: list[Country] - - # Insert data from a csv loaded dataframe to the database (Do this on the page load) - @rx.event - def insert_dataframe_to_db(self): - data = pd.read_csv("data/gapminder2007.csv") - with rx.session() as session: - for _, row in data.iterrows(): - db_record = Country( - country=row['country'], - population=row['pop'], - continent=row['continent'], - ) - session.add(db_record) - session.commit() - - # Fetch data from the database using a computed variable - @rx.var - def data(self) -> list[dict]: - with rx.session() as session: - results = session.exec(select(Country)).all() - self.countries = [result.dict() for result in results] - return self.countries - - # Update the database when a cell value is changed - @rx.event - def cell_value_changed(self, row, col_field, new_value): - self.countries[row][col_field] = new_value - with rx.session() as session: - country = Country(**self.countries[row]) - session.merge(country) - session.commit() - yield rx.toast(f"Cell value changed, Row: \{row}, Column: \{col_field}, New Value: \{new_value}") - - -column_defs = [ - \{"field": "country"\}, - \{"field": "population", "headerName": "Population", "editable": True, "cellEditor": rxe.ag_grid.editors.number\}, - \{"field": "continent", "editable": True, "cellEditor": rxe.ag_grid.editors.select, "cellEditorParams": \{"values": ['Asia', 'Europe', 'Africa', 'Americas', 'Oceania']\}\}, -] - -def index(): - return rxe.ag_grid( - id="ag_grid_basic_editing", - row_data=AGGridDatabaseState.data, - column_defs=column_defs, - on_cell_value_changed=AGGridDatabaseState.cell_value_changed, - width="100%", - height="40vh", - ) - -# Add state and page to the app. -app = rx.App() -app.add_page(index, on_load=AGGridDatabaseState.insert_dataframe_to_db) -``` - -## Using AG Grid Enterprise - -AG Grid offers both community and enterprise versions. See the [AG Grid docs](https://www.ag-grid.com/archive/31.2.0/react-data-grid/licensing/) for details on purchasing a license key. - -To use an AG Grid Enterprise license key with Reflex AG Grid set the environment variable `AG_GRID_LICENSE_KEY`: - -```bash -export AG_GRID_LICENSE_KEY="your_license_key" -``` - -## column_def props - -The following props are available for `column_defs` as well as many others that can be found here: [AG Grid Column Def Docs](https://www.ag-grid.com/react-data-grid/column-properties/). (it is necessary to use snake_case for the keys in Reflex, unlike in the AG Grid docs where camelCase is used) - -- `field`: `str`: The field of the row object to get the cell's data from. -- `col_id`: `str | None`: The unique ID to give the column. This is optional. If missing, the ID will default to the field. -- `type`: `str | None`: The type of the column. -- `cell_data_type`: `bool | str | None`: The data type of the cell values for this column. Can either infer the data type from the row data (true - the default behaviour), define a specific data type (string), or have no data type (false). -- `hide`: `bool`: Set to true for this column to be hidden. -- `editable`: `bool | None`: Set to true if this column is editable, otherwise false. -- `filter`: `AGFilters | str | None`: Filter component to use for this column. Set to true to use the default filter. Set to the name of a provided filter to use that filter. (Check out the Filter Types section of this page for more information) -- `floating_filter`: `bool`: Whether to display a floating filter for this column. -- `header_name`: `str | None`: The name to render in the column header. If not specified and field is specified, the field name will be used as the header name. -- `header_tooltip`: `str | None`: Tooltip for the column header. -- `checkbox_selection`: `bool | None`: Set to true to render a checkbox for row selection. -- `cell_editor`: `AGEditors | str | None`: Provide your own cell editor component for this column's cells. (Check out the Editing section of this page for more information) -- `cell_editor_params`: `dict[str, list[Any]] | None`: Params to be passed to the cellEditor component. - -## Functionality you need is not available/working in Reflex - -All AGGrid options found in this [documentation](https://www.ag-grid.com/react-data-grid/reference/) are mapped in rxe.ag_grid, but some features might not have been fully tested, due to the sheer number of existing features in the underlying AG Grid library. - -If one of the ag_grid props does not import the expected module, you can pass it manually via the props `community_modules` or `enterprise_modules`, which expect a `set[str]` of the module names. You will get a warning in the browser console if a module is missing, so you can check there if a feature is not working as expected. - -You can also report the missing module on our discord or GitHub issues page of the main Reflex repository. - -Best practice is to create a single instance of `ag_grid.api()` with the same `id` as the `id` of the `ag_grid` component that is to be referenced, `"ag_grid_basic_row_selection"` in this first example. - -The example below uses the `select_all()` and `deselect_all()` methods of the AG Grid API to select and deselect all rows in the grid. This method is not available in Reflex directly. Check out this [documentation](https://www.ag-grid.com/react-data-grid/grid-api/#reference-selection-selectAll) to see what the methods look like in the AG Grid docs. - -```md alert info -# Ensure that the docs are set to React tab in AG Grid -``` - -```python demo exec -import reflex as rx -import reflex_enterprise as rxe -import pandas as pd - -df = pd.read_csv( - "data/gapminder2007.csv" -) - -column_defs = [ - {"field": "country", "checkboxSelection": True}, - {"field": "pop"}, - {"field": "continent"}, -] - -def ag_grid_api_simple(): - my_api = rxe.ag_grid.api(id="ag_grid_basic_row_selection") - return rx.vstack( - rxe.ag_grid( - id="ag_grid_basic_row_selection", - row_data=df.to_dict("records"), - column_defs=column_defs, - row_selection="single", - width="100%", - height="40vh", - ), - rx.button("Select All", on_click=my_api.select_all()), - rx.button("Deselect All", on_click=my_api.deselect_all()), - spacing="4", - width="100%", - ) -``` - -📊 **Dataset source:** [gapminder2007.csv](https://raw.githubusercontent.com/plotly/datasets/master/gapminder2007.csv) - -The react code for the `select_all()` event handler is `selectAll = (source?: SelectionEventSourceType) => void;`. - -To use this in Reflex as you can see, it should be called in snake case rather than camel case. The `void` means it doesn't return anything. The `source?` indicates that it takes an optional `source` argument. - -````md alert info -# Another way to use the AG Grid API - -It is also possible to use the AG Grid API directly with the event trigger (`on_click`) of the component. This removes the need to create a variable `my_api`. This is shown in the example below. It is necessary to use the `id` of the `ag_grid` component that is to be referenced. - -```python -rx.button("Select all", on_click=rxe.ag_grid.api(id="ag_grid_basic_row_selection").select_all()), -``` -```` - -### More examples - -The following example lets a user [export the data as a csv](https://www.ag-grid.com/javascript-data-grid/grid-api/#reference-export-exportDataAsCsv) and [adjust the size of columns to fit the available horizontal space](https://www.ag-grid.com/javascript-data-grid/grid-api/#reference-columnSizing-sizeColumnsToFit). (Try resizing the screen and then clicking the resize columns button) - -```python demo exec -import reflex as rx -import reflex_enterprise as rxe -import pandas as pd - -df = pd.read_csv( - "data/gapminder2007.csv" -) - -column_defs = [ - {"field": "country", "checkboxSelection": True}, - {"field": "pop"}, - {"field": "continent"}, -] - -def ag_grid_api_simple2(): - my_api = rxe.ag_grid.api(id="ag_grid_export_and_resize") - return rx.vstack( - rxe.ag_grid( - id="ag_grid_export_and_resize", - row_data=df.to_dict("records"), - column_defs=column_defs, - width="100%", - height="40vh", - ), - rx.button("Export", on_click=my_api.export_data_as_csv()), - rx.button("Resize Columns", on_click=my_api.size_columns_to_fit()), - spacing="4", - width="100%", - ) -``` - -📊 **Dataset source:** [gapminder2007.csv](https://raw.githubusercontent.com/plotly/datasets/master/gapminder2007.csv) - -The react code for both of these is shown below. The key point to see is that both of these functions return `void` and therefore does not return anything. - -`exportDataAsCsv = (params?: CsvExportParams) => void;` - -`sizeColumnsToFit = (paramsOrGridWidth?: ISizeColumnsToFitParams | number) => void;` - -### Example with a Return Value - -This example shows how to get the data from `ag_grid` as a [csv on the backend](https://www.ag-grid.com/javascript-data-grid/grid-api/#reference-export-getDataAsCsv). The data that was passed to the backend is then displayed as a toast with the data. - -```python -import reflex as rx -import reflex_enterprise as rxe -import pandas as pd - -class AGGridStateAPI(rx.State): - def handle_get_data(self, data: str): - yield rx.toast(f"Got CSV data: {data}") - -df = pd.read_csv( - "data/gapminder2007.csv" -) - -column_defs = [ - \{"field": "country", "checkboxSelection": True\}, - \{"field": "pop"\}, - \{"field": "continent"\}, -] - -def ag_grid_api_argument(): - my_api = rxe.ag_grid.api(id="ag_grid_get_data_as_csv") - return rx.vstack( - rxe.ag_grid( - id="ag_grid_get_data_as_csv", - row_data=df.to_dict("records"), - column_defs=column_defs, - width="100%", - height="40vh", - ), - rx.button("Get CSV data on backend", on_click=my_api.get_data_as_csv(callback=AGGridStateAPI.handle_get_data)), - spacing="4", - width="100%", - ) -``` - -The react code for the `get_data_as_csv` method of the AG Grid API is `getDataAsCsv = (params?: CsvExportParams) => string | undefined;`. Here the function returns a `string` (or undefined). - -In Reflex to handle this returned value it is necessary to pass a `callback` as an argument to the `get_data_as_csv` method that will get the returned value. In this example the `handle_get_data` event handler is passed as the callback. This event handler will be called with the returned value from the `get_data_as_csv` method. diff --git a/docs/enterprise/ag_grid/model-wrapper.md b/docs/enterprise/ag_grid/model-wrapper.md deleted file mode 100644 index 27983ef587d..00000000000 --- a/docs/enterprise/ag_grid/model-wrapper.md +++ /dev/null @@ -1,64 +0,0 @@ ---- -order: 6 ---- - -# Model Wrapper - -A model wrapper is an utility used to wrap a database model and provide a consistent interface over it. It allows automatically adding new rows to the database, updating existing rows, and deleting rows. - -## Default Model Wrapper - -You can use the basic functionality of the model wrapper by using the `rxe.model_wrapper` function. This function takes a database model and returns a wrapper object that can be used to interact with the model. - -```python -import reflex_enterprise as rxe - -def index_page(): - return rxe.model_wrapper(class_model=MyModel) -``` - -By default the model_wrapper use the infinite rows model from AgGrid. - -## Custom Model Wrapper - -If the default model wrapper does not fit your needs, you can create a custom model wrapper by subclassing the `rxe.ModelWrapper` class. This allows you to customize the behavior of the model wrapper to fit your specific use case. - -```python -import reflex_enterprise as rxe - -class MyCustomWrapper(rxe.ModelWrapper[MyModel]): - pass -``` - -In the custom model wrapper, you can override the following methods: - -- `_get_columns_defs` -- `_get_data` -- `_row_count` -- `on_value_setter` - -to modify how the model wrapper will behave. - -## SSRM Model Wrapper - -The SSRM model wrapper, used with `rxe.model_wrapper_ssrm`, is a version of the model wrapper that allows you to use the ServerSideRowModel of AgGrid. - -```python -import reflex_enterprise as rxe - -def index_page(): - return rxe.model_wrapper_ssrm(class_model=MyModel) -``` - -## SSRM Custom Model Wrapper - -In the same way you can extend the default model wrapper, you can extend the SSRM custom model wrapper by subclassing the `rxe.ModelWrapperSSRM` class. This allows you to customize the behavior of the model wrapper to fit your specific use case. - -```python -import reflex_enterprise as rxe - -class MyCustomSSRMWrapper(rxe.ModelWrapperSSRM[MyModel]): - pass -``` - -The overridable methods are the same as the standard model wrapper. diff --git a/docs/enterprise/ag_grid/pivot-mode.md b/docs/enterprise/ag_grid/pivot-mode.md deleted file mode 100644 index 8ec4ed58878..00000000000 --- a/docs/enterprise/ag_grid/pivot-mode.md +++ /dev/null @@ -1,128 +0,0 @@ -```python exec -import reflex as rx -import reflex_enterprise as rxe -from pcweb.pages.docs import enterprise -``` - -# Pivot Mode - -Pivot mode allows you to visualize your data in a different way than how they are originally structured in the data source. When pivoting on a column, the values in that column will be used as column headers. This allows you to see the data in a more compact way, and can be useful when you have a lot of data to display. - -To enable pivot mode, set the `pivot_mode` property to `True` in the grid props. Once pivot mode is enabled, you can define which column to pivot on by setting the `pivot` property in a column definition. In addition to the pivot column, at least one column definition must have `row_group` property set to `True` to define the row grouping. - -You can also define how rows are aggregated by passing the `agg_func` property in the column definition. The `agg_func` property should be set to a string that represents the aggregation function to use. The built-in aggregation functions are `sum`, `min`, `max`, `count`, `avg`, `first`, and `last`. - -You can find a live example here: [Pivot Mode Example](https://aggrid.reflex.run/pivot). - -```python demo exec -import pandas as pd -import reflex as rx - -import reflex_enterprise as rxe - -# Olympic winners data (originally from https://www.ag-grid.com/example-assets/olympic-winners.json) -df = pd.read_json("data/olympic-winners.json") - -def pivot_page(): - return rxe.ag_grid( - id="sandbox_grid", - column_defs=[ - {"field": "country", "row_group": True}, - {"field": "sport", "pivot": True}, - {"field": "year", "pivot": True}, - {"field": "gold", "aggFunc": "sum"}, - ], - loading=False, - row_data=df.to_dict("records"), - default_col_def={ - "flex": 1, - "min_width": 130, - "enable_value": True, - "enable_row_group": True, - "enable_pivot": True, - }, - auto_group_column_def={ - "minWidth": 200, - "pinned": "left", - }, - pivot_mode=True, - side_bar="columns", - pivot_panel_show="always", - width="100%", - height="500px", - ), -``` - -# Pivot using State - -```python demo exec -import pandas as pd -import reflex as rx - -import reflex_enterprise as rxe - -df = pd.read_csv("data/wind_dataset.csv") - - -class PivotState(rx.State): - """State for the sandbox page.""" - - pivot = False - row_grouping = False - - @rx.event - def toggle_pivot(self): - """Toggle the pivot.""" - self.pivot = not self.pivot - - @rx.event - def toggle_row_grouping(self): - """Toggle the row grouping.""" - self.row_grouping = not self.row_grouping - - -def sandbox_page(): - """Sandbox page.""" - return rx.vstack( - rx.hstack( - rx.text("Toggle Pivot"), - rx.switch( - on_click=PivotState.toggle_pivot, - name="pivot", - checked=PivotState.pivot, - ), - rx.text("Toggle Row Grouping"), - rx.switch( - on_click=PivotState.toggle_row_grouping, - name="row_grouping", - checked=PivotState.row_grouping, - ) - ), - rxe.ag_grid( - id="sandbox_grid", - column_defs=[ - rxe.ag_grid.column_def( - field="direction", - pivot=True, - ), - rxe.ag_grid.column_def( - field="strength", - ), - rxe.ag_grid.column_def( - field="frequency", - agg_func="count", - row_group=PivotState.row_grouping, - ), - ], - row_data=df.to_dict("records"), - pivot_mode=PivotState.pivot, - pivot_panel_show="onlyWhenPivoting", - width="100%", - height="500px", - ), - width="100%", - ) - -``` - -📊 **Dataset source:** [wind_dataset.csv](https://raw.githubusercontent.com/plotly/datasets/master/wind_dataset.csv) diff --git a/docs/enterprise/ag_grid/theme.md b/docs/enterprise/ag_grid/theme.md deleted file mode 100644 index e3444b14ee3..00000000000 --- a/docs/enterprise/ag_grid/theme.md +++ /dev/null @@ -1,63 +0,0 @@ ---- -order: 3 ---- - -```python exec -import reflex as rx -import reflex_enterprise as rxe -from pcweb.pages.docs import enterprise -``` - -# Themes - -```md alert warning -# Only the old theme API of AG Grid is currently supported. The new theme API is not supported yet. -``` - -You can style your grid with a theme. AG Grid includes the following themes: - -1. `quartz` -2. `alpine` -3. `balham` -4. `material` - -The grid uses `quartz` by default. To use any other theme, set it using the `theme` prop, i.e. `theme="alpine"`. - -```python -import reflex as rx -import reflex_enterprise as rxe -import pandas as pd - -class AGGridThemeState(rx.State): - """The app state.""" - - theme: str = "quartz" - themes: list[str] = ["quartz", "balham", "alpine", "material"] - -df = pd.read_csv("data/gapminder2007.csv") - -column_defs = [ - {"field": "country"}, - {"field": "pop", "headerName": "Population"}, - {"field": "lifeExp", "headerName": "Life Expectancy"}, -] - -def ag_grid_simple_themes(): - return rx.vstack( - rx.hstack( - rx.text("Theme:"), - rx.select(AGGridThemeState.themes, value=AGGridThemeState.theme, on_change=AGGridThemeState.set_theme), - ), - rxe.ag_grid( - id="ag_grid_basic_themes", - row_data=df.to_dict("records"), - column_defs=column_defs, - theme=AGGridThemeState.theme, - width="100%", - height="40vh", - ), - width="100%", - ) -``` - -📊 **Dataset source:** [gapminder2007.csv](https://raw.githubusercontent.com/plotly/datasets/master/gapminder2007.csv) diff --git a/docs/enterprise/ag_grid/value-transformers.md b/docs/enterprise/ag_grid/value-transformers.md deleted file mode 100644 index 75bfd1ff61d..00000000000 --- a/docs/enterprise/ag_grid/value-transformers.md +++ /dev/null @@ -1,75 +0,0 @@ ---- -order: 2 ---- - -```python exec -import reflex as rx -import reflex_enterprise as rxe -from pcweb.pages.docs import enterprise -``` - -# Value Transformers - -AgGrid allow you to apply transformers based on the column of your grid. This allow you to perform operations on the data before displaying it on the grid, without having to pre-process the data on the backend, reducing the load on your application. - -TOC: - -- [Value Getter](#value-getter) -- [Value Formatter](#value-formatter) - -## Value Getter - -`value_getter` is a property of the column definition that allows you to define a function that will be called to get the value of the cell. This function will receive the row data as a parameter and should return the value to be displayed on the cell. - -If you have two columns `col_a` and `col_b` and you want to display the sum of these two columns in a third column `sum`, you can define the `value_getter` of `sum` as follows: - -```python demo exec -import reflex as rx -import reflex_enterprise as rxe -import pandas as pd - - -df = pd.DataFrame({"col_a": [1, 2, 3, 4, 5], "col_b": [10, 20, 30, 40, 50]}) - -column_defs = [ - {"field": "col_a", "header_name": "Column A"}, - {"field": "col_b", "header_name": "Column B"}, - {"field": "sum", "header_name": "Sum", "value_getter": "params.data.col_a + params.data.col_b"}, - rxe.ag_grid.column_def(field="diff", header_name="Difference", value_getter="params.data.col_b - params.data.col_a"), -] - -def ag_grid_value_getter(): - return rxe.ag_grid( - id="ag_grid_value_getter", - row_data=df.to_dict("records"), - column_defs=column_defs, - width="100%", - ) -``` - -## Value Formatter - -`value_formatter` is a property of the column definition that allows you to define a function that will be called to format the value of the cell. This function will receive the value of the cell as a parameter and should return the formatted value to be displayed on the cell. - -If you have a column `price` and you want to display the price with a currency symbol, you can define the `value_formatter` of `price` as follows: - -```python demo exec -import reflex as rx -import reflex_enterprise as rxe -import pandas as pd - -df = pd.DataFrame({"product_name":["Product A", "Product B", "Product C", "Product D", "Product E"], "price": [100, 200, 300, 400, 500]}) -column_defs = [ - {"field": "product_name", "header_name": "Product Name"}, - {"field": "price", "header_name": "Price ($)", "value_formatter": "'$' + params.value"}, - rxe.ag_grid.column_def(col_id="price_eur", header_name="Price (€)", value_formatter="params.data.price + ' €'"), -] - -def ag_grid_value_formatter(): - return rxe.ag_grid( - id="ag_grid_value_formatter", - row_data=df.to_dict("records"), - column_defs=column_defs, - width="100%", - ) -``` diff --git a/docs/enterprise/built-with-reflex.md b/docs/enterprise/built-with-reflex.md deleted file mode 100644 index 85917a81fa2..00000000000 --- a/docs/enterprise/built-with-reflex.md +++ /dev/null @@ -1,17 +0,0 @@ -# Built with Reflex Badge - -The "Built with Reflex" badge appears in the bottom right corner of apps using reflex-enterprise components. - -## Removing the Badge - -To remove the badge, you need to be on the Enterprise tier. - -## Configuration - -```python -import reflex_enterprise as rxe - -config = rxe.Config( - show_built_with_reflex=False, # Requires paid tier -) -``` diff --git a/docs/enterprise/components.md b/docs/enterprise/components.md deleted file mode 100644 index 01d2b049cdc..00000000000 --- a/docs/enterprise/components.md +++ /dev/null @@ -1,119 +0,0 @@ ---- -title: Enterprise Components ---- - -```python exec -import reflex as rx -from pcweb.pages.docs import enterprise -from pcweb.templates.docpage import h1_comp, text_comp_2 -from pcweb.components.icons import get_icon - -def enterprise_component_grid(): - sections = [ - { - "title": "AG Grid", - "description": "Advanced data grid with sorting, filtering, editing, and pagination", - "link": enterprise.ag_grid.index.path, - "components": [ - ("Overview", enterprise.ag_grid.index.path), - ("Column Definitions", enterprise.ag_grid.column_defs.path), - ("Aligned Grids", enterprise.ag_grid.aligned_grids.path), - ("Model Wrapper", enterprise.ag_grid.model_wrapper.path), - ("Pivot Mode", enterprise.ag_grid.pivot_mode.path), - ("Theme", enterprise.ag_grid.theme.path), - ("Value Transformers", enterprise.ag_grid.value_transformers.path), - ] - }, - { - "title": "AG Chart", - "description": "Interactive charts and data visualization", - "link": enterprise.ag_chart.path, - "components": [ - ("Overview", enterprise.ag_chart.path), - ] - }, - { - "title": "Interactive Components", - "description": "Drag-and-drop and mapping functionality", - "link": enterprise.drag_and_drop.path, - "components": [ - ("Drag and Drop", enterprise.drag_and_drop.path), - ("Mapping", enterprise.map.index.path), - ] - }, - { - "title": "Mantine", - "description": "Rich UI components from Mantine library", - "link": enterprise.mantine.index.path, - "components": [ - ("Overview", enterprise.mantine.index.path), - ("Autocomplete", enterprise.mantine.autocomplete.path), - ("Collapse", enterprise.mantine.collapse.path), - ("Combobox", enterprise.mantine.combobox.path), - ("JSON Input", enterprise.mantine.json_input.path), - ("Loading Overlay", enterprise.mantine.loading_overlay.path), - ("Multi Select", enterprise.mantine.multi_select.path), - ("Number Formatter", enterprise.mantine.number_formatter.path), - ("Pill", enterprise.mantine.pill.path), - ("Ring Progress", enterprise.mantine.ring_progress.path), - ("Semi Circle Progress", enterprise.mantine.semi_circle_progress.path), - ("Spoiler", enterprise.mantine.spoiler.path), - ("Tags Input", enterprise.mantine.tags_input.path), - ("Timeline", enterprise.mantine.timeline.path), - ("Tree", enterprise.mantine.tree.path), - ] - } - ] - - cards = [] - for section in sections: - cards.append( - rx.box( - rx.link( - rx.el.h1( - section["title"], - class_name="font-large text-slate-12", - ), - get_icon("new_tab", class_name="text-slate-11 [&>svg]:size-4"), - href=section["link"], - underline="none", - class_name="px-4 py-2 bg-slate-1 hover:bg-slate-3 transition-bg flex flex-row justify-between items-center !text-slate-12", - ), - rx.text( - section["description"], - class_name="px-4 py-2 font-small text-slate-9 border-t border-slate-5", - ), - rx.box( - *[ - rx.link( - comp[0], - href=comp[1], - class_name="font-small text-slate-11 hover:!text-violet-9 transition-color w-fit", - ) - for comp in section["components"] - ], - class_name="flex flex-col gap-2.5 px-4 py-2 border-t border-slate-5", - ), - class_name="flex flex-col border border-slate-5 rounded-xl bg-slate-2 shadow-large overflow-hidden", - ) - ) - - return rx.box( - *cards, - class_name="grid grid-cols-1 lg:grid-cols-2 gap-6", - ) - -component_grid = enterprise_component_grid() -``` - -```python eval -h1_comp(text="Enterprise Components") -``` - -```python eval -text_comp_2(text="Advanced UI components and features to enhance your Reflex applications. Available for free with the 'Built with Reflex' badge, or without the badge with an enterprise license.") -``` - -```python eval -component_grid -``` diff --git a/docs/enterprise/drag-and-drop.md b/docs/enterprise/drag-and-drop.md deleted file mode 100644 index cf38a732f64..00000000000 --- a/docs/enterprise/drag-and-drop.md +++ /dev/null @@ -1,586 +0,0 @@ ---- -title: Drag and Drop ---- - -```python exec -import reflex as rx -import reflex_enterprise as rxe -from pcweb.pages.docs import enterprise -``` - -# Drag and Drop - -Reflex Enterprise provides comprehensive drag and drop functionality for creating interactive UI elements using the `rxe.dnd` module. Built on top of react-dnd, it offers both high-level components for common use cases and low-level hooks for advanced scenarios. - -```md alert warning -# Important: Always decorate functions defining `rxe.dnd.draggable` components with `@rx.memo` to avoid compilation errors. -``` - -## Basic Usage - -### Simple Drag and Drop - -Here's a basic example showing how to create a draggable item and drop target: - -```python demo exec -import reflex as rx -import reflex_enterprise as rxe - -class BasicDndState(rx.State): - drop_count: int = 0 - - def increment_drop_count(self): - self.drop_count += 1 - - -@rx.memo -def draggable_card(): - return rxe.dnd.draggable( - rx.card( - rx.text("Drag me!", weight="bold"), - rx.text("I can be moved around"), - bg="blue.500", - color="white", - p=4, - cursor="grab", - width="200px", - height="100px" - ), - type="BasicCard", - item={"message": "Hello from draggable!"}, - ) - -def basic_drag_drop(): - return rx.vstack( - rx.text(f"Items dropped: {BasicDndState.drop_count}"), - rx.hstack( - draggable_card(), - rxe.dnd.drop_target( - rx.box( - "Drop Zone", - bg="gray.100", - border="2px dashed gray", - min_height="150px", - min_width="200px", - display="flex", - align_items="center", - justify_content="center", - font_weight="bold" - ), - accept=["BasicCard"], - on_drop=BasicDndState.increment_drop_count, - ), - spacing="4", - align="start" - ), - spacing="4" - ) -``` - -### Multi-Position Drag and Drop - -Create a draggable item that can be moved between multiple drop targets: - -```python demo exec -import reflex as rx -import reflex_enterprise as rxe - -class MultiPositionState(rx.State): - card_position: int = 0 - - def set_card_position(self, position: int): - self.card_position = position - - -@rx.memo -def movable_card(): - return rxe.dnd.draggable( - rx.card( - rx.text("Movable Card", weight="bold"), - rx.text("Position: " + MultiPositionState.card_position.to_string()), - bg="purple.500", - color="white", - p=4, - width="180px", - height="120px" - ), - type="MovableCard", - border="2px solid purple", - ) - -def drop_zone(position: int): - params = rxe.dnd.DropTarget.collected_params - return rxe.dnd.drop_target( - rx.cond( - MultiPositionState.card_position == position, - movable_card(), - rx.box( - f"Drop Zone {position}", - color="gray.600", - font_weight="bold" - ) - ), - width="200px", - height="200px", - border="2px solid red", - border_color=rx.cond(params.is_over, "green.500", "red.500"), - bg=rx.cond(params.is_over, "green.100", "blue.100"), - accept=["MovableCard"], - on_drop=lambda _: MultiPositionState.set_card_position(position), - display="flex", - align_items="center", - justify_content="center" - ) - -def multi_position_example(): - return rx.vstack( - rx.text("Drag the card between positions", weight="bold"), - rx.grid( - drop_zone(0), - drop_zone(1), - drop_zone(2), - drop_zone(3), - columns="2", - spacing="4" - ), - spacing="4" - ) -``` - -## Advanced Features - -### State Tracking with Collected Parameters - -Access drag and drop state information using collected parameters: - -```python demo exec -import reflex as rx -import reflex_enterprise as rxe - -class StateTrackingState(rx.State): - drag_info: str = "No drag activity" - - def set_drag_info(self, value: str): - self.drag_info = value - -@rx.memo -def tracked_draggable(): - drag_params = rxe.dnd.Draggable.collected_params - return rxe.dnd.draggable( - rx.card( - rx.text("Tracked Draggable"), - rx.text(rx.cond(drag_params.is_dragging, "Dragging...", "Ready to drag")), - bg=rx.cond(drag_params.is_dragging, "orange.500", "blue.500"), - color="white", - p=4, - opacity=rx.cond(drag_params.is_dragging, 0.5, 1.0) - ), - type="TrackedItem", - on_end=StateTrackingState.set_drag_info("Drag ended") - ) - -def tracked_drop_target(): - drop_params = rxe.dnd.DropTarget.collected_params - return rxe.dnd.drop_target( - rx.box( - rx.text("Smart Drop Zone"), - rx.text(rx.cond(drop_params.is_over, "Ready to receive!", "Waiting...")), - bg=rx.cond(drop_params.is_over, "green.200", "gray.100"), - border=rx.cond(drop_params.is_over, "2px solid green", "2px dashed gray"), - p=4, - min_height="150px", - display="flex", - flex_direction="column", - align_items="center", - justify_content="center" - ), - accept=["TrackedItem"], - on_drop=StateTrackingState.set_drag_info("Item successfully dropped!"), - on_hover=StateTrackingState.set_drag_info("Item hovering over drop zone") - ) - -def state_tracking_example(): - return rx.vstack( - rx.text(f"Status: {StateTrackingState.drag_info}"), - rx.hstack( - tracked_draggable(), - tracked_drop_target(), - spacing="4" - ), - spacing="4" - ) -``` - -### Dynamic Lists with Drag and Drop - -Create dynamic draggable lists using `rx.foreach`: - -```python demo exec -import reflex as rx -import reflex_enterprise as rxe - -class ListItem(rx.Base): - id: str - text: str - list_id: str - -class DynamicListState(rx.State): - list_a: list[ListItem] = [ - ListItem(id="1", text="Item 1", list_id="A"), - ListItem(id="2", text="Item 2", list_id="A"), - ListItem(id="3", text="Item 3", list_id="A"), - ] - list_b: list[ListItem] = [ - ListItem(id="4", text="Item 4", list_id="B"), - ListItem(id="5", text="Item 5", list_id="B"), - ] - - def move_item(self, item_data: dict, target_list: str): - item_id = item_data.get("id") - source_list = item_data.get("list_id") - - if not item_id or not source_list: - return - - # Find the item in the source list - source_items = getattr(self, f"list_{source_list.lower()}") - item_to_move = None - for item in source_items: - if item.id == item_id: - item_to_move = item - break - - if not item_to_move: - return - - # Remove from source list only - if source_list == "A": - self.list_a = [item for item in self.list_a if item.id != item_id] - else: - self.list_b = [item for item in self.list_b if item.id != item_id] - - # Create new item for target list - new_item = ListItem( - id=item_id, - text=item_to_move.text, - list_id=target_list - ) - - # Add to target list - if target_list == "A": - self.list_a.append(new_item) - else: - self.list_b.append(new_item) - -@rx.memo -def draggable_list_item(item: ListItem): - return rxe.dnd.draggable( - rx.card( - rx.text(item.text, weight="bold"), - rx.text(f"From List {item.list_id}", size="2", color="gray.600"), - p=3, - cursor="grab", - _hover={"bg": "gray.50"} - ), - type="ListItem", - item={"id": item.id, "text": item.text, "list_id": item.list_id}, - ) - -def droppable_list(title: str, items: list[ListItem], list_id: str): - return rxe.dnd.drop_target( - rx.vstack( - rx.text(title, weight="bold", size="5"), - rx.vstack( - rx.foreach(items, lambda item, index: draggable_list_item(item=item)), - spacing="2", - min_height="200px", - width="100%" - ), - bg="gray.50", - p=4, - border_radius="md", - border="2px dashed gray", - width="250px" - ), - accept=["ListItem"], - on_drop=lambda item: DynamicListState.move_item(item, list_id) - ) - -def dynamic_list_example(): - return rx.hstack( - droppable_list("List A", DynamicListState.list_a, "A"), - droppable_list("List B", DynamicListState.list_b, "B"), - spacing="6", - align="start" - ) -``` - -## Core Components - -### Draggable - -The `rxe.dnd.draggable` component makes any element draggable: - -**Key Properties:** - -- `type`: String identifier for drag type matching -- `item`: Data object passed to drop handlers -- `on_end`: Called when drag operation ends - -### Drop Target - -The `rxe.dnd.drop_target` component creates areas that accept draggable items: - -**Key Properties:** - -- `accept`: List of drag types this target accepts -- `on_drop`: Called when item is dropped -- `on_hover`: Called when item hovers over target - -### Collected Parameters - -Access real-time drag/drop state: - -**Draggable Parameters (`rxe.dnd.Draggable.collected_params`):** - -- `is_dragging`: Boolean indicating if item is being dragged - -**Drop Target Parameters (`rxe.dnd.DropTarget.collected_params`):** - -- `is_over`: Boolean indicating if draggable is hovering -- `can_drop`: Boolean indicating if drop is allowed - -# API Reference - -### rxe.dnd.draggable - -Creates a draggable component that can be moved around the interface. - -**Parameters:** - -- **`type`** (str, required): String identifier that must match the `accept` list of drop targets -- **`item`** (dict | Callable): Data object passed to drop handlers. Can be a static dictionary or a function that receives a `DragSourceMonitor` and returns data -- **`preview_options`** (dict): Configuration for the drag preview appearance -- **`options`** (dict): Additional drag source options like `dropEffect` -- **`on_end`** (EventHandler): Event handler called when drag operation completes -- **`can_drag`** (Callable): Function that determines if the item can be dragged -- **`is_dragging`** (Callable): Function to override the default dragging state detection -- **`collect`** (Callable): Function to collect custom properties from the drag monitor - -### rxe.dnd.drop_target - -Creates a drop target that can receive draggable items. - -**Parameters:** - -- **`accept`** (str | list[str], required): Drag type(s) this target accepts -- **`options`** (dict): Additional drop target configuration options -- **`on_drop`** (EventHandler): Event handler called when an item is dropped, receives the `item` data -- **`on_hover`** (EventHandler): Event handler called when an item hovers over the target -- **`can_drop`** (Callable): Function that determines if a specific item can be dropped -- **`collect`** (Callable): Function to collect custom properties from the drop monitor - -## Monitor Classes - -### DragSourceMonitor - -Provides information about the drag operation state: - -- **`is_dragging()`**: Returns `True` if this item is currently being dragged -- **`can_drag()`**: Returns `True` if the item can be dragged -- **`get_item()`**: Returns the item data being dragged -- **`get_item_type()`**: Returns the drag type string -- **`get_drop_result()`**: Returns the drop result (available in `on_end`) -- **`did_drop()`**: Returns `True` if the item was successfully dropped - -### DropTargetMonitor - -Provides information about the drop target state: - -- **`is_over()`**: Returns `True` if a draggable item is hovering over this target -- **`can_drop()`**: Returns `True` if the hovering item can be dropped -- **`get_item()`**: Returns the item data of the hovering draggable -- **`get_item_type()`**: Returns the drag type of the hovering item - -## Default Collected Parameters - -### Draggable.collected_params - -```python -{ - "is_dragging": bool, # True when this item is being dragged - "can_drag": bool # True when this item can be dragged -} -``` - -### DropTarget.collected_params - -```python -{ - "is_over": bool, # True when a draggable is hovering - "can_drop": bool, # True when the hovering item can be dropped - "item": dict | None # Data from the hovering draggable item -} -``` - -## Advanced Usage Examples - -### Data Passing with Item Parameter - -The `item` parameter allows you to pass data from draggable components to drop handlers: - -```python demo exec toggle -import reflex as rx -import reflex_enterprise as rxe - -class SimpleState(rx.State): - message: str = "No items dropped yet" - - def set_message_from_item(self, item: dict): - self.message = f"Dropped: {item['name']}" - -def simple_draggable(): - return rxe.dnd.draggable( - rx.box( - "Drag me!", - p=4, - bg="blue.100", - border="1px solid blue", - cursor="grab" - ), - type="simple", - item={"name": "test_item", "value": 42} - ) - -def simple_drop_target(): - return rxe.dnd.drop_target( - rx.box( - rx.text(SimpleState.message), - p=4, - bg="gray.100", - border="2px dashed gray", - min_height="100px" - ), - accept=["simple"], - on_drop=SimpleState.set_message_from_item - ) - -def item_data_example(): - return rx.vstack( - simple_draggable(), - simple_drop_target(), - spacing="4" - ) -``` - -### Custom Collect Functions - -The `collect` parameter allows you to access drag and drop state information in real-time: - -```python demo exec toggle -import reflex as rx -import reflex_enterprise as rxe - -class CollectState(rx.State): - drag_info: str = "No drag activity" - drop_info: str = "No drop activity" - - def handle_drop(self, item: dict): - self.drop_info = f"Dropped: {item.get('name', 'Unknown')}" - return rx.toast(f"Successfully dropped {item.get('name', 'item')}") - - - -def collect_draggable(): - params = rxe.dnd.Draggable.collected_params - return rxe.dnd.draggable( - rx.box( - rx.vstack( - rx.text("Drag me!", weight="bold"), - rx.text(f"Dragging: {params.is_dragging}", size="2"), - rx.text(f"Can drag: {params.can_drag}", size="2"), - spacing="1" - ), - p=4, - bg=rx.cond(params.is_dragging, "blue.200", "blue.100"), - border="1px solid blue", - cursor=rx.cond(params.is_dragging, "grabbing", "grab"), - opacity=rx.cond(params.is_dragging, 0.7, 1.0) - ), - type="collect_item", - item={"id": "collect_test", "name": "Test Item"} - ) - -def collect_drop_target(): - params = rxe.dnd.DropTarget.collected_params - return rxe.dnd.drop_target( - rx.box( - rx.vstack( - rx.text("Drop Zone", weight="bold"), - rx.text(f"Is over: {params.is_over}", size="2"), - rx.text(f"Can drop: {params.can_drop}", size="2"), - rx.cond( - params.item, - rx.text(f"Hovering item: {params.item.get('name', 'Unknown')}", size="2"), - rx.text("No item hovering", size="2") - ), - spacing="1" - ), - p=4, - bg=rx.cond( - params.is_over & params.can_drop, - "green.200", - rx.cond(params.is_over, "yellow.200", "gray.100") - ), - border=rx.cond( - params.is_over & params.can_drop, - "2px solid green", - rx.cond(params.is_over, "2px solid yellow", "2px dashed gray") - ), - min_height="120px" - ), - accept=["collect_item"], - on_drop=CollectState.handle_drop, - ) - -def custom_collect_example(): - return rx.vstack( - rx.text("Real-time Monitor State", weight="bold", size="4"), - rx.hstack( - collect_draggable(), - collect_drop_target(), - spacing="6", - align="start" - ), - rx.text(CollectState.drop_info, size="2", color="gray.600"), - spacing="4" - ) -``` - -## Provider - -Drag and drop functionality requires the `rxe.dnd.provider` component to wrap your app. The provider is automatically added when using `draggable` or `drop_target` components. - -For manual control: - -```python -def app(): - return rxe.dnd.provider( - # Your app content - your_app_content(), - backend="HTML5" # or "Touch" for mobile - ) -``` - -## Best Practices - -1. **Always use `@rx.memo`** on functions containing draggable components -2. **Use descriptive type names** for better debugging -3. **Handle edge cases** in drop handlers (invalid items, etc.) -4. **Provide visual feedback** using collected parameters -5. **Test on mobile devices** with touch backend -6. **Keep item data lightweight** for better performance - ---- - -[← Back to main documentation]({enterprise.overview.path}) diff --git a/docs/enterprise/mantine/autocomplete.md b/docs/enterprise/mantine/autocomplete.md deleted file mode 100644 index 6a9f826c3d7..00000000000 --- a/docs/enterprise/mantine/autocomplete.md +++ /dev/null @@ -1,22 +0,0 @@ ---- -title: Autocomplete ---- - -# Autocomplete component - -`rxe.mantine.autocomplete` is a component for providing suggestions as the user types. It is useful for enhancing user experience by offering relevant options based on input. - -```python demo exec -import reflex as rx -import reflex_enterprise as rxe - -def autocomplete_example(): - return rx.vstack( - rxe.mantine.autocomplete( - data=["Apple", "Banana", "Cherry", "Date", "Elderberry"], - placeholder="Type a fruit", - label="Fruit Autocomplete", - description="Select a fruit from the list", - ), - ) -``` diff --git a/docs/enterprise/mantine/collapse.md b/docs/enterprise/mantine/collapse.md deleted file mode 100644 index de0ca1073d7..00000000000 --- a/docs/enterprise/mantine/collapse.md +++ /dev/null @@ -1,36 +0,0 @@ ---- -title: Collapse ---- - -# Collapse component - -`rxe.mantine.collapse` is a component that allows you to create collapsible sections in your application. It is useful for hiding or showing content based on user interaction, such as clicking a button or a link. - -```python demo exec -import reflex as rx -import reflex_enterprise as rxe - -class CollapseState(rx.State): - is_open: bool = False - - @rx.event - def toggle_collapse(self): - self.is_open = not self.is_open - -def collapse_example(): - return rx.vstack( - rxe.mantine.collapse( - rx.text( - "This is a collapsible section. Click the button to toggle the collapse.", - font_size="lg", - ), - in_=CollapseState.is_open, - label="Collapsible Section", - description="Click the button to toggle the collapse.", - ), - rx.button( - "Toggle Collapse", - on_click=lambda: CollapseState.toggle_collapse, - ), - ) -``` diff --git a/docs/enterprise/mantine/combobox.md b/docs/enterprise/mantine/combobox.md deleted file mode 100644 index c95238634cc..00000000000 --- a/docs/enterprise/mantine/combobox.md +++ /dev/null @@ -1,35 +0,0 @@ ---- -title: Combobox ---- - -```python exec -import reflex as rx -import reflex_enterprise as rxe -from pcweb.pages.docs import enterprise -``` - -# Combobox - -`rxe.mantine.combobox` is a wrapping of the mantine component [Combobox](https://mantine.dev/core/combobox/). It is a simple component that can be used to display a list of options, and allows the user to select one or more options from the list. It can be used in various contexts, such as in a form or as a standalone component. - -```python -import reflex as rx -import reflex_enterprise as rxe - -def combobox_page(): - """Combobox demo.""" - return rxe.mantine.combobox( - rxe.mantine.combobox.target( - rx.input(type="button"), - ), - rxe.mantine.combobox.dropdown( - rxe.mantine.combobox.options( - rxe.mantine.combobox.option("Option 1"), - rxe.mantine.combobox.option("Option 2"), - rxe.mantine.combobox.option("Option 3"), - ), - ), - label="Combobox", - placeholder="Select a value", - ) -``` diff --git a/docs/enterprise/mantine/index.md b/docs/enterprise/mantine/index.md deleted file mode 100644 index 28aa4e0de7e..00000000000 --- a/docs/enterprise/mantine/index.md +++ /dev/null @@ -1,26 +0,0 @@ ---- -title: Mantine -order: 4 ---- - -# Mantine - -Mantine is a React component library that provides a set of high-quality components and hooks for building modern web applications. It is designed to be flexible, customizable, and easy to use, making it a popular choice among developers. - -Some of those components have been integrated into Reflex Enterprise, allowing you to use them in your Reflex applications. The following components are available: - -- JsonInput -- Autocomplete -- ComboBox -- Multiselect -- Pill -- PillsInput -- TagsInput -- Tree -- RingProgress -- SemiCircleProgress -- LoadingOverlay -- NumberFormatter -- Spoiler -- Timeline -- Collapse diff --git a/docs/enterprise/mantine/json-input.md b/docs/enterprise/mantine/json-input.md deleted file mode 100644 index 5737732370a..00000000000 --- a/docs/enterprise/mantine/json-input.md +++ /dev/null @@ -1,35 +0,0 @@ ---- -title: JSON Input ---- - -# JSON Input - -`rxe.mantine.json_input` is a component that allows you to input JSON data in a user-friendly way. It provides validation and formatting features to ensure that the JSON data is correctly structured. - -## Example - -```python demo exec -import reflex as rx -import reflex_enterprise as rxe - -class JsonInputState(rx.State): - json_data: str = "" - - def set_json_data(self, value: str): - self.json_data = value - - -def json_input_example(): - return rxe.mantine.json_input( - id="json-input", - value=JsonInputState.json_data, - placeholder="Enter JSON data", - label="JSON Input", - description="Please enter valid JSON data.", - required=True, - size="md", - format_on_blur=True, - on_change=JsonInputState.set_json_data, - width="300px", - ) -``` diff --git a/docs/enterprise/mantine/loading-overlay.md b/docs/enterprise/mantine/loading-overlay.md deleted file mode 100644 index 30409d63e2e..00000000000 --- a/docs/enterprise/mantine/loading-overlay.md +++ /dev/null @@ -1,33 +0,0 @@ ---- -title: Loading Overlay ---- - -# Loading Overlay component - -`rxe.mantine.loading_overlay` is a component that displays a loading overlay on top of its children. It is useful for indicating that a process is ongoing and prevents user interaction with the underlying content. - -```python demo exec -import reflex as rx -import reflex_enterprise as rxe - -class LoadingOverlayState(rx.State): - loading: bool = False - - @rx.event - def toggle_loading(self): - self.loading = not self.loading - -def loading_overlay_example(): - return rx.container( - rxe.mantine.loading_overlay( - rx.text( - "Loading Overlay Example", - height="200px", - width="100px", - ), - overlay_props={"radius": "sm", "blur": 2}, - visible=LoadingOverlayState.loading, - z_index=1000, - ), - ),rx.button("Toggle Loading", on_click=LoadingOverlayState.toggle_loading), -``` diff --git a/docs/enterprise/mantine/multi-select.md b/docs/enterprise/mantine/multi-select.md deleted file mode 100644 index 5137409be71..00000000000 --- a/docs/enterprise/mantine/multi-select.md +++ /dev/null @@ -1,31 +0,0 @@ ---- -title: MultiSelect ---- - -# MultiSelect component - -`rxe.mantine.multi_select` is a component for selecting multiple options from a list. It allows users to choose one or more items, making it suitable for scenarios where multiple selections are required. - -```python demo exec -import reflex as rx -import reflex_enterprise as rxe - -class MultiSelectState(rx.State): - selected_fruits: list = [] - - def set_selected_fruits(self, value: list): - self.selected_fruits = value - - -def multi_select_example(): - return rx.vstack( - rxe.mantine.multi_select( - label="Select fruits", - placeholder="Pick all that you like", - data=["Apple", "Banana", "Cherry", "Date", "Elderberry"], - value=MultiSelectState.selected_fruits, - on_change=MultiSelectState.set_selected_fruits, - ) - ) - -``` diff --git a/docs/enterprise/mantine/number-formatter.md b/docs/enterprise/mantine/number-formatter.md deleted file mode 100644 index dea1bdf4dbf..00000000000 --- a/docs/enterprise/mantine/number-formatter.md +++ /dev/null @@ -1,28 +0,0 @@ ---- -title: Number Formatter ---- - -# Number Formatter component - -`rxe.mantine.number_formatter` is a component for formatting numbers in a user-friendly way. It allows you to specify the format, precision, and other options for displaying numbers. - -```python demo exec -import reflex as rx -import reflex_enterprise as rxe - -def number_formatter_example(): - return rx.vstack( - rxe.mantine.number_formatter( - value=100, - prefix="$", - ), - rxe.mantine.number_formatter( - value=100, - suffix="€", - ), - rxe.mantine.number_formatter( - value=1234567.89, - thousand_separator=True, - ), - ) -``` diff --git a/docs/enterprise/mantine/pill.md b/docs/enterprise/mantine/pill.md deleted file mode 100644 index 95c5a5884c9..00000000000 --- a/docs/enterprise/mantine/pill.md +++ /dev/null @@ -1,94 +0,0 @@ ---- -title: Pill ---- - -```python exec -import reflex as rx -import reflex_enterprise as rxe -from pcweb.pages.docs import enterprise -``` - -# Pill - -`rxe.mantine.pill` is a wrapping of the mantine component [Pill](https://mantine.dev/core/pill/). It is a simple component that can be used to display a small piece of information, such as a tag or a label. It can be used in various contexts, such as in a list of tags or labels, or as a standalone component. - -```python demo exec -import reflex as rx -import reflex_enterprise as rxe - -def pill_page(): - """Pill demo.""" - return rxe.mantine.pill( - "Pill", - color="blue", - size="md", - variant="outline", - radius="xl", - with_remove_button=True, - on_remove=lambda: rx.toast("Pill on_remove triggered"), - ) -``` - -## Pill Group - -`rxe.mantine.pill.group` allows grouping multiple `rxe.mantine.pill` components together, with a predefined layout. - -```python demo exec -import reflex as rx -import reflex_enterprise as rxe - -def pill_group_page(): - """Pill demo.""" - return rxe.mantine.pill.group( - rxe.mantine.pill("Pill 1"), - rxe.mantine.pill("Pill 2"), - ) -``` - -# PillsInput - -`rxe.mantine.pills_input` is a wrapping of the mantine component [PillsInput](https://mantine.dev/core/pills-input/). It is an utility component that can be used to display a list of tags or labels. It can be used in various contexts, such as in a form or as a standalone component. -By itself it does not include any logic, it only renders given children. - -```md alert info -# For a fully functional out-of-the-box component, consider using [`rxe.mantine.tags_input`](/docs/enterprise/mantine/tags-input/) instead. -``` - -## Example - -```python demo exec -import reflex as rx -import reflex_enterprise as rxe - -class PillInputState(rx.State): - """State for the PillsInput demo.""" - - tags: set[str] = {"Foo", "Bar"} - - @rx.event - def add_tag(self, tag: str): - """Add a tag to the list of tags.""" - self.tags.add(tag) - - @rx.event - def remove_tag(self, tag: str): - """Remove a tag from the list of tags.""" - self.tags.remove(tag) - -def pills_input_page(): - """PillsInput demo.""" - return rxe.mantine.pills_input( - rxe.mantine.pill.group( - rx.foreach( - PillInputState.tags, lambda tag: rxe.mantine.pill(tag, with_remove_button=True, on_remove=PillInputState.remove_tag(tag)) - ), - rxe.mantine.pills_input.field( - placeholder="Enter tags", - # on_blur=PillInputState.add_tag, - ), - ), - label="PillsInput", - id="pills-input", - value=["tag1", "tag2"], - ) -``` diff --git a/docs/enterprise/mantine/ring-progress.md b/docs/enterprise/mantine/ring-progress.md deleted file mode 100644 index 9612531d25f..00000000000 --- a/docs/enterprise/mantine/ring-progress.md +++ /dev/null @@ -1,31 +0,0 @@ ---- -title: Ring Progress ---- - -# Ring Progress component - -`rxe.mantine.ring_progress` is a component for displaying progress in a circular format. It is useful for visualizing completion percentages or other metrics in a compact and visually appealing way. - -```python demo exec -import reflex as rx -import reflex_enterprise as rxe -import random - -class RingProgressState(rx.State): - value: int = 50 - - @rx.event - def random(self): - self.value = random.randint(0, 100) - -def ring_progress_example(): - return rx.vstack( - rxe.mantine.ring_progress( - size=100, - sections=[ - {"value": RingProgressState.value, "color": "blue"}, - ], - ), - rx.button("Randomize", on_click=RingProgressState.random), - ) -``` diff --git a/docs/enterprise/mantine/semi-circle-progress.md b/docs/enterprise/mantine/semi-circle-progress.md deleted file mode 100644 index 89425c0594f..00000000000 --- a/docs/enterprise/mantine/semi-circle-progress.md +++ /dev/null @@ -1,29 +0,0 @@ ---- -title: Semi Circle Progress ---- - -# Semi Circle Progress component - -`rxe.mantine.semi_circle_progress` is a component for displaying progress in a semi-circular format. It is useful for visualizing completion percentages or other metrics in a compact and visually appealing way. - -```python demo exec -import reflex as rx -import reflex_enterprise as rxe -import random - -class SemiCircleProgressState(rx.State): - value: int = 50 - - @rx.event - def random(self): - self.value = random.randint(0, 100) - -def semi_circle_progress_example(): - return rx.vstack( - rxe.mantine.semi_circle_progress( - size=100, - value=SemiCircleProgressState.value, - ), - rx.button("Randomize", on_click=SemiCircleProgressState.random), - ) -``` diff --git a/docs/enterprise/mantine/spoiler.md b/docs/enterprise/mantine/spoiler.md deleted file mode 100644 index 09a1b0dc1a8..00000000000 --- a/docs/enterprise/mantine/spoiler.md +++ /dev/null @@ -1,22 +0,0 @@ ---- -title: Spoiler ---- - -# Spoiler component - -`rxe.mantine.spoiler` is a component that allows you to hide or reveal content. It is useful for displaying additional information or details that may not be immediately relevant to the user. - -```python demo exec -import reflex as rx -import reflex_enterprise as rxe - -def spoiler_example(): - return rx.vstack( - rxe.mantine.spoiler( - "This is a spoiler zone where lorem ipsum text dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit...", - max_height=50, - show_label="Show more", - hide_label="Show less", - ), - ) -``` diff --git a/docs/enterprise/mantine/tags-input.md b/docs/enterprise/mantine/tags-input.md deleted file mode 100644 index 5b24a1a63db..00000000000 --- a/docs/enterprise/mantine/tags-input.md +++ /dev/null @@ -1,55 +0,0 @@ ---- -title: TagsInput ---- - -# TagsInput - -`rxe.mantine.tags_input` is a wrapping of the mantine component [TagsInput](https://mantine.dev/core/tags-input/). It is an utility component that can be used to display a list of tags or labels. It can be used in various contexts, such as in a form or as a standalone component. - -```md alert info -# You can use the props mentioned in the Mantine documentation, but they need to be passed in snake_case. -``` - -## Basic Example - -```python demo exec -import reflex as rx -import reflex_enterprise as rxe - -def tags_input_simple_page(): - """TagsInput demo.""" - return rxe.mantine.tags_input( - placeholder="Enter tags", - label="Press Enter to ad a tag", - ) -``` - -## State Example - -```python demo exec -import reflex as rx -import reflex_enterprise as rxe - -class TagsInputState(rx.State): - """State for the TagsInput component.""" - - tags: list[str] = ["Tag1", "Tag2"] - - @rx.event - def update_tags(self, tags: list[str]): - """Add a tag to the list of tags.""" - self.tags = tags - -def tags_input_page(): - """TagsInput demo.""" - return rxe.mantine.tags_input( - value=TagsInputState.tags, - on_change=TagsInputState.update_tags, - placeholder="Enter tags", - label="TagsInput", - description="This is a TagsInput component", - error="", - size="md", - radius="xl", - ) -``` diff --git a/docs/enterprise/mantine/timeline.md b/docs/enterprise/mantine/timeline.md deleted file mode 100644 index 441f1d98fc0..00000000000 --- a/docs/enterprise/mantine/timeline.md +++ /dev/null @@ -1,35 +0,0 @@ ---- -title: Timeline ---- - -# Timeline component - -`rxe.mantine.timeline` is a component for displaying a sequence of events or milestones in a linear format. It is useful for visualizing progress, history, or any sequential information. - -```python demo exec -import reflex as rx -import reflex_enterprise as rxe - -def timeline_example(): - return rx.vstack( - rxe.mantine.timeline( - rxe.mantine.timeline.item( - title="Step 1", - bullet="•", - ), - rxe.mantine.timeline.item( - title="Step 2", - bullet="•", - ), - rxe.mantine.timeline.item( - title="Step 3", - bullet="•", - ), - active=1, - bullet_size=24, - line_width=2, - color="blue", - - ) - ) -``` diff --git a/docs/enterprise/mantine/tree.md b/docs/enterprise/mantine/tree.md deleted file mode 100644 index a3ce63eddb2..00000000000 --- a/docs/enterprise/mantine/tree.md +++ /dev/null @@ -1,31 +0,0 @@ ---- -title: Tree ---- - -# Tree component - -`rxe.mantine.tree` is a component for displaying hierarchical data in a tree structure. It allows users to expand and collapse nodes, making it easy to navigate through large datasets. - -```md alert warning -# Due to some technical limitations(pydantic), the tree component only supports 5 levels of depths for the `data` props. -``` - -```python demo exec -import reflex as rx -import reflex_enterprise as rxe - -def tree_example(): - # return rx.text("Placeholder for tree example") - return rxe.mantine.tree( - data=[ - { - "value": "0", - "label": "Root", - "children": [ - {"value": "0-1", "label": "Child 1"}, - {"value": "0-2", "label": "Child 2"}, - ], - } - ], - ) -``` diff --git a/docs/enterprise/map/index.md b/docs/enterprise/map/index.md deleted file mode 100644 index fffcf502ac7..00000000000 --- a/docs/enterprise/map/index.md +++ /dev/null @@ -1,575 +0,0 @@ ---- -title: Interactive Maps ---- - -# Interactive Maps - -```python exec -import reflex as rx -import reflex_enterprise as rxe -from pcweb.pages.docs import enterprise -``` - -The map components in Reflex Enterprise provide interactive mapping capabilities built on top of **Leaflet**, one of the most popular open-source JavaScript mapping libraries. These components enable you to create rich, interactive maps with markers, layers, controls, and event handling. - -```md alert info -# All map components are built using Leaflet and react-leaflet, providing a familiar and powerful mapping experience. - -For advanced Leaflet features, refer to the [Leaflet documentation](https://leafletjs.com/reference.html). -``` - -🌍 **[View Live Demo](https://map.reflex.run)** - See the map components in action with interactive examples. - -## Installation & Setup - -Map components are included with `reflex-enterprise`. No additional installation is required. - -## Basic Usage - -Here's a simple example of creating a map with a marker: - -```python demo exec -import reflex as rx -import reflex_enterprise as rxe - -class MapState(rx.State): - center: rxe.map.LatLng = rxe.map.latlng(lat=51.505, lng=-0.09) - zoom: float = 13.0 - -def basic_map(): - return rxe.map( - rxe.map.tile_layer( - url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", - attribution='© OpenStreetMap contributors' - ), - rxe.map.marker( - rxe.map.popup("Hello from London!"), - position=MapState.center, - ), - id="basic-map", - center=MapState.center, - zoom=MapState.zoom, - height="400px", - width="100%", - ) -``` - -## Core Components - -### Map Container - -The `rxe.map()` component is the primary container that holds all other map elements: - -```python -rxe.map( - # Child components (markers, layers, controls) - id="my-map", - center=rxe.map.latlng(lat=51.505, lng=-0.09), - zoom=13, - height="400px", - width="100%" -) -``` - -**Key Properties:** - -- `center`: Initial map center coordinates -- `zoom`: Initial zoom level (0-18+ depending on tile provider) -- `bounds`: Alternative to center/zoom, fits map to bounds -- `height`/`width`: Map container dimensions - -### Tile Layers - -Tile layers provide the base map imagery. The most common is OpenStreetMap: - -```python -rxe.map.tile_layer( - url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", - attribution='© OpenStreetMap contributors' -) -``` - -### Markers - -Add point markers to specific locations: - -```python demo exec -import reflex as rx -import reflex_enterprise as rxe - -def markers_example(): - return rxe.map( - rxe.map.tile_layer( - url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", - attribution='© OpenStreetMap contributors' - ), - rxe.map.marker( - rxe.map.popup( - rx.vstack( - rx.text("Big Ben", weight="bold"), - rx.text("Famous clock tower in London"), - spacing="2" - ) - ), - position=rxe.map.latlng(lat=51.4994, lng=-0.1245), - ), - rxe.map.marker( - rxe.map.popup("London Eye"), - position=rxe.map.latlng(lat=51.5033, lng=-0.1196), - ), - id="markers-map", - center=rxe.map.latlng(lat=51.501, lng=-0.122), - zoom=14, - height="400px", - width="100%", - ) -``` - -### Vector Layers - -Draw shapes and areas on the map: - -```python demo exec -import reflex as rx -import reflex_enterprise as rxe - -def vectors_example(): - return rxe.map( - rxe.map.tile_layer( - url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", - attribution='© OpenStreetMap contributors' - ), - # Circle (radius in meters) - rxe.map.circle( - center=rxe.map.latlng(lat=51.505, lng=-0.09), - radius=500, - path_options=rxe.map.path_options( - color="#ff0000", - fill_color="#ff3333", - fill_opacity=0.3, - weight=2 - ) - ), - # Polygon - rxe.map.polygon( - positions=[ - rxe.map.latlng(lat=51.515, lng=-0.08), - rxe.map.latlng(lat=51.515, lng=-0.07), - rxe.map.latlng(lat=51.520, lng=-0.07), - rxe.map.latlng(lat=51.520, lng=-0.08), - ], - path_options=rxe.map.path_options( - color="#0000ff", - fill_color="#3333ff", - fill_opacity=0.3 - ) - ), - # Polyline - rxe.map.polyline( - positions=[ - rxe.map.latlng(lat=51.500, lng=-0.095), - rxe.map.latlng(lat=51.510, lng=-0.085), - rxe.map.latlng(lat=51.515, lng=-0.095), - ], - path_options=rxe.map.path_options( - color="#00ff00", - weight=4 - ) - ), - id="vectors-map", - center=rxe.map.latlng(lat=51.510, lng=-0.08), - zoom=13, - height="400px", - width="100%", - ) -``` - -## Interactive Features - -### Event Handling - -Maps support comprehensive event handling for user interactions: - -```python demo exec -import reflex as rx -import reflex_enterprise as rxe - -class InteractiveMapState(rx.State): - last_click: str = "No clicks yet" - current_zoom: float = 13.0 - - def handle_map_click(self, event): - lat = event.get("latlng", {}).get("lat", 0) - lng = event.get("latlng", {}).get("lng", 0) - self.last_click = f"Clicked at: {lat:.4f}, {lng:.4f}" - - def handle_zoom_change(self, event): - self.current_zoom = float(event.get("target", {}).get("_zoom", 13.0)) - -def interactive_example(): - return rx.vstack( - rx.text(f"Last click: {InteractiveMapState.last_click}"), - rx.text(f"Current zoom: {InteractiveMapState.current_zoom}"), - rxe.map( - rxe.map.tile_layer( - url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", - attribution='© OpenStreetMap contributors' - ), - id="interactive-map", - center=rxe.map.latlng(lat=51.505, lng=-0.09), - zoom=InteractiveMapState.current_zoom, - height="350px", - width="100%", - on_click=InteractiveMapState.handle_map_click, - on_zoom=InteractiveMapState.handle_zoom_change, - ), - spacing="3" - ) -``` - -### Map Controls - -Add UI controls for enhanced user interaction: - -```python demo exec -import reflex as rx -import reflex_enterprise as rxe - -def controls_example(): - return rxe.map( - rxe.map.tile_layer( - url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", - attribution='© OpenStreetMap contributors' - ), - rxe.map.zoom_control(position="topright"), - rxe.map.scale_control(position="bottomleft"), - rxe.map.attribution_control(position="bottomright"), - id="controls-map", - center=rxe.map.latlng(lat=51.505, lng=-0.09), - zoom=13, - height="400px", - width="100%", - ) -``` - -## Helper Functions - -### Coordinate Creation - -```python -# Create latitude/longitude coordinates -center = rxe.map.latlng(lat=51.505, lng=-0.09, nround=4) - -# Create bounds -bounds = rxe.map.latlng_bounds( - corner1_lat=51.49, corner1_lng=-0.11, - corner2_lat=51.52, corner2_lng=-0.07 -) -``` - -## Map API - -The Map API provides programmatic control over your maps, allowing you to manipulate the map programmatically from your Reflex state methods. - -### Getting the API Reference - -To access the Map API, you need to get a reference to your map using its ID: - -```python -map_api = rxe.map.api("my-map-id") -``` - -### Interactive Demo - -Here are some commonly used API methods demonstrated in action: - -```python demo exec -import reflex as rx -import reflex_enterprise as rxe - -map_api = rxe.map.api("api-demo-map") - -class MapAPIState(rx.State): - current_location: str = "London" - - def fly_to_london(self): - yield map_api.fly_to([51.505, -0.09], 13) - self.current_location = "London" - - def fly_to_paris(self): - yield map_api.fly_to([48.8566, 2.3522], 13) - self.current_location = "Paris" - -def map_api_example(): - return rx.vstack( - rx.text(f"Current location: {MapAPIState.current_location}"), - rx.hstack( - rx.button("Fly to London", on_click=MapAPIState.fly_to_london), - rx.button("Fly to Paris", on_click=MapAPIState.fly_to_paris), - rx.button("Zoom Out", on_click=map_api.set_zoom(8)), - rx.button("Log Center", on_click=map_api.get_center(callback=rx.console_log)), - spacing="2" - ), - rxe.map( - rxe.map.tile_layer( - url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", - attribution='© OpenStreetMap contributors' - ), - id="api-demo-map", - center=rxe.map.latlng(lat=51.505, lng=-0.09), - zoom=13.0, - height="350px", - width="100%", - ), - spacing="3" - ) -``` - -### Common API Methods - -**View Control:** - -- `fly_to(latlng, zoom, options)` - Smooth animated movement to location -- `set_view(latlng, zoom, options)` - Instant movement to location -- `set_zoom(zoom)` - Change zoom level -- `zoom_in()` / `zoom_out()` - Zoom by one level -- `fit_bounds(bounds, options)` - Fit map to specific bounds - -**Location Services:** - -- `locate(options)` - Get user's current location -- `stop_locate()` - Stop location tracking - -**Information Retrieval:** - -- `get_center(callback)` - Get current map center -- `get_zoom(callback)` - Get current zoom level -- `get_bounds(callback)` - Get current map bounds -- `get_size(callback)` - Get map container size - -**Layer Management:** - -- `add_layer(layer)` - Add a layer to the map -- `remove_layer(layer)` - Remove a layer from the map -- `has_layer(layer)` - Check if layer exists on map - -### Full Leaflet API Access - -```md alert info -# The Map API provides access to the complete Leaflet map API. Any method available on a Leaflet map instance can be called through the MapAPI instance. - -Function names are automatically converted from snake_case (Python) to camelCase (JavaScript). -``` - -This means you can use any method from the [Leaflet Map documentation](https://leafletjs.com/reference.html#map). For example: - -**Python (snake_case) → JavaScript (camelCase):** - -- `map_api.pan_to(latlng)` → `map.panTo(latlng)` -- `map_api.set_max_bounds(bounds)` → `map.setMaxBounds(bounds)` -- `map_api.get_pixel_bounds()` → `map.getPixelBounds()` -- `map_api.container_point_to_lat_lng(point)` → `map.containerPointToLatLng(point)` - -### Advanced Example - -```python demo exec -import reflex as rx -import reflex_enterprise as rxe - -class AdvancedMapState(rx.State): - constraints_applied: bool = False - location_tracking: bool = False - location_status: str = "Location tracking disabled" - - def set_location_status(self, status: str): - self.location_status = status - - def setup_map_constraints(self): - map_api = rxe.map.api("advanced-demo-map") - - # Set maximum bounds (restrict panning to London area) - max_bounds = rxe.map.latlng_bounds( - corner1_lat=51.4, corner1_lng=-0.3, - corner2_lat=51.6, corner2_lng=0.1 - ) - yield map_api.set_max_bounds(max_bounds) - - # Set min/max zoom levels - yield map_api.set_min_zoom(10) - yield map_api.set_max_zoom(16) - - # Disable scroll wheel zoom - yield map_api.scroll_wheel_zoom(False) - - self.constraints_applied = True - - def remove_constraints(self): - map_api = rxe.map.api("advanced-demo-map") - - # Remove bounds restriction - yield map_api.set_max_bounds(None) - - # Reset zoom limits - yield map_api.set_min_zoom(1) - yield map_api.set_max_zoom(18) - - # Re-enable scroll wheel zoom - yield map_api.scroll_wheel_zoom(True) - - self.constraints_applied = False - - def toggle_location_tracking(self): - map_api = rxe.map.api("advanced-demo-map") - - if self.location_tracking == False: - # Start location tracking - locate_options = rxe.map.locate_options( - set_view=True, - max_zoom=16, - timeout=10000, - enable_high_accuracy=True, - watch=False # Single location request - ) - yield map_api.locate(locate_options) - self.location_tracking = True - self.location_status = "Requesting location..." - else: - # Stop location tracking - yield map_api.stop_locate() - self.location_tracking = False - self.location_status = "Location tracking disabled" - -def advanced_example(): - return rx.vstack( - rx.hstack( - rx.button( - rx.cond(AdvancedMapState.constraints_applied, "Remove Constraints", "Apply Constraints"), - on_click=rx.cond(AdvancedMapState.constraints_applied, AdvancedMapState.remove_constraints, AdvancedMapState.setup_map_constraints), - color_scheme="blue" - ), - rx.button( - rx.cond(AdvancedMapState.location_tracking, "Disable Location", "Enable Location"), - on_click=AdvancedMapState.toggle_location_tracking, - color_scheme="green" - ), - spacing="3" - ), - rx.text(f"Status: {AdvancedMapState.location_status}"), - rx.text( - rx.cond( - AdvancedMapState.constraints_applied, - "Constraints: Applied (restricted to London area, zoom 10-16, no scroll wheel)", - "Constraints: None" - ) - ), - rxe.map( - rxe.map.tile_layer( - url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", - attribution='© OpenStreetMap contributors' - ), - rxe.map.marker( - rxe.map.popup("Try panning and zooming when constraints are applied!"), - position=rxe.map.latlng(lat=51.505, lng=-0.09), - ), - id="advanced-demo-map", - center=rxe.map.latlng(lat=51.505, lng=-0.09), - zoom=12.0, - height="400px", - width="100%", - on_locationfound=lambda e: AdvancedMapState.set_location_status("Location found!"), - on_locationerror=lambda e: AdvancedMapState.set_location_status("Location error - permission denied or unavailable"), - ), - spacing="3" - ) -``` - -### Callback Handling - -Many API methods that retrieve information require callbacks to handle the results: - -```python -class CallbackMapState(rx.State): - map_info: str = "" - - def handle_center_result(self, result): - lat = result.get("lat", 0) - lng = result.get("lng", 0) - self.map_info = f"Center: {lat:.4f}, {lng:.4f}" - - def handle_bounds_result(self, result): - # result will contain bounds information - self.map_info = f"Bounds: {result}" - - def get_map_info(self): - map_api = rxe.map.api("info-map") - yield map_api.get_center(self.handle_center_result) - # or - yield map_api.get_bounds(self.handle_bounds_result) -``` - -## Available Events - -The map components support a comprehensive set of events: - -**Map Events:** - -- `on_click`, `on_dblclick` - Mouse click events -- `on_zoom`, `on_zoom_start`, `on_zoom_end` - Zoom events -- `on_move`, `on_move_start`, `on_move_end` - Pan events -- `on_resize` - Map container resize -- `on_load`, `on_unload` - Map lifecycle - -**Location Events:** - -- `on_locationfound`, `on_locationerror` - Geolocation - -**Layer Events:** - -- `on_layeradd`, `on_layerremove` - Layer management - -**Popup Events:** - -- `on_popupopen`, `on_popupclose` - Popup lifecycle -- `on_tooltipopen`, `on_tooltipclose` - Tooltip lifecycle - -## Common Patterns - -### Dynamic Markers - -```python -class DynamicMapState(rx.State): - markers: list[dict] = [ - {"lat": 51.505, "lng": -0.09, "title": "London"}, - {"lat": 48.8566, "lng": 2.3522, "title": "Paris"}, - {"lat": 52.5200, "lng": 13.4050, "title": "Berlin"}, - ] - -def dynamic_markers(): - return rxe.map( - rxe.map.tile_layer(url="..."), - rx.foreach( - DynamicMapState.markers, - lambda marker: rxe.map.marker( - rxe.map.popup(marker["title"]), - position=rxe.map.latlng( - lat=marker["lat"], - lng=marker["lng"] - ) - ) - ), - # ... map configuration - ) -``` - -## Best Practices - -1. **Always include attribution** for tile providers -2. **Set reasonable zoom levels** (typically 1-18) -3. **Use bounds for multiple markers** instead of arbitrary center/zoom -4. **Handle loading states** for dynamic map content -5. **Optimize marker rendering** for large datasets using clustering -6. **Test on mobile devices** for touch interactions - ---- - -[← Back to main documentation]({enterprise.overview.path}) diff --git a/docs/enterprise/overview.md b/docs/enterprise/overview.md deleted file mode 100644 index 38e8867b421..00000000000 --- a/docs/enterprise/overview.md +++ /dev/null @@ -1,298 +0,0 @@ ---- -title: Reflex Enterprise ---- - -# Reflex Enterprise - -```python exec -from pcweb.pages.docs import enterprise -import reflex as rx -try: - import reflex_enterprise as rxe - from reflex_enterprise.components.ag_grid.resource import RendererParams -except ImportError: - rxe = None - RendererParams = None -``` - -Reflex Enterprise is a package containing paid features built on top of Reflex. - -```md alert info -# Despite being an enterprise package, free users can use the components from this package. A badge "Built with Reflex" will be shown in the bottom right corner of the app. - -For more information on the badge, visit [Built with Reflex]({enterprise.built_with_reflex.path}). -``` - -## Installation - -`reflex-enterprise` must be installed alongside `reflex` to access the enterprise features. - -You can install it from pypi with the following command: - -```bash -pip install reflex-enterprise -``` - -## Features - -```python exec -# Create master data organized by category -categories_data = [ - { - "category": "Configuration", - "description": "Core enterprise features for deployment and branding", - "count": 2, - "components": [ - { - "feature": "show_built_with_reflex", - "description": "Toggle the 'Built with Reflex' badge in your app", - "cloud_tier": "Enterprise", - "self_hosted_tier": "Enterprise", - "link": "/docs/enterprise/built-with-reflex", - }, - { - "feature": "use_single_port", - "description": "Enable single-port deployment by proxying backend to frontend", - "cloud_tier": "Free", - "self_hosted_tier": "Free", - "link": "/docs/enterprise/single-port-proxy", - }, - ] - }, - { - "category": "AGGrid and AGChart", - "description": "Advanced data visualization and grid components", - "count": 2, - "components": [ - { - "feature": "AgGrid", - "description": "Advanced data grid with enterprise features (sorting, filtering, grouping)", - "cloud_tier": "Free", - "self_hosted_tier": "Free", - "link": "/docs/enterprise/ag-grid", - }, - { - "feature": "AGCharts", - "description": "Interactive charts and data visualization components", - "cloud_tier": "Free", - "self_hosted_tier": "Free", - "link": "/docs/enterprise/ag-chart", - }, - ] - }, - { - "category": "Interactive Components", - "description": "Interactive UI features including drag-and-drop and mapping", - "count": 2, - "components": [ - { - "feature": "Drag and Drop", - "description": "Drag and drop functionality for interactive UI elements", - "cloud_tier": "Free", - "self_hosted_tier": "Free", - "link": "/docs/enterprise/drag-and-drop", - }, - { - "feature": "Mapping", - "description": "Interactive maps with markers, layers, and controls", - "cloud_tier": "Free", - "self_hosted_tier": "Free", - "link": "/docs/enterprise/map", - }, - ] - }, - { - "category": "Mantine", - "description": "Rich UI components from Mantine library", - "count": 15, - "components": [ - { - "feature": "Autocomplete", - "description": "Auto-completing text input with dropdown suggestions", - "cloud_tier": "Free", - "self_hosted_tier": "Free", - "link": "/docs/enterprise/mantine/autocomplete", - }, - { - "feature": "Combobox", - "description": "Searchable dropdown with custom options and filtering", - "cloud_tier": "Free", - "self_hosted_tier": "Free", - "link": "/docs/enterprise/mantine/combobox", - }, - { - "feature": "Multi Select", - "description": "Multi-selection dropdown with tags and search", - "cloud_tier": "Free", - "self_hosted_tier": "Free", - "link": "/docs/enterprise/mantine/multi-select", - }, - { - "feature": "Tags Input", - "description": "Input field for creating and managing tags", - "cloud_tier": "Free", - "self_hosted_tier": "Free", - "link": "/docs/enterprise/mantine/tags-input", - }, - { - "feature": "Json Input", - "description": "JSON editor with syntax highlighting and validation", - "cloud_tier": "Free", - "self_hosted_tier": "Free", - "link": "/docs/enterprise/mantine/json-input", - }, - { - "feature": "Pill", - "description": "Small rounded elements for tags, badges, and labels", - "cloud_tier": "Free", - "self_hosted_tier": "Free", - "link": "/docs/enterprise/mantine/pill", - }, - { - "feature": "Tree", - "description": "Hierarchical tree view with expandable nodes", - "cloud_tier": "Free", - "self_hosted_tier": "Free", - "link": "/docs/enterprise/mantine/tree", - }, - { - "feature": "Timeline", - "description": "Timeline component for displaying chronological events", - "cloud_tier": "Free", - "self_hosted_tier": "Free", - "link": "/docs/enterprise/mantine/timeline", - }, - { - "feature": "Number Formatter", - "description": "Format and display numbers with customizable formatting", - "cloud_tier": "Free", - "self_hosted_tier": "Free", - "link": "/docs/enterprise/mantine/number-formatter", - }, - { - "feature": "Ring Progress", - "description": "Circular progress indicator with customizable styling", - "cloud_tier": "Free", - "self_hosted_tier": "Free", - "link": "/docs/enterprise/mantine/ring-progress", - }, - { - "feature": "Semi Circle Progress", - "description": "Semi-circular progress indicator for dashboards", - "cloud_tier": "Free", - "self_hosted_tier": "Free", - "link": "/docs/enterprise/mantine/semi-circle-progress", - }, - { - "feature": "Loading Overlay", - "description": "Loading overlay with spinner for async operations", - "cloud_tier": "Free", - "self_hosted_tier": "Free", - "link": "/docs/enterprise/mantine/loading-overlay", - }, - { - "feature": "Spoiler", - "description": "Collapsible content container with show/hide toggle", - "cloud_tier": "Free", - "self_hosted_tier": "Free", - "link": "/docs/enterprise/mantine/spoiler", - }, - { - "feature": "Collapse", - "description": "Animated collapsible content with smooth transitions", - "cloud_tier": "Free", - "self_hosted_tier": "Free", - "link": "/docs/enterprise/mantine/collapse", - }, - ] - }, -] - -if rxe is not None: - @rxe.arrow_func - def custom_link_renderer(params: RendererParams): - """Custom cell renderer for links in AG Grid.""" - return rx.link( - params.value, - href=params.data.link, - ) - - grid = rxe.ag_grid( - column_defs=[ - { - "field": "category", - "header_name": "Category", - "cell_renderer": "agGroupCellRenderer", - "suppress_menu": True, - "width": 220, - }, - { - "field": "description", - "width": 500, - }, - { - "field": "count", - "header_name": "Components", - "width": 150, - }, - ], - row_data=categories_data, - master_detail=True, - detail_cell_renderer_params={ - "detail_grid_options": { - "column_defs": [ - { - "field": "feature", - "header_name": "Component/Feature", - "cell_renderer": custom_link_renderer, - "width": 250 - }, - {"field": "description", "header_name": "Description", "width": 350}, - {"field": "cloud_tier", "header_name": "Cloud Tier", "width": 120}, - {"field": "self_hosted_tier", "header_name": "Self-hosted Tier", "width": 140}, - ], - "suppress_context_menu": True, - "row_height": 35, - }, - "get_detail_row_data": lambda params: rx.vars.function.FunctionStringVar( - "params.successCallback" - ).call(params.data.components), - }, - id="features-grid", - width="100%", - detail_row_height=200, - detail_row_auto_height=True, - height="400px", - loading=False, -) - grid.api.set_grid_option("suppressContextMenu", True) -else: - grid = rx.text("Reflex Enterprise not available. Install with: pip install reflex-enterprise") -``` - -```python eval -grid -``` - -## Usage of reflex_enterprise. - -Using `rxe.App` as your `app` is required to use any of the components provided by the enterprise package, as well as config options provided by `rxe.Config`. - -### In the main file - -Instead of the usual `rx.App()` to create your app, use the following: - -```python -import reflex_enterprise as rxe -app = rxe.App() -``` - -### In rxconfig.py - -```python -import reflex_enterprise as rxe -config = rxe.Config( - app_name="MyApp", - ... # you can pass all rx.Config arguments as well as the one specific to rxe.Config -) -``` diff --git a/docs/enterprise/react_flow/basic_flow.md b/docs/enterprise/react_flow/basic_flow.md deleted file mode 100644 index b057fdc918a..00000000000 --- a/docs/enterprise/react_flow/basic_flow.md +++ /dev/null @@ -1,96 +0,0 @@ -# Basic Flow Example - -This example demonstrates a simple flow diagram with three nodes and two edges, showing how nodes can be connected and how edges can be animated. - -### Nodes - -- Input Node – starting point -- Default Node – standard content node -- Output Node – endpoint of the flow - -### Edges - -- Input → Default (animated) -- Default → Output - -### Interactivity - -- Nodes can be moved -- Edges update dynamically -- Users can drag from handles to create new edges -- Zoom, pan, and mini-map controls are available - -### Visual Layout - -- Flow fits viewport automatically -- Background grid for orientation -- Light and dark color modes supported - -### Example Flow - -```python demo exec -import reflex as rx -import reflex_enterprise as rxe -from reflex_enterprise.components.flow.types import Node, Edge - -# Common style for all nodes -node_style = { - "backgroundColor": "#ffcc00", - "color": "#000000", - "padding": "10px", - "borderRadius": "5px" -} - -class FlowState(rx.State): - nodes: list[Node] = [ - { - "id": "1", - "type": "input", - "position": {"x": 100, "y": 100}, - "data": {"label": "Input Node"}, - "style": node_style - }, - { - "id": "2", - "type": "default", - "position": {"x": 300, "y": 200}, - "data": {"label": "Default Node"}, - "style": node_style - }, - { - "id": "3", - "type": "output", - "position": {"x": 500, "y": 100}, - "data": {"label": "Output Node"}, - "style": node_style - }, - ] - - edges: list[Edge] = [ - {"id": "e1-2", "source": "1", "target": "2", "animated": True}, - {"id": "e2-3", "source": "2", "target": "3"}, - ] - - -def flow_example(): - return rx.box( - rxe.flow( - # Core flow components - rxe.flow.controls(), - rxe.flow.background(), - rxe.flow.mini_map(), - - # Flow configuration - default_nodes=FlowState.nodes, - default_edges=FlowState.edges, - nodes=FlowState.nodes, - edges=FlowState.edges, - - # Visual settings - fit_view=True, - attribution_position="bottom-right", - ), - height="100vh", - width="100vw", - ) -``` diff --git a/docs/enterprise/react_flow/components.md b/docs/enterprise/react_flow/components.md deleted file mode 100644 index 04ba0536166..00000000000 --- a/docs/enterprise/react_flow/components.md +++ /dev/null @@ -1,92 +0,0 @@ -# Flow Components - -This page documents the main components provided by the `rxe.flow` library. - -## rxe.flow.provider - -The `FlowProvider` component is a context provider that makes it possible to access a flow’s internal state outside of the `` component. Many of the hooks we provide rely on this component to work. - -**Props:** - -- `initial_nodes`: `Sequence[Node]` - These nodes are used to initialize the flow. They are not dynamic. -- `default_edges`: `Sequence[Edge]` - These edges are used to initialize the flow. They are not dynamic. -- `initial_width`: `float` - The initial width is necessary to be able to use fitView on the server. -- `initial_height`: `float` - The initial height is necessary to be able to use fitView on the server. -- `fit_view`: `bool` - When true, the flow will be zoomed and panned to fit all the nodes initially provided. -- `initial_fit_view_options`: `FitViewOptions` - You can provide an object of options to customize the initial fitView behavior. -- `initial_min_zoom`: `float` - Initial minimum zoom level. -- `initial_max_zoom`: `float` - Initial maximum zoom level. -- `node_origin`: `NodeOrigin` - The origin of the node to use when placing it in the flow or looking up its x and y position. -- `node_extent`: `CoordinateExtent` - The boundary a node can be moved in. - -## rxe.flow - -The `Flow` component is the main component that renders the flow. It takes in nodes and edges, and provides event handlers for user interactions. - -**Props:** - -- `nodes`: `Sequence[Node]` - An array of nodes to render in a controlled flow. -- `edges`: `Sequence[Edge]` - An array of edges to render in a controlled flow. -- `default_nodes`: `Sequence[Node]` - The initial nodes to render in an uncontrolled flow. -- `default_edges`: `Sequence[Edge]` - The initial edges to render in an uncontrolled flow. -- `node_types`: `Mapping[str, Any]` - Custom node types. -- `edge_types`: `Mapping[str, Any]` - Custom edge types. -- `on_nodes_change`: Event handler for when nodes change. -- `on_edges_change`: Event handler for when edges change. -- `on_connect`: Event handler for when a connection is made. -- `fit_view`: `bool` - When true, the flow will be zoomed and panned to fit all the nodes initially provided. -- `fit_view_options`: `FitViewOptions` - Options for `fit_view`. -- `style`: The style of the component. - -## rxe.flow.background - -The `Background` component renders a background for the flow. It can be a pattern of lines, dots, or a cross. - -**Props:** - -- `color`: `str` - Color of the pattern. -- `bg_color`: `str` - Color of the background. -- `variant`: `Literal["lines", "dots", "cross"]` - The type of pattern to render. -- `gap`: `float | tuple[float, float]` - The gap between patterns. -- `size`: `float` - The size of the pattern elements. - -**Example:** - -```python -rxe.flow.background(variant="dots", gap=20, size=1) -``` - -## rxe.flow.controls - -The `Controls` component renders a panel with buttons to zoom in, zoom out, fit the view, and lock the viewport. - -**Props:** - -- `show_zoom`: `bool` - Whether to show the zoom buttons. -- `show_fit_view`: `bool` - Whether to show the fit view button. -- `show_interactive`: `bool` - Whether to show the lock button. -- `position`: `PanelPosition` - The position of the controls on the pane. - -**Example:** - -```python -rxe.flow.controls() -``` - -## rxe.flow.mini_map - -The `MiniMap` component renders a small overview of your flow. - -**Props:** - -- `node_color`: `str | Any` - Color of nodes on minimap. -- `node_stroke_color`: `str | Any` - Stroke color of nodes on minimap. -- `pannable`: `bool` - Determines whether you can pan the viewport by dragging inside the minimap. -- `zoomable`: `bool` - Determines whether you can zoom the viewport by scrolling inside the minimap. -- `position`: `PanelPosition` - Position of minimap on pane. - -**Example:** - -```python -rxe.flow.mini_map(pannable=True, zoomable=True) -``` diff --git a/docs/enterprise/react_flow/edges.md b/docs/enterprise/react_flow/edges.md deleted file mode 100644 index 25142f70aca..00000000000 --- a/docs/enterprise/react_flow/edges.md +++ /dev/null @@ -1,217 +0,0 @@ -# Edges - -Edges connect nodes together in a flow. This page explains how to define, customize, and interact with edges in Reflex Flow. - -## The Edge Type - -An edge is represented as a Python dictionary with the following fields: - -- `id` (`str`) – Unique identifier for the edge. -- `source` (`str`) – ID of the source node. -- `target` (`str`) – ID of the target node. -- `type` (`str`) – Edge type defined in `edge_types`. -- `sourceHandle` (`str | None`) – Optional source handle ID. -- `targetHandle` (`str | None`) – Optional target handle ID. -- `animated` (`bool`) – Whether the edge should animate. -- `hidden` (`bool`) – Whether the edge is hidden. -- `deletable` (`bool`) – Whether the edge can be removed. -- `selectable` (`bool`) – Whether the edge can be selected. -- `data` (`dict`) – Arbitrary metadata. -- `label` (`Any`) – Label rendered along the edge. -- `style` (`dict`) – Custom styles. -- `className` (`str`) – CSS class for the edge. - -## Basic Edge Types - -Reflex Flow comes with several built-in edge types: - -### Default Edge Types - -```python -edges: list[Edge] = [ - {"id": "e1", "source": "1", "target": "2", "type": "default"}, - {"id": "e2", "source": "2", "target": "3", "type": "straight"}, - {"id": "e3", "source": "3", "target": "4", "type": "step"}, - {"id": "e4", "source": "4", "target": "5", "type": "smoothstep"}, - {"id": "e5", "source": "5", "target": "6", "type": "bezier"}, -] -``` - -- **default** – Standard curved edge -- **straight** – Direct line between nodes -- **step** – Right-angled path with steps -- **smoothstep** – Smooth right-angled path -- **bezier** – Curved bezier path - -## Edge Styling - -### Basic Styling - -Add visual styling to edges using the `style` property: - -```python -edges: list[Edge] = [ - { - "id": "styled-edge", - "source": "1", - "target": "2", - "style": { - "stroke": "#ff6b6b", - "strokeWidth": 3, - } - } -] -``` - -### Animated Edges - -Make edges animate with flowing dots: - -```python -edges: list[Edge] = [ - { - "id": "animated-edge", - "source": "1", - "target": "2", - "animated": True, - "style": {"stroke": "#4dabf7"} - } -] -``` - -### Edge Labels - -Add text labels to edges: - -```python -edges: list[Edge] = [ - { - "id": "labeled-edge", - "source": "1", - "target": "2", - "label": "Connection", - "style": {"stroke": "#51cf66"} - } -] -``` - -# Custom Edges - -React Flow in Reflex also allows you to define custom edge types. This is useful when you want edges to carry extra functionality (like buttons, labels, or dynamic styling) beyond the default straight or bezier connectors. - -```python demo exec -import reflex as rx -import reflex_enterprise as rxe -from reflex_enterprise.components.flow.types import ( - ConnectionInProgress, - Edge, - NoConnection, - Node, - Position, -) - -class SimpleEdgeDemoState(rx.State): - nodes: list[Node] = [ - {"id": "1", "position": {"x": 0, "y": 0}, "data": {"label": "Node A"}, "style": {"color": "#000000",}}, - {"id": "2", "position": {"x": 250, "y": 150}, "data": {"label": "Node B"}, "style": {"color": "#000000",}}, - ] - edges: list[Edge] = [ - {"id": "e1-2", "source": "1", "target": "2", "type": "button"} - ] - - @rx.event - def set_nodes(self, nodes: list[Node]): - self.nodes = nodes - - @rx.event - def set_edges(self, edges: list[Edge]): - self.edges = edges - - @rx.event - def handle_connect_end( - self, - connection_status: NoConnection | ConnectionInProgress, - ): - if not connection_status["isValid"]: - new_edge = { - "id": f"{connection_status['fromNode']['id']}-{connection_status['toNode']['id']}", - "source": connection_status["fromNode"]["id"], - "target": connection_status["toNode"]["id"], - "type": "button", - } - self.edges.append(new_edge) - - - -@rx.memo -def button_edge( - id: rx.Var[str], - sourceX: rx.Var[float], - sourceY: rx.Var[float], - targetX: rx.Var[float], - targetY: rx.Var[float], - sourcePosition: rx.Var[Position], - targetPosition: rx.Var[Position], - markerEnd: rx.Var[str], -): - bezier_path = rxe.components.flow.util.get_bezier_path( - source_x=sourceX, - source_y=sourceY, - target_x=targetX, - target_y=targetY, - source_position=sourcePosition, - target_position=targetPosition, - ) - - mid_x = bezier_path.label_x - mid_y = bezier_path.label_y - - return rx.fragment( - rxe.flow.base_edge(path=bezier_path.path, markerEnd=markerEnd), - rxe.flow.edge_label_renderer( - rx.el.div( - rx.el.button( - "×", - class_name=("w-[30px] h-[30px] border-2 border-gray-200 bg-gray-200 text-black rounded-full text-[12px] pt-0 cursor-pointer hover:bg-gray-400 hover:text-white"), - on_click=rx.run_script( - rxe.flow.api.set_edges( - rx.vars.FunctionStringVar.create( - "Array.prototype.filter.call" - ).call( - rxe.flow.api.get_edges(), - rx.Var(f"((edge) => edge.id !== {id})"), - ), - ) - ), - style={ - "position": "absolute", - "left": f"{mid_x}px", - "top": f"{mid_y}px", - "transform": "translate(-50%, -50%)", - "pointerEvents": "all", - }, - ), - ) - ), - ) - -def very_simple_custom_edge_example(): - return rx.box( - rxe.flow( - rxe.flow.background(), - default_nodes=SimpleEdgeDemoState.nodes, - default_edges=SimpleEdgeDemoState.edges, - nodes=SimpleEdgeDemoState.nodes, - edges=SimpleEdgeDemoState.edges, - on_connect=lambda connection: SimpleEdgeDemoState.set_edges( - rxe.flow.util.add_edge(connection, SimpleEdgeDemoState.edges) - ), - on_connect_end=lambda status, event: SimpleEdgeDemoState.handle_connect_end(status), - edge_types={"button": button_edge}, - fit_view=True, - ), - height="100vh", - width="100%", - ) - -``` diff --git a/docs/enterprise/react_flow/examples.md b/docs/enterprise/react_flow/examples.md deleted file mode 100644 index edbfe836fe5..00000000000 --- a/docs/enterprise/react_flow/examples.md +++ /dev/null @@ -1,234 +0,0 @@ -# Example React Flow Components - -This section showcases examples of interactive flow components built with Reflex and Reflex Enterprise. Learn how to create dynamic nodes, edges, and custom behaviors for building flow diagrams in your React apps. - -## Add Node on Edge Drop - -In this example, we demonstrate how to dynamically add nodes to a flow when a connection is dropped onto the canvas. When the user drops a connection, a new node is created at the drop point, and an edge is added between the source node and the new node. - -```python demo exec -import reflex as rx - -import reflex_enterprise as rxe -from reflex_enterprise.components.flow.types import ( - ConnectionInProgress, - Edge, - NoConnection, - Node, - XYPosition, -) - -node_style = { - "color": "#000000", -} - -initial_nodes: list[Node] = [ - { - "id": "0", - "type": "input", - "data": {"label": "Node"}, - "position": {"x": 0, "y": 50}, - "style": node_style - }, -] - - -class AddNodesOnEdgeDropState(rx.State): - nodes: rx.Field[list[Node]] = rx.field(default_factory=lambda: initial_nodes) - edges: rx.Field[list[Edge]] = rx.field(default_factory=list) - node_id: int = 1 - - @rx.event - def increment(self): - self.node_id += 1 - - @rx.event - def set_nodes(self, nodes: list[Node]): - self.nodes = nodes - - @rx.event - def set_edges(self, edges: list[Edge]): - self.edges = edges - - @rx.event - def handle_connect_end( - self, - connection_status: NoConnection | ConnectionInProgress, - event: rx.event.PointerEventInfo, - flow_position: XYPosition, - ): - if not connection_status["isValid"]: - node_id = str(self.node_id) - self.increment() - self.nodes.append( - { - "id": node_id, - "position": flow_position, - "data": {"label": f"Node {node_id}"}, - "origin": (0.5, 0.0), - "style": node_style - } - ) - self.edges.append( - { - "id": node_id, - "source": connection_status["fromNode"]["id"], - "target": node_id, - "style": node_style - } - ) - -def add_node_on_edge_drop(): - return rx.box( - rxe.flow.provider( - rxe.flow( - rxe.flow.controls(), - rxe.flow.mini_map(), - rxe.flow.background(), - on_connect=lambda connection: AddNodesOnEdgeDropState.set_edges( - rxe.flow.util.add_edge(connection, AddNodesOnEdgeDropState.edges) - ), - on_connect_end=( - lambda connection_status, event: ( - AddNodesOnEdgeDropState.handle_connect_end( - connection_status, - event, - rxe.flow.api.screen_to_flow_position( - x=event.client_x, - y=event.client_y, - ), - ) - ) - ), - nodes=AddNodesOnEdgeDropState.nodes, - edges=AddNodesOnEdgeDropState.edges, - default_nodes=AddNodesOnEdgeDropState.nodes, - default_edges=AddNodesOnEdgeDropState.edges, - on_nodes_change=lambda changes: AddNodesOnEdgeDropState.set_nodes( - rxe.flow.util.apply_node_changes( - AddNodesOnEdgeDropState.nodes, changes - ) - ), - on_edges_change=lambda changes: AddNodesOnEdgeDropState.set_edges( - rxe.flow.util.apply_edge_changes( - AddNodesOnEdgeDropState.edges, changes - ) - ), - fit_view=True, - fit_view_options={"padding": 2}, - node_origin=(0.5, 0.0), - ) - ), - height="100vh", - width="100vw", - ) - -``` - -## Connection Limit on Custom Node - -This example demonstrates how to create a custom node with a connection limit on its handle. The handle can be configured to allow a specific number of connections, or no connections at all, using the isConnectable property. This is useful when you want to restrict the number of connections a node can have. - -```python demo exec -import reflex as rx - -import reflex_enterprise as rxe -from reflex_enterprise.components.flow.types import Edge, HandleType, Node, Position - -node_style = { - "color": "#000000", -} - -class ConnectionLimitState(rx.State): - nodes: rx.Field[list[Node]] = rx.field( - default_factory=lambda: [ - { - "id": "1", - "type": "input", - "data": {"label": "Node 1"}, - "position": {"x": 0, "y": 25}, - "sourcePosition": "right", - "style": node_style - }, - { - "id": "2", - "type": "custom", - "data": {}, - "position": {"x": 250, "y": 50}, - "style": node_style - }, - { - "id": "3", - "type": "input", - "data": {"label": "Node 2"}, - "position": {"x": 0, "y": 100}, - "sourcePosition": "right", - "style": node_style - }, - ] - ) - edges: rx.Field[list[Edge]] = rx.field(default_factory=list) - - @rx.event - def set_nodes(self, nodes: list[Node]): - self.nodes = nodes - - @rx.event - def set_edges(self, edges: list[Edge]): - self.edges = edges - - -@rx.memo -def custom_handle( - type: rx.Var[HandleType], position: rx.Var[Position], connection_count: rx.Var[int] -): - connections = rxe.flow.api.get_node_connections() - return rxe.flow.handle( - type=type, - position=position, - connection_count=connection_count, - is_connectable=connections.length() < connection_count.guess_type(), - ) - - -@rx.memo -def custom_node(): - return rx.el.div( - custom_handle(type="target", position="left", connection_count=1), - rx.el.div("← Only one edge allowed"), - class_name="border border-1 p-2 rounded-sm", - border_color=rx.color_mode_cond("black", ""), - color="black", - bg="white", - ) - - -def connection_limit(): - return rx.box( - rxe.flow( - rxe.flow.background(), - nodes=ConnectionLimitState.nodes, - edges=ConnectionLimitState.edges, - default_nodes=ConnectionLimitState.nodes, - default_edges=ConnectionLimitState.edges, - on_nodes_change=lambda changes: ConnectionLimitState.set_nodes( - rxe.flow.util.apply_node_changes(ConnectionLimitState.nodes, changes) - ), - on_edges_change=lambda changes: ConnectionLimitState.set_edges( - rxe.flow.util.apply_edge_changes(ConnectionLimitState.edges, changes) - ), - on_connect=lambda connection: ConnectionLimitState.set_edges( - rxe.flow.util.add_edge(connection, ConnectionLimitState.edges) - ), - node_types={ - "custom": rx.vars.function.ArgsFunctionOperation.create( - (), custom_node() - ) - }, - color_mode="light", - fit_view=True, - ), - height="100vh", - width="100vw", - ) -``` diff --git a/docs/enterprise/react_flow/hooks.md b/docs/enterprise/react_flow/hooks.md deleted file mode 100644 index e9d12d95369..00000000000 --- a/docs/enterprise/react_flow/hooks.md +++ /dev/null @@ -1,33 +0,0 @@ -# Hooks (API) - -The `rxe.flow.api` module provides hooks to interact with the Flow instance. These hooks are wrappers around the `useReactFlow` hook from React Flow. - -## Node Hooks - -- `get_nodes()`: Returns an array of all nodes in the flow. -- `set_nodes(nodes)`: Sets the nodes in the flow. -- `add_nodes(nodes)`: Adds nodes to the flow. -- `get_node(id)`: Returns a node by its ID. -- `update_node(id, node_update, replace=False)`: Updates a node in the flow. -- `update_node_data(id, data_update, replace=False)`: Updates a node's data in the flow. - -## Edge Hooks - -- `get_edges()`: Returns an array of all edges in the flow. -- `set_edges(edges)`: Sets the edges in the flow. -- `add_edges(edges)`: Adds edges to the flow. -- `get_edge(id)`: Returns an edge by its ID. -- `update_edge(id, edge_update, replace=False)`: Updates an edge in the flow. -- `update_edge_data(id, data_update, replace=False)`: Updates an edge's data in the flow. - -## Viewport Hooks - -- `screen_to_flow_position(x, y, snap_to_grid=False)`: Translates a screen pixel position to a flow position. -- `flow_to_screen_position(x, y)`: Translates a position inside the flow’s canvas to a screen pixel position. - -## Other Hooks - -- `to_object()`: Converts the React Flow state to a JSON object. -- `get_intersecting_nodes(node, partially=True, nodes=None)`: Find all the nodes currently intersecting with a given node or rectangle. -- `get_node_connections(id=None, handle_type=None, handle_id=None)`: This hook returns an array of connections on a specific node, handle type ("source", "target") or handle ID. -- `get_connection()`: Returns the current connection state when there is an active connection interaction. diff --git a/docs/enterprise/react_flow/interactivity.md b/docs/enterprise/react_flow/interactivity.md deleted file mode 100644 index 05eccee3882..00000000000 --- a/docs/enterprise/react_flow/interactivity.md +++ /dev/null @@ -1,70 +0,0 @@ -# Adding Interactivity to Your Flow - -This guide shows how to create an interactive flow in Reflex, allowing you to select, drag, and connect nodes and edges. - -## Define the State - -We start by defining the nodes and edges of the flow. The `FlowState` class holds the nodes and edges as state variables and includes event handlers to respond to changes. - -```python -import reflex as rx -import reflex_enterprise as rxe -from reflex_enterprise.components.flow.types import Node, Edge - -class FlowState(rx.State): - nodes: list[Node] = [ - {"id": "1", "type": "input", "position": {"x": 100, "y": 100}, "data": {"label": "Node 1"}}, - {"id": "2", "type": "default", "position": {"x": 300, "y": 200}, "data": {"label": "Node 2"}}, - ] - - edges: list[Edge] = [ - {"id": "e1-2", "source": "1", "target": "2", "label": "Connection", "type": "step"} - ] -``` - -# Add Event Handlers - -Event handlers allow the flow to respond to user interactions such as dragging nodes, updating edges, or creating new connections. - -```python -@rx.event -def set_nodes(self, nodes: list[Node]): - self.nodes = nodes - -@rx.event -def set_edges(self, edges: list[Edge]): - self.edges = edges - -``` - -- set_nodes updates nodes when they are moved or edited. - -- set_edges updates edges when they are modified or deleted. - -## Render the Interactive Flow - -Finally, we render the flow using **rxe.flow**, passing in the state and event handlers. Additional UI features include zoom/pan controls, a background grid, and a mini-map for navigation. - -```python -def interactive_flow(): - return rx.box( - rxe.flow( - rxe.flow.controls(), - rxe.flow.background(), - rxe.flow.mini_map(), - nodes=FlowState.nodes, - edges=FlowState.edges, - on_nodes_change=lambda node_changes: FlowState.set_nodes( - rxe.flow.util.apply_node_changes(FlowState.nodes, node_changes) - ), - on_edges_change=lambda edge_changes: FlowState.set_edges( - rxe.flow.util.apply_edge_changes(FlowState.edges, edge_changes) - ), - fit_view=True, - - attribution_position="bottom-right", - ), - height="100vh", - width="100vw", - ) -``` diff --git a/docs/enterprise/react_flow/nodes.md b/docs/enterprise/react_flow/nodes.md deleted file mode 100644 index 398afdfdd2e..00000000000 --- a/docs/enterprise/react_flow/nodes.md +++ /dev/null @@ -1,277 +0,0 @@ -# Nodes - -Nodes are the fundamental building blocks of a flow. This page explains how to define and customize nodes in Reflex Flow. - -## The Node Type - -A node is represented as a Python dictionary with the following fields: - -- `id` (`str`) – Unique identifier for the node. -- `position` (`dict`) – Position of the node with `x` and `y` coordinates. -- `data` (`dict`) – Arbitrary data passed to the node component. -- `type` (`str`) – Node type defined in `node_types`. -- `sourcePosition` (`str`) – Controls source handle position ("top", "right", "bottom", "left"). -- `targetPosition` (`str`) – Controls target handle position ("top", "right", "bottom", "left"). -- `hidden` (`bool`) – Whether the node is visible on the canvas. -- `selected` (`bool`) – Whether the node is currently selected. -- `draggable` (`bool`) – Whether the node can be dragged. -- `selectable` (`bool`) – Whether the node can be selected. -- `connectable` (`bool`) – Whether the node can be connected to other nodes. -- `deletable` (`bool`) – Whether the node can be deleted. -- `width` (`float`) – Width of the node. -- `height` (`float`) – Height of the node. -- `parentId` (`str`) – Parent node ID for creating sub-flows. -- `style` (`dict`) – Custom styles for the node. -- `className` (`str`) – CSS class name for the node. - -## Built-in Node Types - -Reflex Flow includes several built-in node types: - -```python -nodes: list[Node] = [ - {"id": "1", "type": "input", "position": {"x": 100, "y": 100}, "data": {"label": "Start"}}, - {"id": "2", "type": "default", "position": {"x": 300, "y": 100}, "data": {"label": "Process"}}, - {"id": "3", "type": "output", "position": {"x": 500, "y": 100}, "data": {"label": "End"}}, -] -``` - -- **input** – Entry point with only source handles -- **default** – Standard node with both source and target handles -- **output** – Exit point with only target handles - -## Basic Node Configuration - -### Node Positioning - -```python -node = { - "id": "positioned-node", - "type": "default", - "position": {"x": 250, "y": 150}, - "data": {"label": "Positioned Node"} -} -``` - -### Node Styling - -```python -styled_node = { - "id": "styled-node", - "type": "default", - "position": {"x": 100, "y": 200}, - "data": {"label": "Custom Style"}, - "style": { - "background": "#ff6b6b", - "color": "white", - "border": "2px solid #ff5252", - "borderRadius": "8px", - "padding": "10px" - } -} -``` - -### Handle Positioning - -```python -node_with_handles = { - "id": "handle-node", - "type": "default", - "position": {"x": 300, "y": 300}, - "data": {"label": "Custom Handles"}, - "sourcePosition": "right", - "targetPosition": "left" -} -``` - -# Custom Nodes - -Creating custom nodes is as easy as building a regular React component and passing it to the `node_types`. Since they’re standard React components, you can display any content and implement any functionality you need. Plus, you’ll have access to a range of props that allow you to extend and customize the default node behavior. - -Below is an example custom node using a `color picker` component. - -```python demo exec - -from typing import Any - -import reflex as rx - -import reflex_enterprise as rxe -from reflex_enterprise.components.flow.types import Connection, Edge, Node - - -class CustomNodeState(rx.State): - bg_color: rx.Field[str] = rx.field(default="#c9f1dd") - nodes: rx.Field[list[Node]] = rx.field( - default_factory=lambda: [ - { - "id": "1", - "type": "input", - "data": {"label": "An input node"}, - "position": {"x": 0, "y": 50}, - "sourcePosition": "right", - "style": {"color": "#000000",} - }, - { - "id": "2", - "type": "selectorNode", - "data": { - "color": "#c9f1dd", - }, - "position": {"x": 300, "y": 50}, - }, - { - "id": "3", - "type": "output", - "data": {"label": "Output A"}, - "position": {"x": 650, "y": 25}, - "targetPosition": "left", - "style": {"color": "#000000",} - }, - { - "id": "4", - "type": "output", - "data": {"label": "Output B"}, - "position": {"x": 650, "y": 100}, - "targetPosition": "left", - "style": {"color": "#000000",} - }, - ] - ) - edges: rx.Field[list[Edge]] = rx.field( - default_factory=lambda: [ - { - "id": "e1-2", - "source": "1", - "target": "2", - "animated": True, - }, - { - "id": "e2a-3", - "source": "2", - "target": "3", - "animated": True, - }, - { - "id": "e2b-4", - "source": "2", - "target": "4", - "animated": True, - }, - ] - ) - - @rx.event - def on_change_color(self, color: str): - self.nodes = [ - node - if node["id"] != "2" or "data" not in node - else {**node, "data": {**node["data"], "color": color}} - for node in self.nodes - ] - self.bg_color = color - - @rx.event - def set_nodes(self, nodes: list[Node]): - self.nodes = nodes - - @rx.event - def set_edges(self, edges: list[Edge]): - self.edges = edges - - -@rx.memo -def color_selector_node(data: rx.Var[dict], isConnectable: rx.Var[bool]): - data = data.to(dict) - return rx.el.div( - rxe.flow.handle( - type="target", - position="left", - is_connectable=isConnectable, - ), - rx.el.div( - "Custom Color Picker Node: ", - rx.el.strong(data["color"]), - ), - rx.el.input( - class_name="nodrag", - type="color", - on_change=CustomNodeState.on_change_color, - default_value=data["color"], - ), - rxe.flow.handle( - type="source", - position="right", - is_connectable=isConnectable, - ), - class_name="border border-1 p-2 rounded-sm", - border_color=rx.color_mode_cond("black", ""), - color="black", - bg="white", - ) - - -def node_stroke_color(node: rx.vars.ObjectVar[Node]): - return rx.match( - node["type"], - ("input", "#0041d0"), - ( - "selectorNode", - CustomNodeState.bg_color, - ), - ("output", "#ff0072"), - None, - ) - - -def node_color(node: rx.vars.ObjectVar[Node]): - return rx.match( - node["type"], - ( - "selectorNode", - CustomNodeState.bg_color, - ), - "#fff", - ) - -def custom_node(): - return rx.box( - rxe.flow( - rxe.flow.background(bg_color=CustomNodeState.bg_color), - rxe.flow.mini_map( - node_stroke_color=rx.vars.function.ArgsFunctionOperation.create( - ("node",), node_stroke_color(rx.Var("node").to(Node)) - ), - node_color=rx.vars.function.ArgsFunctionOperation.create( - ("node",), node_color(rx.Var("node").to(Node)) - ), - ), - rxe.flow.controls(), - default_nodes=CustomNodeState.nodes, - default_edges=CustomNodeState.edges, - nodes=CustomNodeState.nodes, - edges=CustomNodeState.edges, - on_nodes_change=lambda changes: CustomNodeState.set_nodes( - rxe.flow.util.apply_node_changes(CustomNodeState.nodes, changes) - ), - on_edges_change=lambda changes: CustomNodeState.set_edges( - rxe.flow.util.apply_edge_changes(CustomNodeState.edges, changes) - ), - on_connect=lambda connection: CustomNodeState.set_edges( - rxe.flow.util.add_edge( - connection.to(dict).merge({"animated": True}).to(Connection), - CustomNodeState.edges, - ) - ), - node_types={"selectorNode": color_selector_node}, - color_mode="light", - snap_grid=(20, 20), - default_viewport={"x": 0, "y": 0, "zoom": 1.5}, - snap_to_grid=True, - attribution_position="bottom-left", - fit_view=True, - ), - height="100vh", - width="100vw", - ) -``` diff --git a/docs/enterprise/react_flow/overview.md b/docs/enterprise/react_flow/overview.md deleted file mode 100644 index 8c4e183642c..00000000000 --- a/docs/enterprise/react_flow/overview.md +++ /dev/null @@ -1,23 +0,0 @@ -# Overview - -At its core, a flow diagram is an interactive graph composed of nodes connected by edges. To help understand the key concepts, let’s go over the main components of a flow. - -### Nodes - -Nodes are the building blocks of a flow. While there are a few default node types available, the real power comes from customizing them. You can design nodes to include interactive elements, display dynamic data, or support multiple connection points. The framework provides the foundation—you provide the functionality and style. - -### Handles - -Handles are the points on a node where edges attach. They typically appear on the top, bottom, left, or right sides of a node, but can be positioned and styled freely. Nodes can have multiple handles, allowing for complex connection setups. - -### Edges - -Edges are the connections between nodes. Each edge requires a source node and a target node. Edges can be styled and customized, and nodes with multiple handles can support multiple edges. Custom edges can include interactive elements, specialized routing, or unique visual styles beyond simple lines. - -### Connection Line - -When creating a new edge, you can click and drag from one handle to another. While dragging, the placeholder edge is called a connection line. Connection lines behave like edges and can be customized in appearance and behavior. - -### Viewport - -The viewport is the visible area containing the flow. Each node has x- and y-coordinates representing its position. Moving the viewport changes these coordinates, and zooming in or out adjusts the zoom level. The viewport ensures the diagram remains navigable and interactive. diff --git a/docs/enterprise/react_flow/theming.md b/docs/enterprise/react_flow/theming.md deleted file mode 100644 index 41361caac7f..00000000000 --- a/docs/enterprise/react_flow/theming.md +++ /dev/null @@ -1,75 +0,0 @@ -# Theming - -You can customize the appearance of the Flow component using CSS. The Flow component comes with a default theme, which you can override with your own styles. - -## CSS Variables - -The Flow component uses CSS variables for theming. You can override these variables to change the appearance of the flow. Here are some of the most common variables: - -```css -.react-flow { - --xy-background-color: #f7f9fb; - --xy-node-border-default: 1px solid #ededed; - --xy-node-boxshadow-default: - 0px 3.54px 4.55px 0px #00000005, 0px 3.54px 4.55px 0px #0000000d, - 0px 0.51px 1.01px 0px #0000001a; - --xy-node-border-radius-default: 8px; - --xy-handle-background-color-default: #ffffff; - --xy-handle-border-color-default: #aaaaaa; - --xy-edge-label-color-default: #505050; -} -``` - -## Custom Stylesheets - -You can add custom stylesheets to your app to override the default styles. To do this, add the `stylesheets` prop to your `rxe.App` or `rx.App` instance: - -```python -app = rxe.App( - stylesheets=[ - "/css/my-custom-styles.css", - ], -) -``` - -Then, create a file `assets/css/my-custom-styles.css` in your project and add your custom styles there. - -## Customizing Node and Edge Styles - -You can also apply custom styles to individual nodes and edges using the `style` and `className` props. - -### Using the style prop - -You can pass a style dictionary to the `style` prop of a node or edge: - -```python -node = { - "id": "1", - "position": {"x": 100, "y": 100}, - "data": {"label": "Node 1"}, - "style": {"backgroundColor": "#ffcc00"}, -} -``` - -### Using the className prop - -You can also pass a class name to the `className` prop and define the styles in your CSS file: - -```python -# In your python code -node = { - "id": "1", - "position": {"x": 100, "y": 100}, - "data": {"label": "Node 1"}, - "className": "my-custom-node", -} -``` - -```css -/* In your CSS file */ -.my-custom-node { - background-color: #ffcc00; - border: 2px solid #ff9900; - border-radius: 10px; -} -``` diff --git a/docs/enterprise/react_flow/utils.md b/docs/enterprise/react_flow/utils.md deleted file mode 100644 index 1f013857428..00000000000 --- a/docs/enterprise/react_flow/utils.md +++ /dev/null @@ -1,29 +0,0 @@ -# Utility Functions - -The `rxe.flow.util` module provides utility functions for working with Flow components. - -## Path Utilities - -These functions are used to calculate the path for an edge. - -- `get_simple_bezier_path(source_x, source_y, target_x, target_y, source_position="bottom", target_position="top")`: Returns everything you need to render a simple bezier edge between two nodes. -- `get_bezier_path(source_x, source_y, target_x, target_y, source_position="bottom", target_position="top", curvature=0.5)`: Returns everything you need to render a bezier edge between two nodes. -- `get_straight_path(source_x, source_y, target_x, target_y)`: Calculates the straight line path between two points. -- `get_smooth_step_path(source_x, source_y, target_x, target_y, source_position="bottom", target_position="top", border_radius=5, center_x=None, center_y=None, offset=20, step_position=0.5)`: Returns everything you need to render a stepped path between two nodes. - -## Change Handlers - -These functions are used to apply changes to nodes and edges from the `on_nodes_change` and `on_edges_change` event handlers. - -- `apply_node_changes(nodes, changes)`: Applies changes to nodes in the flow. -- `apply_edge_changes(edges, changes)`: Applies changes to edges in the flow. - -## Edge and Connection Utilities - -- `add_edge(params, edges)`: Creates a new edge in the flow. - -## Graph Utilities - -- `get_incomers(node_id, nodes, edges)`: Returns all incoming nodes connected to the given node. -- `get_outgoers(node_id, nodes, edges)`: Returns all outgoing nodes connected to the given node. -- `get_connected_edges(nodes, edges)`: Returns all edges connected to the given nodes. diff --git a/docs/enterprise/single-port-proxy.md b/docs/enterprise/single-port-proxy.md deleted file mode 100644 index 91c4a18b494..00000000000 --- a/docs/enterprise/single-port-proxy.md +++ /dev/null @@ -1,15 +0,0 @@ -# Single Port Proxy - -Enable single-port deployment by proxying the backend to the frontend port. - -## Configuration - -```python -import reflex_enterprise as rxe - -config = rxe.Config( - use_single_port=True, -) -``` - -This allows your application to run on a single port, which is useful for deployment scenarios where you can only expose one port. diff --git a/docs/hosting/adding-members.md b/docs/hosting/adding-members.md deleted file mode 100644 index 76712312313..00000000000 --- a/docs/hosting/adding-members.md +++ /dev/null @@ -1,37 +0,0 @@ -```python exec -import reflex as rx -from pcweb.constants import REFLEX_ASSETS_CDN -from reflex_image_zoom import image_zoom -``` - -# Project - -A project is a collection of applications (apps / websites). - -Every project has its own billing page that are accessible to Admins. - -## Adding Team Members - -To see the team members of a project click on the `Members` tab in the Cloud UI on the project page. - -If you are a User you have the ability to create, deploy and delete apps, but you do not have the power to add or delete users from that project. You must be an Admin for that. - -As an Admin you will see the an `Add user` button in the top right of the screen, as shown in the image below. Clicking on this will allow you to add a user to the project. You will need to enter the email address of the user you wish to add. - -```python eval -image_zoom(rx.image(src=f"{REFLEX_ASSETS_CDN}other/hosting_adding_team_members.webp", alt="Adding team members to Reflex Cloud")) -``` - -```python eval -rx.box(height="20px") -``` - -```md alert warning -# Currently a User must already have logged in once before they can be added to a project. - -At this time a User must be logged in to be added to a project. In future there will be automatic email invites sent to add new users who have never logged in before. -``` - -## Other project settings - -Clicking on the `Settings` tab in the Cloud UI on the project page allows a user to change the `project name`, check the `project id` and, if the project is not your default project, delete the project. diff --git a/docs/hosting/app-management.md b/docs/hosting/app-management.md deleted file mode 100644 index c7c19ba94c9..00000000000 --- a/docs/hosting/app-management.md +++ /dev/null @@ -1,58 +0,0 @@ -```python exec -import reflex as rx -from pcweb.constants import REFLEX_ASSETS_CDN -from reflex_image_zoom import image_zoom -from pcweb.pages.docs import hosting -from pcweb.pages import docs -from pcweb.styles.styles import get_code_style, cell_style -``` - -# App - -In Reflex Cloud an "app" (or "application" or "website") refers to a web application built using the Reflex framework, which can be deployed and managed within the Cloud platform. - -You can deploy an app using the `reflex deploy` command. - -There are many actions you can take in the Cloud UI to manage your app. Below are some of the most common actions you may want to take. - -## Stopping an App - -To stop an app follow the arrow in the image below and press on the `Stop app` button. Pausing an app will stop it from running and will not be accessible to users until you resume it. In addition, this will stop you being billed for your app. - -```python eval -image_zoom(rx.image(src=f"{REFLEX_ASSETS_CDN}other/stopping_app.webp", padding_bottom="20px")) -``` - -```md alert info -# CLI Command to stop an app - -`reflex cloud apps stop [OPTIONS] [APP_ID]` -``` - -## Deleting an App - -To delete an app click on the `Settings` tab in the Cloud UI on the app page. - -```python eval -image_zoom(rx.image(src=f"{REFLEX_ASSETS_CDN}other/environment_variables.webp")) -``` - -Then click on the `Danger` tab as shown below. - -```python eval -image_zoom(rx.image(src=f"{REFLEX_ASSETS_CDN}other/deleting_app.webp")) -``` - -Here there is a `Delete app` button. Pressing this button will delete the app and all of its data. This action is irreversible. - -```md alert info -# CLI Command to delete an app - -`reflex cloud apps delete [OPTIONS] [APP_ID]` -``` - -## Other app settings - -Clicking on the `Settings` tab in the Cloud UI on the app page also allows a user to change the `app name`, change the `app description` and check the `app id`. - -The other app settings also allows users to edit and add secrets (environment variables) to the app. For more information on secrets, see the [Secrets (Environment Variables)]({hosting.secrets_environment_vars.path}) page. diff --git a/docs/hosting/billing.md b/docs/hosting/billing.md deleted file mode 100644 index 0d5abb07034..00000000000 --- a/docs/hosting/billing.md +++ /dev/null @@ -1,32 +0,0 @@ -```python exec -import reflex as rx -from reflex_image_zoom import image_zoom -from pcweb.pages.pricing.calculator import compute_table_base -from pcweb.pages.docs import hosting -``` - -## Overview - -Billing for Reflex Cloud is monthly per project. Project owners and admins are able to view and manage the billing page. - -The billing for a project is comprised of two parts - number of `seats` and `compute`. - -## Seats - -Projects on a paid plan can invite collaborators to join their project. - -Each additional collaborator is considered a `seat` and is charged on a flat monthly rate. Project owners and admins can manage permissions and roles for each seat in the settings tab on the project page. - -## Compute - -Reflex Cloud is billed on a per second basis so you only pay for when your app is being used by your end users. When your app is idle, you are not charged. - -For more information on compute pricing, please see the [compute]({hosting.compute.path}) page. - -## Manage Billing - -To manage your billing, you can go to the `Billing` tab in the Cloud UI on the project page. - -## Setting Billing Limits - -If you want to set a billing limit for your project, you can do so by going to the `Billing` tab in the Cloud UI on the project page. diff --git a/docs/hosting/compute.md b/docs/hosting/compute.md deleted file mode 100644 index 92196d9b76b..00000000000 --- a/docs/hosting/compute.md +++ /dev/null @@ -1,216 +0,0 @@ -```python exec -import reflex as rx -from reflex_image_zoom import image_zoom -from pcweb.pages.pricing.calculator import compute_table_base -``` - -## Compute Usage - -Reflex Cloud is billed on a per second basis so you only pay for when your app is being used by your end users. When your app is idle, you are not charged. - -This allows you to deploy your app on larger sizes and multiple regions without worrying about paying for idle compute. We bill on a per second basis so you only pay for the compute you use. - -By default your app stays alive for 5 minutes after the no users are connected. After this time your app will be considered idle and you will not be charged. Start up times usually take less than 1 second for you apps to come back online. - -#### Warm vs Cold Start - -- Apps below `c2m2` are considered warm starts and are usually less than 1 second. -- If your app is larger than `c2m2` it will be a cold start which takes around 15 seconds. If you want to avoid this you can reserve a machine. - -## Compute Pricing Table - -```python eval -compute_table_base() -``` - -## Reserved Machines (Coming Soon) - -If you expect your apps to be continuously receiving users, you may want to reserve a machine instead of having us manage your compute. - -This will be a flat monthly rate for the machine. - -## Monitoring Usage - -To monitor your projects usage, you can go to the billing tab in the Reflex Cloud UI on the project page. - -Here you can see the current billing and usage for your project. - -## Real Life Examples of compute charges on the paid tiers - -```md alert -# Single Application - Single Region - -Anna, a hobbyist game developer, built a pixel art generator and hosted it on Reflex Cloud so fellow artists could use it anytime. She deployed her app in the San Francisco region, where she lives. If her users use the site for an hour a day, how much would Anna pay? - -**Facts:** - -- **Machine size:** `c1m1` (1 CPU, 1 GB Memory) - `$0.083` per hour -- **Regions:** `1` (SJC) -- **Avg usage per day per region:** `1 Hour` - -**Maths:** - -`1 region * 1 hour * 30 days = 30 compute hours` - -`30 * 0.083 = 2.49` - -(assuming a 30 day month) - -Anna's total cost for compute would be `$2.49` for the month. However, since paid users receive a `$10` credit, her compute cost is fully covered. - -**Charge for compute:** - -`$0.00 dollars` -``` - -```md alert -# Single Application - Multi Region - -Bob created a social media application and decided to host it on Reflex Cloud. Bob has users in Paris, London, San Jose and Sydney. Bob decided to deploy his application to all those region as well as additional one in Paris since that where Bob lives. If users use the site in each region for 30 minutes a day how much would Bob pay? - -**Facts:** - -- **Machine size:** `c1m1` (1 CPU, 1 GB Memory) - `$0.083` Cost per hour -- **Regions:** `5` (CDG x 2, LHR x 1, SJC x 1, SYD x 1) -- **Avg usage per day per region:** `0.5 Hours` - -**Maths:** - -`5 regions * 0.5 hours * 30 days = 75 compute hours` - -`75 * 0.083 = 6.23` - -(assuming a 30 day month) - -Bob would owe `$6.23` for this month. However since Bob is a paid user they receive a `$10` credit which brings Bob's bill down to `$0`. - -**Charge for compute:** - -`$0.00 dollars` -``` - -```md alert -# Single Growing Application - Multi Region - -Charlie, a small startup founder, built a finance tracking app that allows users to create and share finance insights in real time. As his user base expanded across different regions, he needed a multi-region setup to reduce latency and improve performance. To ensure a smooth experience, he deployed his app on Reflex Cloud using a `c1m2` machine in four regions. - -If users access the app on average for **16 hours per week** in each region, how much would Charlie pay? - -**Facts:** - -- **Machine size:** `c1m2` (1 CPU, 2 GB Memory) - `$0.157` per hour -- **Regions:** `4` -- **Avg usage per week per region:** `16 Hours` - -**Maths:** - -`4 regions * 16 hours * 4 weeks = 256 compute hours` - -`256 * 0.157 = 40.19` - -(assuming 4 weeks in a month) - -Charlie would owe `$40.19` for this month. However since Charlie is a paid user they receive a `$10` credit which brings Bob's bill down to `$30.19`. - -**Charge for compute:** - -`$30.19 dollars` -``` - -```md alert -# Single Application High-Performance App - Single Region - -David, an **AI enthusiast**, developed a **real-time image enhancement tool** that allows photographers to upscale and enhance their images using machine learning. Since his app requires more processing power, he deployed it on a **`c2m2` machine**, which offers increased CPU and memory to handle the intensive AI workloads. - -With users accessing the app **2 hours per day** over a **30-day month**, how much would David pay? - -**Facts:** - -- **Machine size:** `c2m2` (2 CPU, 2 GB Memory) - `$0.166` per hour -- **Regions:** `1` -- **Avg usage per day:** `2 Hours` - -**Maths:** - -`1 region * 2 hours * 30 days = 60 compute hours` - -`60 * 0.166 = 9.96` - -(assuming a 30 day month) - -David would owe `$9.96` for this month. However since David is a paid user they receive a `$10` credit, he will not be charged for compute for this month. - -**Charge for compute:** - -`$0.00 dollars` -``` - -```md alert -# Single Fast Scaling App - Multiple Regions - -Emily, a **productivity app developer**, built a **real-time team collaboration tool** that helps remote teams manage tasks and communicate efficiently. With users spread across multiple locations, she needed **low-latency performance** to ensure a seamless experience. To achieve this, Emily deployed her app using a `c1m1` machine in **three regions**. - -With users actively using the app **6 hours per day in each region** over a **30-day month**, how much would Emily pay? - -**Facts:** - -- **Machine size:** `c1m1` (1 CPU, 1 GB Memory) - `$0.083` per hour -- **Regions:** `3` -- **Avg usage per day per region:** `6 Hours` - -**Maths:** - -`3 regions * 6 hours * 30 days = 540 compute hours` - -`540 * 0.083 = 44.82` - -(assuming a 30 day month) - -Emily would owe `$44.82` for this month. However since Emily is a paid user they receive a `$10` credit which brings Emily's bill down to `$34.82`. - -**Charge for compute:** - -`$34.82 dollars` -``` - -```md alert -# Multiple Apps - Multiple Regions - -Fred, a **freelance developer**, built a **portfolio of web applications** that cater to different clients across the globe. He has built **5 apps** where **4 apps** have a small amount of traffic with an average of **0.5 hours a day** and **1 app** that has a high amount of traffic with an average of **6 hours** a day. He has deployed the 4 small traffic apps on a `c1m1` machine in **1 region** each and the high traffic app on a `c1m1` machine in **2 regions**. How much would Fred pay? - -**Facts for 4 small traffic apps:** - -- **Machine size:** `c1m1` (1 CPU, 1 GB Memory) - `$0.083` per hour -- **Regions:** `1` -- **Avg usage per day per region:** `0.5 Hours` - -**Facts for 1 large traffic app:** - -- **Machine size:** `c1m1` (1 CPU, 1 GB Memory) - `$0.083` per hour -- **Regions:** `2` -- **Avg usage per day per region:** `6 Hours` - -**Maths:** - -4 small traffic apps: - -`4 apps * 1 region * 0.5 hours * 30 days = 60 compute hours` - -1 large traffic apps: - -`2 regions * 6 hours * 30 days = 360 compute hours` - -Total compute hours = `60 + 360 = 420 compute hours` - -`420 * 0.083 = 34.86` - -(assuming a 30 day month) - -Fred would owe `$34.86` for this month. However since Fred is a paid user they receive a `$10` credit which brings Fred's bill down to `$24.86`. - -**Charge for compute:** - -`$24.82 dollars` -``` - -One thing that is important to note is that in the hypothetical example where you have `50 people` using your app `continuously for 24 hours` or if you have `1 person` using your app `continuously for 24 hours`, you `will be charged the same amount` as the charge is based on the amount of time your app up and not the number of users using your app. In `both these examples` your `app is up for 24 hours` and therefore you will be `charged for 24 hours of compute`. diff --git a/docs/hosting/config_file.md b/docs/hosting/config_file.md deleted file mode 100644 index 1a0a78c8bbc..00000000000 --- a/docs/hosting/config_file.md +++ /dev/null @@ -1,185 +0,0 @@ -```python exec -import reflex as rx -from reflex_image_zoom import image_zoom -from pcweb import constants -from pcweb.pages.docs import hosting -from pcweb.pages import docs -from pcweb.styles.styles import get_code_style, cell_style -``` - -## What is reflex cloud config? - -The following command: - -```bash -reflex cloud config -``` - -generates a `cloud.yml` configuration file used to deploy your Reflex app to the Reflex cloud platform. This file tells Reflex how and where to run your app in the cloud. - -## Configuration File Structure - -The `cloud.yml` file uses YAML format and supports the following structure. **All fields are optional** and will use sensible defaults if not specified: - -```yaml -# Basic deployment settings -name: my-app-prod # Optional: defaults to project folder name -description: "Production deployment" # Optional: empty by default -projectname: my-client-project # Optional: defaults to personal project - -# Infrastructure settings -regions: # Optional: defaults to sjc: 1 - sjc: 1 # San Jose (# of machines) - lhr: 2 # London (# of machines) -vmtype: c2m2 # Optional: defaults to c1m1 - -# Custom domain and environment -hostname: myapp # Optional: myapp.reflex.dev -envfile: .env.production # Optional: defaults to .env - -# Additional dependencies -packages: # Optional: empty by default - - procps -``` - -## Configuration Options Reference - -```python demo-only -rx.table.root( - rx.table.header( - rx.table.row( - rx.table.column_header_cell(rx.text("Option", size="1", weight="bold", color=rx.color("slate", 11))), - rx.table.column_header_cell(rx.text("Type", size="1", weight="bold", color=rx.color("slate", 11))), - rx.table.column_header_cell(rx.text("Default", size="1", weight="bold", color=rx.color("slate", 11))), - rx.table.column_header_cell(rx.text("Description", size="1", weight="bold", color=rx.color("slate", 11))), - align="center" - ) - ), - rx.table.body(*[ - rx.table.row( - rx.table.cell(rx.text(option, class_name="text-sm")), - rx.table.cell(rx.text(type_, class_name="text-sm")), - rx.table.cell(rx.text(default, class_name="text-sm")), - rx.table.cell(rx.link(description, href=link, class_name="text-sm") if link else rx.text(description, size="1", weight="regular")), - align="center" - ) for option, type_, default, description, link in [ - ("name", "string", "folder name", "Deployment identifier in dashboard", None), - ("description", "string", "empty", "Description of deployment", None), - ("regions", "object", "sjc: 1", "Region deployment mapping", "/docs/hosting/regions"), - ("vmtype", "string", "c1m1", "Virtual machine specifications", "/docs/hosting/machine-types"), - ("hostname", "string", "null", "Custom subdomain", None), - ("envfile", "string", ".env", "Environment variables file path", "/docs/hosting/secrets-environment-vars"), - ("project", "uuid", "null", "Project uuid", None), - ("projectname", "string", "null", "Project name", None), - ("packages", "array", "empty", "Additional system packages", None), - ("include_db", "boolean", "false", "Include local sqlite", None), - ("strategy", "string", "auto", "Deployment strategy", None) - ] - ]), - variant="ghost", - size="2", - width="100%", - max_width="800px", -) -``` - -## Configuration Options - -For details of specific sections click the links in the table. - -### Projects - -Organize deployments using projects: - -```yaml -projectname: client-alpha # Groups related deployments -``` - -You can also specify a project uuid instead of name: - -```yaml -project: 12345678-1234-1234-1234-1234567890ab -``` - -You can go to the homepage of the project in the reflex cloud dashboard to find your project uuid in the url `{constants.REFLEX_CLOUD_URL.rstrip("/")}/project/uuid` - -### Apt Packages - -Install additional system packages your application requires. Package names are based on the apt package manager: - -```yaml -packages: - - procps=2.0.32-1 # Version pinning is optional - - imagemagick - - ffmpeg -``` - -### Include SQLite - -Include local sqlite database: - -```yaml -include_db: true -``` - -This is not persistent and will be lost on restart. It is recommended to use a database service instead. - -### Strategy - -Deployment strategy: -Available strategies: - -- `immediate`: [Default] Deploy immediately -- `rolling`: Deploy in a rolling manner -- `bluegreen`: Deploy in a blue-green manner -- `canary`: Deploy in a canary manner, boot as single machine verify its health and then restart the rest. - -```yaml -strategy: immediate -``` - -## Multi-Environment Setup - -**Development (`cloud-dev.yml`):** - -```yaml -name: myapp-dev -description: "Development environment" -vmtype: c1m1 -envfile: .env.development -``` - -**Staging (`cloud-staging.yml`):** - -```yaml -name: myapp-staging -description: "Staging environment" -regions: - sjc: 1 -vmtype: c2m2 -envfile: .env.staging -``` - -**Production (`cloud-prod.yml`):** - -```yaml -name: myapp-production -description: "Production environment" -regions: - sjc: 2 - lhr: 1 -vmtype: c4m4 -hostname: myapp -envfile: .env.production -``` - -Deploy with specific configuration files: - -```bash -# Use default cloud.yml -reflex deploy - -# Use specific configuration file -reflex deploy --config cloud-prod.yml -reflex deploy --config cloud-staging.yml -``` diff --git a/docs/hosting/custom-domains.md b/docs/hosting/custom-domains.md deleted file mode 100644 index 7f78a984aa9..00000000000 --- a/docs/hosting/custom-domains.md +++ /dev/null @@ -1,75 +0,0 @@ -```python exec -import reflex as rx -from pcweb.constants import REFLEX_ASSETS_CDN -from reflex_image_zoom import image_zoom -``` - -# Custom Domains - -With the Enterprise tier of Reflex Cloud you can use your own custom domain to host your app. - -## Prerequisites - -You must purchase a domain from a domain registrar such as GoDaddy, Cloudflare, Namecheap, or AWS. - -For this tutorial we will use GoDaddy and the example domain `tomgotsman.us`. - -## Steps - -Once you have purchased your domain, you can add it to your Reflex Cloud app by following these steps: - -1 - Ensure you have deployed your app to Reflex Cloud. - -2 - Once your app is deployed click the `Custom Domain` tab and add your custom domain to the input field and press the Add domain button. You should now see a page like below: - -```python eval -image_zoom(rx.image(src=f"{REFLEX_ASSETS_CDN}other/custom-domains-DNS-inputs.webp")) -``` - -```python eval -rx.box(height="20px") -``` - -3 - On the domain registrar's website, navigate to the DNS settings for your domain. It should look something like the image below: - -```python eval -image_zoom(rx.image(src=f"{REFLEX_ASSETS_CDN}other/custom-domains-DNS-before.webp")) -``` - -```python eval -rx.box(height="20px") -``` - -4 - Add all four of the DNS records provided by Reflex Cloud to your domain registrar's DNS settings. If there is already an A name record, delete it and replace it with the one provided by Reflex Cloud. Your DNS settings should look like the image below: - -```python eval -image_zoom(rx.image(src=f"{REFLEX_ASSETS_CDN}other/custom-domains-DNS-after.webp")) -``` - -```md alert warning -# It may alert you that this record will resolve on ######.tomgotsman.us.tomgotsman.us. - -If this happens ensure that you select to only have the record resolve on ######.tomgotsman.us. -``` - -```md alert warning -# Your domain provider may not support an Apex CNAME record, in this case just use an A record. - -![Image showing failed CNAME record](/custom-domains-CNAME-fail.png) -``` - -```python eval -rx.box(height="20px") -``` - -5 - Once you have added the DNS records, refresh the page on the Reflex Cloud page (it may take a few minutes to a few hours to update successfully). If the records are correct, you should see a success message like the one below: - -```python eval -image_zoom(rx.image(src=f"{REFLEX_ASSETS_CDN}other/custom-domains-success.webp")) -``` - -```python eval -rx.box(height="20px") -``` - -6 - Now redeploy your app using the `reflex deploy` command and your app should now be live on your custom domain! diff --git a/docs/hosting/databricks.md b/docs/hosting/databricks.md deleted file mode 100644 index e7207d1c6e3..00000000000 --- a/docs/hosting/databricks.md +++ /dev/null @@ -1,203 +0,0 @@ -```python exec -import reflex as rx -from pcweb import constants -from pcweb.styles.styles import get_code_style, cell_style - -``` - -# Deploy Reflex to Databricks - -This guide walks you through deploying a Reflex web application on Databricks using the Apps platform. - -## Prerequisites - -- Databricks workspace with Unity Catalog enabled -- GitHub repository containing your Reflex application -- Reflex Enterprise license (for single-port deployment) - -## Step 1: Connect Your Repository - -1. **Link GitHub Repository** - - Navigate to your Databricks workspace - - Go to your user directory - - Click **Create** → **Git folder** - - Paste the URL of your GitHub repository containing the Reflex application - -## Step 2: Configure Application Settings - -### Create Configuration File - -Create a new file called `app.yaml` directly in Databricks (not in GitHub): - -```yaml -command: - ["reflex", "run", "--env", "prod", "--backend-port", "$DATABRICKS_APP_PORT"] - -env: - - name: "HOME" - value: "/tmp/reflex" - - name: "REFLEX_ACCESS_TOKEN" - value: "your-token-here" - - name: "DATABRICKS_WAREHOUSE_ID" - value: "your-sql-warehouse-id" - - name: "DATABRICKS_CATALOG" - value: "your-catalog-name" - - name: "DATABRICKS_SCHEMA" - value: "your-schema-name" - - name: "REFLEX_SHOW_BUILT_WITH_REFLEX" - value: 0 -``` - -### Obtain Required Tokens - -1. **Reflex Access Token** - - Visit [Reflex Cloud Tokens](<{constants.REFLEX_CLOUD_URL.rstrip("/")}/tokens/>) - - Navigate to Account Settings → Tokens - - Create a new token and copy the value - - Replace `your-token-here` in the configuration -2. **Databricks Resources** - - Update `DATABRICKS_WAREHOUSE_ID` with your SQL warehouse ID - - Update `DATABRICKS_CATALOG` with your target catalog name - - Update `DATABRICKS_SCHEMA` with your target schema name - -## Step 3: Enable Single-Port Deployment - -Update your Reflex application for Databricks compatibility: - -### Update rxconfig.py - -```python -import reflex as rx -import reflex_enterprise as rxe - -rxe.Config(app_name="app", use_single_port=True) -``` - -### Update Application Entry Point - -Modify your main application file where you define `rx.App`: - -```python -import reflex_enterprise as rxe - -app = rxe.App( - # your app configuration -) -``` - -```md alert info -# Also add `reflex-enterprise` and `asgiproxy` to your `requirements.txt` file. -``` - -## Step 4: Create Databricks App - -1. **Navigate to Apps** - - Go to **Compute** → **Apps** - - Click **Create App** -2. **Configure Application** - - Select **Custom App** - - Configure SQL warehouse for your application - -## Step 5: Set Permissions - -If you are using the `samples` Catalog then you can skip the permissions section. - -### Catalog Permissions - -1. Navigate to **Catalog** → Select your target catalog -2. Go to **Permissions** -3. Add the app's service principal user -4. Grant the following permissions: - - **USE CATALOG** - - **USE SCHEMA** - -### Schema Permissions - -1. Navigate to the specific schema -2. Go to **Permissions** -3. Grant the following permissions: - - **USE SCHEMA** - - **EXECUTE** - - **SELECT** - - **READ VOLUME** (if required) - -## Step 6: Deploy Application - -1. **Initiate Deployment** - - Click **Deploy** in the Apps interface - - When prompted for the code path, provide your Git folder path or select your repository folder -2. **Monitor Deployment** - - The deployment process will begin automatically - - Monitor logs for any configuration issues - -## Updating Your Application - -To deploy updates from your GitHub repository: - -1. **Pull Latest Changes** - - In the deployment interface, click **Deployment Source** - - Select **main** branch - - Click **Pull** to fetch the latest changes from GitHub -2. **Redeploy** - - Click **Deploy** again to apply the updates - -## Configuration Reference - -```python eval -rx.table.root( - rx.table.header( - rx.table.row( - rx.table.column_header_cell("Environment Variable"), - rx.table.column_header_cell("Description"), - rx.table.column_header_cell("Example"), - ), - ), - rx.table.body( - rx.table.row( - rx.table.cell(rx.code("HOME")), - rx.table.cell("Application home directory"), - rx.table.cell(rx.code("/tmp/reflex")), - ), - rx.table.row( - rx.table.cell(rx.code("REFLEX_ACCESS_TOKEN")), - rx.table.cell("Authentication for Reflex Cloud"), - rx.table.cell(rx.code("rx_token_...")), - ), - rx.table.row( - rx.table.cell(rx.code("DATABRICKS_WAREHOUSE_ID")), - rx.table.cell("SQL warehouse identifier"), - rx.table.cell("Auto-assigned"), - ), - rx.table.row( - rx.table.cell(rx.code("DATABRICKS_CATALOG")), - rx.table.cell("Target catalog name"), - rx.table.cell(rx.code("main")), - ), - rx.table.row( - rx.table.cell(rx.code("DATABRICKS_SCHEMA")), - rx.table.cell("Target schema name"), - rx.table.cell(rx.code("default")), - ), - rx.table.row( - rx.table.cell(rx.code("REFLEX_SHOW_BUILT_WITH_REFLEX")), - rx.table.cell("Show Reflex branding (Enterprise only)"), - rx.table.cell([rx.code("0"), " or ", rx.code("1")]), - ), - ), - variant="surface", - margin_y="1em", -) -``` - -## Troubleshooting - -- **Permission Errors**: Verify that all catalog and schema permissions are correctly set -- **Port Issues**: Ensure you're using `$DATABRICKS_APP_PORT` and single-port configuration -- **Token Issues**: Verify your Reflex access token is valid and properly configured -- **Deployment Failures**: Check the deployment logs for specific error messages - -## Notes - -- Single-port deployment requires Reflex Enterprise -- Configuration must be created directly in Databricks, not pushed from GitHub -- Updates require manual pulling from the deployment interface diff --git a/docs/hosting/deploy-quick-start.md b/docs/hosting/deploy-quick-start.md deleted file mode 100644 index cd2c7495f2b..00000000000 --- a/docs/hosting/deploy-quick-start.md +++ /dev/null @@ -1,88 +0,0 @@ -# Reflex Cloud - Quick Start - -```python exec -import reflex as rx -from pcweb.constants import REFLEX_ASSETS_CDN -from reflex_image_zoom import image_zoom -from pcweb.pages import docs -``` - -So far, we have been running our apps locally on our own machines. -But what if we want to share our apps with the world? This is where -the hosting service comes in. - -## Quick Start - -Reflex’s hosting service makes it easy to deploy your apps without worrying about configuring the infrastructure. - -### Prerequisites - -1. Hosting service requires `reflex>=0.6.6`. -2. This tutorial assumes you have successfully `reflex init` and `reflex run` your app. -3. Also make sure you have a `requirements.txt` file at the top level app directory that contains all your python dependencies! (To create a `requirements.txt` file, run `pip freeze > requirements.txt`.) - -### Authentication - -First run the command below to login / signup to your Reflex Cloud account: (command line) - -```bash -reflex login -``` - -You will be redirected to your browser where you can authenticate through Github or Gmail. - -### Web UI - -Once you are at this URL and you have successfully authenticated, click on the one project you have in your workspace. You should get a screen like this: - -```python eval -image_zoom(rx.image(src=f"{REFLEX_ASSETS_CDN}other/cloud_project_page.webp", alt="Reflex Cloud Dashboard")) -``` - -This screen shows the login command and the deploy command. As we are already logged in, we can skip the login command. - -### Deployment - -Now you can start deploying your app. - -In your cloud UI copy the `reflex deploy` command similar to the one shown below. - -```bash -reflex deploy --project 2a432b8f-2605-4753-####-####0cd1#### -``` - -In your project directory (where you would normally run `reflex run`) paste this command. - -The command is by default interactive. It asks you a few questions for information required for the deployment. - -1. The first question will compare your `requirements.txt` to your python environment and if they are different then it will ask you if you want to update your `requirements.txt` or to continue with the current one. If they are identical this question will not appear. To create a `requirements.txt` file, run `pip freeze > requirements.txt`. -2. The second question will search for a deployed app with the name of your current app, if it does not find one then it will ask if you wish to proceed in deploying your new app. -3. The third question is optional and will ask you for an app description. - -That’s it! You should receive some feedback on the progress of your deployment and in a few minutes your app should be up. 🎉 - -For detailed information about the deploy command and its options, see the [Deploy API Reference]({docs.cloud.deploy.path}) and the [CLI Reference](https://reflex.dev/docs/api-reference/cli/). - -```md alert info -# Once your code is uploaded, the hosting service will start the deployment. After a complete upload, exiting from the command **does not** affect the deployment process. The command prints a message when you can safely close it without affecting the deployment. -``` - -If you go back to the Cloud UI you should be able to see your deployed app and other useful app information. - -```md alert info -# Setup a Cloud Config File - -To create a `config.yml` file for your app to set your app configuration check out the [Cloud Config Docs]({docs.hosting.config_file.path}). -``` - -```md alert info -# Moving around the Cloud UI - -To go back, i.e. from an app to a project or from a project to your list of projects you just click the `REFLEX logo` in the top left corner of the page. -``` - -```md alert info -# All flag values are saved between runs - -All your flag values, i.e. environment variables or regions or tokens, are saved between runs. This means that if you run a command and you pass a flag value, the next time you run the same command the flag value will be the same as the last time you ran it. This means you should only set the flag values again if you want to change them. -``` diff --git a/docs/hosting/deploy-with-github-actions.md b/docs/hosting/deploy-with-github-actions.md deleted file mode 100644 index 603ff7b0324..00000000000 --- a/docs/hosting/deploy-with-github-actions.md +++ /dev/null @@ -1,119 +0,0 @@ -```python exec -from pcweb.pages import docs -import reflex as rx -from pcweb.styles.styles import get_code_style, cell_style - -github_actions_configs = [ - { - "name": "auth_token", - "description": "Reflex authentication token stored in GitHub Secrets.", - "required": True, - "default": "N/A" - }, - { - "name": "project_id", - "description": "The ID of the project you want to deploy to.", - "required": True, - "default": "N/A" - }, - { - "name": "app_directory", - "description": "The directory containing your Reflex app.", - "required": False, - "default": ". (root)" - }, - { - "name": "extra_args", - "description": "Additional arguments to pass to the `reflex deploy` command.", - "required": False, - "default": "N/A" - }, - { - "name": "python_version", - "description": "The Python version to use for the deployment environment.", - "required": False, - "default": "3.12" - } -] -``` - -# Deploy with Github Actions - -This GitHub Action simplifies the deployment of Reflex applications to Reflex Cloud. It handles setting up the environment, installing the Reflex CLI, and deploying your app with minimal configuration. - -```md alert info -# This action requires `reflex>=0.6.6` -``` - -**Features:** - -- Deploy Reflex apps directly from your GitHub repository to Reflex Cloud. -- Supports subdirectory-based app structures. -- Securely uses authentication tokens via GitHub Secrets. - -## Usage - -### Add the Action to Your Workflow - -Create a `.github/workflows/deploy.yml` file in your repository and add the following: - -```yaml -name: Deploy Reflex App - -on: - push: - branches: - - main - -jobs: - deploy: - runs-on: ubuntu-latest - steps: - - name: Deploy to Reflex Cloud - uses: reflex-dev/reflex-deploy-action@v1 - with: - auth_token: ${\{ secrets.REFLEX_PROJECT_ID }} - project_id: ${\{ secrets.REFLEX_PROJECT_ID }} - app_directory: "my-app-folder" # Optional, defaults to root - extra_args: "--env THIRD_PARTY_APIKEY=***" # Optional - python_version: "3.12" # Optional -``` - -### Set Up Your Secrets - -Store your Reflex authentication token securely in your repository's secrets: - -1. Go to your GitHub repository. -2. Navigate to Settings > Secrets and variables > Actions > New repository secret. -3. Create new secrets for `REFLEX_AUTH_TOKEN` and `REFLEX_PROJECT_ID`. - -(Create a `REFLEX_AUTH_TOKEN` in the tokens tab of your UI, check out these [docs]({docs.hosting.tokens.path}#tokens). - -The `REFLEX_PROJECT_ID` can be found in the UI when you click on the How to deploy button on the top right when inside a project and copy the ID after the `--project` flag.) - -### Inputs - -```python eval -rx.table.root( - rx.table.header( - rx.table.row( - rx.table.column_header_cell("Name"), - rx.table.column_header_cell("Description"), - rx.table.column_header_cell("required"), - rx.table.column_header_cell("Default"), - ), - ), - rx.table.body( - *[ - rx.table.row( - rx.table.cell(rx.code(github_config["name"], style=get_code_style("violet"))), - rx.table.cell(github_config["description"], style=cell_style), - rx.table.cell(rx.code(github_config["required"], style=get_code_style("violet"))), - rx.table.cell(rx.code(github_config["default"], style=get_code_style("violet")), min_width="100px"), - ) - for github_config in github_actions_configs - ] - ), - variant="surface", -) -``` diff --git a/docs/hosting/logs.md b/docs/hosting/logs.md deleted file mode 100644 index 39f90eb3a20..00000000000 --- a/docs/hosting/logs.md +++ /dev/null @@ -1,48 +0,0 @@ -```python exec -import reflex as rx -from pcweb.constants import REFLEX_ASSETS_CDN -from reflex_image_zoom import image_zoom -from pcweb.pages.docs import hosting -from pcweb.pages import docs -from pcweb.styles.styles import get_code_style, cell_style -``` - -## View Logs - -To view the app logs follow the arrow in the image below and press on the `Logs` dropdown. - -```python eval -image_zoom(rx.image(src=f"{REFLEX_ASSETS_CDN}other/view_logs.webp", padding_bottom="20px")) -``` - -```md alert info -# CLI Command to view logs - -`reflex cloud apps logs [OPTIONS] [APP_ID]` -``` - -## View Deployment Logs and Deployment History - -To view the deployment history follow the arrow in the image below and press on the `Deployments`. - -```python eval -image_zoom(rx.image(src=f"{REFLEX_ASSETS_CDN}other/view_deployment_logs.webp")) -``` - -This brings you to the page below where you can see the deployment history of your app. Click on deployment you wish to explore further. - -```python eval -image_zoom(rx.image(src=f"{REFLEX_ASSETS_CDN}other/view_deployment_logs_2.webp", padding_bottom="20px")) -``` - -```md alert info -# CLI Command to view deployment history - -`reflex cloud apps history [OPTIONS] [APP_ID]` -``` - -This brings you to the page below where you can view the deployment logs of your app by clicking the `Build logs` dropdown. - -```python eval -image_zoom(rx.image(src=f"{REFLEX_ASSETS_CDN}other/view_deployment_logs_3.webp")) -``` diff --git a/docs/hosting/machine-types.md b/docs/hosting/machine-types.md deleted file mode 100644 index 14e77e72584..00000000000 --- a/docs/hosting/machine-types.md +++ /dev/null @@ -1,34 +0,0 @@ -```python exec -import reflex as rx -from pcweb.constants import REFLEX_ASSETS_CDN -from reflex_image_zoom import image_zoom -from pcweb.pages.docs import hosting -from pcweb.pages import docs -from pcweb.styles.styles import get_code_style, cell_style -``` - -## Machine Types - -To scale your app you can choose different VMTypes. VMTypes are different configurations of CPU and RAM. - -To scale your VM in the Cloud UI, click on the `Settings` tab in the Cloud UI on the app page, and then click on the `Scale` tab as shown below. Clicking on the `Change VM` button will allow you to scale your app. - -```python eval -image_zoom(rx.image(src=f"{REFLEX_ASSETS_CDN}other/scaling_vms.webp", padding_bottom="20px")) -``` - -### VMTypes in the CLI - -To get all the possible VMTypes you can run the following command: - -```bash -reflex cloud vmtypes -``` - -To set which VMType to use when deploying your app you can pass the `--vmtype` flag with the id of the VMType. For example: - -```bash -reflex deploy --project f88b1574-f101-####-####-5f########## --vmtype c2m4 -``` - -This will deploy your app with the `c2m4` VMType, giving your app 2 CPU cores and 4 GB of RAM. diff --git a/docs/hosting/regions.md b/docs/hosting/regions.md deleted file mode 100644 index c6e1de0d7bc..00000000000 --- a/docs/hosting/regions.md +++ /dev/null @@ -1,149 +0,0 @@ -```python exec -import reflex as rx -from reflex_image_zoom import image_zoom -from pcweb.constants import REFLEX_ASSETS_CDN, REFLEX_CLOUD_URL -from pcweb.pages.docs import hosting -from pcweb.pages import docs -from pcweb.styles.styles import get_code_style, cell_style - - -REGIONS_DICT = { - "ams": "Amsterdam, Netherlands", - "arn": "Stockholm, Sweden", - "bom": "Mumbai, India", - "cdg": "Paris, France", - "dfw": "Dallas, Texas (US)", - "ewr": "Secaucus, NJ (US)", - "fra": "Frankfurt, Germany", - "gru": "Sao Paulo, Brazil", - "iad": "Ashburn, Virginia (US)", - "jnb": "Johannesburg, South Africa", - "lax": "Los Angeles, California (US)", - "lhr": "London, United Kingdom", - "nrt": "Tokyo, Japan", - "ord": "Chicago, Illinois (US)", - "sjc": "San Jose, California (US)", - "sin": "Singapore, Singapore", - "syd": "Sydney, Australia", - "yyz": "Toronto, Canada", -} - -COUNTRIES_CODES = { - "ams": "NL", - "arn": "SE", - "bom": "IN", - "cdg": "FR", - "dfw": "US", - "ewr": "US", - "fra": "DE", - "gru": "BR", - "iad": "US", - "jnb": "ZA", - "lax": "US", - "lhr": "GB", - "nrt": "JP", - "ord": "US", - "sjc": "US", - "sin": "SG", - "syd": "AU", - "yyz": "CA", -} - - -``` - -## Regions - -To scale your app you can choose different regions. Regions are different locations around the world where your app can be deployed. - -To scale your app to multiple regions in the Cloud UI, click on the `Settings` tab in the Cloud UI on the app page, and then click on the `Regions` tab as shown below. Clicking on the `Add new region` button will allow you to scale your app to multiple regions. - -```python eval -image_zoom(rx.image(src=f"{REFLEX_ASSETS_CDN}other/scaling_regions.webp", padding_bottom="20px")) -``` - -The table below show all the regions that can be deployed in. - -```python eval -rx.el.table( - rx.el.thead( - rx.el.tr( - rx.el.th( - rx.el.div( - "Region", - ), - class_name="px-6 py-3 text-left text-sm font-semibold text-secondary-12 text-nowrap", - ), - rx.el.th( - rx.el.div( - "Country", - ), - class_name="px-6 py-3 text-left text-sm font-semibold text-secondary-12 text-nowrap", - ), - ), - class_name="bg-slate-2", - ), - rx.el.tbody( - *[ - rx.el.tr( - rx.el.td( - rx.el.div( - region, - class_name="h-5 rounded-md border justify-start items-center inline-flex bg-slate-1 text-xs font-medium shrink-0 px-1.5 w-fit text-slate-12 border-slate-6" - ), - class_name="px-6 py-3", - ), - rx.el.td( - rx.el.div( - rx.image( - src=f"{REFLEX_CLOUD_URL.rstrip('/')}/flags/{COUNTRIES_CODES[region]}.svg", - class_name="rounded-[2px] mr-2 w-5 h-4", - ), - REGIONS_DICT[region], - class_name="flex flex-row items-center gap-2", - ), - class_name="px-6 py-3 text-sm font-medium text-slate-9" - ), - class_name="even:bg-slate-2 odd:bg-slate-1 hover:bg-secondary-3", - ) - for region in REGIONS_DICT.keys() - ], - class_name="divide-y divide-slate-4", - ), - class_name="w-full table-fixed rounded-xl overflow-hidden divide-y divide-slate-4", -) -``` - -### Selecting Regions to Deploy in the CLI - -Below is an example of how to deploy your app in several regions: - -```bash -reflex deploy --project f88b1574-f101-####-####-5f########## --region sjc --region iad -``` - -By default all apps are deloyed in `sjc` if no other regions are given. If you wish to deploy in another region or several regions you can pass the `--region` flag (`-r` also works) with the region code. Check out all the regions that we can deploy to below: - -## Config File - -To create a `config.yml` file for your app run the command below: - -```bash -reflex cloud config -``` - -This will create a yaml file similar to the one below where you can edit the app configuration: - -```yaml -name: medo -description: "" -regions: - sjc: 1 - lhr: 2 -vmtype: c1m1 -hostname: null -envfile: .env -project: null -packages: - - procps -``` diff --git a/docs/hosting/secrets-environment-vars.md b/docs/hosting/secrets-environment-vars.md deleted file mode 100644 index 84fb719a375..00000000000 --- a/docs/hosting/secrets-environment-vars.md +++ /dev/null @@ -1,53 +0,0 @@ -```python exec -import reflex as rx -from pcweb.constants import REFLEX_ASSETS_CDN -from reflex_image_zoom import image_zoom -``` - -# Secrets (Environment Variables) - -## Adding Secrets through the CLI - -Below is an example of how to use an environment variable file. You can pass the `--envfile` flag with the path to the env file. For example: - -```bash -reflex deploy --project f88b1574-f101-####-####-5f########## --envfile .env -``` - -In this example the path to the file is `.env`. - -If you prefer to pass the environment variables manually below is deployment command example: - -```bash -reflex deploy --project f88b1574-f101-####-####-5f########## --env OPENAI_API_KEY=sk-proj-vD4i9t6U############################ -``` - -They are passed after the `--env` flag as key value pairs. - -To pass multiple environment variables, you can repeat the `--env` tag. i.e. `reflex deploy --project f88b1574-f101-####-####-5f########## --env KEY1=VALUE1 --env KEY2=VALUE`. The `--envfile` flag will override any envs set manually. - -```md alert info -# More information on Environment Variables - -Environment variables are encrypted and safely stored. We recommend that backend API keys or secrets are entered as `envs`. Make sure to enter the `envs` without any quotation marks. We do not show the values of them in any CLI commands, only their names (or keys). - -You access the values of `envs` by referencing `os.environ` with their names as keys in your app's backend. For example, if you set an env `ASYNC_DB_URL`, you are able to access it by `os.environ["ASYNC_DB_URL"]`. Some Python libraries automatically look for certain environment variables. For example, `OPENAI_API_KEY` for the `openai` python client. The `boto3` client credentials can be configured by setting `AWS_ACCESS_KEY_ID`,`AWS_SECRET_ACCESS_KEY`. This information is typically available in the documentation of the Python packages you use. -``` - -## Adding Secrets through the Cloud UI - -To find the secrets tab, click on the `Settings` tab in the Cloud UI on the app page. - -```python eval -image_zoom(rx.image(src=f"{REFLEX_ASSETS_CDN}other/environment_variables.webp")) -``` - -Then click on the `Secrets` tab as shown below. - -```python eval -image_zoom(rx.image(src=f"{REFLEX_ASSETS_CDN}other/environment_variables_2.webp")) -``` - -From here you can add or edit your environment variables. You will need to restart your app for these changes to take effect. - -This functionality in the UI can be disabled by an admin of the project. diff --git a/docs/hosting/self-hosting.md b/docs/hosting/self-hosting.md deleted file mode 100644 index f974914c226..00000000000 --- a/docs/hosting/self-hosting.md +++ /dev/null @@ -1,122 +0,0 @@ -```python exec -import reflex as rx - -from pcweb.pages.docs import getting_started -from pcweb.pages.pricing.pricing import pricing_path -``` - -# Self Hosting - -We recommend using `reflex deploy`, but if this does not fit your use case then you can also host your apps yourself. - -Clone your code to a server and install the [requirements]({getting_started.installation.path}). - -## API URL - -Edit your `rxconfig.py` file and set `api_url` to the publicly accessible IP -address or hostname of your server, with the port `:8000` at the end. Setting -this correctly is essential for the frontend to interact with the backend state. - -For example if your server is at `app.example.com`, your config would look like this: - -```python -config = rx.Config( - app_name="your_app_name", - api_url="http://app.example.com:8000", -) -``` - -It is also possible to set the environment variable `API_URL` at run time or -export time to retain the default for local development. - -## Production Mode - -Then run your app in production mode: - -```bash -reflex run --env prod -``` - -Production mode creates an optimized build of your app. By default, the static -frontend of the app (HTML, Javascript, CSS) will be exposed on port `3000` and -the backend (event handlers) will be listening on port `8000`. - -```md alert warning -# Reverse Proxy and Websockets - -Because the backend uses websockets, some reverse proxy servers, like [nginx](https://nginx.org/en/docs/http/websocket.html) or [apache](https://httpd.apache.org/docs/2.4/mod/mod_proxy.html#protoupgrade), must be configured to pass the `Upgrade` header to allow backend connectivity. -``` - -## Exporting a Static Build - -Exporting a static build of the frontend allows the app to be served using a -static hosting provider, like Netlify or Github Pages. Be sure `api_url` is set -to an accessible backend URL when the frontend is exported. - -```bash -API_URL=http://app.example.com:8000 reflex export -``` - -This will create a `frontend.zip` file with your app's minified HTML, -Javascript, and CSS build that can be uploaded to your static hosting service. - -It also creates a `backend.zip` file with your app's backend python code to -upload to your server and run. - -You can export only the frontend or backend by passing in the `--frontend-only` -or `--backend-only` flags. - -It is also possible to export the components without zipping. To do -this, use the `--no-zip` parameter. This provides the frontend in the -`.web/build/client/` directory and the backend can be found in the root directory of -the project. - -## Reflex Container Service - -Another option is to run your Reflex service in a container. For this -purpose, a `Dockerfile` and additional documentation is available in the Reflex -project in the directory `docker-example`. - -For the build of the container image it is necessary to edit the `rxconfig.py` -and the add the `requirements.txt` -to your project folder. The following changes are necessary in `rxconfig.py`: - -```python -config = rx.Config( - app_name="app", - api_url="http://app.example.com:8000", -) -``` - -Notice that the `api_url` should be set to the externally accessible hostname or -IP, as the client browser must be able to connect to it directly to establish -interactivity. - -You can find the `requirements.txt` in the `docker-example` folder of the -project too. - -The project structure should looks like this: - -```bash -hello -├── .web -├── assets -├── hello -│ ├── __init__.py -│ └── hello.py -├── rxconfig.py -├── Dockerfile -└── requirements.txt -``` - -After all changes have been made, the container image can now be created as follows. - -```bash -docker build -t reflex-project:latest . -``` - -Finally, you can start your Reflex container service as follows. - -```bash -docker run -d -p 3000:3000 -p 8000:8000 --name app reflex-project:latest -``` diff --git a/docs/hosting/tokens.md b/docs/hosting/tokens.md deleted file mode 100644 index 7c65428acb3..00000000000 --- a/docs/hosting/tokens.md +++ /dev/null @@ -1,21 +0,0 @@ -```python exec -import reflex as rx -from pcweb.constants import REFLEX_ASSETS_CDN -from reflex_image_zoom import image_zoom -``` - -# Tokens - -A token gives someone else all the permissions you have as a User or Admin. They can run any Reflex Cloud command from the CLI as if they were you, using the `--token` flag. A common use case is for GitHub Actions (you store this token in your secrets). - -To access or create tokens, first click the avatar in the top-right corner to open the drop-down menu, then click `Account Settings`. - -```python eval -image_zoom(rx.image(src=f"{REFLEX_ASSETS_CDN}other/hosting_tokens_1.webp", alt="Adding tokens to Reflex Cloud", padding="1em 0em")) -``` - -Clicking `Account Settings` will redirect you to both the `Settings` and `Tokens` dashboards. Click the `Tokens` tab at the top to access your tokens or create new ones. - -```python eval -image_zoom(rx.image(src=f"{REFLEX_ASSETS_CDN}other/hosting_tokens_2.webp", alt="Adding tokens to Reflex Cloud", padding="1em 0em")) -``` From 4fe2c25a1a8adcd1d3f34cd0e0fa6d6a8a5c53ac Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Tue, 24 Mar 2026 13:19:03 -0700 Subject: [PATCH 004/113] i had enough of you prettier --- pyproject.toml | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index ea7de088842..4efe251951b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -279,9 +279,18 @@ rev = "v1.1.408" hooks = [{ id = "pyright", args = ["reflex", "tests"], language = "system" }] [[tool.pre-commit.repos]] -repo = "https://github.com/pre-commit/mirrors-prettier" -rev = "f62a70a3a7114896b062de517d72829ea1c884b6" -hooks = [{ id = "prettier", require_serial = true }] +repo = "https://github.com/biomejs/pre-commit" +rev = "v0.6.1" +hooks = [ + { id = "biome-format", args = [ + "--indent-width", + "2", + "--indent-style", + "space", + ], additional_dependencies = [ + "@biomejs/biome@2.4.8", + ] }, +] [tool.uv] required-version = ">=0.7.0" From a8875af0a9d870e8896ebc62b0b3383e2c9fb9e2 Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Tue, 24 Mar 2026 14:15:08 -0700 Subject: [PATCH 005/113] add markdown parsing --- packages/reflex-docgen/pyproject.toml | 1 + .../src/reflex_docgen/__init__.py | 32 ++ .../src/reflex_docgen/_markdown.py | 409 ++++++++++++++++++ tests/units/docgen/__init__.py | 0 .../test_class_and_component.py} | 0 tests/units/docgen/test_markdown.py | 378 ++++++++++++++++ uv.lock | 11 + 7 files changed, 831 insertions(+) create mode 100644 packages/reflex-docgen/src/reflex_docgen/_markdown.py create mode 100644 tests/units/docgen/__init__.py rename tests/units/{test_docgen.py => docgen/test_class_and_component.py} (100%) create mode 100644 tests/units/docgen/test_markdown.py diff --git a/packages/reflex-docgen/pyproject.toml b/packages/reflex-docgen/pyproject.toml index 21d9d7c39c1..1f56cd4a8a8 100644 --- a/packages/reflex-docgen/pyproject.toml +++ b/packages/reflex-docgen/pyproject.toml @@ -8,6 +8,7 @@ maintainers = [{ name = "Khaleel Al-Adhami", email = "khaleel@reflex.dev" }] requires-python = ">=3.10" dependencies = [ "griffelib>=2.0.1", + "mistletoe>=1.4.0", "reflex", "typing-extensions", "typing-inspection>=0.4.2", diff --git a/packages/reflex-docgen/src/reflex_docgen/__init__.py b/packages/reflex-docgen/src/reflex_docgen/__init__.py index acf408b8aa9..df0ebfd63fa 100644 --- a/packages/reflex-docgen/src/reflex_docgen/__init__.py +++ b/packages/reflex-docgen/src/reflex_docgen/__init__.py @@ -16,16 +16,48 @@ get_component_event_handlers as get_component_event_handlers, ) from reflex_docgen._component import get_component_props as get_component_props +from reflex_docgen._markdown import Block as Block +from reflex_docgen._markdown import BoldSpan as BoldSpan +from reflex_docgen._markdown import CodeBlock as CodeBlock +from reflex_docgen._markdown import CodeSpan as CodeSpan +from reflex_docgen._markdown import DirectiveBlock as DirectiveBlock +from reflex_docgen._markdown import Document as Document +from reflex_docgen._markdown import FrontMatter as FrontMatter +from reflex_docgen._markdown import HeadingBlock as HeadingBlock +from reflex_docgen._markdown import ImageSpan as ImageSpan +from reflex_docgen._markdown import ItalicSpan as ItalicSpan +from reflex_docgen._markdown import LinkSpan as LinkSpan +from reflex_docgen._markdown import Span as Span +from reflex_docgen._markdown import StrikethroughSpan as StrikethroughSpan +from reflex_docgen._markdown import TextBlock as TextBlock +from reflex_docgen._markdown import TextSpan as TextSpan +from reflex_docgen._markdown import parse_document as parse_document __all__ = [ + "Block", + "BoldSpan", "ClassDocumentation", + "CodeBlock", + "CodeSpan", "ComponentDocumentation", + "DirectiveBlock", + "Document", "EventHandlerDocumentation", "FieldDocumentation", + "FrontMatter", + "HeadingBlock", + "ImageSpan", + "ItalicSpan", + "LinkSpan", "MethodDocumentation", "PropDocumentation", + "Span", + "StrikethroughSpan", + "TextBlock", + "TextSpan", "generate_class_documentation", "generate_documentation", "get_component_event_handlers", "get_component_props", + "parse_document", ] diff --git a/packages/reflex-docgen/src/reflex_docgen/_markdown.py b/packages/reflex-docgen/src/reflex_docgen/_markdown.py new file mode 100644 index 00000000000..f002ef5a4aa --- /dev/null +++ b/packages/reflex-docgen/src/reflex_docgen/_markdown.py @@ -0,0 +1,409 @@ +"""Parse Reflex documentation markdown files using mistletoe.""" + +from __future__ import annotations + +import re +from dataclasses import dataclass +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from mistletoe.block_token import BlockToken + +# --------------------------------------------------------------------------- +# Span types — inline content without exposing mistletoe +# --------------------------------------------------------------------------- + + +@dataclass(frozen=True, slots=True, kw_only=True) +class TextSpan: + """Plain text. + + Attributes: + text: The text content. + """ + + text: str + + +@dataclass(frozen=True, slots=True, kw_only=True) +class BoldSpan: + """Bold (strong) text. + + Attributes: + children: The inline spans inside the bold. + """ + + children: tuple[Span, ...] + + +@dataclass(frozen=True, slots=True, kw_only=True) +class ItalicSpan: + """Italic (emphasis) text. + + Attributes: + children: The inline spans inside the italic. + """ + + children: tuple[Span, ...] + + +@dataclass(frozen=True, slots=True, kw_only=True) +class StrikethroughSpan: + """Strikethrough text. + + Attributes: + children: The inline spans inside the strikethrough. + """ + + children: tuple[Span, ...] + + +@dataclass(frozen=True, slots=True, kw_only=True) +class CodeSpan: + """Inline code. + + Attributes: + code: The code text. + """ + + code: str + + +@dataclass(frozen=True, slots=True, kw_only=True) +class LinkSpan: + """A hyperlink. + + Attributes: + children: The inline spans forming the link text. + target: The URL target. + """ + + children: tuple[Span, ...] + target: str + + +@dataclass(frozen=True, slots=True, kw_only=True) +class ImageSpan: + """An inline image. + + Attributes: + children: The inline spans forming the alt text. + src: The image source URL. + """ + + children: tuple[Span, ...] + src: str + + +#: Union of all inline span types. +Span = ( + TextSpan + | BoldSpan + | ItalicSpan + | StrikethroughSpan + | CodeSpan + | LinkSpan + | ImageSpan +) + +# --------------------------------------------------------------------------- +# Block types +# --------------------------------------------------------------------------- + + +@dataclass(frozen=True, slots=True, kw_only=True) +class FrontMatter: + """YAML frontmatter extracted from a markdown document. + + Attributes: + raw: The raw YAML string (without delimiters). + """ + + raw: str + + +@dataclass(frozen=True, slots=True, kw_only=True) +class CodeBlock: + """A fenced code block with optional language and flags. + + Attributes: + language: The language identifier (e.g. "python"). + flags: Additional flags after the language (e.g. ("demo", "exec")). + content: The code content inside the block. + """ + + language: str | None + flags: tuple[str, ...] + content: str + + +@dataclass(frozen=True, slots=True, kw_only=True) +class DirectiveBlock: + """A markdown directive block (```md [args...] ```). + + Covers alert, video, definition, section, and any future md directives. + + Attributes: + name: The directive name (e.g. "alert", "video", "definition", "section"). + args: Additional arguments after the name (e.g. ("info",) or ("https://...",)). + content: The raw content inside the block. + """ + + name: str + args: tuple[str, ...] + content: str + + +@dataclass(frozen=True, slots=True, kw_only=True) +class HeadingBlock: + """A markdown heading. + + Attributes: + level: The heading level (1-6). + children: The inline spans forming the heading text. + """ + + level: int + children: tuple[Span, ...] + + +@dataclass(frozen=True, slots=True, kw_only=True) +class TextBlock: + """A block of markdown text (paragraph or other inline content). + + Attributes: + children: The inline spans forming the text content. + """ + + children: tuple[Span, ...] + + +#: Union of all block types that can appear in a parsed document. +Block = FrontMatter | CodeBlock | DirectiveBlock | HeadingBlock | TextBlock + + +@dataclass(frozen=True, slots=True, kw_only=True) +class Document: + """A parsed Reflex documentation markdown file. + + Attributes: + frontmatter: The YAML frontmatter, if present. + blocks: The sequence of content blocks in document order. + """ + + frontmatter: FrontMatter | None + blocks: tuple[Block, ...] + + @property + def headings(self) -> tuple[HeadingBlock, ...]: + """Return all headings in the document.""" + return tuple(b for b in self.blocks if isinstance(b, HeadingBlock)) + + @property + def code_blocks(self) -> tuple[CodeBlock, ...]: + """Return all code blocks in the document.""" + return tuple(b for b in self.blocks if isinstance(b, CodeBlock)) + + @property + def directives(self) -> tuple[DirectiveBlock, ...]: + """Return all directive blocks in the document.""" + return tuple(b for b in self.blocks if isinstance(b, DirectiveBlock)) + + +# --------------------------------------------------------------------------- +# Parsing helpers +# --------------------------------------------------------------------------- + +_FRONTMATTER_RE = re.compile(r"\A---\n(.*?\n)---\n", re.DOTALL) + + +def _extract_frontmatter(source: str) -> tuple[FrontMatter | None, str]: + """Extract YAML frontmatter from the beginning of a markdown string. + + Args: + source: The raw markdown source. + + Returns: + A tuple of (FrontMatter or None, remaining source). + """ + m = _FRONTMATTER_RE.match(source) + if m is None: + return None, source + return FrontMatter(raw=m.group(1).strip()), source[m.end() :] + + +def _convert_span(token: object) -> Span: + """Convert a mistletoe span token into a Span. + + Args: + token: A mistletoe span token. + + Returns: + The corresponding Span. + """ + from mistletoe.span_token import ( + Emphasis, + Image, + InlineCode, + Link, + RawText, + Strikethrough, + Strong, + ) + + if isinstance(token, RawText): + return TextSpan(text=token.content) + + if isinstance(token, InlineCode): + # InlineCode.children is a tuple of one RawText element. + children = token.children + if ( + not isinstance(children, tuple) + or len(children) != 1 + or not isinstance(children[0], RawText) + ): + msg = f"Expected InlineCode to have a single RawText child, got {children!r}" + raise TypeError(msg) + return CodeSpan(code=children[0].content) + + if isinstance(token, Strong): + return BoldSpan(children=_convert_children(token)) + + if isinstance(token, Emphasis): + return ItalicSpan(children=_convert_children(token)) + + if isinstance(token, Strikethrough): + return StrikethroughSpan(children=_convert_children(token)) + + if isinstance(token, Link): + return LinkSpan(children=_convert_children(token), target=token.target) + + if isinstance(token, Image): + return ImageSpan(children=_convert_children(token), src=token.src) + + # Unknown span type — render as plain text via its content or repr. + content = getattr(token, "content", None) or str(token) + return TextSpan(text=content) + + +def _convert_children(token: object) -> tuple[Span, ...]: + """Convert the children of a mistletoe token into Spans. + + Args: + token: A mistletoe token with a children attribute. + + Returns: + A tuple of Span objects. + """ + children = getattr(token, "children", None) + if children is None: + return () + return tuple(_convert_span(child) for child in children) + + +def _parse_info_string(info: str) -> tuple[str | None, tuple[str, ...]]: + """Parse a fenced code block info string into language and flags. + + Args: + info: The info string (e.g. "python demo exec"). + + Returns: + A tuple of (language, flags). + """ + parts = info.strip().split() + if not parts: + return None, () + return parts[0], tuple(parts[1:]) + + +def _convert_block(token: BlockToken) -> Block | None: + """Convert a mistletoe block token into a docgen Block. + + Args: + token: The mistletoe block token. + + Returns: + A Block, or None if the token should be skipped. + """ + from mistletoe.block_token import BlockCode, CodeFence, Heading, Paragraph + from mistletoe.span_token import RawText + + if isinstance(token, Heading): + return HeadingBlock(level=token.level, children=_convert_children(token)) + + if isinstance(token, (BlockCode, CodeFence)): + if isinstance(token, CodeFence): + # CodeFence.info_string contains the full info string including language. + info = token.info_string or "" + else: + info = getattr(token, "language", "") or "" + language, flags = _parse_info_string(info) + + # CodeFence/BlockCode.children is a tuple of one RawText element. + children = token.children + if not children: + content = "" + elif ( + not isinstance(children, tuple) + or len(children) != 1 + or not isinstance(children[0], RawText) + ): + msg = f"Expected code block to have a single RawText child, got {children!r}" + raise TypeError(msg) + else: + content = children[0].content + # Strip trailing newline that mistletoe adds. + content = content.rstrip("\n") + + # ```md [args...]``` blocks become DirectiveBlocks. + if language == "md" and flags: + return DirectiveBlock( + name=flags[0], + args=flags[1:], + content=content, + ) + + return CodeBlock(language=language, flags=flags, content=content) + + if isinstance(token, Paragraph): + spans = _convert_children(token) + if spans: + return TextBlock(children=spans) + return None + + # For other block types (lists, blockquotes, etc.), convert children + # or fall back to a TextSpan of the raw content. + spans = _convert_children(token) + if spans: + return TextBlock(children=spans) + return None + + +def parse_document(source: str) -> Document: + """Parse a Reflex documentation markdown file into a Document. + + Args: + source: The raw markdown source string. + + Returns: + A parsed Document with frontmatter and content blocks. + """ + from mistletoe.block_token import BlockToken + from mistletoe.block_token import Document as MistletoeDocument + + frontmatter, remaining = _extract_frontmatter(source) + doc = MistletoeDocument(remaining) + + blocks: list[Block] = [] + if doc.children: + for child in doc.children: + # mistletoe.block_token.Document guarantees children are BlockToken + # instances (see its docstring: "Its children are block tokens"). + if not isinstance(child, BlockToken): + msg = f"Expected BlockToken, got {type(child).__name__}" + raise TypeError(msg) + block = _convert_block(child) + if block is not None: + blocks.append(block) + + return Document(frontmatter=frontmatter, blocks=tuple(blocks)) diff --git a/tests/units/docgen/__init__.py b/tests/units/docgen/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/units/test_docgen.py b/tests/units/docgen/test_class_and_component.py similarity index 100% rename from tests/units/test_docgen.py rename to tests/units/docgen/test_class_and_component.py diff --git a/tests/units/docgen/test_markdown.py b/tests/units/docgen/test_markdown.py new file mode 100644 index 00000000000..2c9defbd9d8 --- /dev/null +++ b/tests/units/docgen/test_markdown.py @@ -0,0 +1,378 @@ +"""Tests for reflex-docgen markdown parsing.""" + +from pathlib import Path + +import pytest +from reflex_docgen import ( + BoldSpan, + CodeSpan, + FrontMatter, + ImageSpan, + ItalicSpan, + LinkSpan, + StrikethroughSpan, + TextBlock, + TextSpan, + parse_document, +) + +_DOCS_DIR = Path(__file__).resolve().parents[3] / "docs" + + +def test_no_frontmatter(): + """Parsing a document without frontmatter returns None.""" + doc = parse_document("# Hello\n\nWorld\n") + assert doc.frontmatter is None + + +def test_basic_frontmatter(): + """A simple YAML frontmatter block is extracted.""" + source = "---\ntitle: Test\n---\n# Hello\n" + doc = parse_document(source) + assert doc.frontmatter is not None + assert doc.frontmatter.raw == "title: Test" + + +def test_multiline_frontmatter(): + """Multi-line YAML frontmatter is preserved.""" + source = "---\ncomponents:\n - rx.button\n\nButton: |\n lambda **props: rx.button(**props)\n---\n# Button\n" + doc = parse_document(source) + assert doc.frontmatter is not None + assert "components:" in doc.frontmatter.raw + assert "rx.button" in doc.frontmatter.raw + + +def test_frontmatter_not_in_blocks(): + """Frontmatter should not appear in the blocks list.""" + source = "---\ntitle: Test\n---\n# Hello\n" + doc = parse_document(source) + assert not any(isinstance(b, FrontMatter) for b in doc.blocks) + + +def test_h1(): + """A level-1 heading is parsed correctly.""" + doc = parse_document("# Title\n") + assert len(doc.headings) == 1 + assert doc.headings[0].level == 1 + assert doc.headings[0].children == (TextSpan(text="Title"),) + + +def test_multiple_heading_levels(): + """Headings at different levels are all captured.""" + source = "# H1\n\n## H2\n\n### H3\n" + doc = parse_document(source) + assert len(doc.headings) == 3 + assert [h.level for h in doc.headings] == [1, 2, 3] + assert [h.children for h in doc.headings] == [ + (TextSpan(text="H1"),), + (TextSpan(text="H2"),), + (TextSpan(text="H3"),), + ] + + +def test_heading_with_inline_code(): + """Inline code in headings is preserved as a CodeSpan.""" + doc = parse_document("# The `rx.button` Component\n") + children = doc.headings[0].children + assert children == ( + TextSpan(text="The "), + CodeSpan(code="rx.button"), + TextSpan(text=" Component"), + ) + + +def test_plain_code_block(): + """A fenced code block with only a language is parsed.""" + source = "```python\nx = 1\n```\n" + doc = parse_document(source) + assert len(doc.code_blocks) == 1 + cb = doc.code_blocks[0] + assert cb.language == "python" + assert cb.flags == () + assert cb.content == "x = 1" + + +def test_code_block_with_flags(): + """A fenced code block with demo and exec flags is parsed.""" + source = "```python demo exec\nclass Foo:\n pass\n```\n" + doc = parse_document(source) + assert len(doc.code_blocks) == 1 + cb = doc.code_blocks[0] + assert cb.language == "python" + assert cb.flags == ("demo", "exec") + + +def test_code_block_demo_only(): + """A fenced code block with only the demo flag is parsed.""" + source = "```python demo\nrx.button('Click')\n```\n" + doc = parse_document(source) + cb = doc.code_blocks[0] + assert cb.language == "python" + assert cb.flags == ("demo",) + + +def test_code_block_exec_only(): + """A fenced code block with only the exec flag is parsed.""" + source = "```python exec\nimport reflex as rx\n```\n" + doc = parse_document(source) + cb = doc.code_blocks[0] + assert cb.language == "python" + assert cb.flags == ("exec",) + + +def test_code_block_eval(): + """A fenced code block with the eval flag is parsed.""" + source = "```python eval\nrx.text('hello')\n```\n" + doc = parse_document(source) + cb = doc.code_blocks[0] + assert cb.language == "python" + assert cb.flags == ("eval",) + + +def test_code_block_no_language(): + """A fenced code block without a language is parsed.""" + source = "```\nplain text\n```\n" + doc = parse_document(source) + assert len(doc.code_blocks) == 1 + cb = doc.code_blocks[0] + assert cb.language is None + assert cb.flags == () + + +def test_directive_alert(): + """An md alert directive is parsed as a DirectiveBlock.""" + source = "```md alert\n# Warning Title\n\nThis is the body.\n```\n" + doc = parse_document(source) + assert len(doc.directives) == 1 + d = doc.directives[0] + assert d.name == "alert" + assert d.args == () + + +def test_directive_alert_with_variant(): + """An md alert with a variant like 'info' preserves the variant as an arg.""" + source = "```md alert info\n# Note\nSome info.\n```\n" + doc = parse_document(source) + d = doc.directives[0] + assert d.name == "alert" + assert d.args == ("info",) + + +def test_directive_alert_warning(): + """An md alert warning directive preserves the warning arg.""" + source = "```md alert warning\nDo not do this.\n```\n" + doc = parse_document(source) + d = doc.directives[0] + assert d.name == "alert" + assert d.args == ("warning",) + + +def test_directive_video(): + """An md video directive captures the URL as an arg.""" + source = "```md video https://youtube.com/embed/abc123\n```\n" + doc = parse_document(source) + d = doc.directives[0] + assert d.name == "video" + assert d.args == ("https://youtube.com/embed/abc123",) + + +def test_directive_definition(): + """An md definition directive is parsed.""" + source = "```md definition\nSome definition content.\n```\n" + doc = parse_document(source) + d = doc.directives[0] + assert d.name == "definition" + assert d.args == () + assert d.content == "Some definition content." + + +def test_directive_section(): + """An md section directive is parsed.""" + source = "```md section\nSection content here.\n```\n" + doc = parse_document(source) + d = doc.directives[0] + assert d.name == "section" + assert d.args == () + + +def test_directive_not_in_code_blocks(): + """Directive blocks should not appear in the code_blocks list.""" + source = "```md alert\nBody\n```\n" + doc = parse_document(source) + assert len(doc.code_blocks) == 0 + assert len(doc.directives) == 1 + + +def test_simple_paragraph(): + """A plain paragraph is captured as a TextBlock with a TextSpan.""" + doc = parse_document("Hello world.\n") + text_blocks = [b for b in doc.blocks if isinstance(b, TextBlock)] + assert len(text_blocks) == 1 + assert text_blocks[0].children == (TextSpan(text="Hello world."),) + + +def test_paragraph_with_inline_code(): + """Inline code in paragraphs is preserved as a CodeSpan.""" + doc = parse_document("Use `rx.button` for buttons.\n") + text_blocks = [b for b in doc.blocks if isinstance(b, TextBlock)] + assert text_blocks[0].children == ( + TextSpan(text="Use "), + CodeSpan(code="rx.button"), + TextSpan(text=" for buttons."), + ) + + +def test_bold_text(): + """Bold text is parsed into a BoldSpan.""" + doc = parse_document("This is **bold** text.\n") + text_blocks = [b for b in doc.blocks if isinstance(b, TextBlock)] + assert text_blocks[0].children == ( + TextSpan(text="This is "), + BoldSpan(children=(TextSpan(text="bold"),)), + TextSpan(text=" text."), + ) + + +def test_italic_text(): + """Italic text is parsed into an ItalicSpan.""" + doc = parse_document("This is *italic* text.\n") + text_blocks = [b for b in doc.blocks if isinstance(b, TextBlock)] + assert text_blocks[0].children == ( + TextSpan(text="This is "), + ItalicSpan(children=(TextSpan(text="italic"),)), + TextSpan(text=" text."), + ) + + +def test_strikethrough_text(): + """Strikethrough text is parsed into a StrikethroughSpan.""" + doc = parse_document("This is ~~struck~~ text.\n") + text_blocks = [b for b in doc.blocks if isinstance(b, TextBlock)] + assert text_blocks[0].children == ( + TextSpan(text="This is "), + StrikethroughSpan(children=(TextSpan(text="struck"),)), + TextSpan(text=" text."), + ) + + +def test_link(): + """Links are parsed into LinkSpans.""" + doc = parse_document("Click [here](http://example.com) now.\n") + text_blocks = [b for b in doc.blocks if isinstance(b, TextBlock)] + assert text_blocks[0].children == ( + TextSpan(text="Click "), + LinkSpan(children=(TextSpan(text="here"),), target="http://example.com"), + TextSpan(text=" now."), + ) + + +def test_image(): + """Images are parsed into ImageSpans.""" + doc = parse_document("See ![alt text](image.png) here.\n") + text_blocks = [b for b in doc.blocks if isinstance(b, TextBlock)] + assert text_blocks[0].children == ( + TextSpan(text="See "), + ImageSpan(children=(TextSpan(text="alt text"),), src="image.png"), + TextSpan(text=" here."), + ) + + +def test_nested_spans(): + """Bold containing code is parsed as nested spans.""" + doc = parse_document("Use **`rx.button`** here.\n") + text_blocks = [b for b in doc.blocks if isinstance(b, TextBlock)] + assert text_blocks[0].children == ( + TextSpan(text="Use "), + BoldSpan(children=(CodeSpan(code="rx.button"),)), + TextSpan(text=" here."), + ) + + +def test_mixed_document(): + """A document with frontmatter, headings, code, and directives is fully parsed.""" + source = ( + "---\ntitle: Test\n---\n" + "# Title\n\n" + "Some text.\n\n" + "```python demo\ncode()\n```\n\n" + "```md alert\nAlert body.\n```\n\n" + "## Section\n" + ) + doc = parse_document(source) + assert doc.frontmatter is not None + assert len(doc.headings) == 2 + assert len(doc.code_blocks) == 1 + assert len(doc.directives) == 1 + + +def test_empty_document(): + """An empty string produces an empty Document.""" + doc = parse_document("") + assert doc.frontmatter is None + assert doc.blocks == () + + +def test_realistic_doc_structure(): + """Verify parsing of a realistic Reflex doc structure.""" + source = """\ +--- +components: + - rx.button +--- + +```python exec +import reflex as rx +``` + +# Button + +Buttons trigger events. + +```python demo exec +class State(rx.State): + count: int = 0 + +def button_demo(): + return rx.button("Click", on_click=State.increment) +``` + +```md alert info +# Important + +Use `on_click` for click events. +``` + +```md video https://youtube.com/embed/abc123 + +``` + +## Variants +""" + doc = parse_document(source) + assert doc.frontmatter is not None + assert "rx.button" in doc.frontmatter.raw + assert len(doc.headings) == 2 + assert doc.headings[0].children == (TextSpan(text="Button"),) + assert doc.headings[1].children == (TextSpan(text="Variants"),) + assert len(doc.code_blocks) == 2 + assert doc.code_blocks[0].flags == ("exec",) + assert doc.code_blocks[1].flags == ("demo", "exec") + assert len(doc.directives) == 2 + assert doc.directives[0].name == "alert" + assert doc.directives[0].args == ("info",) + assert doc.directives[1].name == "video" + + +_ALL_MD_FILES = sorted(_DOCS_DIR.rglob("*.md")) + + +@pytest.mark.parametrize( + "md_file", _ALL_MD_FILES, ids=lambda p: str(p.relative_to(_DOCS_DIR)) +) +def test_parse_all_doc_files(md_file: Path): + """Every markdown file in docs/ should parse without errors.""" + source = md_file.read_text() + doc = parse_document(source) + # Sanity check: a non-empty file should produce at least one block. + if source.strip(): + assert len(doc.blocks) > 0 diff --git a/uv.lock b/uv.lock index 154e46b320c..c5eb24affe2 100644 --- a/uv.lock +++ b/uv.lock @@ -903,6 +903,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" }, ] +[[package]] +name = "mistletoe" +version = "1.5.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/31/ae/d33647e2a26a8899224f36afc5e7b7a670af30f1fd87231e9f07ca19d673/mistletoe-1.5.1.tar.gz", hash = "sha256:c5571ce6ca9cfdc7ce9151c3ae79acb418e067812000907616427197648030a3", size = 111769, upload-time = "2025-12-07T16:19:01.066Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/20/60/0980fefdc4d12c18c1bbab9d62852f27aded8839233c7b0a9827aaf395f5/mistletoe-1.5.1-py3-none-any.whl", hash = "sha256:d3e97664798261503f685f6a6281b092628367cf3128fc68a015a993b0c4feb3", size = 55331, upload-time = "2025-12-07T16:18:59.65Z" }, +] + [[package]] name = "narwhals" version = "2.18.0" @@ -2150,6 +2159,7 @@ version = "0.0.1" source = { editable = "packages/reflex-docgen" } dependencies = [ { name = "griffelib" }, + { name = "mistletoe" }, { name = "reflex" }, { name = "typing-extensions" }, { name = "typing-inspection" }, @@ -2158,6 +2168,7 @@ dependencies = [ [package.metadata] requires-dist = [ { name = "griffelib", specifier = ">=2.0.1" }, + { name = "mistletoe", specifier = ">=1.4.0" }, { name = "reflex", editable = "." }, { name = "typing-extensions" }, { name = "typing-inspection", specifier = ">=0.4.2" }, From a7d4e3d76353813039ed49fd49d887990913ef31 Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Tue, 24 Mar 2026 14:48:54 -0700 Subject: [PATCH 006/113] improve parser and move into its own namespace --- .../src/reflex_docgen/__init__.py | 34 +- .../src/reflex_docgen/_markdown.py | 409 -------------- .../src/reflex_docgen/markdown/__init__.py | 53 ++ .../src/reflex_docgen/markdown/_parser.py | 335 +++++++++++ .../src/reflex_docgen/markdown/_types.py | 528 ++++++++++++++++++ tests/units/docgen/test_markdown.py | 264 ++++++++- 6 files changed, 1181 insertions(+), 442 deletions(-) delete mode 100644 packages/reflex-docgen/src/reflex_docgen/_markdown.py create mode 100644 packages/reflex-docgen/src/reflex_docgen/markdown/__init__.py create mode 100644 packages/reflex-docgen/src/reflex_docgen/markdown/_parser.py create mode 100644 packages/reflex-docgen/src/reflex_docgen/markdown/_types.py diff --git a/packages/reflex-docgen/src/reflex_docgen/__init__.py b/packages/reflex-docgen/src/reflex_docgen/__init__.py index df0ebfd63fa..bd22e88d501 100644 --- a/packages/reflex-docgen/src/reflex_docgen/__init__.py +++ b/packages/reflex-docgen/src/reflex_docgen/__init__.py @@ -1,5 +1,6 @@ """Module for generating documentation for Reflex components and classes.""" +from reflex_docgen import markdown as markdown from reflex_docgen._class import ClassDocumentation as ClassDocumentation from reflex_docgen._class import FieldDocumentation as FieldDocumentation from reflex_docgen._class import MethodDocumentation as MethodDocumentation @@ -16,48 +17,17 @@ get_component_event_handlers as get_component_event_handlers, ) from reflex_docgen._component import get_component_props as get_component_props -from reflex_docgen._markdown import Block as Block -from reflex_docgen._markdown import BoldSpan as BoldSpan -from reflex_docgen._markdown import CodeBlock as CodeBlock -from reflex_docgen._markdown import CodeSpan as CodeSpan -from reflex_docgen._markdown import DirectiveBlock as DirectiveBlock -from reflex_docgen._markdown import Document as Document -from reflex_docgen._markdown import FrontMatter as FrontMatter -from reflex_docgen._markdown import HeadingBlock as HeadingBlock -from reflex_docgen._markdown import ImageSpan as ImageSpan -from reflex_docgen._markdown import ItalicSpan as ItalicSpan -from reflex_docgen._markdown import LinkSpan as LinkSpan -from reflex_docgen._markdown import Span as Span -from reflex_docgen._markdown import StrikethroughSpan as StrikethroughSpan -from reflex_docgen._markdown import TextBlock as TextBlock -from reflex_docgen._markdown import TextSpan as TextSpan -from reflex_docgen._markdown import parse_document as parse_document __all__ = [ - "Block", - "BoldSpan", "ClassDocumentation", - "CodeBlock", - "CodeSpan", "ComponentDocumentation", - "DirectiveBlock", - "Document", "EventHandlerDocumentation", "FieldDocumentation", - "FrontMatter", - "HeadingBlock", - "ImageSpan", - "ItalicSpan", - "LinkSpan", "MethodDocumentation", "PropDocumentation", - "Span", - "StrikethroughSpan", - "TextBlock", - "TextSpan", "generate_class_documentation", "generate_documentation", "get_component_event_handlers", "get_component_props", - "parse_document", + "markdown", ] diff --git a/packages/reflex-docgen/src/reflex_docgen/_markdown.py b/packages/reflex-docgen/src/reflex_docgen/_markdown.py deleted file mode 100644 index f002ef5a4aa..00000000000 --- a/packages/reflex-docgen/src/reflex_docgen/_markdown.py +++ /dev/null @@ -1,409 +0,0 @@ -"""Parse Reflex documentation markdown files using mistletoe.""" - -from __future__ import annotations - -import re -from dataclasses import dataclass -from typing import TYPE_CHECKING - -if TYPE_CHECKING: - from mistletoe.block_token import BlockToken - -# --------------------------------------------------------------------------- -# Span types — inline content without exposing mistletoe -# --------------------------------------------------------------------------- - - -@dataclass(frozen=True, slots=True, kw_only=True) -class TextSpan: - """Plain text. - - Attributes: - text: The text content. - """ - - text: str - - -@dataclass(frozen=True, slots=True, kw_only=True) -class BoldSpan: - """Bold (strong) text. - - Attributes: - children: The inline spans inside the bold. - """ - - children: tuple[Span, ...] - - -@dataclass(frozen=True, slots=True, kw_only=True) -class ItalicSpan: - """Italic (emphasis) text. - - Attributes: - children: The inline spans inside the italic. - """ - - children: tuple[Span, ...] - - -@dataclass(frozen=True, slots=True, kw_only=True) -class StrikethroughSpan: - """Strikethrough text. - - Attributes: - children: The inline spans inside the strikethrough. - """ - - children: tuple[Span, ...] - - -@dataclass(frozen=True, slots=True, kw_only=True) -class CodeSpan: - """Inline code. - - Attributes: - code: The code text. - """ - - code: str - - -@dataclass(frozen=True, slots=True, kw_only=True) -class LinkSpan: - """A hyperlink. - - Attributes: - children: The inline spans forming the link text. - target: The URL target. - """ - - children: tuple[Span, ...] - target: str - - -@dataclass(frozen=True, slots=True, kw_only=True) -class ImageSpan: - """An inline image. - - Attributes: - children: The inline spans forming the alt text. - src: The image source URL. - """ - - children: tuple[Span, ...] - src: str - - -#: Union of all inline span types. -Span = ( - TextSpan - | BoldSpan - | ItalicSpan - | StrikethroughSpan - | CodeSpan - | LinkSpan - | ImageSpan -) - -# --------------------------------------------------------------------------- -# Block types -# --------------------------------------------------------------------------- - - -@dataclass(frozen=True, slots=True, kw_only=True) -class FrontMatter: - """YAML frontmatter extracted from a markdown document. - - Attributes: - raw: The raw YAML string (without delimiters). - """ - - raw: str - - -@dataclass(frozen=True, slots=True, kw_only=True) -class CodeBlock: - """A fenced code block with optional language and flags. - - Attributes: - language: The language identifier (e.g. "python"). - flags: Additional flags after the language (e.g. ("demo", "exec")). - content: The code content inside the block. - """ - - language: str | None - flags: tuple[str, ...] - content: str - - -@dataclass(frozen=True, slots=True, kw_only=True) -class DirectiveBlock: - """A markdown directive block (```md [args...] ```). - - Covers alert, video, definition, section, and any future md directives. - - Attributes: - name: The directive name (e.g. "alert", "video", "definition", "section"). - args: Additional arguments after the name (e.g. ("info",) or ("https://...",)). - content: The raw content inside the block. - """ - - name: str - args: tuple[str, ...] - content: str - - -@dataclass(frozen=True, slots=True, kw_only=True) -class HeadingBlock: - """A markdown heading. - - Attributes: - level: The heading level (1-6). - children: The inline spans forming the heading text. - """ - - level: int - children: tuple[Span, ...] - - -@dataclass(frozen=True, slots=True, kw_only=True) -class TextBlock: - """A block of markdown text (paragraph or other inline content). - - Attributes: - children: The inline spans forming the text content. - """ - - children: tuple[Span, ...] - - -#: Union of all block types that can appear in a parsed document. -Block = FrontMatter | CodeBlock | DirectiveBlock | HeadingBlock | TextBlock - - -@dataclass(frozen=True, slots=True, kw_only=True) -class Document: - """A parsed Reflex documentation markdown file. - - Attributes: - frontmatter: The YAML frontmatter, if present. - blocks: The sequence of content blocks in document order. - """ - - frontmatter: FrontMatter | None - blocks: tuple[Block, ...] - - @property - def headings(self) -> tuple[HeadingBlock, ...]: - """Return all headings in the document.""" - return tuple(b for b in self.blocks if isinstance(b, HeadingBlock)) - - @property - def code_blocks(self) -> tuple[CodeBlock, ...]: - """Return all code blocks in the document.""" - return tuple(b for b in self.blocks if isinstance(b, CodeBlock)) - - @property - def directives(self) -> tuple[DirectiveBlock, ...]: - """Return all directive blocks in the document.""" - return tuple(b for b in self.blocks if isinstance(b, DirectiveBlock)) - - -# --------------------------------------------------------------------------- -# Parsing helpers -# --------------------------------------------------------------------------- - -_FRONTMATTER_RE = re.compile(r"\A---\n(.*?\n)---\n", re.DOTALL) - - -def _extract_frontmatter(source: str) -> tuple[FrontMatter | None, str]: - """Extract YAML frontmatter from the beginning of a markdown string. - - Args: - source: The raw markdown source. - - Returns: - A tuple of (FrontMatter or None, remaining source). - """ - m = _FRONTMATTER_RE.match(source) - if m is None: - return None, source - return FrontMatter(raw=m.group(1).strip()), source[m.end() :] - - -def _convert_span(token: object) -> Span: - """Convert a mistletoe span token into a Span. - - Args: - token: A mistletoe span token. - - Returns: - The corresponding Span. - """ - from mistletoe.span_token import ( - Emphasis, - Image, - InlineCode, - Link, - RawText, - Strikethrough, - Strong, - ) - - if isinstance(token, RawText): - return TextSpan(text=token.content) - - if isinstance(token, InlineCode): - # InlineCode.children is a tuple of one RawText element. - children = token.children - if ( - not isinstance(children, tuple) - or len(children) != 1 - or not isinstance(children[0], RawText) - ): - msg = f"Expected InlineCode to have a single RawText child, got {children!r}" - raise TypeError(msg) - return CodeSpan(code=children[0].content) - - if isinstance(token, Strong): - return BoldSpan(children=_convert_children(token)) - - if isinstance(token, Emphasis): - return ItalicSpan(children=_convert_children(token)) - - if isinstance(token, Strikethrough): - return StrikethroughSpan(children=_convert_children(token)) - - if isinstance(token, Link): - return LinkSpan(children=_convert_children(token), target=token.target) - - if isinstance(token, Image): - return ImageSpan(children=_convert_children(token), src=token.src) - - # Unknown span type — render as plain text via its content or repr. - content = getattr(token, "content", None) or str(token) - return TextSpan(text=content) - - -def _convert_children(token: object) -> tuple[Span, ...]: - """Convert the children of a mistletoe token into Spans. - - Args: - token: A mistletoe token with a children attribute. - - Returns: - A tuple of Span objects. - """ - children = getattr(token, "children", None) - if children is None: - return () - return tuple(_convert_span(child) for child in children) - - -def _parse_info_string(info: str) -> tuple[str | None, tuple[str, ...]]: - """Parse a fenced code block info string into language and flags. - - Args: - info: The info string (e.g. "python demo exec"). - - Returns: - A tuple of (language, flags). - """ - parts = info.strip().split() - if not parts: - return None, () - return parts[0], tuple(parts[1:]) - - -def _convert_block(token: BlockToken) -> Block | None: - """Convert a mistletoe block token into a docgen Block. - - Args: - token: The mistletoe block token. - - Returns: - A Block, or None if the token should be skipped. - """ - from mistletoe.block_token import BlockCode, CodeFence, Heading, Paragraph - from mistletoe.span_token import RawText - - if isinstance(token, Heading): - return HeadingBlock(level=token.level, children=_convert_children(token)) - - if isinstance(token, (BlockCode, CodeFence)): - if isinstance(token, CodeFence): - # CodeFence.info_string contains the full info string including language. - info = token.info_string or "" - else: - info = getattr(token, "language", "") or "" - language, flags = _parse_info_string(info) - - # CodeFence/BlockCode.children is a tuple of one RawText element. - children = token.children - if not children: - content = "" - elif ( - not isinstance(children, tuple) - or len(children) != 1 - or not isinstance(children[0], RawText) - ): - msg = f"Expected code block to have a single RawText child, got {children!r}" - raise TypeError(msg) - else: - content = children[0].content - # Strip trailing newline that mistletoe adds. - content = content.rstrip("\n") - - # ```md [args...]``` blocks become DirectiveBlocks. - if language == "md" and flags: - return DirectiveBlock( - name=flags[0], - args=flags[1:], - content=content, - ) - - return CodeBlock(language=language, flags=flags, content=content) - - if isinstance(token, Paragraph): - spans = _convert_children(token) - if spans: - return TextBlock(children=spans) - return None - - # For other block types (lists, blockquotes, etc.), convert children - # or fall back to a TextSpan of the raw content. - spans = _convert_children(token) - if spans: - return TextBlock(children=spans) - return None - - -def parse_document(source: str) -> Document: - """Parse a Reflex documentation markdown file into a Document. - - Args: - source: The raw markdown source string. - - Returns: - A parsed Document with frontmatter and content blocks. - """ - from mistletoe.block_token import BlockToken - from mistletoe.block_token import Document as MistletoeDocument - - frontmatter, remaining = _extract_frontmatter(source) - doc = MistletoeDocument(remaining) - - blocks: list[Block] = [] - if doc.children: - for child in doc.children: - # mistletoe.block_token.Document guarantees children are BlockToken - # instances (see its docstring: "Its children are block tokens"). - if not isinstance(child, BlockToken): - msg = f"Expected BlockToken, got {type(child).__name__}" - raise TypeError(msg) - block = _convert_block(child) - if block is not None: - blocks.append(block) - - return Document(frontmatter=frontmatter, blocks=tuple(blocks)) diff --git a/packages/reflex-docgen/src/reflex_docgen/markdown/__init__.py b/packages/reflex-docgen/src/reflex_docgen/markdown/__init__.py new file mode 100644 index 00000000000..7b55b2bd08e --- /dev/null +++ b/packages/reflex-docgen/src/reflex_docgen/markdown/__init__.py @@ -0,0 +1,53 @@ +"""Markdown parsing and types for Reflex documentation.""" + +from reflex_docgen.markdown._parser import parse_document as parse_document +from reflex_docgen.markdown._types import Block as Block +from reflex_docgen.markdown._types import BoldSpan as BoldSpan +from reflex_docgen.markdown._types import CodeBlock as CodeBlock +from reflex_docgen.markdown._types import CodeSpan as CodeSpan +from reflex_docgen.markdown._types import DirectiveBlock as DirectiveBlock +from reflex_docgen.markdown._types import Document as Document +from reflex_docgen.markdown._types import FrontMatter as FrontMatter +from reflex_docgen.markdown._types import HeadingBlock as HeadingBlock +from reflex_docgen.markdown._types import ImageSpan as ImageSpan +from reflex_docgen.markdown._types import ItalicSpan as ItalicSpan +from reflex_docgen.markdown._types import LineBreakSpan as LineBreakSpan +from reflex_docgen.markdown._types import LinkSpan as LinkSpan +from reflex_docgen.markdown._types import ListBlock as ListBlock +from reflex_docgen.markdown._types import ListItem as ListItem +from reflex_docgen.markdown._types import QuoteBlock as QuoteBlock +from reflex_docgen.markdown._types import Span as Span +from reflex_docgen.markdown._types import StrikethroughSpan as StrikethroughSpan +from reflex_docgen.markdown._types import TableBlock as TableBlock +from reflex_docgen.markdown._types import TableCell as TableCell +from reflex_docgen.markdown._types import TableRow as TableRow +from reflex_docgen.markdown._types import TextBlock as TextBlock +from reflex_docgen.markdown._types import TextSpan as TextSpan +from reflex_docgen.markdown._types import ThematicBreakBlock as ThematicBreakBlock + +__all__ = [ + "Block", + "BoldSpan", + "CodeBlock", + "CodeSpan", + "DirectiveBlock", + "Document", + "FrontMatter", + "HeadingBlock", + "ImageSpan", + "ItalicSpan", + "LineBreakSpan", + "LinkSpan", + "ListBlock", + "ListItem", + "QuoteBlock", + "Span", + "StrikethroughSpan", + "TableBlock", + "TableCell", + "TableRow", + "TextBlock", + "TextSpan", + "ThematicBreakBlock", + "parse_document", +] diff --git a/packages/reflex-docgen/src/reflex_docgen/markdown/_parser.py b/packages/reflex-docgen/src/reflex_docgen/markdown/_parser.py new file mode 100644 index 00000000000..0e4bc2e2f4a --- /dev/null +++ b/packages/reflex-docgen/src/reflex_docgen/markdown/_parser.py @@ -0,0 +1,335 @@ +"""Parse Reflex documentation markdown files using mistletoe.""" + +from __future__ import annotations + +import re +from collections.abc import Sequence +from typing import TYPE_CHECKING, cast + +from reflex_docgen.markdown._types import ( + Block, + BoldSpan, + CodeBlock, + CodeSpan, + DirectiveBlock, + Document, + FrontMatter, + HeadingBlock, + ImageSpan, + ItalicSpan, + LineBreakSpan, + LinkSpan, + ListBlock, + ListItem, + QuoteBlock, + Span, + StrikethroughSpan, + TableBlock, + TableCell, + TableRow, + TextBlock, + TextSpan, + ThematicBreakBlock, +) + +if TYPE_CHECKING: + from mistletoe.block_token import BlockToken + +_FRONTMATTER_RE = re.compile(r"\A---\n(.*?\n)---\n", re.DOTALL) + + +def _extract_frontmatter(source: str) -> tuple[FrontMatter | None, str]: + """Extract YAML frontmatter from the beginning of a markdown string. + + Args: + source: The raw markdown source. + + Returns: + A tuple of (FrontMatter or None, remaining source). + """ + m = _FRONTMATTER_RE.match(source) + if m is None: + return None, source + return FrontMatter(raw=m.group(1).strip()), source[m.end() :] + + +def _convert_span(token: object) -> Span: + """Convert a mistletoe span token into a Span. + + Args: + token: A mistletoe span token. + + Returns: + The corresponding Span. + """ + from mistletoe.span_token import ( + Emphasis, + EscapeSequence, + Image, + InlineCode, + LineBreak, + Link, + RawText, + Strikethrough, + Strong, + ) + + if isinstance(token, RawText): + return TextSpan(text=token.content) + + if isinstance(token, InlineCode): + # InlineCode.children is a tuple of one RawText element. + children = token.children + if ( + not isinstance(children, tuple) + or len(children) != 1 + or not isinstance(children[0], RawText) + ): + msg = ( + f"Expected InlineCode to have a single RawText child, got {children!r}" + ) + raise TypeError(msg) + return CodeSpan(code=children[0].content) + + if isinstance(token, Strong): + return BoldSpan(children=_convert_children(token)) + + if isinstance(token, Emphasis): + return ItalicSpan(children=_convert_children(token)) + + if isinstance(token, Strikethrough): + return StrikethroughSpan(children=_convert_children(token)) + + if isinstance(token, Link): + return LinkSpan(children=_convert_children(token), target=token.target) + + if isinstance(token, Image): + return ImageSpan(children=_convert_children(token), src=token.src) + + if isinstance(token, LineBreak): + return LineBreakSpan(soft=token.soft) + + if isinstance(token, EscapeSequence): + # EscapeSequence.children is a tuple of one RawText with the escaped char. + children = token.children + if ( + not isinstance(children, tuple) + or len(children) != 1 + or not isinstance(children[0], RawText) + ): + msg = f"Expected EscapeSequence to have a single RawText child, got {children!r}" + raise TypeError(msg) + return TextSpan(text=children[0].content) + + msg = f"Unsupported span token type: {type(token).__name__}" + raise TypeError(msg) + + +def _convert_children(token: object) -> tuple[Span, ...]: + """Convert the children of a mistletoe token into Spans. + + Args: + token: A mistletoe token with a children attribute. + + Returns: + A tuple of Span objects. + """ + children = getattr(token, "children", None) + if children is None: + return () + return tuple(_convert_span(child) for child in children) + + +def _parse_info_string(info: str) -> tuple[str | None, tuple[str, ...]]: + """Parse a fenced code block info string into language and flags. + + Args: + info: The info string (e.g. "python demo exec"). + + Returns: + A tuple of (language, flags). + """ + parts = info.strip().split() + if not parts: + return None, () + return parts[0], tuple(parts[1:]) + + +def _convert_block(token: BlockToken) -> Block | None: + """Convert a mistletoe block token into a docgen Block. + + Args: + token: The mistletoe block token. + + Returns: + A Block, or None if the token should be skipped. + """ + from mistletoe.block_token import BlockCode as MistletoeBlockCode + from mistletoe.block_token import CodeFence as MistletoeCodeFence + from mistletoe.block_token import Heading as MistletoeHeading + from mistletoe.block_token import List as MistletoeList + from mistletoe.block_token import ListItem as MistletoeListItem + from mistletoe.block_token import Paragraph as MistletoeParagraph + from mistletoe.block_token import Quote as MistletoeQuote + from mistletoe.block_token import SetextHeading as MistletoeSetextHeading + from mistletoe.block_token import Table as MistletoeTable + from mistletoe.block_token import ThematicBreak as MistletoeThematicBreak + from mistletoe.span_token import RawText as MistletoeRawText + + if isinstance(token, (MistletoeHeading, MistletoeSetextHeading)): + return HeadingBlock(level=token.level, children=_convert_children(token)) + + if isinstance(token, (MistletoeBlockCode, MistletoeCodeFence)): + if isinstance(token, MistletoeCodeFence): + # CodeFence.info_string contains the full info string including language. + info = token.info_string or "" + else: + info = getattr(token, "language", "") or "" + language, flags = _parse_info_string(info) + + # CodeFence/BlockCode.children is a tuple of one RawText element. + children = token.children + if not children: + content = "" + elif ( + not isinstance(children, tuple) + or len(children) != 1 + or not isinstance(children[0], MistletoeRawText) + ): + msg = ( + f"Expected code block to have a single RawText child, got {children!r}" + ) + raise TypeError(msg) + else: + content = children[0].content + # Strip trailing newline that mistletoe adds. + content = content.rstrip("\n") + + # ```md [args...]``` blocks become DirectiveBlocks. + if language == "md" and flags: + return DirectiveBlock( + name=flags[0], + args=flags[1:], + content=content, + ) + + return CodeBlock(language=language, flags=flags, content=content) + + if isinstance(token, MistletoeParagraph): + spans = _convert_children(token) + if spans: + return TextBlock(children=spans) + return None + + if isinstance(token, MistletoeList): + items: list[ListItem] = [] + if token.children: + for item_token in token.children: + if not isinstance(item_token, MistletoeListItem): + msg = f"Expected ListItem, got {type(item_token).__name__}" + raise TypeError(msg) + item_blocks = _convert_block_children(item_token) + items.append(ListItem(children=item_blocks)) + # List.start is an instance attribute (int | None) but pyright sees + # the classmethod start(cls, line) instead. + list_start = cast("int | None", token.start) # pyright: ignore[reportAttributeAccessIssue] + return ListBlock( + ordered=list_start is not None, + start=list_start, + items=tuple(items), + ) + + if isinstance(token, MistletoeQuote): + return QuoteBlock(children=_convert_block_children(token)) + + if isinstance(token, MistletoeTable): + header = _convert_table_row(token.header, token.column_align) + rows = [ + _convert_table_row(row_token, token.column_align) + for row_token in (token.children or ()) + ] + return TableBlock(header=header, rows=tuple(rows)) + + if isinstance(token, MistletoeThematicBreak): + return ThematicBreakBlock() + + msg = f"Unsupported block token type: {type(token).__name__}" + raise TypeError(msg) + + +def _convert_block_children(token: BlockToken) -> tuple[Block, ...]: + """Convert the block-level children of a container token. + + Args: + token: A mistletoe container block token. + + Returns: + A tuple of Block objects. + """ + from mistletoe.block_token import BlockToken + + if not token.children: + return () + result: list[Block] = [] + for child in token.children: + if not isinstance(child, BlockToken): + msg = f"Expected BlockToken, got {type(child).__name__}" + raise TypeError(msg) + block = _convert_block(child) + if block is not None: + result.append(block) + return tuple(result) + + +def _convert_table_row( + row_token: object, column_align: Sequence[int | None] +) -> TableRow: + """Convert a mistletoe TableRow into a TableRow. + + Args: + row_token: A mistletoe TableRow token. + column_align: The column alignment list from the Table. + + Returns: + A TableRow. + """ + align_map = {None: None, 0: None, 1: "left", 2: "right", 3: "center"} + children = getattr(row_token, "children", None) or () + cells = [ + TableCell( + children=_convert_children(cell_token), + align=align_map.get(column_align[i] if i < len(column_align) else None), + ) + for i, cell_token in enumerate(children) + ] + return TableRow(cells=tuple(cells)) + + +def parse_document(source: str) -> Document: + """Parse a Reflex documentation markdown file into a Document. + + Args: + source: The raw markdown source string. + + Returns: + A parsed Document with frontmatter and content blocks. + """ + from mistletoe.block_token import BlockToken + from mistletoe.block_token import Document as MistletoeDocument + + frontmatter, remaining = _extract_frontmatter(source) + doc = MistletoeDocument(remaining) + + blocks: list[Block] = [] + if doc.children: + for child in doc.children: + # mistletoe.block_token.Document guarantees children are BlockToken + # instances (see its docstring: "Its children are block tokens"). + if not isinstance(child, BlockToken): + msg = f"Expected BlockToken, got {type(child).__name__}" + raise TypeError(msg) + block = _convert_block(child) + if block is not None: + blocks.append(block) + + return Document(frontmatter=frontmatter, blocks=tuple(blocks)) diff --git a/packages/reflex-docgen/src/reflex_docgen/markdown/_types.py b/packages/reflex-docgen/src/reflex_docgen/markdown/_types.py new file mode 100644 index 00000000000..22eb7f01710 --- /dev/null +++ b/packages/reflex-docgen/src/reflex_docgen/markdown/_types.py @@ -0,0 +1,528 @@ +"""Markdown document types — spans, blocks, and document.""" + +from __future__ import annotations + +import re +from dataclasses import dataclass + +# --------------------------------------------------------------------------- +# Span types — inline content without exposing mistletoe +# --------------------------------------------------------------------------- + + +@dataclass(frozen=True, slots=True, kw_only=True) +class TextSpan: + """Plain text. + + Attributes: + text: The text content. + """ + + text: str + + def as_markdown(self) -> str: + """Render back to markdown. + + Returns: + A markdown string. + """ + return self.text + + +@dataclass(frozen=True, slots=True, kw_only=True) +class BoldSpan: + """Bold (strong) text. + + Attributes: + children: The inline spans inside the bold. + """ + + children: tuple[Span, ...] + + def as_markdown(self) -> str: + """Render back to markdown. + + Returns: + A markdown string. + """ + inner = "".join(c.as_markdown() for c in self.children) + return f"**{inner}**" + + +@dataclass(frozen=True, slots=True, kw_only=True) +class ItalicSpan: + """Italic (emphasis) text. + + Attributes: + children: The inline spans inside the italic. + """ + + children: tuple[Span, ...] + + def as_markdown(self) -> str: + """Render back to markdown. + + Returns: + A markdown string. + """ + inner = "".join(c.as_markdown() for c in self.children) + return f"*{inner}*" + + +@dataclass(frozen=True, slots=True, kw_only=True) +class StrikethroughSpan: + """Strikethrough text. + + Attributes: + children: The inline spans inside the strikethrough. + """ + + children: tuple[Span, ...] + + def as_markdown(self) -> str: + """Render back to markdown. + + Returns: + A markdown string. + """ + inner = "".join(c.as_markdown() for c in self.children) + return f"~~{inner}~~" + + +@dataclass(frozen=True, slots=True, kw_only=True) +class CodeSpan: + """Inline code. + + Attributes: + code: The code text. + """ + + code: str + + def as_markdown(self) -> str: + """Render back to markdown. + + Returns: + A markdown string. + """ + return f"`{self.code}`" + + +@dataclass(frozen=True, slots=True, kw_only=True) +class LinkSpan: + """A hyperlink. + + Attributes: + children: The inline spans forming the link text. + target: The URL target. + """ + + children: tuple[Span, ...] + target: str + + def as_markdown(self) -> str: + """Render back to markdown. + + Returns: + A markdown string. + """ + inner = "".join(c.as_markdown() for c in self.children) + return f"[{inner}]({self.target})" + + +@dataclass(frozen=True, slots=True, kw_only=True) +class ImageSpan: + """An inline image. + + Attributes: + children: The inline spans forming the alt text. + src: The image source URL. + """ + + children: tuple[Span, ...] + src: str + + def as_markdown(self) -> str: + """Render back to markdown. + + Returns: + A markdown string. + """ + inner = "".join(c.as_markdown() for c in self.children) + return f"![{inner}]({self.src})" + + +@dataclass(frozen=True, slots=True, kw_only=True) +class LineBreakSpan: + """A line break. + + Attributes: + soft: Whether this is a soft line break. + """ + + soft: bool + + def as_markdown(self) -> str: + """Render back to markdown. + + Returns: + A markdown string. + """ + return "\n" if self.soft else " \n" + + +#: Union of all inline span types. +Span = ( + TextSpan + | BoldSpan + | ItalicSpan + | StrikethroughSpan + | CodeSpan + | LinkSpan + | ImageSpan + | LineBreakSpan +) + + +def _spans_as_markdown(spans: tuple[Span, ...]) -> str: + """Render a sequence of spans back to markdown. + + Args: + spans: The inline spans to render. + + Returns: + A markdown string. + """ + return "".join(s.as_markdown() for s in spans) + + +_BACKTICK_FENCE_RE = re.compile(r"^(`{3,})", re.MULTILINE) + + +def _fence_for(content: str) -> str: + """Return a backtick fence long enough to wrap *content* safely. + + Args: + content: The code block content that may contain backtick fences. + + Returns: + A backtick fence string (at least 3 backticks). + """ + max_run = 3 + for m in _BACKTICK_FENCE_RE.finditer(content): + max_run = max(max_run, len(m.group(1)) + 1) + return "`" * max_run + + +# --------------------------------------------------------------------------- +# Block types +# --------------------------------------------------------------------------- + + +@dataclass(frozen=True, slots=True, kw_only=True) +class FrontMatter: + """YAML frontmatter extracted from a markdown document. + + Attributes: + raw: The raw YAML string (without delimiters). + """ + + raw: str + + def as_markdown(self) -> str: + """Render back to markdown. + + Returns: + A markdown string. + """ + return f"---\n{self.raw}\n---" + + +@dataclass(frozen=True, slots=True, kw_only=True) +class CodeBlock: + """A fenced code block with optional language and flags. + + Attributes: + language: The language identifier (e.g. "python"). + flags: Additional flags after the language (e.g. ("demo", "exec")). + content: The code content inside the block. + """ + + language: str | None + flags: tuple[str, ...] + content: str + + def as_markdown(self) -> str: + """Render back to markdown. + + Returns: + A markdown string. + """ + info = self.language or "" + if self.flags: + info = f"{info} {' '.join(self.flags)}" if info else " ".join(self.flags) + fence = _fence_for(self.content) + return f"{fence}{info}\n{self.content}\n{fence}" + + +@dataclass(frozen=True, slots=True, kw_only=True) +class DirectiveBlock: + """A markdown directive block (```md [args...] ```). + + Covers alert, video, definition, section, and any future md directives. + + Attributes: + name: The directive name (e.g. "alert", "video", "definition", "section"). + args: Additional arguments after the name (e.g. ("info",) or ("https://...",)). + content: The raw content inside the block. + """ + + name: str + args: tuple[str, ...] + content: str + + def as_markdown(self) -> str: + """Render back to markdown. + + Returns: + A markdown string. + """ + info_parts = ["md", self.name, *self.args] + fence = _fence_for(self.content) + return f"{fence}{' '.join(info_parts)}\n{self.content}\n{fence}" + + +@dataclass(frozen=True, slots=True, kw_only=True) +class HeadingBlock: + """A markdown heading. + + Attributes: + level: The heading level (1-6). + children: The inline spans forming the heading text. + """ + + level: int + children: tuple[Span, ...] + + def as_markdown(self) -> str: + """Render back to markdown. + + Returns: + A markdown string. + """ + return f"{'#' * self.level} {_spans_as_markdown(self.children)}" + + +@dataclass(frozen=True, slots=True, kw_only=True) +class TextBlock: + """A block of markdown text (paragraph or other inline content). + + Attributes: + children: The inline spans forming the text content. + """ + + children: tuple[Span, ...] + + def as_markdown(self) -> str: + """Render back to markdown. + + Returns: + A markdown string. + """ + return _spans_as_markdown(self.children) + + +@dataclass(frozen=True, slots=True, kw_only=True) +class ListItem: + """A single item in a list. + + Attributes: + children: The block-level content of the list item. + """ + + children: tuple[Block, ...] + + +@dataclass(frozen=True, slots=True, kw_only=True) +class ListBlock: + """An ordered or unordered list. + + Attributes: + ordered: Whether the list is ordered. + start: The starting number for ordered lists, or None for unordered. + items: The list items. + """ + + ordered: bool + start: int | None + items: tuple[ListItem, ...] + + def as_markdown(self) -> str: + """Render back to markdown. + + Returns: + A markdown string. + """ + lines: list[str] = [] + for i, item in enumerate(self.items): + prefix = f"{(self.start or 1) + i}. " if self.ordered else "- " + item_md = "\n\n".join(child.as_markdown() for child in item.children) + first, *rest = item_md.split("\n") + lines.append(f"{prefix}{first}") + indent = " " * len(prefix) + lines.extend(f"{indent}{line}" if line else "" for line in rest) + return "\n".join(lines) + + +@dataclass(frozen=True, slots=True, kw_only=True) +class QuoteBlock: + """A block quote. + + Attributes: + children: The block-level content inside the quote. + """ + + children: tuple[Block, ...] + + def as_markdown(self) -> str: + """Render back to markdown. + + Returns: + A markdown string. + """ + inner = "\n\n".join(child.as_markdown() for child in self.children) + return "\n".join(f"> {line}" if line else ">" for line in inner.split("\n")) + + +@dataclass(frozen=True, slots=True, kw_only=True) +class TableCell: + """A single cell in a table. + + Attributes: + children: The inline spans forming the cell content. + align: The column alignment ("left", "right", "center", or None). + """ + + children: tuple[Span, ...] + align: str | None + + +@dataclass(frozen=True, slots=True, kw_only=True) +class TableRow: + """A row in a table. + + Attributes: + cells: The cells in the row. + """ + + cells: tuple[TableCell, ...] + + def as_markdown(self) -> str: + """Render back to markdown. + + Returns: + A markdown string. + """ + cells = " | ".join(_spans_as_markdown(cell.children) for cell in self.cells) + return f"| {cells} |" + + +@dataclass(frozen=True, slots=True, kw_only=True) +class TableBlock: + """A table. + + Attributes: + header: The header row. + rows: The body rows. + """ + + header: TableRow + rows: tuple[TableRow, ...] + + def as_markdown(self) -> str: + """Render back to markdown. + + Returns: + A markdown string. + """ + lines = [self.header.as_markdown()] + sep_parts: list[str] = [] + for cell in self.header.cells: + if cell.align == "left": + sep_parts.append(":---") + elif cell.align == "right": + sep_parts.append("---:") + elif cell.align == "center": + sep_parts.append(":---:") + else: + sep_parts.append("---") + lines.append(f"| {' | '.join(sep_parts)} |") + lines.extend(row.as_markdown() for row in self.rows) + return "\n".join(lines) + + +@dataclass(frozen=True, slots=True, kw_only=True) +class ThematicBreakBlock: + """A thematic break (horizontal rule).""" + + def as_markdown(self) -> str: + """Render back to markdown. + + Returns: + A markdown string. + """ + return "---" + + +#: Union of all block types that can appear in a parsed document. +Block = ( + FrontMatter + | CodeBlock + | DirectiveBlock + | HeadingBlock + | TextBlock + | ListBlock + | QuoteBlock + | TableBlock + | ThematicBreakBlock +) + + +@dataclass(frozen=True, slots=True, kw_only=True) +class Document: + """A parsed Reflex documentation markdown file. + + Attributes: + frontmatter: The YAML frontmatter, if present. + blocks: The sequence of content blocks in document order. + """ + + frontmatter: FrontMatter | None + blocks: tuple[Block, ...] + + @property + def headings(self) -> tuple[HeadingBlock, ...]: + """Return all headings in the document.""" + return tuple(b for b in self.blocks if isinstance(b, HeadingBlock)) + + @property + def code_blocks(self) -> tuple[CodeBlock, ...]: + """Return all code blocks in the document.""" + return tuple(b for b in self.blocks if isinstance(b, CodeBlock)) + + @property + def directives(self) -> tuple[DirectiveBlock, ...]: + """Return all directive blocks in the document.""" + return tuple(b for b in self.blocks if isinstance(b, DirectiveBlock)) + + def as_markdown(self) -> str: + """Render the full document back to markdown. + + Returns: + A markdown string. + """ + parts: list[str] = [] + if self.frontmatter: + parts.append(self.frontmatter.as_markdown()) + parts.extend(block.as_markdown() for block in self.blocks) + return "\n\n".join(parts) + "\n" diff --git a/tests/units/docgen/test_markdown.py b/tests/units/docgen/test_markdown.py index 2c9defbd9d8..347d839c593 100644 --- a/tests/units/docgen/test_markdown.py +++ b/tests/units/docgen/test_markdown.py @@ -3,7 +3,7 @@ from pathlib import Path import pytest -from reflex_docgen import ( +from reflex_docgen.markdown import ( BoldSpan, CodeSpan, FrontMatter, @@ -376,3 +376,265 @@ def test_parse_all_doc_files(md_file: Path): # Sanity check: a non-empty file should produce at least one block. if source.strip(): assert len(doc.blocks) > 0 + # Verify as_markdown doesn't crash on any doc file. + doc.as_markdown() + + +# --------------------------------------------------------------------------- +# as_markdown round-trip tests +# --------------------------------------------------------------------------- + + +def test_as_markdown_text_span(): + """TextSpan renders back to plain text.""" + assert TextSpan(text="hello").as_markdown() == "hello" + + +def test_as_markdown_code_span(): + """CodeSpan renders back with backticks.""" + assert CodeSpan(code="rx.button").as_markdown() == "`rx.button`" + + +def test_as_markdown_bold_span(): + """BoldSpan renders back with double asterisks.""" + span = BoldSpan(children=(TextSpan(text="bold"),)) + assert span.as_markdown() == "**bold**" + + +def test_as_markdown_italic_span(): + """ItalicSpan renders back with single asterisks.""" + span = ItalicSpan(children=(TextSpan(text="italic"),)) + assert span.as_markdown() == "*italic*" + + +def test_as_markdown_strikethrough_span(): + """StrikethroughSpan renders back with tildes.""" + span = StrikethroughSpan(children=(TextSpan(text="struck"),)) + assert span.as_markdown() == "~~struck~~" + + +def test_as_markdown_link_span(): + """LinkSpan renders back as a markdown link.""" + span = LinkSpan(children=(TextSpan(text="click"),), target="http://x.com") + assert span.as_markdown() == "[click](http://x.com)" + + +def test_as_markdown_image_span(): + """ImageSpan renders back as a markdown image.""" + span = ImageSpan(children=(TextSpan(text="alt"),), src="img.png") + assert span.as_markdown() == "![alt](img.png)" + + +def test_as_markdown_line_break_soft(): + """Soft LineBreakSpan renders as a newline.""" + from reflex_docgen.markdown import LineBreakSpan + + assert LineBreakSpan(soft=True).as_markdown() == "\n" + + +def test_as_markdown_line_break_hard(): + """Hard LineBreakSpan renders as two spaces + newline.""" + from reflex_docgen.markdown import LineBreakSpan + + assert LineBreakSpan(soft=False).as_markdown() == " \n" + + +def test_as_markdown_nested_spans(): + """Nested spans render correctly.""" + span = BoldSpan(children=(CodeSpan(code="x"), TextSpan(text=" = 1"))) + assert span.as_markdown() == "**`x` = 1**" + + +def test_as_markdown_heading(): + """HeadingBlock renders with the correct number of hashes.""" + from reflex_docgen.markdown import HeadingBlock + + h1 = HeadingBlock(level=1, children=(TextSpan(text="Title"),)) + assert h1.as_markdown() == "# Title" + h3 = HeadingBlock(level=3, children=(TextSpan(text="Sub"),)) + assert h3.as_markdown() == "### Sub" + + +def test_as_markdown_heading_with_inline(): + """HeadingBlock with mixed spans renders correctly.""" + from reflex_docgen.markdown import HeadingBlock + + h = HeadingBlock( + level=2, + children=( + TextSpan(text="The "), + CodeSpan(code="rx.button"), + TextSpan(text=" API"), + ), + ) + assert h.as_markdown() == "## The `rx.button` API" + + +def test_as_markdown_text_block(): + """TextBlock renders its children as a paragraph.""" + block = TextBlock( + children=(TextSpan(text="Hello "), BoldSpan(children=(TextSpan(text="world"),))) + ) + assert block.as_markdown() == "Hello **world**" + + +def test_as_markdown_code_block(): + """CodeBlock renders as a fenced code block.""" + from reflex_docgen.markdown import CodeBlock + + cb = CodeBlock(language="python", flags=("demo", "exec"), content="x = 1") + assert cb.as_markdown() == "```python demo exec\nx = 1\n```" + + +def test_as_markdown_code_block_no_language(): + """CodeBlock without language renders with empty info string.""" + from reflex_docgen.markdown import CodeBlock + + cb = CodeBlock(language=None, flags=(), content="plain") + assert cb.as_markdown() == "```\nplain\n```" + + +def test_as_markdown_directive(): + """DirectiveBlock renders as a fenced md block.""" + from reflex_docgen.markdown import DirectiveBlock + + d = DirectiveBlock(name="alert", args=("info",), content="Be careful.") + assert d.as_markdown() == "```md alert info\nBe careful.\n```" + + +def test_as_markdown_list_unordered(): + """Unordered ListBlock renders with dashes.""" + from reflex_docgen.markdown import ListBlock, ListItem + + lb = ListBlock( + ordered=False, + start=None, + items=( + ListItem(children=(TextBlock(children=(TextSpan(text="one"),)),)), + ListItem(children=(TextBlock(children=(TextSpan(text="two"),)),)), + ), + ) + assert lb.as_markdown() == "- one\n- two" + + +def test_as_markdown_list_ordered(): + """Ordered ListBlock renders with numbers.""" + from reflex_docgen.markdown import ListBlock, ListItem + + lb = ListBlock( + ordered=True, + start=1, + items=( + ListItem(children=(TextBlock(children=(TextSpan(text="first"),)),)), + ListItem(children=(TextBlock(children=(TextSpan(text="second"),)),)), + ), + ) + assert lb.as_markdown() == "1. first\n2. second" + + +def test_as_markdown_quote(): + """QuoteBlock renders with > prefix.""" + from reflex_docgen.markdown import QuoteBlock + + q = QuoteBlock(children=(TextBlock(children=(TextSpan(text="wise words"),)),)) + assert q.as_markdown() == "> wise words" + + +def test_as_markdown_table(): + """TableBlock renders as a markdown table.""" + from reflex_docgen.markdown import TableBlock, TableCell, TableRow + + table = TableBlock( + header=TableRow( + cells=( + TableCell(children=(TextSpan(text="Name"),), align=None), + TableCell(children=(TextSpan(text="Value"),), align="right"), + ) + ), + rows=( + TableRow( + cells=( + TableCell(children=(TextSpan(text="a"),), align=None), + TableCell(children=(TextSpan(text="1"),), align="right"), + ) + ), + ), + ) + expected = "| Name | Value |\n| --- | ---: |\n| a | 1 |" + assert table.as_markdown() == expected + + +def test_as_markdown_thematic_break(): + """ThematicBreakBlock renders as ---.""" + from reflex_docgen.markdown import ThematicBreakBlock + + assert ThematicBreakBlock().as_markdown() == "---" + + +def test_as_markdown_frontmatter(): + """FrontMatter renders with --- delimiters.""" + assert FrontMatter(raw="title: Test").as_markdown() == "---\ntitle: Test\n---" + + +def test_as_markdown_document_roundtrip(): + """Document.as_markdown produces valid markdown that re-parses consistently.""" + source = """\ +--- +title: Test +--- + +# Hello **world** + +Use `rx.button` for [buttons](http://example.com). + +```python demo exec +x = 1 +``` + +```md alert info +# Warning +Be careful. +``` + +- item one +- item **two** + +--- +""" + doc = parse_document(source) + rendered = doc.as_markdown() + doc2 = parse_document(rendered) + # The re-parsed document should produce the same markdown. + assert doc2.as_markdown() == rendered + + +def test_nested_code_block_in_directive(): + """A directive using more backticks can contain inner code fences.""" + source = "````md alert\n# Example\n\n```python\nx = 1\n```\n````\n" + doc = parse_document(source) + assert len(doc.directives) == 1 + d = doc.directives[0] + assert d.name == "alert" + assert "```python" in d.content + assert "x = 1" in d.content + + +def test_nested_code_block_in_code_block(): + """A code block using more backticks can contain inner code fences.""" + source = "````python demo\nrx.markdown(\n '''```python\nx = 1\n```'''\n)\n````\n" + doc = parse_document(source) + assert len(doc.code_blocks) == 1 + cb = doc.code_blocks[0] + assert cb.language == "python" + assert cb.flags == ("demo",) + assert "```python" in cb.content + + +def test_nested_code_block_roundtrip(): + """Nested code blocks survive a parse-render-reparse cycle.""" + source = "````md alert warning\n# Note\n\n```python\nx = 1\n```\n````\n" + doc = parse_document(source) + rendered = doc.as_markdown() + doc2 = parse_document(rendered) + assert len(doc2.directives) == 1 + assert doc2.directives[0].content == doc.directives[0].content From 9abe14fd526b278f696b5f34e0ec0b030de6216a Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Tue, 24 Mar 2026 14:49:57 -0700 Subject: [PATCH 007/113] read with utf 8 --- tests/units/docgen/test_markdown.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/units/docgen/test_markdown.py b/tests/units/docgen/test_markdown.py index 347d839c593..ef2a0f47891 100644 --- a/tests/units/docgen/test_markdown.py +++ b/tests/units/docgen/test_markdown.py @@ -371,7 +371,7 @@ def button_demo(): ) def test_parse_all_doc_files(md_file: Path): """Every markdown file in docs/ should parse without errors.""" - source = md_file.read_text() + source = md_file.read_text(encoding="utf-8") doc = parse_document(source) # Sanity check: a non-empty file should produce at least one block. if source.strip(): From 15399a43c118447d325f76934be375cb628aaf10 Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Tue, 24 Mar 2026 15:15:29 -0700 Subject: [PATCH 008/113] add uv parsing --- packages/reflex-docgen/pyproject.toml | 1 + .../src/reflex_docgen/markdown/_types.py | 16 ++++++++++++++++ uv.lock | 2 ++ 3 files changed, 19 insertions(+) diff --git a/packages/reflex-docgen/pyproject.toml b/packages/reflex-docgen/pyproject.toml index 1f56cd4a8a8..87c5ea4964c 100644 --- a/packages/reflex-docgen/pyproject.toml +++ b/packages/reflex-docgen/pyproject.toml @@ -9,6 +9,7 @@ requires-python = ">=3.10" dependencies = [ "griffelib>=2.0.1", "mistletoe>=1.4.0", + "pyyaml>=6.0", "reflex", "typing-extensions", "typing-inspection>=0.4.2", diff --git a/packages/reflex-docgen/src/reflex_docgen/markdown/_types.py b/packages/reflex-docgen/src/reflex_docgen/markdown/_types.py index 22eb7f01710..513c698540f 100644 --- a/packages/reflex-docgen/src/reflex_docgen/markdown/_types.py +++ b/packages/reflex-docgen/src/reflex_docgen/markdown/_types.py @@ -229,6 +229,22 @@ class FrontMatter: raw: str + def get_data(self) -> dict[str, object]: + """Parse the raw YAML frontmatter into a dictionary. + + Returns: + The parsed YAML data. + """ + import yaml + + result = yaml.safe_load(self.raw) + if result is None: + return {} + if not isinstance(result, dict): + msg = f"Expected frontmatter to be a YAML mapping, got {type(result).__name__}" + raise TypeError(msg) + return result + def as_markdown(self) -> str: """Render back to markdown. diff --git a/uv.lock b/uv.lock index c5eb24affe2..36ddb410075 100644 --- a/uv.lock +++ b/uv.lock @@ -2160,6 +2160,7 @@ source = { editable = "packages/reflex-docgen" } dependencies = [ { name = "griffelib" }, { name = "mistletoe" }, + { name = "pyyaml" }, { name = "reflex" }, { name = "typing-extensions" }, { name = "typing-inspection" }, @@ -2169,6 +2170,7 @@ dependencies = [ requires-dist = [ { name = "griffelib", specifier = ">=2.0.1" }, { name = "mistletoe", specifier = ">=1.4.0" }, + { name = "pyyaml", specifier = ">=6.0" }, { name = "reflex", editable = "." }, { name = "typing-extensions" }, { name = "typing-inspection", specifier = ">=0.4.2" }, From 8daad0c8f468eb2958b588b9ccefe5b6aff14a4d Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Tue, 24 Mar 2026 15:44:29 -0700 Subject: [PATCH 009/113] parse components --- .../src/reflex_docgen/markdown/__init__.py | 2 + .../src/reflex_docgen/markdown/_parser.py | 45 +++++++- .../src/reflex_docgen/markdown/_types.py | 50 ++++++--- tests/units/docgen/test_markdown.py | 105 +++++++++++++++++- 4 files changed, 178 insertions(+), 24 deletions(-) diff --git a/packages/reflex-docgen/src/reflex_docgen/markdown/__init__.py b/packages/reflex-docgen/src/reflex_docgen/markdown/__init__.py index 7b55b2bd08e..3b8ca760cc9 100644 --- a/packages/reflex-docgen/src/reflex_docgen/markdown/__init__.py +++ b/packages/reflex-docgen/src/reflex_docgen/markdown/__init__.py @@ -5,6 +5,7 @@ from reflex_docgen.markdown._types import BoldSpan as BoldSpan from reflex_docgen.markdown._types import CodeBlock as CodeBlock from reflex_docgen.markdown._types import CodeSpan as CodeSpan +from reflex_docgen.markdown._types import ComponentPreview as ComponentPreview from reflex_docgen.markdown._types import DirectiveBlock as DirectiveBlock from reflex_docgen.markdown._types import Document as Document from reflex_docgen.markdown._types import FrontMatter as FrontMatter @@ -30,6 +31,7 @@ "BoldSpan", "CodeBlock", "CodeSpan", + "ComponentPreview", "DirectiveBlock", "Document", "FrontMatter", diff --git a/packages/reflex-docgen/src/reflex_docgen/markdown/_parser.py b/packages/reflex-docgen/src/reflex_docgen/markdown/_parser.py index 0e4bc2e2f4a..a5c8346b2f9 100644 --- a/packages/reflex-docgen/src/reflex_docgen/markdown/_parser.py +++ b/packages/reflex-docgen/src/reflex_docgen/markdown/_parser.py @@ -11,6 +11,7 @@ BoldSpan, CodeBlock, CodeSpan, + ComponentPreview, DirectiveBlock, Document, FrontMatter, @@ -38,6 +39,10 @@ _FRONTMATTER_RE = re.compile(r"\A---\n(.*?\n)---\n", re.DOTALL) +#: Known frontmatter keys that are not component preview lambdas. +_KNOWN_KEYS = frozenset({"components", "only_low_level", "title"}) + + def _extract_frontmatter(source: str) -> tuple[FrontMatter | None, str]: """Extract YAML frontmatter from the beginning of a markdown string. @@ -50,7 +55,45 @@ def _extract_frontmatter(source: str) -> tuple[FrontMatter | None, str]: m = _FRONTMATTER_RE.match(source) if m is None: return None, source - return FrontMatter(raw=m.group(1).strip()), source[m.end() :] + + import yaml + + data = yaml.safe_load(m.group(1)) + if not isinstance(data, dict): + data = {} + + # components + raw_components = data.get("components", []) + if not isinstance(raw_components, list): + raw_components = [] + components = tuple(str(c) for c in raw_components) + + # only_low_level + raw_oll = data.get("only_low_level", []) + if isinstance(raw_oll, list): + only_low_level = bool(raw_oll and raw_oll[0]) + else: + only_low_level = bool(raw_oll) + + # title + raw_title = data.get("title") + title = str(raw_title) if raw_title is not None else None + + # component previews — any key not in _KNOWN_KEYS with a string value + previews: list[ComponentPreview] = [] + for key, value in data.items(): + if key not in _KNOWN_KEYS and isinstance(value, str): + previews.append(ComponentPreview(name=key, source=value.strip())) + + return ( + FrontMatter( + components=components, + only_low_level=only_low_level, + title=title, + component_previews=tuple(previews), + ), + source[m.end() :], + ) def _convert_span(token: object) -> Span: diff --git a/packages/reflex-docgen/src/reflex_docgen/markdown/_types.py b/packages/reflex-docgen/src/reflex_docgen/markdown/_types.py index 513c698540f..152689718a8 100644 --- a/packages/reflex-docgen/src/reflex_docgen/markdown/_types.py +++ b/packages/reflex-docgen/src/reflex_docgen/markdown/_types.py @@ -220,30 +220,33 @@ def _fence_for(content: str) -> str: @dataclass(frozen=True, slots=True, kw_only=True) -class FrontMatter: - """YAML frontmatter extracted from a markdown document. +class ComponentPreview: + """A component preview lambda from frontmatter. Attributes: - raw: The raw YAML string (without delimiters). + name: The component class name (e.g. "Button", "DialogRoot"). + source: The lambda source string. """ - raw: str + name: str + source: str - def get_data(self) -> dict[str, object]: - """Parse the raw YAML frontmatter into a dictionary. - Returns: - The parsed YAML data. - """ - import yaml +@dataclass(frozen=True, slots=True, kw_only=True) +class FrontMatter: + """YAML frontmatter extracted from a markdown document. - result = yaml.safe_load(self.raw) - if result is None: - return {} - if not isinstance(result, dict): - msg = f"Expected frontmatter to be a YAML mapping, got {type(result).__name__}" - raise TypeError(msg) - return result + Attributes: + components: Component paths to document (e.g. ``["rx.button"]``). + only_low_level: Whether to show only low-level component variants. + title: An optional page title. + component_previews: Preview lambdas keyed by component class name. + """ + + components: tuple[str, ...] + only_low_level: bool + title: str | None + component_previews: tuple[ComponentPreview, ...] def as_markdown(self) -> str: """Render back to markdown. @@ -251,7 +254,18 @@ def as_markdown(self) -> str: Returns: A markdown string. """ - return f"---\n{self.raw}\n---" + import yaml + + data: dict[str, object] = {} + if self.components: + data["components"] = list(self.components) + if self.only_low_level: + data["only_low_level"] = [True] + if self.title is not None: + data["title"] = self.title + for preview in self.component_previews: + data[preview.name] = preview.source + return f"---\n{yaml.dump(data, default_flow_style=False, sort_keys=False).rstrip()}\n---" @dataclass(frozen=True, slots=True, kw_only=True) diff --git a/tests/units/docgen/test_markdown.py b/tests/units/docgen/test_markdown.py index ef2a0f47891..0f589406d78 100644 --- a/tests/units/docgen/test_markdown.py +++ b/tests/units/docgen/test_markdown.py @@ -6,6 +6,7 @@ from reflex_docgen.markdown import ( BoldSpan, CodeSpan, + ComponentPreview, FrontMatter, ImageSpan, ItalicSpan, @@ -30,7 +31,7 @@ def test_basic_frontmatter(): source = "---\ntitle: Test\n---\n# Hello\n" doc = parse_document(source) assert doc.frontmatter is not None - assert doc.frontmatter.raw == "title: Test" + assert doc.frontmatter.title == "Test" def test_multiline_frontmatter(): @@ -38,8 +39,9 @@ def test_multiline_frontmatter(): source = "---\ncomponents:\n - rx.button\n\nButton: |\n lambda **props: rx.button(**props)\n---\n# Button\n" doc = parse_document(source) assert doc.frontmatter is not None - assert "components:" in doc.frontmatter.raw - assert "rx.button" in doc.frontmatter.raw + assert doc.frontmatter.components == ("rx.button",) + assert len(doc.frontmatter.component_previews) == 1 + assert doc.frontmatter.component_previews[0].name == "Button" def test_frontmatter_not_in_blocks(): @@ -49,6 +51,90 @@ def test_frontmatter_not_in_blocks(): assert not any(isinstance(b, FrontMatter) for b in doc.blocks) +def test_empty_frontmatter(): + """Empty frontmatter (no content between ---) is not recognized.""" + doc = parse_document("---\n---\n# Hello\n") + assert doc.frontmatter is None + + +def test_only_low_level_list_true(): + """only_low_level as a YAML list with True is parsed.""" + source = "---\nonly_low_level:\n - True\n---\n# Dialog\n" + fm = parse_document(source).frontmatter + assert fm is not None + assert fm.only_low_level is True + + +def test_only_low_level_scalar(): + """only_low_level as a scalar boolean is parsed.""" + source = "---\nonly_low_level: true\n---\n# Dialog\n" + fm = parse_document(source).frontmatter + assert fm is not None + assert fm.only_low_level is True + + +def test_only_low_level_default_false(): + """only_low_level defaults to False when absent.""" + source = "---\ncomponents:\n - rx.box\n---\n# Box\n" + fm = parse_document(source).frontmatter + assert fm is not None + assert fm.only_low_level is False + + +def test_component_previews(): + """A component preview lambda is extracted from frontmatter.""" + source = '---\ncomponents:\n - rx.button\n\nButton: |\n lambda **props: rx.button("Click me", **props)\n---\n# Button\n' + fm = parse_document(source).frontmatter + assert fm is not None + assert len(fm.component_previews) == 1 + preview = fm.component_previews[0] + assert preview.name == "Button" + assert "rx.button" in preview.source + assert preview.source.startswith("lambda") + + +def test_multiple_previews(): + """Multiple component preview lambdas are extracted.""" + source = "---\ncomponents:\n - rx.input\n\nInput: |\n lambda **props: rx.input(**props)\n\nInputSlot: |\n lambda **props: rx.input(rx.input.slot(**props))\n---\n# Input\n" + fm = parse_document(source).frontmatter + assert fm is not None + assert len(fm.component_previews) == 2 + names = [p.name for p in fm.component_previews] + assert "Input" in names + assert "InputSlot" in names + + +def test_as_markdown_frontmatter_with_previews(): + """FrontMatter with component previews renders correctly.""" + fm = FrontMatter( + components=("rx.button",), + only_low_level=False, + title=None, + component_previews=( + ComponentPreview( + name="Button", + source='lambda **props: rx.button("Click", **props)', + ), + ), + ) + md = fm.as_markdown() + assert md.startswith("---\n") + assert md.endswith("\n---") + assert "rx.button" in md + assert "Button:" in md + + +def test_as_markdown_frontmatter_with_only_low_level(): + """FrontMatter with only_low_level renders the field.""" + fm = FrontMatter( + components=(), + only_low_level=True, + title=None, + component_previews=(), + ) + assert "only_low_level" in fm.as_markdown() + + def test_h1(): """A level-1 heading is parsed correctly.""" doc = parse_document("# Title\n") @@ -350,7 +436,7 @@ def button_demo(): """ doc = parse_document(source) assert doc.frontmatter is not None - assert "rx.button" in doc.frontmatter.raw + assert doc.frontmatter.components == ("rx.button",) assert len(doc.headings) == 2 assert doc.headings[0].children == (TextSpan(text="Button"),) assert doc.headings[1].children == (TextSpan(text="Variants"),) @@ -573,7 +659,16 @@ def test_as_markdown_thematic_break(): def test_as_markdown_frontmatter(): """FrontMatter renders with --- delimiters.""" - assert FrontMatter(raw="title: Test").as_markdown() == "---\ntitle: Test\n---" + fm = FrontMatter( + components=(), + only_low_level=False, + title="Test", + component_previews=(), + ) + md = fm.as_markdown() + assert md.startswith("---\n") + assert md.endswith("\n---") + assert "title: Test" in md def test_as_markdown_document_roundtrip(): From 694f17c9cf9ecfe7d32e5c72f3fcab0f6f966010 Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Tue, 24 Mar 2026 16:21:08 -0700 Subject: [PATCH 010/113] move reflex packages --- packages/reflex-components/README.md | 1 + packages/reflex-components/pyproject.toml | 13 + .../src/reflex_components/__init__.py | 30 +++ .../src/reflex_components}/base/__init__.py | 0 .../src/reflex_components}/base/app_wrap.py | 2 +- .../src/reflex_components}/base/bare.py | 0 .../src/reflex_components}/base/body.py | 2 +- .../src/reflex_components}/base/document.py | 0 .../reflex_components}/base/error_boundary.py | 4 +- .../src/reflex_components}/base/fragment.py | 0 .../src/reflex_components}/base/link.py | 2 +- .../src/reflex_components}/base/meta.py | 6 +- .../src/reflex_components}/base/script.py | 2 +- .../reflex_components}/base/strict_mode.py | 0 .../src/reflex_components}/core/__init__.py | 0 .../reflex_components}/core/auto_scroll.py | 2 +- .../src/reflex_components}/core/banner.py | 16 +- .../reflex_components}/core/breakpoints.py | 0 .../src/reflex_components}/core/clipboard.py | 2 +- .../src/reflex_components}/core/colors.py | 0 .../src/reflex_components}/core/cond.py | 2 +- .../src/reflex_components}/core/debounce.py | 0 .../src/reflex_components}/core/foreach.py | 4 +- .../src/reflex_components}/core/helmet.py | 0 .../src/reflex_components}/core/html.py | 2 +- .../core/layout/__init__.py | 0 .../src/reflex_components}/core/match.py | 2 +- .../src/reflex_components}/core/responsive.py | 2 +- .../src/reflex_components}/core/sticky.py | 12 +- .../src/reflex_components}/core/upload.py | 10 +- .../reflex_components}/core/window_events.py | 2 +- .../datadisplay/__init__.py | 0 .../reflex_components}/datadisplay/code.py | 10 +- .../datadisplay/dataeditor.py | 4 +- .../reflex_components}/datadisplay/logo.py | 0 .../datadisplay/shiki_code_block.py | 12 +- .../src/reflex_components}/el/__init__.py | 2 +- .../src/reflex_components}/el/element.py | 0 .../el/elements/__init__.py | 0 .../reflex_components}/el/elements/base.py | 2 +- .../reflex_components}/el/elements/forms.py | 2 +- .../reflex_components}/el/elements/inline.py | 0 .../reflex_components}/el/elements/media.py | 2 +- .../el/elements/metadata.py | 6 +- .../reflex_components}/el/elements/other.py | 0 .../reflex_components}/el/elements/scripts.py | 4 +- .../el/elements/sectioning.py | 0 .../reflex_components}/el/elements/tables.py | 0 .../el/elements/typography.py | 0 .../src/reflex_components}/gridjs/__init__.py | 0 .../reflex_components}/gridjs/datatable.py | 0 .../src/reflex_components}/lucide/__init__.py | 0 .../src/reflex_components}/lucide/icon.py | 0 .../reflex_components}/markdown/__init__.py | 0 .../reflex_components}/markdown/markdown.py | 28 +- .../src/reflex_components}/moment/__init__.py | 0 .../src/reflex_components}/moment/moment.py | 0 .../src/reflex_components}/plotly/__init__.py | 0 .../src/reflex_components}/plotly/plotly.py | 2 +- .../src/reflex_components}/radix/__init__.py | 0 .../radix/primitives/__init__.py | 0 .../radix/primitives/accordion.py | 10 +- .../radix/primitives/base.py | 2 +- .../radix/primitives/dialog.py | 2 +- .../radix/primitives/drawer.py | 6 +- .../radix/primitives/form.py | 6 +- .../radix/primitives/progress.py | 8 +- .../radix/primitives/slider.py | 2 +- .../radix/themes/__init__.py | 0 .../reflex_components}/radix/themes/base.py | 2 +- .../radix/themes/color_mode.py | 8 +- .../radix/themes/components/__init__.py | 0 .../radix/themes/components/alert_dialog.py | 6 +- .../radix/themes/components/aspect_ratio.py | 2 +- .../radix/themes/components/avatar.py | 4 +- .../radix/themes/components/badge.py | 6 +- .../radix/themes/components/button.py | 6 +- .../radix/themes/components/callout.py | 8 +- .../radix/themes/components/card.py | 6 +- .../radix/themes/components/checkbox.py | 8 +- .../radix/themes/components/checkbox_cards.py | 4 +- .../radix/themes/components/checkbox_group.py | 4 +- .../radix/themes/components/context_menu.py | 4 +- .../radix/themes/components/data_list.py | 4 +- .../radix/themes/components/dialog.py | 6 +- .../radix/themes/components/dropdown_menu.py | 4 +- .../radix/themes/components/hover_card.py | 6 +- .../radix/themes/components/icon_button.py | 10 +- .../radix/themes/components/inset.py | 6 +- .../radix/themes/components/popover.py | 6 +- .../radix/themes/components/progress.py | 4 +- .../radix/themes/components/radio.py | 4 +- .../radix/themes/components/radio_cards.py | 4 +- .../radix/themes/components/radio_group.py | 8 +- .../radix/themes/components/scroll_area.py | 2 +- .../themes/components/segmented_control.py | 4 +- .../radix/themes/components/select.py | 4 +- .../radix/themes/components/separator.py | 4 +- .../radix/themes/components/skeleton.py | 4 +- .../radix/themes/components/slider.py | 4 +- .../radix/themes/components/spinner.py | 4 +- .../radix/themes/components/switch.py | 4 +- .../radix/themes/components/table.py | 6 +- .../radix/themes/components/tabs.py | 6 +- .../radix/themes/components/text_area.py | 8 +- .../radix/themes/components/text_field.py | 8 +- .../radix/themes/components/tooltip.py | 2 +- .../radix/themes/layout/__init__.py | 0 .../radix/themes/layout/base.py | 4 +- .../radix/themes/layout/box.py | 4 +- .../radix/themes/layout/center.py | 0 .../radix/themes/layout/container.py | 6 +- .../radix/themes/layout/flex.py | 6 +- .../radix/themes/layout/grid.py | 6 +- .../radix/themes/layout/list.py | 12 +- .../radix/themes/layout/section.py | 6 +- .../radix/themes/layout/spacer.py | 0 .../radix/themes/layout/stack.py | 4 +- .../radix/themes/typography/__init__.py | 0 .../radix/themes/typography/base.py | 0 .../radix/themes/typography/blockquote.py | 6 +- .../radix/themes/typography/code.py | 8 +- .../radix/themes/typography/heading.py | 8 +- .../radix/themes/typography/link.py | 14 +- .../radix/themes/typography/text.py | 8 +- .../react_player/__init__.py | 0 .../reflex_components}/react_player/audio.py | 2 +- .../react_player/react_player.py | 2 +- .../reflex_components}/react_player/video.py | 2 +- .../react_router/__init__.py | 0 .../reflex_components}/react_router/dom.py | 2 +- .../reflex_components}/recharts/__init__.py | 0 .../reflex_components}/recharts/cartesian.py | 0 .../src/reflex_components}/recharts/charts.py | 2 +- .../reflex_components}/recharts/general.py | 0 .../src/reflex_components}/recharts/polar.py | 0 .../reflex_components}/recharts/recharts.py | 0 .../src/reflex_components}/sonner/__init__.py | 0 .../src/reflex_components}/sonner/toast.py | 2 +- pyi_hashes.json | 241 +++++++++--------- pyproject.toml | 4 +- reflex/app.py | 32 +-- reflex/compiler/compiler.py | 3 +- reflex/compiler/utils.py | 13 +- reflex/components/__init__.py | 106 +++++++- reflex/components/component.py | 26 +- reflex/components/dynamic.py | 3 +- reflex/components/tags/iter_tag.py | 7 +- reflex/config.py | 2 +- reflex/constants/colors.py | 2 +- reflex/event.py | 4 +- reflex/experimental/__init__.py | 3 +- reflex/state.py | 2 +- reflex/style.py | 3 +- reflex/utils/codespaces.py | 6 +- reflex/utils/misc.py | 2 +- reflex/utils/pyi_generator.py | 43 +++- reflex/utils/types.py | 2 +- reflex/vars/object.py | 2 +- scripts/make_pyi.py | 7 +- .../test_extra_overlay_function.py | 2 +- tests/integration/test_icon.py | 5 +- tests/units/compiler/test_compiler.py | 4 +- tests/units/components/base/test_bare.py | 2 +- tests/units/components/base/test_link.py | 2 +- tests/units/components/base/test_script.py | 3 +- tests/units/components/core/test_banner.py | 6 +- tests/units/components/core/test_colors.py | 2 +- tests/units/components/core/test_cond.py | 6 +- tests/units/components/core/test_debounce.py | 2 +- tests/units/components/core/test_foreach.py | 16 +- tests/units/components/core/test_html.py | 2 +- tests/units/components/core/test_match.py | 6 +- .../units/components/core/test_responsive.py | 4 +- tests/units/components/core/test_upload.py | 5 +- .../units/components/datadisplay/test_code.py | 3 +- .../components/datadisplay/test_dataeditor.py | 2 +- .../components/datadisplay/test_datatable.py | 2 +- .../components/datadisplay/test_shiki_code.py | 10 +- tests/units/components/el/test_svg.py | 2 +- tests/units/components/forms/test_form.py | 3 +- .../components/graphing/test_recharts.py | 4 +- tests/units/components/lucide/test_icon.py | 4 +- .../components/markdown/test_markdown.py | 10 +- .../components/radix/test_icon_button.py | 4 +- tests/units/components/radix/test_layout.py | 2 +- .../components/recharts/test_cartesian.py | 2 +- tests/units/components/recharts/test_polar.py | 2 +- tests/units/components/test_component.py | 8 +- .../units/components/test_component_state.py | 2 +- .../components/typography/test_markdown.py | 2 +- tests/units/test_app.py | 10 +- tests/units/test_event.py | 2 +- tests/units/test_var.py | 4 +- tests/units/utils/test_serializers.py | 2 +- uv.lock | 8 + 196 files changed, 692 insertions(+), 498 deletions(-) create mode 100644 packages/reflex-components/README.md create mode 100644 packages/reflex-components/pyproject.toml create mode 100644 packages/reflex-components/src/reflex_components/__init__.py rename {reflex/components => packages/reflex-components/src/reflex_components}/base/__init__.py (100%) rename {reflex/components => packages/reflex-components/src/reflex_components}/base/app_wrap.py (89%) rename {reflex/components => packages/reflex-components/src/reflex_components}/base/bare.py (100%) rename {reflex/components => packages/reflex-components/src/reflex_components}/base/body.py (67%) rename {reflex/components => packages/reflex-components/src/reflex_components}/base/document.py (100%) rename {reflex/components => packages/reflex-components/src/reflex_components}/base/error_boundary.py (98%) rename {reflex/components => packages/reflex-components/src/reflex_components}/base/fragment.py (100%) rename {reflex/components => packages/reflex-components/src/reflex_components}/base/link.py (94%) rename {reflex/components => packages/reflex-components/src/reflex_components}/base/meta.py (88%) rename {reflex/components => packages/reflex-components/src/reflex_components}/base/script.py (97%) rename {reflex/components => packages/reflex-components/src/reflex_components}/base/strict_mode.py (100%) rename {reflex/components => packages/reflex-components/src/reflex_components}/core/__init__.py (100%) rename {reflex/components => packages/reflex-components/src/reflex_components}/core/auto_scroll.py (98%) rename {reflex/components => packages/reflex-components/src/reflex_components}/core/banner.py (97%) rename {reflex/components => packages/reflex-components/src/reflex_components}/core/breakpoints.py (100%) rename {reflex/components => packages/reflex-components/src/reflex_components}/core/clipboard.py (98%) rename {reflex/components => packages/reflex-components/src/reflex_components}/core/colors.py (100%) rename {reflex/components => packages/reflex-components/src/reflex_components}/core/cond.py (99%) rename {reflex/components => packages/reflex-components/src/reflex_components}/core/debounce.py (100%) rename {reflex/components => packages/reflex-components/src/reflex_components}/core/foreach.py (98%) rename {reflex/components => packages/reflex-components/src/reflex_components}/core/helmet.py (100%) rename {reflex/components => packages/reflex-components/src/reflex_components}/core/html.py (95%) rename {reflex/components => packages/reflex-components/src/reflex_components}/core/layout/__init__.py (100%) rename {reflex/components => packages/reflex-components/src/reflex_components}/core/match.py (99%) rename {reflex/components => packages/reflex-components/src/reflex_components}/core/responsive.py (96%) rename {reflex/components => packages/reflex-components/src/reflex_components}/core/sticky.py (90%) rename {reflex/components => packages/reflex-components/src/reflex_components}/core/upload.py (98%) rename {reflex/components => packages/reflex-components/src/reflex_components}/core/window_events.py (98%) rename {reflex/components => packages/reflex-components/src/reflex_components}/datadisplay/__init__.py (100%) rename {reflex/components => packages/reflex-components/src/reflex_components}/datadisplay/code.py (97%) rename {reflex/components => packages/reflex-components/src/reflex_components}/datadisplay/dataeditor.py (99%) rename {reflex/components => packages/reflex-components/src/reflex_components}/datadisplay/logo.py (100%) rename {reflex/components => packages/reflex-components/src/reflex_components}/datadisplay/shiki_code_block.py (98%) rename {reflex/components => packages/reflex-components/src/reflex_components}/el/__init__.py (92%) rename {reflex/components => packages/reflex-components/src/reflex_components}/el/element.py (100%) rename {reflex/components => packages/reflex-components/src/reflex_components}/el/elements/__init__.py (100%) rename {reflex/components => packages/reflex-components/src/reflex_components}/el/elements/base.py (98%) rename {reflex/components => packages/reflex-components/src/reflex_components}/el/elements/forms.py (99%) rename {reflex/components => packages/reflex-components/src/reflex_components}/el/elements/inline.py (100%) rename {reflex/components => packages/reflex-components/src/reflex_components}/el/elements/media.py (99%) rename {reflex/components => packages/reflex-components/src/reflex_components}/el/elements/metadata.py (93%) rename {reflex/components => packages/reflex-components/src/reflex_components}/el/elements/other.py (100%) rename {reflex/components => packages/reflex-components/src/reflex_components}/el/elements/scripts.py (91%) rename {reflex/components => packages/reflex-components/src/reflex_components}/el/elements/sectioning.py (100%) rename {reflex/components => packages/reflex-components/src/reflex_components}/el/elements/tables.py (100%) rename {reflex/components => packages/reflex-components/src/reflex_components}/el/elements/typography.py (100%) rename {reflex/components => packages/reflex-components/src/reflex_components}/gridjs/__init__.py (100%) rename {reflex/components => packages/reflex-components/src/reflex_components}/gridjs/datatable.py (100%) rename {reflex/components => packages/reflex-components/src/reflex_components}/lucide/__init__.py (100%) rename {reflex/components => packages/reflex-components/src/reflex_components}/lucide/icon.py (100%) rename {reflex/components => packages/reflex-components/src/reflex_components}/markdown/__init__.py (100%) rename {reflex/components => packages/reflex-components/src/reflex_components}/markdown/markdown.py (95%) rename {reflex/components => packages/reflex-components/src/reflex_components}/moment/__init__.py (100%) rename {reflex/components => packages/reflex-components/src/reflex_components}/moment/moment.py (100%) rename {reflex/components => packages/reflex-components/src/reflex_components}/plotly/__init__.py (100%) rename {reflex/components => packages/reflex-components/src/reflex_components}/plotly/plotly.py (99%) rename {reflex/components => packages/reflex-components/src/reflex_components}/radix/__init__.py (100%) rename {reflex/components => packages/reflex-components/src/reflex_components}/radix/primitives/__init__.py (100%) rename {reflex/components => packages/reflex-components/src/reflex_components}/radix/primitives/accordion.py (98%) rename {reflex/components => packages/reflex-components/src/reflex_components}/radix/primitives/base.py (96%) rename {reflex/components => packages/reflex-components/src/reflex_components}/radix/primitives/dialog.py (99%) rename {reflex/components => packages/reflex-components/src/reflex_components}/radix/primitives/drawer.py (98%) rename {reflex/components => packages/reflex-components/src/reflex_components}/radix/primitives/form.py (96%) rename {reflex/components => packages/reflex-components/src/reflex_components}/radix/primitives/progress.py (94%) rename {reflex/components => packages/reflex-components/src/reflex_components}/radix/primitives/slider.py (98%) rename {reflex/components => packages/reflex-components/src/reflex_components}/radix/themes/__init__.py (100%) rename {reflex/components => packages/reflex-components/src/reflex_components}/radix/themes/base.py (99%) rename {reflex/components => packages/reflex-components/src/reflex_components}/radix/themes/color_mode.py (96%) rename {reflex/components => packages/reflex-components/src/reflex_components}/radix/themes/components/__init__.py (100%) rename {reflex/components => packages/reflex-components/src/reflex_components}/radix/themes/components/alert_dialog.py (95%) rename {reflex/components => packages/reflex-components/src/reflex_components}/radix/themes/components/aspect_ratio.py (86%) rename {reflex/components => packages/reflex-components/src/reflex_components}/radix/themes/components/avatar.py (91%) rename {reflex/components => packages/reflex-components/src/reflex_components}/radix/themes/components/badge.py (86%) rename {reflex/components => packages/reflex-components/src/reflex_components}/radix/themes/components/button.py (89%) rename {reflex/components => packages/reflex-components/src/reflex_components}/radix/themes/components/callout.py (91%) rename {reflex/components => packages/reflex-components/src/reflex_components}/radix/themes/components/card.py (81%) rename {reflex/components => packages/reflex-components/src/reflex_components}/radix/themes/components/checkbox.py (95%) rename {reflex/components => packages/reflex-components/src/reflex_components}/radix/themes/components/checkbox_cards.py (92%) rename {reflex/components => packages/reflex-components/src/reflex_components}/radix/themes/components/checkbox_group.py (93%) rename {reflex/components => packages/reflex-components/src/reflex_components}/radix/themes/components/context_menu.py (99%) rename {reflex/components => packages/reflex-components/src/reflex_components}/radix/themes/components/data_list.py (94%) rename {reflex/components => packages/reflex-components/src/reflex_components}/radix/themes/components/dialog.py (94%) rename {reflex/components => packages/reflex-components/src/reflex_components}/radix/themes/components/dropdown_menu.py (99%) rename {reflex/components => packages/reflex-components/src/reflex_components}/radix/themes/components/hover_card.py (95%) rename {reflex/components => packages/reflex-components/src/reflex_components}/radix/themes/components/icon_button.py (92%) rename {reflex/components => packages/reflex-components/src/reflex_components}/radix/themes/components/inset.py (87%) rename {reflex/components => packages/reflex-components/src/reflex_components}/radix/themes/components/popover.py (96%) rename {reflex/components => packages/reflex-components/src/reflex_components}/radix/themes/components/progress.py (95%) rename {reflex/components => packages/reflex-components/src/reflex_components}/radix/themes/components/radio.py (87%) rename {reflex/components => packages/reflex-components/src/reflex_components}/radix/themes/components/radio_cards.py (96%) rename {reflex/components => packages/reflex-components/src/reflex_components}/radix/themes/components/radio_group.py (96%) rename {reflex/components => packages/reflex-components/src/reflex_components}/radix/themes/components/scroll_area.py (93%) rename {reflex/components => packages/reflex-components/src/reflex_components}/radix/themes/components/segmented_control.py (95%) rename {reflex/components => packages/reflex-components/src/reflex_components}/radix/themes/components/select.py (98%) rename {reflex/components => packages/reflex-components/src/reflex_components}/radix/themes/components/separator.py (89%) rename {reflex/components => packages/reflex-components/src/reflex_components}/radix/themes/components/skeleton.py (88%) rename {reflex/components => packages/reflex-components/src/reflex_components}/radix/themes/components/slider.py (96%) rename {reflex/components => packages/reflex-components/src/reflex_components}/radix/themes/components/spinner.py (78%) rename {reflex/components => packages/reflex-components/src/reflex_components}/radix/themes/components/switch.py (94%) rename {reflex/components => packages/reflex-components/src/reflex_components}/radix/themes/components/table.py (95%) rename {reflex/components => packages/reflex-components/src/reflex_components}/radix/themes/components/tabs.py (96%) rename {reflex/components => packages/reflex-components/src/reflex_components}/radix/themes/components/text_area.py (94%) rename {reflex/components => packages/reflex-components/src/reflex_components}/radix/themes/components/text_field.py (95%) rename {reflex/components => packages/reflex-components/src/reflex_components}/radix/themes/components/tooltip.py (98%) rename {reflex/components => packages/reflex-components/src/reflex_components}/radix/themes/layout/__init__.py (100%) rename {reflex/components => packages/reflex-components/src/reflex_components}/radix/themes/layout/base.py (89%) rename {reflex/components => packages/reflex-components/src/reflex_components}/radix/themes/layout/box.py (70%) rename {reflex/components => packages/reflex-components/src/reflex_components}/radix/themes/layout/center.py (100%) rename {reflex/components => packages/reflex-components/src/reflex_components}/radix/themes/layout/container.py (90%) rename {reflex/components => packages/reflex-components/src/reflex_components}/radix/themes/layout/flex.py (91%) rename {reflex/components => packages/reflex-components/src/reflex_components}/radix/themes/layout/grid.py (92%) rename {reflex/components => packages/reflex-components/src/reflex_components}/radix/themes/layout/list.py (93%) rename {reflex/components => packages/reflex-components/src/reflex_components}/radix/themes/layout/section.py (77%) rename {reflex/components => packages/reflex-components/src/reflex_components}/radix/themes/layout/spacer.py (100%) rename {reflex/components => packages/reflex-components/src/reflex_components}/radix/themes/layout/stack.py (93%) rename {reflex/components => packages/reflex-components/src/reflex_components}/radix/themes/typography/__init__.py (100%) rename {reflex/components => packages/reflex-components/src/reflex_components}/radix/themes/typography/base.py (100%) rename {reflex/components => packages/reflex-components/src/reflex_components}/radix/themes/typography/blockquote.py (84%) rename {reflex/components => packages/reflex-components/src/reflex_components}/radix/themes/typography/code.py (83%) rename {reflex/components => packages/reflex-components/src/reflex_components}/radix/themes/typography/heading.py (87%) rename {reflex/components => packages/reflex-components/src/reflex_components}/radix/themes/typography/link.py (90%) rename {reflex/components => packages/reflex-components/src/reflex_components}/radix/themes/typography/text.py (92%) rename {reflex/components => packages/reflex-components/src/reflex_components}/react_player/__init__.py (100%) rename {reflex/components => packages/reflex-components/src/reflex_components}/react_player/audio.py (63%) rename {reflex/components => packages/reflex-components/src/reflex_components}/react_player/react_player.py (99%) rename {reflex/components => packages/reflex-components/src/reflex_components}/react_player/video.py (63%) rename {reflex/components => packages/reflex-components/src/reflex_components}/react_router/__init__.py (100%) rename {reflex/components => packages/reflex-components/src/reflex_components}/react_router/dom.py (97%) rename {reflex/components => packages/reflex-components/src/reflex_components}/recharts/__init__.py (100%) rename {reflex/components => packages/reflex-components/src/reflex_components}/recharts/cartesian.py (100%) rename {reflex/components => packages/reflex-components/src/reflex_components}/recharts/charts.py (99%) rename {reflex/components => packages/reflex-components/src/reflex_components}/recharts/general.py (100%) rename {reflex/components => packages/reflex-components/src/reflex_components}/recharts/polar.py (100%) rename {reflex/components => packages/reflex-components/src/reflex_components}/recharts/recharts.py (100%) rename {reflex/components => packages/reflex-components/src/reflex_components}/sonner/__init__.py (100%) rename {reflex/components => packages/reflex-components/src/reflex_components}/sonner/toast.py (99%) diff --git a/packages/reflex-components/README.md b/packages/reflex-components/README.md new file mode 100644 index 00000000000..e5e46a36522 --- /dev/null +++ b/packages/reflex-components/README.md @@ -0,0 +1 @@ +# Reflex Components\n\nUI components for Reflex. diff --git a/packages/reflex-components/pyproject.toml b/packages/reflex-components/pyproject.toml new file mode 100644 index 00000000000..60653b9bc96 --- /dev/null +++ b/packages/reflex-components/pyproject.toml @@ -0,0 +1,13 @@ +[project] +name = "reflex-components" +version = "0.0.1" +description = "UI components for Reflex." +readme = "README.md" +authors = [{ name = "Khaleel Al-Adhami", email = "khaleel@reflex.dev" }] +maintainers = [{ name = "Khaleel Al-Adhami", email = "khaleel@reflex.dev" }] +requires-python = ">=3.10" +dependencies = [] + +[build-system] +requires = ["uv_build>=0.10.11,<0.12.0"] +build-backend = "uv_build" diff --git a/packages/reflex-components/src/reflex_components/__init__.py b/packages/reflex-components/src/reflex_components/__init__.py new file mode 100644 index 00000000000..6d1d787cc2e --- /dev/null +++ b/packages/reflex-components/src/reflex_components/__init__.py @@ -0,0 +1,30 @@ +"""Reflex UI components package.""" + +from __future__ import annotations + +from reflex.utils import lazy_loader + +_SUBMODULES: set[str] = { + "base", + "core", + "datadisplay", + "el", + "gridjs", + "lucide", + "markdown", + "moment", + "plotly", + "radix", + "react_player", + "react_router", + "recharts", + "sonner", +} + +_SUBMOD_ATTRS: dict[str, list[str]] = {} + +__getattr__, __dir__, __all__ = lazy_loader.attach( + __name__, + submodules=_SUBMODULES, + submod_attrs=_SUBMOD_ATTRS, +) diff --git a/reflex/components/base/__init__.py b/packages/reflex-components/src/reflex_components/base/__init__.py similarity index 100% rename from reflex/components/base/__init__.py rename to packages/reflex-components/src/reflex_components/base/__init__.py diff --git a/reflex/components/base/app_wrap.py b/packages/reflex-components/src/reflex_components/base/app_wrap.py similarity index 89% rename from reflex/components/base/app_wrap.py rename to packages/reflex-components/src/reflex_components/base/app_wrap.py index c7cfe650544..de868b6efac 100644 --- a/reflex/components/base/app_wrap.py +++ b/packages/reflex-components/src/reflex_components/base/app_wrap.py @@ -1,6 +1,6 @@ """Top-level component that wraps the entire app.""" -from reflex.components.base.fragment import Fragment +from reflex_components.base.fragment import Fragment from reflex.components.component import Component from reflex.vars.base import Var diff --git a/reflex/components/base/bare.py b/packages/reflex-components/src/reflex_components/base/bare.py similarity index 100% rename from reflex/components/base/bare.py rename to packages/reflex-components/src/reflex_components/base/bare.py diff --git a/reflex/components/base/body.py b/packages/reflex-components/src/reflex_components/base/body.py similarity index 67% rename from reflex/components/base/body.py rename to packages/reflex-components/src/reflex_components/base/body.py index 2327aa2d366..5ce7b0e8c0d 100644 --- a/reflex/components/base/body.py +++ b/packages/reflex-components/src/reflex_components/base/body.py @@ -1,6 +1,6 @@ """Display the page body.""" -from reflex.components.el import elements +from reflex_components.el import elements class Body(elements.Body): diff --git a/reflex/components/base/document.py b/packages/reflex-components/src/reflex_components/base/document.py similarity index 100% rename from reflex/components/base/document.py rename to packages/reflex-components/src/reflex_components/base/document.py diff --git a/reflex/components/base/error_boundary.py b/packages/reflex-components/src/reflex_components/base/error_boundary.py similarity index 98% rename from reflex/components/base/error_boundary.py rename to packages/reflex-components/src/reflex_components/base/error_boundary.py index 5b91442cb8c..6f608357529 100644 --- a/reflex/components/base/error_boundary.py +++ b/packages/reflex-components/src/reflex_components/base/error_boundary.py @@ -3,8 +3,8 @@ from __future__ import annotations from reflex.components.component import Component, field -from reflex.components.datadisplay.logo import svg_logo -from reflex.components.el import a, button, div, h2, hr, p, pre, svg +from reflex_components.datadisplay.logo import svg_logo +from reflex_components.el import a, button, div, h2, hr, p, pre, svg from reflex.event import EventHandler, set_clipboard from reflex.state import FrontendEventExceptionState from reflex.vars.base import Var diff --git a/reflex/components/base/fragment.py b/packages/reflex-components/src/reflex_components/base/fragment.py similarity index 100% rename from reflex/components/base/fragment.py rename to packages/reflex-components/src/reflex_components/base/fragment.py diff --git a/reflex/components/base/link.py b/packages/reflex-components/src/reflex_components/base/link.py similarity index 94% rename from reflex/components/base/link.py rename to packages/reflex-components/src/reflex_components/base/link.py index 7af286e49a1..9e532b67ab9 100644 --- a/reflex/components/base/link.py +++ b/packages/reflex-components/src/reflex_components/base/link.py @@ -1,7 +1,7 @@ """Display the title of the current page.""" from reflex.components.component import field -from reflex.components.el.elements.base import BaseHTML +from reflex_components.el.elements.base import BaseHTML from reflex.vars.base import Var diff --git a/reflex/components/base/meta.py b/packages/reflex-components/src/reflex_components/base/meta.py similarity index 88% rename from reflex/components/base/meta.py rename to packages/reflex-components/src/reflex_components/base/meta.py index a8f81da18ea..557ff12ea52 100644 --- a/reflex/components/base/meta.py +++ b/packages/reflex-components/src/reflex_components/base/meta.py @@ -2,10 +2,10 @@ from __future__ import annotations -from reflex.components.base.bare import Bare +from reflex_components.base.bare import Bare from reflex.components.component import field -from reflex.components.el import elements -from reflex.components.el.elements.metadata import Meta as Meta # for compatibility +from reflex_components.el import elements +from reflex_components.el.elements.metadata import Meta as Meta # for compatibility from reflex.vars.base import Var diff --git a/reflex/components/base/script.py b/packages/reflex-components/src/reflex_components/base/script.py similarity index 97% rename from reflex/components/base/script.py rename to packages/reflex-components/src/reflex_components/base/script.py index 507e6db324e..1cf0df47ce0 100644 --- a/reflex/components/base/script.py +++ b/packages/reflex-components/src/reflex_components/base/script.py @@ -3,7 +3,7 @@ from __future__ import annotations from reflex.components import el as elements -from reflex.components.core.helmet import helmet +from reflex_components.core.helmet import helmet from reflex.utils import console diff --git a/reflex/components/base/strict_mode.py b/packages/reflex-components/src/reflex_components/base/strict_mode.py similarity index 100% rename from reflex/components/base/strict_mode.py rename to packages/reflex-components/src/reflex_components/base/strict_mode.py diff --git a/reflex/components/core/__init__.py b/packages/reflex-components/src/reflex_components/core/__init__.py similarity index 100% rename from reflex/components/core/__init__.py rename to packages/reflex-components/src/reflex_components/core/__init__.py diff --git a/reflex/components/core/auto_scroll.py b/packages/reflex-components/src/reflex_components/core/auto_scroll.py similarity index 98% rename from reflex/components/core/auto_scroll.py rename to packages/reflex-components/src/reflex_components/core/auto_scroll.py index a4eb6f56725..9a606140ecd 100644 --- a/reflex/components/core/auto_scroll.py +++ b/packages/reflex-components/src/reflex_components/core/auto_scroll.py @@ -4,7 +4,7 @@ import dataclasses -from reflex.components.el.elements.typography import Div +from reflex_components.el.elements.typography import Div from reflex.constants.compiler import MemoizationDisposition, MemoizationMode from reflex.utils.imports import ImportDict from reflex.vars.base import Var, get_unique_variable_name diff --git a/reflex/components/core/banner.py b/packages/reflex-components/src/reflex_components/core/banner.py similarity index 97% rename from reflex/components/core/banner.py rename to packages/reflex-components/src/reflex_components/core/banner.py index 8160faf38c5..90bb36d422c 100644 --- a/reflex/components/core/banner.py +++ b/packages/reflex-components/src/reflex_components/core/banner.py @@ -3,19 +3,19 @@ from __future__ import annotations from reflex import constants -from reflex.components.base.fragment import Fragment +from reflex_components.base.fragment import Fragment from reflex.components.component import Component -from reflex.components.core.cond import cond -from reflex.components.el.elements.typography import Div -from reflex.components.lucide.icon import Icon -from reflex.components.radix.themes.components.dialog import ( +from reflex_components.core.cond import cond +from reflex_components.el.elements.typography import Div +from reflex_components.lucide.icon import Icon +from reflex_components.radix.themes.components.dialog import ( DialogContent, DialogRoot, DialogTitle, ) -from reflex.components.radix.themes.layout.flex import Flex -from reflex.components.radix.themes.typography.text import Text -from reflex.components.sonner.toast import ToastProps, toast_ref +from reflex_components.radix.themes.layout.flex import Flex +from reflex_components.radix.themes.typography.text import Text +from reflex_components.sonner.toast import ToastProps, toast_ref from reflex.constants import Dirs, Hooks, Imports from reflex.constants.compiler import CompileVars from reflex.environment import environment diff --git a/reflex/components/core/breakpoints.py b/packages/reflex-components/src/reflex_components/core/breakpoints.py similarity index 100% rename from reflex/components/core/breakpoints.py rename to packages/reflex-components/src/reflex_components/core/breakpoints.py diff --git a/reflex/components/core/clipboard.py b/packages/reflex-components/src/reflex_components/core/clipboard.py similarity index 98% rename from reflex/components/core/clipboard.py rename to packages/reflex-components/src/reflex_components/core/clipboard.py index 0001a459f57..7cd5ebb6ca2 100644 --- a/reflex/components/core/clipboard.py +++ b/packages/reflex-components/src/reflex_components/core/clipboard.py @@ -4,7 +4,7 @@ from collections.abc import Sequence -from reflex.components.base.fragment import Fragment +from reflex_components.base.fragment import Fragment from reflex.components.component import field from reflex.components.tags.tag import Tag from reflex.constants.compiler import Hooks diff --git a/reflex/components/core/colors.py b/packages/reflex-components/src/reflex_components/core/colors.py similarity index 100% rename from reflex/components/core/colors.py rename to packages/reflex-components/src/reflex_components/core/colors.py diff --git a/reflex/components/core/cond.py b/packages/reflex-components/src/reflex_components/core/cond.py similarity index 99% rename from reflex/components/core/cond.py rename to packages/reflex-components/src/reflex_components/core/cond.py index eefcc04ef9d..61a87f93969 100644 --- a/reflex/components/core/cond.py +++ b/packages/reflex-components/src/reflex_components/core/cond.py @@ -4,7 +4,7 @@ from typing import Any, overload -from reflex.components.base.fragment import Fragment +from reflex_components.base.fragment import Fragment from reflex.components.component import BaseComponent, Component, field from reflex.components.tags import CondTag, Tag from reflex.constants import Dirs diff --git a/reflex/components/core/debounce.py b/packages/reflex-components/src/reflex_components/core/debounce.py similarity index 100% rename from reflex/components/core/debounce.py rename to packages/reflex-components/src/reflex_components/core/debounce.py diff --git a/reflex/components/core/foreach.py b/packages/reflex-components/src/reflex_components/core/foreach.py similarity index 98% rename from reflex/components/core/foreach.py rename to packages/reflex-components/src/reflex_components/core/foreach.py index 38f921e5cf8..18dcec42527 100644 --- a/reflex/components/core/foreach.py +++ b/packages/reflex-components/src/reflex_components/core/foreach.py @@ -8,9 +8,9 @@ from hashlib import md5 from typing import Any -from reflex.components.base.fragment import Fragment +from reflex_components.base.fragment import Fragment from reflex.components.component import Component, field -from reflex.components.core.cond import cond +from reflex_components.core.cond import cond from reflex.components.tags import IterTag from reflex.constants import MemoizationMode from reflex.constants.state import FIELD_MARKER diff --git a/reflex/components/core/helmet.py b/packages/reflex-components/src/reflex_components/core/helmet.py similarity index 100% rename from reflex/components/core/helmet.py rename to packages/reflex-components/src/reflex_components/core/helmet.py diff --git a/reflex/components/core/html.py b/packages/reflex-components/src/reflex_components/core/html.py similarity index 95% rename from reflex/components/core/html.py rename to packages/reflex-components/src/reflex_components/core/html.py index 33a18fbcbc4..0db92633448 100644 --- a/reflex/components/core/html.py +++ b/packages/reflex-components/src/reflex_components/core/html.py @@ -1,7 +1,7 @@ """A html component.""" from reflex.components.component import field -from reflex.components.el.elements.typography import Div +from reflex_components.el.elements.typography import Div from reflex.vars.base import Var diff --git a/reflex/components/core/layout/__init__.py b/packages/reflex-components/src/reflex_components/core/layout/__init__.py similarity index 100% rename from reflex/components/core/layout/__init__.py rename to packages/reflex-components/src/reflex_components/core/layout/__init__.py diff --git a/reflex/components/core/match.py b/packages/reflex-components/src/reflex_components/core/match.py similarity index 99% rename from reflex/components/core/match.py rename to packages/reflex-components/src/reflex_components/core/match.py index f567cb79d37..38fd9b1b4b6 100644 --- a/reflex/components/core/match.py +++ b/packages/reflex-components/src/reflex_components/core/match.py @@ -3,7 +3,7 @@ import textwrap from typing import Any, cast -from reflex.components.base import Fragment +from reflex_components.base import Fragment from reflex.components.component import BaseComponent, Component, MemoizationLeaf, field from reflex.components.tags import Tag from reflex.components.tags.match_tag import MatchTag diff --git a/reflex/components/core/responsive.py b/packages/reflex-components/src/reflex_components/core/responsive.py similarity index 96% rename from reflex/components/core/responsive.py rename to packages/reflex-components/src/reflex_components/core/responsive.py index e1c7f0cb305..a1251fc548b 100644 --- a/reflex/components/core/responsive.py +++ b/packages/reflex-components/src/reflex_components/core/responsive.py @@ -1,6 +1,6 @@ """Responsive components.""" -from reflex.components.radix.themes.layout.box import Box +from reflex_components.radix.themes.layout.box import Box # Add responsive styles shortcuts. diff --git a/reflex/components/core/sticky.py b/packages/reflex-components/src/reflex_components/core/sticky.py similarity index 90% rename from reflex/components/core/sticky.py rename to packages/reflex-components/src/reflex_components/core/sticky.py index 4373e0e2fc6..59d90238911 100644 --- a/reflex/components/core/sticky.py +++ b/packages/reflex-components/src/reflex_components/core/sticky.py @@ -1,12 +1,12 @@ """Components for displaying the Reflex sticky logo.""" from reflex.components.component import ComponentNamespace -from reflex.components.core.colors import color -from reflex.components.core.cond import color_mode_cond -from reflex.components.core.responsive import desktop_only -from reflex.components.el.elements.inline import A -from reflex.components.el.elements.media import Path, Rect, Svg -from reflex.components.radix.themes.typography.text import Text +from reflex_components.core.colors import color +from reflex_components.core.cond import color_mode_cond +from reflex_components.core.responsive import desktop_only +from reflex_components.el.elements.inline import A +from reflex_components.el.elements.media import Path, Rect, Svg +from reflex_components.radix.themes.typography.text import Text from reflex.style import Style diff --git a/reflex/components/core/upload.py b/packages/reflex-components/src/reflex_components/core/upload.py similarity index 98% rename from reflex/components/core/upload.py rename to packages/reflex-components/src/reflex_components/core/upload.py index b6fdd476001..69fa2c05d93 100644 --- a/reflex/components/core/upload.py +++ b/packages/reflex-components/src/reflex_components/core/upload.py @@ -7,7 +7,7 @@ from typing import Any, ClassVar from reflex.app import UploadFile -from reflex.components.base.fragment import Fragment +from reflex_components.base.fragment import Fragment from reflex.components.component import ( Component, ComponentNamespace, @@ -15,10 +15,10 @@ StatefulComponent, field, ) -from reflex.components.core.cond import cond -from reflex.components.el.elements.forms import Input -from reflex.components.radix.themes.layout.box import Box -from reflex.components.sonner.toast import toast +from reflex_components.core.cond import cond +from reflex_components.el.elements.forms import Input +from reflex_components.radix.themes.layout.box import Box +from reflex_components.sonner.toast import toast from reflex.constants import Dirs from reflex.constants.compiler import Hooks, Imports from reflex.environment import environment diff --git a/reflex/components/core/window_events.py b/packages/reflex-components/src/reflex_components/core/window_events.py similarity index 98% rename from reflex/components/core/window_events.py rename to packages/reflex-components/src/reflex_components/core/window_events.py index 3e2e980ef1f..5b70f355a2e 100644 --- a/reflex/components/core/window_events.py +++ b/packages/reflex-components/src/reflex_components/core/window_events.py @@ -5,7 +5,7 @@ from typing import Any, cast import reflex as rx -from reflex.components.base.fragment import Fragment +from reflex_components.base.fragment import Fragment from reflex.components.component import StatefulComponent, field from reflex.constants.compiler import Hooks from reflex.event import key_event, no_args_event_spec diff --git a/reflex/components/datadisplay/__init__.py b/packages/reflex-components/src/reflex_components/datadisplay/__init__.py similarity index 100% rename from reflex/components/datadisplay/__init__.py rename to packages/reflex-components/src/reflex_components/datadisplay/__init__.py diff --git a/reflex/components/datadisplay/code.py b/packages/reflex-components/src/reflex_components/datadisplay/code.py similarity index 97% rename from reflex/components/datadisplay/code.py rename to packages/reflex-components/src/reflex_components/datadisplay/code.py index c80172cbdb9..f7dede64cbf 100644 --- a/reflex/components/datadisplay/code.py +++ b/packages/reflex-components/src/reflex_components/datadisplay/code.py @@ -6,11 +6,11 @@ from typing import ClassVar, Literal from reflex.components.component import Component, ComponentNamespace, field -from reflex.components.core.cond import color_mode_cond -from reflex.components.lucide.icon import Icon -from reflex.components.markdown.markdown import MarkdownComponentMap -from reflex.components.radix.themes.components.button import Button -from reflex.components.radix.themes.layout.box import Box +from reflex_components.core.cond import color_mode_cond +from reflex_components.lucide.icon import Icon +from reflex_components.markdown.markdown import MarkdownComponentMap +from reflex_components.radix.themes.components.button import Button +from reflex_components.radix.themes.layout.box import Box from reflex.constants.colors import Color from reflex.event import set_clipboard from reflex.style import Style diff --git a/reflex/components/datadisplay/dataeditor.py b/packages/reflex-components/src/reflex_components/datadisplay/dataeditor.py similarity index 99% rename from reflex/components/datadisplay/dataeditor.py rename to packages/reflex-components/src/reflex_components/datadisplay/dataeditor.py index cae0e7a9080..7eca448d109 100644 --- a/reflex/components/datadisplay/dataeditor.py +++ b/packages/reflex-components/src/reflex_components/datadisplay/dataeditor.py @@ -502,7 +502,7 @@ def create(cls, *children, **props) -> Component: Raises: ValueError: invalid input. """ - from reflex.components.el import Div + from reflex_components.el import Div columns = props.get("columns", []) data = props.get("data", []) @@ -564,7 +564,7 @@ def _get_app_wrap_components() -> dict[tuple[int, str], Component]: Returns: The app wrap components. """ - from reflex.components.el import Div + from reflex_components.el import Div class Portal(Div): def get_ref(self): diff --git a/reflex/components/datadisplay/logo.py b/packages/reflex-components/src/reflex_components/datadisplay/logo.py similarity index 100% rename from reflex/components/datadisplay/logo.py rename to packages/reflex-components/src/reflex_components/datadisplay/logo.py diff --git a/reflex/components/datadisplay/shiki_code_block.py b/packages/reflex-components/src/reflex_components/datadisplay/shiki_code_block.py similarity index 98% rename from reflex/components/datadisplay/shiki_code_block.py rename to packages/reflex-components/src/reflex_components/datadisplay/shiki_code_block.py index 30984edcfd0..a8410228360 100644 --- a/reflex/components/datadisplay/shiki_code_block.py +++ b/packages/reflex-components/src/reflex_components/datadisplay/shiki_code_block.py @@ -9,13 +9,13 @@ from typing import Any, Literal from reflex.components.component import Component, ComponentNamespace, field -from reflex.components.core.colors import color -from reflex.components.core.cond import color_mode_cond -from reflex.components.el.elements.forms import Button -from reflex.components.lucide.icon import Icon -from reflex.components.markdown.markdown import MarkdownComponentMap +from reflex_components.core.colors import color +from reflex_components.core.cond import color_mode_cond +from reflex_components.el.elements.forms import Button +from reflex_components.lucide.icon import Icon +from reflex_components.markdown.markdown import MarkdownComponentMap from reflex.components.props import NoExtrasAllowedProps -from reflex.components.radix.themes.layout.box import Box +from reflex_components.radix.themes.layout.box import Box from reflex.event import run_script, set_clipboard from reflex.style import Style from reflex.utils.exceptions import VarTypeError diff --git a/reflex/components/el/__init__.py b/packages/reflex-components/src/reflex_components/el/__init__.py similarity index 92% rename from reflex/components/el/__init__.py rename to packages/reflex-components/src/reflex_components/el/__init__.py index 88acb2a313b..7db37e751a5 100644 --- a/reflex/components/el/__init__.py +++ b/packages/reflex-components/src/reflex_components/el/__init__.py @@ -13,7 +13,7 @@ for k, attrs in elements._MAPPING.items() } _EXTRA_MAPPINGS: dict[str, str] = { - "a": "reflex.components.react_router.link", + "a": "reflex_components.react_router.link", } __getattr__, __dir__, __all__ = lazy_loader.attach( diff --git a/reflex/components/el/element.py b/packages/reflex-components/src/reflex_components/el/element.py similarity index 100% rename from reflex/components/el/element.py rename to packages/reflex-components/src/reflex_components/el/element.py diff --git a/reflex/components/el/elements/__init__.py b/packages/reflex-components/src/reflex_components/el/elements/__init__.py similarity index 100% rename from reflex/components/el/elements/__init__.py rename to packages/reflex-components/src/reflex_components/el/elements/__init__.py diff --git a/reflex/components/el/elements/base.py b/packages/reflex-components/src/reflex_components/el/elements/base.py similarity index 98% rename from reflex/components/el/elements/base.py rename to packages/reflex-components/src/reflex_components/el/elements/base.py index 70e9cff1beb..0f431bae41d 100644 --- a/reflex/components/el/elements/base.py +++ b/packages/reflex-components/src/reflex_components/el/elements/base.py @@ -3,7 +3,7 @@ from typing import Literal from reflex.components.component import field -from reflex.components.el.element import Element +from reflex_components.el.element import Element from reflex.vars.base import Var AutoCapitalize = Literal["off", "none", "on", "sentences", "words", "characters"] diff --git a/reflex/components/el/elements/forms.py b/packages/reflex-components/src/reflex_components/el/elements/forms.py similarity index 99% rename from reflex/components/el/elements/forms.py rename to packages/reflex-components/src/reflex_components/el/elements/forms.py index ce9b7de03cf..e0765c1d832 100644 --- a/reflex/components/el/elements/forms.py +++ b/packages/reflex-components/src/reflex_components/el/elements/forms.py @@ -7,7 +7,7 @@ from typing import Any, ClassVar, Literal from reflex.components.component import field -from reflex.components.el.element import Element +from reflex_components.el.element import Element from reflex.components.tags.tag import Tag from reflex.constants import Dirs, EventTriggers from reflex.event import ( diff --git a/reflex/components/el/elements/inline.py b/packages/reflex-components/src/reflex_components/el/elements/inline.py similarity index 100% rename from reflex/components/el/elements/inline.py rename to packages/reflex-components/src/reflex_components/el/elements/inline.py diff --git a/reflex/components/el/elements/media.py b/packages/reflex-components/src/reflex_components/el/elements/media.py similarity index 99% rename from reflex/components/el/elements/media.py rename to packages/reflex-components/src/reflex_components/el/elements/media.py index a53076e124c..335c21a7fac 100644 --- a/reflex/components/el/elements/media.py +++ b/packages/reflex-components/src/reflex_components/el/elements/media.py @@ -4,7 +4,7 @@ from reflex import Component, ComponentNamespace from reflex.components.component import field -from reflex.components.el.elements.inline import ReferrerPolicy +from reflex_components.el.elements.inline import ReferrerPolicy from reflex.constants.colors import Color from reflex.vars.base import Var diff --git a/reflex/components/el/elements/metadata.py b/packages/reflex-components/src/reflex_components/el/elements/metadata.py similarity index 93% rename from reflex/components/el/elements/metadata.py rename to packages/reflex-components/src/reflex_components/el/elements/metadata.py index 1b7c2089f1d..bd3a490e7e4 100644 --- a/reflex/components/el/elements/metadata.py +++ b/packages/reflex-components/src/reflex_components/el/elements/metadata.py @@ -1,9 +1,9 @@ """Metadata classes.""" from reflex.components.component import field -from reflex.components.el.element import Element -from reflex.components.el.elements.inline import ReferrerPolicy -from reflex.components.el.elements.media import CrossOrigin +from reflex_components.el.element import Element +from reflex_components.el.elements.inline import ReferrerPolicy +from reflex_components.el.elements.media import CrossOrigin from reflex.vars.base import Var from .base import BaseHTML diff --git a/reflex/components/el/elements/other.py b/packages/reflex-components/src/reflex_components/el/elements/other.py similarity index 100% rename from reflex/components/el/elements/other.py rename to packages/reflex-components/src/reflex_components/el/elements/other.py diff --git a/reflex/components/el/elements/scripts.py b/packages/reflex-components/src/reflex_components/el/elements/scripts.py similarity index 91% rename from reflex/components/el/elements/scripts.py rename to packages/reflex-components/src/reflex_components/el/elements/scripts.py index 2435a749c98..4527282117f 100644 --- a/reflex/components/el/elements/scripts.py +++ b/packages/reflex-components/src/reflex_components/el/elements/scripts.py @@ -1,8 +1,8 @@ """Scripts classes.""" from reflex.components.component import field -from reflex.components.el.elements.inline import ReferrerPolicy -from reflex.components.el.elements.media import CrossOrigin +from reflex_components.el.elements.inline import ReferrerPolicy +from reflex_components.el.elements.media import CrossOrigin from reflex.vars.base import Var from .base import BaseHTML diff --git a/reflex/components/el/elements/sectioning.py b/packages/reflex-components/src/reflex_components/el/elements/sectioning.py similarity index 100% rename from reflex/components/el/elements/sectioning.py rename to packages/reflex-components/src/reflex_components/el/elements/sectioning.py diff --git a/reflex/components/el/elements/tables.py b/packages/reflex-components/src/reflex_components/el/elements/tables.py similarity index 100% rename from reflex/components/el/elements/tables.py rename to packages/reflex-components/src/reflex_components/el/elements/tables.py diff --git a/reflex/components/el/elements/typography.py b/packages/reflex-components/src/reflex_components/el/elements/typography.py similarity index 100% rename from reflex/components/el/elements/typography.py rename to packages/reflex-components/src/reflex_components/el/elements/typography.py diff --git a/reflex/components/gridjs/__init__.py b/packages/reflex-components/src/reflex_components/gridjs/__init__.py similarity index 100% rename from reflex/components/gridjs/__init__.py rename to packages/reflex-components/src/reflex_components/gridjs/__init__.py diff --git a/reflex/components/gridjs/datatable.py b/packages/reflex-components/src/reflex_components/gridjs/datatable.py similarity index 100% rename from reflex/components/gridjs/datatable.py rename to packages/reflex-components/src/reflex_components/gridjs/datatable.py diff --git a/reflex/components/lucide/__init__.py b/packages/reflex-components/src/reflex_components/lucide/__init__.py similarity index 100% rename from reflex/components/lucide/__init__.py rename to packages/reflex-components/src/reflex_components/lucide/__init__.py diff --git a/reflex/components/lucide/icon.py b/packages/reflex-components/src/reflex_components/lucide/icon.py similarity index 100% rename from reflex/components/lucide/icon.py rename to packages/reflex-components/src/reflex_components/lucide/icon.py diff --git a/reflex/components/markdown/__init__.py b/packages/reflex-components/src/reflex_components/markdown/__init__.py similarity index 100% rename from reflex/components/markdown/__init__.py rename to packages/reflex-components/src/reflex_components/markdown/__init__.py diff --git a/reflex/components/markdown/markdown.py b/packages/reflex-components/src/reflex_components/markdown/markdown.py similarity index 95% rename from reflex/components/markdown/markdown.py rename to packages/reflex-components/src/reflex_components/markdown/markdown.py index 8da316bb621..4a460a09550 100644 --- a/reflex/components/markdown/markdown.py +++ b/packages/reflex-components/src/reflex_components/markdown/markdown.py @@ -17,7 +17,7 @@ CustomComponent, field, ) -from reflex.components.el.elements.typography import Div +from reflex_components.el.elements.typography import Div from reflex.components.tags.tag import Tag from reflex.utils import console from reflex.utils.imports import ImportDict, ImportTypes, ImportVar @@ -88,79 +88,79 @@ def create( def _h1(value: object): - from reflex.components.radix.themes.typography.heading import Heading + from reflex_components.radix.themes.typography.heading import Heading return Heading.create(value, as_="h1", size="6", margin_y="0.5em") def _h2(value: object): - from reflex.components.radix.themes.typography.heading import Heading + from reflex_components.radix.themes.typography.heading import Heading return Heading.create(value, as_="h2", size="5", margin_y="0.5em") def _h3(value: object): - from reflex.components.radix.themes.typography.heading import Heading + from reflex_components.radix.themes.typography.heading import Heading return Heading.create(value, as_="h3", size="4", margin_y="0.5em") def _h4(value: object): - from reflex.components.radix.themes.typography.heading import Heading + from reflex_components.radix.themes.typography.heading import Heading return Heading.create(value, as_="h4", size="3", margin_y="0.5em") def _h5(value: object): - from reflex.components.radix.themes.typography.heading import Heading + from reflex_components.radix.themes.typography.heading import Heading return Heading.create(value, as_="h5", size="2", margin_y="0.5em") def _h6(value: object): - from reflex.components.radix.themes.typography.heading import Heading + from reflex_components.radix.themes.typography.heading import Heading return Heading.create(value, as_="h6", size="1", margin_y="0.5em") def _p(value: object): - from reflex.components.radix.themes.typography.text import Text + from reflex_components.radix.themes.typography.text import Text return Text.create(value, margin_y="1em") def _ul(value: object): - from reflex.components.radix.themes.layout.list import UnorderedList + from reflex_components.radix.themes.layout.list import UnorderedList return UnorderedList.create(value, margin_y="1em") def _ol(value: object): - from reflex.components.radix.themes.layout.list import OrderedList + from reflex_components.radix.themes.layout.list import OrderedList return OrderedList.create(value, margin_y="1em") def _li(value: object): - from reflex.components.radix.themes.layout.list import ListItem + from reflex_components.radix.themes.layout.list import ListItem return ListItem.create(value, margin_y="0.5em") def _a(value: object): - from reflex.components.radix.themes.typography.link import Link + from reflex_components.radix.themes.typography.link import Link return Link.create(value) def _code(value: object): - from reflex.components.radix.themes.typography.code import Code + from reflex_components.radix.themes.typography.code import Code return Code.create(value) def _codeblock(value: object, **props): - from reflex.components.datadisplay.code import CodeBlock + from reflex_components.datadisplay.code import CodeBlock return CodeBlock.create(value, margin_y="1em", wrap_long_lines=True, **props) diff --git a/reflex/components/moment/__init__.py b/packages/reflex-components/src/reflex_components/moment/__init__.py similarity index 100% rename from reflex/components/moment/__init__.py rename to packages/reflex-components/src/reflex_components/moment/__init__.py diff --git a/reflex/components/moment/moment.py b/packages/reflex-components/src/reflex_components/moment/moment.py similarity index 100% rename from reflex/components/moment/moment.py rename to packages/reflex-components/src/reflex_components/moment/moment.py diff --git a/reflex/components/plotly/__init__.py b/packages/reflex-components/src/reflex_components/plotly/__init__.py similarity index 100% rename from reflex/components/plotly/__init__.py rename to packages/reflex-components/src/reflex_components/plotly/__init__.py diff --git a/reflex/components/plotly/plotly.py b/packages/reflex-components/src/reflex_components/plotly/plotly.py similarity index 99% rename from reflex/components/plotly/plotly.py rename to packages/reflex-components/src/reflex_components/plotly/plotly.py index 3f66a1eeffc..03d2fcd5510 100644 --- a/reflex/components/plotly/plotly.py +++ b/packages/reflex-components/src/reflex_components/plotly/plotly.py @@ -5,7 +5,7 @@ from typing import TYPE_CHECKING, Any, TypedDict, TypeVar from reflex.components.component import Component, NoSSRComponent, field -from reflex.components.core.cond import color_mode_cond +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 diff --git a/reflex/components/radix/__init__.py b/packages/reflex-components/src/reflex_components/radix/__init__.py similarity index 100% rename from reflex/components/radix/__init__.py rename to packages/reflex-components/src/reflex_components/radix/__init__.py diff --git a/reflex/components/radix/primitives/__init__.py b/packages/reflex-components/src/reflex_components/radix/primitives/__init__.py similarity index 100% rename from reflex/components/radix/primitives/__init__.py rename to packages/reflex-components/src/reflex_components/radix/primitives/__init__.py diff --git a/reflex/components/radix/primitives/accordion.py b/packages/reflex-components/src/reflex_components/radix/primitives/accordion.py similarity index 98% rename from reflex/components/radix/primitives/accordion.py rename to packages/reflex-components/src/reflex_components/radix/primitives/accordion.py index 0579dffee7e..ec0a095c99b 100644 --- a/reflex/components/radix/primitives/accordion.py +++ b/packages/reflex-components/src/reflex_components/radix/primitives/accordion.py @@ -6,11 +6,11 @@ from typing import Any, ClassVar, Literal from reflex.components.component import Component, ComponentNamespace, field -from reflex.components.core.colors import color -from reflex.components.core.cond import cond -from reflex.components.lucide.icon import Icon -from reflex.components.radix.primitives.base import RadixPrimitiveComponent -from reflex.components.radix.themes.base import LiteralAccentColor, LiteralRadius +from reflex_components.core.colors import color +from reflex_components.core.cond import cond +from reflex_components.lucide.icon import Icon +from reflex_components.radix.primitives.base import RadixPrimitiveComponent +from reflex_components.radix.themes.base import LiteralAccentColor, LiteralRadius from reflex.constants.compiler import MemoizationMode from reflex.event import EventHandler from reflex.style import Style diff --git a/reflex/components/radix/primitives/base.py b/packages/reflex-components/src/reflex_components/radix/primitives/base.py similarity index 96% rename from reflex/components/radix/primitives/base.py rename to packages/reflex-components/src/reflex_components/radix/primitives/base.py index 21a6815e557..48fc2bb394a 100644 --- a/reflex/components/radix/primitives/base.py +++ b/packages/reflex-components/src/reflex_components/radix/primitives/base.py @@ -49,7 +49,7 @@ def create(cls, *children: Any, **props: Any) -> Component: Returns: The new RadixPrimitiveTriggerComponent instance. """ - from reflex.components.el.elements.typography import Div + from reflex_components.el.elements.typography import Div for child in children: if "on_click" in getattr(child, "event_triggers", {}): diff --git a/reflex/components/radix/primitives/dialog.py b/packages/reflex-components/src/reflex_components/radix/primitives/dialog.py similarity index 99% rename from reflex/components/radix/primitives/dialog.py rename to packages/reflex-components/src/reflex_components/radix/primitives/dialog.py index c559346ea0a..716a2742c42 100644 --- a/reflex/components/radix/primitives/dialog.py +++ b/packages/reflex-components/src/reflex_components/radix/primitives/dialog.py @@ -3,7 +3,7 @@ from typing import Any, ClassVar from reflex.components.component import ComponentNamespace, field -from reflex.components.el import elements +from reflex_components.el import elements from reflex.constants.compiler import MemoizationMode from reflex.event import EventHandler, no_args_event_spec, passthrough_event_spec from reflex.vars.base import Var diff --git a/reflex/components/radix/primitives/drawer.py b/packages/reflex-components/src/reflex_components/radix/primitives/drawer.py similarity index 98% rename from reflex/components/radix/primitives/drawer.py rename to packages/reflex-components/src/reflex_components/radix/primitives/drawer.py index 35a7950ab53..1a98ad158e6 100644 --- a/reflex/components/radix/primitives/drawer.py +++ b/packages/reflex-components/src/reflex_components/radix/primitives/drawer.py @@ -8,9 +8,9 @@ from typing import Any, Literal from reflex.components.component import Component, ComponentNamespace, field -from reflex.components.radix.primitives.base import RadixPrimitiveComponent -from reflex.components.radix.themes.base import Theme -from reflex.components.radix.themes.layout.flex import Flex +from reflex_components.radix.primitives.base import RadixPrimitiveComponent +from reflex_components.radix.themes.base import Theme +from reflex_components.radix.themes.layout.flex import Flex from reflex.constants.compiler import MemoizationMode from reflex.event import EventHandler, no_args_event_spec, passthrough_event_spec from reflex.vars.base import Var diff --git a/reflex/components/radix/primitives/form.py b/packages/reflex-components/src/reflex_components/radix/primitives/form.py similarity index 96% rename from reflex/components/radix/primitives/form.py rename to packages/reflex-components/src/reflex_components/radix/primitives/form.py index 3ef2bb8dc30..5bc60419f74 100644 --- a/reflex/components/radix/primitives/form.py +++ b/packages/reflex-components/src/reflex_components/radix/primitives/form.py @@ -5,9 +5,9 @@ from typing import Any, Literal from reflex.components.component import ComponentNamespace, field -from reflex.components.core.debounce import DebounceInput -from reflex.components.el.elements.forms import Form as HTMLForm -from reflex.components.radix.themes.components.text_field import TextFieldRoot +from reflex_components.core.debounce import DebounceInput +from reflex_components.el.elements.forms import Form as HTMLForm +from reflex_components.radix.themes.components.text_field import TextFieldRoot from reflex.event import EventHandler, no_args_event_spec from reflex.vars.base import Var diff --git a/reflex/components/radix/primitives/progress.py b/packages/reflex-components/src/reflex_components/radix/primitives/progress.py similarity index 94% rename from reflex/components/radix/primitives/progress.py rename to packages/reflex-components/src/reflex_components/radix/primitives/progress.py index 6ad5d367bfb..fc86e5622c7 100644 --- a/reflex/components/radix/primitives/progress.py +++ b/packages/reflex-components/src/reflex_components/radix/primitives/progress.py @@ -5,10 +5,10 @@ from typing import Any from reflex.components.component import Component, ComponentNamespace, field -from reflex.components.core.colors import color -from reflex.components.radix.primitives.accordion import DEFAULT_ANIMATION_DURATION -from reflex.components.radix.primitives.base import RadixPrimitiveComponentWithClassName -from reflex.components.radix.themes.base import LiteralAccentColor, LiteralRadius +from reflex_components.core.colors import color +from reflex_components.radix.primitives.accordion import DEFAULT_ANIMATION_DURATION +from reflex_components.radix.primitives.base import RadixPrimitiveComponentWithClassName +from reflex_components.radix.themes.base import LiteralAccentColor, LiteralRadius from reflex.vars.base import Var diff --git a/reflex/components/radix/primitives/slider.py b/packages/reflex-components/src/reflex_components/radix/primitives/slider.py similarity index 98% rename from reflex/components/radix/primitives/slider.py rename to packages/reflex-components/src/reflex_components/radix/primitives/slider.py index b04b5f6bb74..7bae34540e4 100644 --- a/reflex/components/radix/primitives/slider.py +++ b/packages/reflex-components/src/reflex_components/radix/primitives/slider.py @@ -6,7 +6,7 @@ from typing import Any, Literal from reflex.components.component import Component, ComponentNamespace, field -from reflex.components.radix.primitives.base import RadixPrimitiveComponentWithClassName +from reflex_components.radix.primitives.base import RadixPrimitiveComponentWithClassName from reflex.event import EventHandler, passthrough_event_spec from reflex.vars.base import Var diff --git a/reflex/components/radix/themes/__init__.py b/packages/reflex-components/src/reflex_components/radix/themes/__init__.py similarity index 100% rename from reflex/components/radix/themes/__init__.py rename to packages/reflex-components/src/reflex_components/radix/themes/__init__.py diff --git a/reflex/components/radix/themes/base.py b/packages/reflex-components/src/reflex_components/radix/themes/base.py similarity index 99% rename from reflex/components/radix/themes/base.py rename to packages/reflex-components/src/reflex_components/radix/themes/base.py index 22cbc82c3fa..77d9036d5e8 100644 --- a/reflex/components/radix/themes/base.py +++ b/packages/reflex-components/src/reflex_components/radix/themes/base.py @@ -6,7 +6,7 @@ from reflex.components import Component from reflex.components.component import field -from reflex.components.core.breakpoints import Responsive +from reflex_components.core.breakpoints import Responsive from reflex.components.tags import Tag from reflex.utils.imports import ImportDict, ImportVar from reflex.vars.base import Var diff --git a/reflex/components/radix/themes/color_mode.py b/packages/reflex-components/src/reflex_components/radix/themes/color_mode.py similarity index 96% rename from reflex/components/radix/themes/color_mode.py rename to packages/reflex-components/src/reflex_components/radix/themes/color_mode.py index a444df79c5d..36ef88f61e6 100644 --- a/reflex/components/radix/themes/color_mode.py +++ b/packages/reflex-components/src/reflex_components/radix/themes/color_mode.py @@ -20,10 +20,10 @@ from typing import Any, Literal, get_args from reflex.components.component import BaseComponent, field -from reflex.components.core.cond import Cond, color_mode_cond, cond -from reflex.components.lucide.icon import Icon -from reflex.components.radix.themes.components.dropdown_menu import dropdown_menu -from reflex.components.radix.themes.components.switch import Switch +from reflex_components.core.cond import Cond, color_mode_cond, cond +from reflex_components.lucide.icon import Icon +from reflex_components.radix.themes.components.dropdown_menu import dropdown_menu +from reflex_components.radix.themes.components.switch import Switch from reflex.style import ( LIGHT_COLOR_MODE, color_mode, diff --git a/reflex/components/radix/themes/components/__init__.py b/packages/reflex-components/src/reflex_components/radix/themes/components/__init__.py similarity index 100% rename from reflex/components/radix/themes/components/__init__.py rename to packages/reflex-components/src/reflex_components/radix/themes/components/__init__.py diff --git a/reflex/components/radix/themes/components/alert_dialog.py b/packages/reflex-components/src/reflex_components/radix/themes/components/alert_dialog.py similarity index 95% rename from reflex/components/radix/themes/components/alert_dialog.py rename to packages/reflex-components/src/reflex_components/radix/themes/components/alert_dialog.py index 17c38511b6c..879a5fa4a17 100644 --- a/reflex/components/radix/themes/components/alert_dialog.py +++ b/packages/reflex-components/src/reflex_components/radix/themes/components/alert_dialog.py @@ -3,9 +3,9 @@ from typing import Literal from reflex.components.component import ComponentNamespace, field -from reflex.components.core.breakpoints import Responsive -from reflex.components.el import elements -from reflex.components.radix.themes.base import ( +from reflex_components.core.breakpoints import Responsive +from reflex_components.el import elements +from reflex_components.radix.themes.base import ( RadixThemesComponent, RadixThemesTriggerComponent, ) diff --git a/reflex/components/radix/themes/components/aspect_ratio.py b/packages/reflex-components/src/reflex_components/radix/themes/components/aspect_ratio.py similarity index 86% rename from reflex/components/radix/themes/components/aspect_ratio.py rename to packages/reflex-components/src/reflex_components/radix/themes/components/aspect_ratio.py index d71811b6dd2..6e94a5e5bba 100644 --- a/reflex/components/radix/themes/components/aspect_ratio.py +++ b/packages/reflex-components/src/reflex_components/radix/themes/components/aspect_ratio.py @@ -1,7 +1,7 @@ """Interactive components provided by @radix-ui/themes.""" from reflex.components.component import field -from reflex.components.radix.themes.base import RadixThemesComponent +from reflex_components.radix.themes.base import RadixThemesComponent from reflex.vars.base import Var diff --git a/reflex/components/radix/themes/components/avatar.py b/packages/reflex-components/src/reflex_components/radix/themes/components/avatar.py similarity index 91% rename from reflex/components/radix/themes/components/avatar.py rename to packages/reflex-components/src/reflex_components/radix/themes/components/avatar.py index 2f22e7498e0..ad4b1033df8 100644 --- a/reflex/components/radix/themes/components/avatar.py +++ b/packages/reflex-components/src/reflex_components/radix/themes/components/avatar.py @@ -3,8 +3,8 @@ from typing import Literal from reflex.components.component import field -from reflex.components.core.breakpoints import Responsive -from reflex.components.radix.themes.base import ( +from reflex_components.core.breakpoints import Responsive +from reflex_components.radix.themes.base import ( LiteralAccentColor, LiteralRadius, RadixThemesComponent, diff --git a/reflex/components/radix/themes/components/badge.py b/packages/reflex-components/src/reflex_components/radix/themes/components/badge.py similarity index 86% rename from reflex/components/radix/themes/components/badge.py rename to packages/reflex-components/src/reflex_components/radix/themes/components/badge.py index a9f51dba97b..f1efb6157f9 100644 --- a/reflex/components/radix/themes/components/badge.py +++ b/packages/reflex-components/src/reflex_components/radix/themes/components/badge.py @@ -3,9 +3,9 @@ from typing import Literal from reflex.components.component import field -from reflex.components.core.breakpoints import Responsive -from reflex.components.el import elements -from reflex.components.radix.themes.base import ( +from reflex_components.core.breakpoints import Responsive +from reflex_components.el import elements +from reflex_components.radix.themes.base import ( LiteralAccentColor, LiteralRadius, RadixThemesComponent, diff --git a/reflex/components/radix/themes/components/button.py b/packages/reflex-components/src/reflex_components/radix/themes/components/button.py similarity index 89% rename from reflex/components/radix/themes/components/button.py rename to packages/reflex-components/src/reflex_components/radix/themes/components/button.py index 6caa4362514..10d713e70d3 100644 --- a/reflex/components/radix/themes/components/button.py +++ b/packages/reflex-components/src/reflex_components/radix/themes/components/button.py @@ -3,9 +3,9 @@ from typing import Literal from reflex.components.component import field -from reflex.components.core.breakpoints import Responsive -from reflex.components.el import elements -from reflex.components.radix.themes.base import ( +from reflex_components.core.breakpoints import Responsive +from reflex_components.el import elements +from reflex_components.radix.themes.base import ( LiteralAccentColor, LiteralRadius, LiteralVariant, diff --git a/reflex/components/radix/themes/components/callout.py b/packages/reflex-components/src/reflex_components/radix/themes/components/callout.py similarity index 91% rename from reflex/components/radix/themes/components/callout.py rename to packages/reflex-components/src/reflex_components/radix/themes/components/callout.py index b51f40e6bdc..b86cf86f1ac 100644 --- a/reflex/components/radix/themes/components/callout.py +++ b/packages/reflex-components/src/reflex_components/radix/themes/components/callout.py @@ -4,10 +4,10 @@ import reflex as rx from reflex.components.component import Component, ComponentNamespace, field -from reflex.components.core.breakpoints import Responsive -from reflex.components.el import elements -from reflex.components.lucide.icon import Icon -from reflex.components.radix.themes.base import LiteralAccentColor, RadixThemesComponent +from reflex_components.core.breakpoints import Responsive +from reflex_components.el import elements +from reflex_components.lucide.icon import Icon +from reflex_components.radix.themes.base import LiteralAccentColor, RadixThemesComponent from reflex.vars.base import Var CalloutVariant = Literal["soft", "surface", "outline"] diff --git a/reflex/components/radix/themes/components/card.py b/packages/reflex-components/src/reflex_components/radix/themes/components/card.py similarity index 81% rename from reflex/components/radix/themes/components/card.py rename to packages/reflex-components/src/reflex_components/radix/themes/components/card.py index 10234dbeb67..f588924ba5d 100644 --- a/reflex/components/radix/themes/components/card.py +++ b/packages/reflex-components/src/reflex_components/radix/themes/components/card.py @@ -3,9 +3,9 @@ from typing import Literal from reflex.components.component import field -from reflex.components.core.breakpoints import Responsive -from reflex.components.el import elements -from reflex.components.radix.themes.base import RadixThemesComponent +from reflex_components.core.breakpoints import Responsive +from reflex_components.el import elements +from reflex_components.radix.themes.base import RadixThemesComponent from reflex.vars.base import Var diff --git a/reflex/components/radix/themes/components/checkbox.py b/packages/reflex-components/src/reflex_components/radix/themes/components/checkbox.py similarity index 95% rename from reflex/components/radix/themes/components/checkbox.py rename to packages/reflex-components/src/reflex_components/radix/themes/components/checkbox.py index d9b4dee7d28..09d2a03b9b9 100644 --- a/reflex/components/radix/themes/components/checkbox.py +++ b/packages/reflex-components/src/reflex_components/radix/themes/components/checkbox.py @@ -3,14 +3,14 @@ from typing import Literal from reflex.components.component import Component, ComponentNamespace, field -from reflex.components.core.breakpoints import Responsive -from reflex.components.radix.themes.base import ( +from reflex_components.core.breakpoints import Responsive +from reflex_components.radix.themes.base import ( LiteralAccentColor, LiteralSpacing, RadixThemesComponent, ) -from reflex.components.radix.themes.layout.flex import Flex -from reflex.components.radix.themes.typography.text import Text +from reflex_components.radix.themes.layout.flex import Flex +from reflex_components.radix.themes.typography.text import Text from reflex.event import EventHandler, passthrough_event_spec from reflex.vars.base import Var diff --git a/reflex/components/radix/themes/components/checkbox_cards.py b/packages/reflex-components/src/reflex_components/radix/themes/components/checkbox_cards.py similarity index 92% rename from reflex/components/radix/themes/components/checkbox_cards.py rename to packages/reflex-components/src/reflex_components/radix/themes/components/checkbox_cards.py index e8bf228bb72..3dd06314739 100644 --- a/reflex/components/radix/themes/components/checkbox_cards.py +++ b/packages/reflex-components/src/reflex_components/radix/themes/components/checkbox_cards.py @@ -4,8 +4,8 @@ from typing import Literal from reflex.components.component import field -from reflex.components.core.breakpoints import Responsive -from reflex.components.radix.themes.base import LiteralAccentColor, RadixThemesComponent +from reflex_components.core.breakpoints import Responsive +from reflex_components.radix.themes.base import LiteralAccentColor, RadixThemesComponent from reflex.vars.base import Var diff --git a/reflex/components/radix/themes/components/checkbox_group.py b/packages/reflex-components/src/reflex_components/radix/themes/components/checkbox_group.py similarity index 93% rename from reflex/components/radix/themes/components/checkbox_group.py rename to packages/reflex-components/src/reflex_components/radix/themes/components/checkbox_group.py index e3146157e71..723c84a4489 100644 --- a/reflex/components/radix/themes/components/checkbox_group.py +++ b/packages/reflex-components/src/reflex_components/radix/themes/components/checkbox_group.py @@ -5,8 +5,8 @@ from typing import Literal from reflex.components.component import field -from reflex.components.core.breakpoints import Responsive -from reflex.components.radix.themes.base import LiteralAccentColor, RadixThemesComponent +from reflex_components.core.breakpoints import Responsive +from reflex_components.radix.themes.base import LiteralAccentColor, RadixThemesComponent from reflex.vars.base import Var diff --git a/reflex/components/radix/themes/components/context_menu.py b/packages/reflex-components/src/reflex_components/radix/themes/components/context_menu.py similarity index 99% rename from reflex/components/radix/themes/components/context_menu.py rename to packages/reflex-components/src/reflex_components/radix/themes/components/context_menu.py index 0d53b4d173d..140a60ececa 100644 --- a/reflex/components/radix/themes/components/context_menu.py +++ b/packages/reflex-components/src/reflex_components/radix/themes/components/context_menu.py @@ -3,8 +3,8 @@ from typing import ClassVar, Literal from reflex.components.component import ComponentNamespace, field -from reflex.components.core.breakpoints import Responsive -from reflex.components.radix.themes.base import LiteralAccentColor, RadixThemesComponent +from reflex_components.core.breakpoints import Responsive +from reflex_components.radix.themes.base import LiteralAccentColor, RadixThemesComponent from reflex.constants.compiler import MemoizationMode from reflex.event import EventHandler, no_args_event_spec, passthrough_event_spec from reflex.vars.base import Var diff --git a/reflex/components/radix/themes/components/data_list.py b/packages/reflex-components/src/reflex_components/radix/themes/components/data_list.py similarity index 94% rename from reflex/components/radix/themes/components/data_list.py rename to packages/reflex-components/src/reflex_components/radix/themes/components/data_list.py index b8217eda3e1..d235abf552e 100644 --- a/reflex/components/radix/themes/components/data_list.py +++ b/packages/reflex-components/src/reflex_components/radix/themes/components/data_list.py @@ -4,8 +4,8 @@ from typing import Literal from reflex.components.component import field -from reflex.components.core.breakpoints import Responsive -from reflex.components.radix.themes.base import LiteralAccentColor, RadixThemesComponent +from reflex_components.core.breakpoints import Responsive +from reflex_components.radix.themes.base import LiteralAccentColor, RadixThemesComponent from reflex.vars.base import Var diff --git a/reflex/components/radix/themes/components/dialog.py b/packages/reflex-components/src/reflex_components/radix/themes/components/dialog.py similarity index 94% rename from reflex/components/radix/themes/components/dialog.py rename to packages/reflex-components/src/reflex_components/radix/themes/components/dialog.py index dfbd3cae21f..3c90dbd5179 100644 --- a/reflex/components/radix/themes/components/dialog.py +++ b/packages/reflex-components/src/reflex_components/radix/themes/components/dialog.py @@ -3,9 +3,9 @@ from typing import Literal from reflex.components.component import ComponentNamespace, field -from reflex.components.core.breakpoints import Responsive -from reflex.components.el import elements -from reflex.components.radix.themes.base import ( +from reflex_components.core.breakpoints import Responsive +from reflex_components.el import elements +from reflex_components.radix.themes.base import ( RadixThemesComponent, RadixThemesTriggerComponent, ) diff --git a/reflex/components/radix/themes/components/dropdown_menu.py b/packages/reflex-components/src/reflex_components/radix/themes/components/dropdown_menu.py similarity index 99% rename from reflex/components/radix/themes/components/dropdown_menu.py rename to packages/reflex-components/src/reflex_components/radix/themes/components/dropdown_menu.py index dd074e8539f..b0ef82321a4 100644 --- a/reflex/components/radix/themes/components/dropdown_menu.py +++ b/packages/reflex-components/src/reflex_components/radix/themes/components/dropdown_menu.py @@ -3,8 +3,8 @@ from typing import ClassVar, Literal from reflex.components.component import ComponentNamespace, field -from reflex.components.core.breakpoints import Responsive -from reflex.components.radix.themes.base import ( +from reflex_components.core.breakpoints import Responsive +from reflex_components.radix.themes.base import ( LiteralAccentColor, RadixThemesComponent, RadixThemesTriggerComponent, diff --git a/reflex/components/radix/themes/components/hover_card.py b/packages/reflex-components/src/reflex_components/radix/themes/components/hover_card.py similarity index 95% rename from reflex/components/radix/themes/components/hover_card.py rename to packages/reflex-components/src/reflex_components/radix/themes/components/hover_card.py index 5a779c19b53..f42ac9dcc46 100644 --- a/reflex/components/radix/themes/components/hover_card.py +++ b/packages/reflex-components/src/reflex_components/radix/themes/components/hover_card.py @@ -3,9 +3,9 @@ from typing import Literal from reflex.components.component import ComponentNamespace, field -from reflex.components.core.breakpoints import Responsive -from reflex.components.el import elements -from reflex.components.radix.themes.base import ( +from reflex_components.core.breakpoints import Responsive +from reflex_components.el import elements +from reflex_components.radix.themes.base import ( RadixThemesComponent, RadixThemesTriggerComponent, ) diff --git a/reflex/components/radix/themes/components/icon_button.py b/packages/reflex-components/src/reflex_components/radix/themes/components/icon_button.py similarity index 92% rename from reflex/components/radix/themes/components/icon_button.py rename to packages/reflex-components/src/reflex_components/radix/themes/components/icon_button.py index 002d0740312..0abf8aa331a 100644 --- a/reflex/components/radix/themes/components/icon_button.py +++ b/packages/reflex-components/src/reflex_components/radix/themes/components/icon_button.py @@ -5,11 +5,11 @@ from typing import Literal from reflex.components.component import Component, field -from reflex.components.core.breakpoints import Responsive -from reflex.components.core.match import Match -from reflex.components.el import elements -from reflex.components.lucide import Icon -from reflex.components.radix.themes.base import ( +from reflex_components.core.breakpoints import Responsive +from reflex_components.core.match import Match +from reflex_components.el import elements +from reflex_components.lucide import Icon +from reflex_components.radix.themes.base import ( LiteralAccentColor, LiteralRadius, LiteralVariant, diff --git a/reflex/components/radix/themes/components/inset.py b/packages/reflex-components/src/reflex_components/radix/themes/components/inset.py similarity index 87% rename from reflex/components/radix/themes/components/inset.py rename to packages/reflex-components/src/reflex_components/radix/themes/components/inset.py index 8d4a45a79e4..ca77854cee5 100644 --- a/reflex/components/radix/themes/components/inset.py +++ b/packages/reflex-components/src/reflex_components/radix/themes/components/inset.py @@ -3,9 +3,9 @@ from typing import Literal from reflex.components.component import field -from reflex.components.core.breakpoints import Responsive -from reflex.components.el import elements -from reflex.components.radix.themes.base import RadixThemesComponent +from reflex_components.core.breakpoints import Responsive +from reflex_components.el import elements +from reflex_components.radix.themes.base import RadixThemesComponent from reflex.vars.base import Var LiteralButtonSize = Literal["1", "2", "3", "4"] diff --git a/reflex/components/radix/themes/components/popover.py b/packages/reflex-components/src/reflex_components/radix/themes/components/popover.py similarity index 96% rename from reflex/components/radix/themes/components/popover.py rename to packages/reflex-components/src/reflex_components/radix/themes/components/popover.py index 9c9f248b7cd..c5eb06f72b0 100644 --- a/reflex/components/radix/themes/components/popover.py +++ b/packages/reflex-components/src/reflex_components/radix/themes/components/popover.py @@ -3,9 +3,9 @@ from typing import Literal from reflex.components.component import ComponentNamespace, field -from reflex.components.core.breakpoints import Responsive -from reflex.components.el import elements -from reflex.components.radix.themes.base import ( +from reflex_components.core.breakpoints import Responsive +from reflex_components.el import elements +from reflex_components.radix.themes.base import ( RadixThemesComponent, RadixThemesTriggerComponent, ) diff --git a/reflex/components/radix/themes/components/progress.py b/packages/reflex-components/src/reflex_components/radix/themes/components/progress.py similarity index 95% rename from reflex/components/radix/themes/components/progress.py rename to packages/reflex-components/src/reflex_components/radix/themes/components/progress.py index 97b0701325a..a3b9b23b8b3 100644 --- a/reflex/components/radix/themes/components/progress.py +++ b/packages/reflex-components/src/reflex_components/radix/themes/components/progress.py @@ -3,8 +3,8 @@ from typing import Literal from reflex.components.component import Component, field -from reflex.components.core.breakpoints import Responsive -from reflex.components.radix.themes.base import LiteralAccentColor, RadixThemesComponent +from reflex_components.core.breakpoints import Responsive +from reflex_components.radix.themes.base import LiteralAccentColor, RadixThemesComponent from reflex.style import Style from reflex.vars.base import Var diff --git a/reflex/components/radix/themes/components/radio.py b/packages/reflex-components/src/reflex_components/radix/themes/components/radio.py similarity index 87% rename from reflex/components/radix/themes/components/radio.py rename to packages/reflex-components/src/reflex_components/radix/themes/components/radio.py index 87493f758d6..3e9556736e9 100644 --- a/reflex/components/radix/themes/components/radio.py +++ b/packages/reflex-components/src/reflex_components/radix/themes/components/radio.py @@ -3,8 +3,8 @@ from typing import Literal from reflex.components.component import field -from reflex.components.core.breakpoints import Responsive -from reflex.components.radix.themes.base import LiteralAccentColor, RadixThemesComponent +from reflex_components.core.breakpoints import Responsive +from reflex_components.radix.themes.base import LiteralAccentColor, RadixThemesComponent from reflex.vars.base import Var diff --git a/reflex/components/radix/themes/components/radio_cards.py b/packages/reflex-components/src/reflex_components/radix/themes/components/radio_cards.py similarity index 96% rename from reflex/components/radix/themes/components/radio_cards.py rename to packages/reflex-components/src/reflex_components/radix/themes/components/radio_cards.py index 674446b5dff..404af9e1355 100644 --- a/reflex/components/radix/themes/components/radio_cards.py +++ b/packages/reflex-components/src/reflex_components/radix/themes/components/radio_cards.py @@ -4,8 +4,8 @@ from typing import ClassVar, Literal from reflex.components.component import field -from reflex.components.core.breakpoints import Responsive -from reflex.components.radix.themes.base import LiteralAccentColor, RadixThemesComponent +from reflex_components.core.breakpoints import Responsive +from reflex_components.radix.themes.base import LiteralAccentColor, RadixThemesComponent from reflex.event import EventHandler, passthrough_event_spec from reflex.vars.base import Var diff --git a/reflex/components/radix/themes/components/radio_group.py b/packages/reflex-components/src/reflex_components/radix/themes/components/radio_group.py similarity index 96% rename from reflex/components/radix/themes/components/radio_group.py rename to packages/reflex-components/src/reflex_components/radix/themes/components/radio_group.py index 32167edbad3..bd65f565d94 100644 --- a/reflex/components/radix/themes/components/radio_group.py +++ b/packages/reflex-components/src/reflex_components/radix/themes/components/radio_group.py @@ -7,14 +7,14 @@ import reflex as rx from reflex.components.component import Component, ComponentNamespace, field -from reflex.components.core.breakpoints import Responsive -from reflex.components.radix.themes.base import ( +from reflex_components.core.breakpoints import Responsive +from reflex_components.radix.themes.base import ( LiteralAccentColor, LiteralSpacing, RadixThemesComponent, ) -from reflex.components.radix.themes.layout.flex import Flex -from reflex.components.radix.themes.typography.text import Text +from reflex_components.radix.themes.layout.flex import Flex +from reflex_components.radix.themes.typography.text import Text from reflex.event import EventHandler, passthrough_event_spec from reflex.utils import types from reflex.vars.base import LiteralVar, Var diff --git a/reflex/components/radix/themes/components/scroll_area.py b/packages/reflex-components/src/reflex_components/radix/themes/components/scroll_area.py similarity index 93% rename from reflex/components/radix/themes/components/scroll_area.py rename to packages/reflex-components/src/reflex_components/radix/themes/components/scroll_area.py index ca1b6a2d1e0..b80804f4d53 100644 --- a/reflex/components/radix/themes/components/scroll_area.py +++ b/packages/reflex-components/src/reflex_components/radix/themes/components/scroll_area.py @@ -3,7 +3,7 @@ from typing import Literal from reflex.components.component import field -from reflex.components.radix.themes.base import RadixThemesComponent +from reflex_components.radix.themes.base import RadixThemesComponent from reflex.vars.base import Var diff --git a/reflex/components/radix/themes/components/segmented_control.py b/packages/reflex-components/src/reflex_components/radix/themes/components/segmented_control.py similarity index 95% rename from reflex/components/radix/themes/components/segmented_control.py rename to packages/reflex-components/src/reflex_components/radix/themes/components/segmented_control.py index b8cadb1b512..773c6154b0c 100644 --- a/reflex/components/radix/themes/components/segmented_control.py +++ b/packages/reflex-components/src/reflex_components/radix/themes/components/segmented_control.py @@ -7,8 +7,8 @@ from typing import ClassVar, Literal from reflex.components.component import field -from reflex.components.core.breakpoints import Responsive -from reflex.components.radix.themes.base import LiteralAccentColor, RadixThemesComponent +from reflex_components.core.breakpoints import Responsive +from reflex_components.radix.themes.base import LiteralAccentColor, RadixThemesComponent from reflex.event import EventHandler from reflex.vars.base import Var diff --git a/reflex/components/radix/themes/components/select.py b/packages/reflex-components/src/reflex_components/radix/themes/components/select.py similarity index 98% rename from reflex/components/radix/themes/components/select.py rename to packages/reflex-components/src/reflex_components/radix/themes/components/select.py index c4592224e80..9f2dc494266 100644 --- a/reflex/components/radix/themes/components/select.py +++ b/packages/reflex-components/src/reflex_components/radix/themes/components/select.py @@ -5,8 +5,8 @@ import reflex as rx from reflex.components.component import Component, ComponentNamespace, field -from reflex.components.core.breakpoints import Responsive -from reflex.components.radix.themes.base import ( +from reflex_components.core.breakpoints import Responsive +from reflex_components.radix.themes.base import ( LiteralAccentColor, LiteralRadius, RadixThemesComponent, diff --git a/reflex/components/radix/themes/components/separator.py b/packages/reflex-components/src/reflex_components/radix/themes/components/separator.py similarity index 89% rename from reflex/components/radix/themes/components/separator.py rename to packages/reflex-components/src/reflex_components/radix/themes/components/separator.py index fd0d4f9fa2b..a3912d2b349 100644 --- a/reflex/components/radix/themes/components/separator.py +++ b/packages/reflex-components/src/reflex_components/radix/themes/components/separator.py @@ -3,8 +3,8 @@ from typing import Literal from reflex.components.component import field -from reflex.components.core.breakpoints import Responsive -from reflex.components.radix.themes.base import LiteralAccentColor, RadixThemesComponent +from reflex_components.core.breakpoints import Responsive +from reflex_components.radix.themes.base import LiteralAccentColor, RadixThemesComponent from reflex.vars.base import LiteralVar, Var LiteralSeparatorSize = Literal["1", "2", "3", "4"] diff --git a/reflex/components/radix/themes/components/skeleton.py b/packages/reflex-components/src/reflex_components/radix/themes/components/skeleton.py similarity index 88% rename from reflex/components/radix/themes/components/skeleton.py rename to packages/reflex-components/src/reflex_components/radix/themes/components/skeleton.py index 4e33ec64a12..15f4e1875d1 100644 --- a/reflex/components/radix/themes/components/skeleton.py +++ b/packages/reflex-components/src/reflex_components/radix/themes/components/skeleton.py @@ -1,8 +1,8 @@ """Skeleton theme from Radix components.""" from reflex.components.component import field -from reflex.components.core.breakpoints import Responsive -from reflex.components.radix.themes.base import RadixLoadingProp, RadixThemesComponent +from reflex_components.core.breakpoints import Responsive +from reflex_components.radix.themes.base import RadixLoadingProp, RadixThemesComponent from reflex.constants.compiler import MemoizationMode from reflex.vars.base import Var diff --git a/reflex/components/radix/themes/components/slider.py b/packages/reflex-components/src/reflex_components/radix/themes/components/slider.py similarity index 96% rename from reflex/components/radix/themes/components/slider.py rename to packages/reflex-components/src/reflex_components/radix/themes/components/slider.py index 04836bcdea7..14c067b5e97 100644 --- a/reflex/components/radix/themes/components/slider.py +++ b/packages/reflex-components/src/reflex_components/radix/themes/components/slider.py @@ -6,8 +6,8 @@ from typing import Literal from reflex.components.component import Component, field -from reflex.components.core.breakpoints import Responsive -from reflex.components.radix.themes.base import LiteralAccentColor, RadixThemesComponent +from reflex_components.core.breakpoints import Responsive +from reflex_components.radix.themes.base import LiteralAccentColor, RadixThemesComponent from reflex.event import EventHandler, passthrough_event_spec from reflex.utils.types import typehint_issubclass from reflex.vars.base import Var diff --git a/reflex/components/radix/themes/components/spinner.py b/packages/reflex-components/src/reflex_components/radix/themes/components/spinner.py similarity index 78% rename from reflex/components/radix/themes/components/spinner.py rename to packages/reflex-components/src/reflex_components/radix/themes/components/spinner.py index 57d9a497645..e3b0e41b8e1 100644 --- a/reflex/components/radix/themes/components/spinner.py +++ b/packages/reflex-components/src/reflex_components/radix/themes/components/spinner.py @@ -3,8 +3,8 @@ from typing import Literal from reflex.components.component import field -from reflex.components.core.breakpoints import Responsive -from reflex.components.radix.themes.base import RadixLoadingProp, RadixThemesComponent +from reflex_components.core.breakpoints import Responsive +from reflex_components.radix.themes.base import RadixLoadingProp, RadixThemesComponent from reflex.vars.base import Var LiteralSpinnerSize = Literal["1", "2", "3"] diff --git a/reflex/components/radix/themes/components/switch.py b/packages/reflex-components/src/reflex_components/radix/themes/components/switch.py similarity index 94% rename from reflex/components/radix/themes/components/switch.py rename to packages/reflex-components/src/reflex_components/radix/themes/components/switch.py index 6c465bfbecd..c8f0f43e5df 100644 --- a/reflex/components/radix/themes/components/switch.py +++ b/packages/reflex-components/src/reflex_components/radix/themes/components/switch.py @@ -3,8 +3,8 @@ from typing import Literal from reflex.components.component import field -from reflex.components.core.breakpoints import Responsive -from reflex.components.radix.themes.base import LiteralAccentColor, RadixThemesComponent +from reflex_components.core.breakpoints import Responsive +from reflex_components.radix.themes.base import LiteralAccentColor, RadixThemesComponent from reflex.event import EventHandler, passthrough_event_spec from reflex.vars.base import Var diff --git a/reflex/components/radix/themes/components/table.py b/packages/reflex-components/src/reflex_components/radix/themes/components/table.py similarity index 95% rename from reflex/components/radix/themes/components/table.py rename to packages/reflex-components/src/reflex_components/radix/themes/components/table.py index 13d92d27848..3ba8a26e21e 100644 --- a/reflex/components/radix/themes/components/table.py +++ b/packages/reflex-components/src/reflex_components/radix/themes/components/table.py @@ -3,9 +3,9 @@ from typing import ClassVar, Literal from reflex.components.component import ComponentNamespace, field -from reflex.components.core.breakpoints import Responsive -from reflex.components.el import elements -from reflex.components.radix.themes.base import CommonPaddingProps, RadixThemesComponent +from reflex_components.core.breakpoints import Responsive +from reflex_components.el import elements +from reflex_components.radix.themes.base import CommonPaddingProps, RadixThemesComponent from reflex.vars.base import Var diff --git a/reflex/components/radix/themes/components/tabs.py b/packages/reflex-components/src/reflex_components/radix/themes/components/tabs.py similarity index 96% rename from reflex/components/radix/themes/components/tabs.py rename to packages/reflex-components/src/reflex_components/radix/themes/components/tabs.py index eb5c136cd71..3f15cad786a 100644 --- a/reflex/components/radix/themes/components/tabs.py +++ b/packages/reflex-components/src/reflex_components/radix/themes/components/tabs.py @@ -5,9 +5,9 @@ from typing import Any, ClassVar, Literal from reflex.components.component import Component, ComponentNamespace, field -from reflex.components.core.breakpoints import Responsive -from reflex.components.core.colors import color -from reflex.components.radix.themes.base import LiteralAccentColor, RadixThemesComponent +from reflex_components.core.breakpoints import Responsive +from reflex_components.core.colors import color +from reflex_components.radix.themes.base import LiteralAccentColor, RadixThemesComponent from reflex.constants.compiler import MemoizationMode from reflex.event import EventHandler, passthrough_event_spec from reflex.vars.base import Var diff --git a/reflex/components/radix/themes/components/text_area.py b/packages/reflex-components/src/reflex_components/radix/themes/components/text_area.py similarity index 94% rename from reflex/components/radix/themes/components/text_area.py rename to packages/reflex-components/src/reflex_components/radix/themes/components/text_area.py index e76dc01be9e..9bd83b9abf8 100644 --- a/reflex/components/radix/themes/components/text_area.py +++ b/packages/reflex-components/src/reflex_components/radix/themes/components/text_area.py @@ -3,10 +3,10 @@ from typing import Literal from reflex.components.component import Component, field -from reflex.components.core.breakpoints import Responsive -from reflex.components.core.debounce import DebounceInput -from reflex.components.el import elements -from reflex.components.radix.themes.base import ( +from reflex_components.core.breakpoints import Responsive +from reflex_components.core.debounce import DebounceInput +from reflex_components.el import elements +from reflex_components.radix.themes.base import ( LiteralAccentColor, LiteralRadius, RadixThemesComponent, diff --git a/reflex/components/radix/themes/components/text_field.py b/packages/reflex-components/src/reflex_components/radix/themes/components/text_field.py similarity index 95% rename from reflex/components/radix/themes/components/text_field.py rename to packages/reflex-components/src/reflex_components/radix/themes/components/text_field.py index 8e8d581c36b..248941d81fa 100644 --- a/reflex/components/radix/themes/components/text_field.py +++ b/packages/reflex-components/src/reflex_components/radix/themes/components/text_field.py @@ -5,10 +5,10 @@ from typing import Literal from reflex.components.component import Component, ComponentNamespace, field -from reflex.components.core.breakpoints import Responsive -from reflex.components.core.debounce import DebounceInput -from reflex.components.el import elements -from reflex.components.radix.themes.base import ( +from reflex_components.core.breakpoints import Responsive +from reflex_components.core.debounce import DebounceInput +from reflex_components.el import elements +from reflex_components.radix.themes.base import ( LiteralAccentColor, LiteralRadius, RadixThemesComponent, diff --git a/reflex/components/radix/themes/components/tooltip.py b/packages/reflex-components/src/reflex_components/radix/themes/components/tooltip.py similarity index 98% rename from reflex/components/radix/themes/components/tooltip.py rename to packages/reflex-components/src/reflex_components/radix/themes/components/tooltip.py index 9915f7a5c72..9f4cd2f5184 100644 --- a/reflex/components/radix/themes/components/tooltip.py +++ b/packages/reflex-components/src/reflex_components/radix/themes/components/tooltip.py @@ -3,7 +3,7 @@ from typing import Literal from reflex.components.component import Component, field -from reflex.components.radix.themes.base import RadixThemesComponent +from reflex_components.radix.themes.base import RadixThemesComponent from reflex.constants.compiler import MemoizationMode from reflex.event import EventHandler, no_args_event_spec, passthrough_event_spec from reflex.utils import format diff --git a/reflex/components/radix/themes/layout/__init__.py b/packages/reflex-components/src/reflex_components/radix/themes/layout/__init__.py similarity index 100% rename from reflex/components/radix/themes/layout/__init__.py rename to packages/reflex-components/src/reflex_components/radix/themes/layout/__init__.py diff --git a/reflex/components/radix/themes/layout/base.py b/packages/reflex-components/src/reflex_components/radix/themes/layout/base.py similarity index 89% rename from reflex/components/radix/themes/layout/base.py rename to packages/reflex-components/src/reflex_components/radix/themes/layout/base.py index b7f512ea6e2..5f881d0ce49 100644 --- a/reflex/components/radix/themes/layout/base.py +++ b/packages/reflex-components/src/reflex_components/radix/themes/layout/base.py @@ -5,8 +5,8 @@ from typing import Literal from reflex.components.component import field -from reflex.components.core.breakpoints import Responsive -from reflex.components.radix.themes.base import ( +from reflex_components.core.breakpoints import Responsive +from reflex_components.radix.themes.base import ( CommonMarginProps, CommonPaddingProps, RadixThemesComponent, diff --git a/reflex/components/radix/themes/layout/box.py b/packages/reflex-components/src/reflex_components/radix/themes/layout/box.py similarity index 70% rename from reflex/components/radix/themes/layout/box.py rename to packages/reflex-components/src/reflex_components/radix/themes/layout/box.py index 3932d2b9e1a..0303b0d56e2 100644 --- a/reflex/components/radix/themes/layout/box.py +++ b/packages/reflex-components/src/reflex_components/radix/themes/layout/box.py @@ -2,8 +2,8 @@ from __future__ import annotations -from reflex.components.el import elements -from reflex.components.radix.themes.base import RadixThemesComponent +from reflex_components.el import elements +from reflex_components.radix.themes.base import RadixThemesComponent class Box(elements.Div, RadixThemesComponent): diff --git a/reflex/components/radix/themes/layout/center.py b/packages/reflex-components/src/reflex_components/radix/themes/layout/center.py similarity index 100% rename from reflex/components/radix/themes/layout/center.py rename to packages/reflex-components/src/reflex_components/radix/themes/layout/center.py diff --git a/reflex/components/radix/themes/layout/container.py b/packages/reflex-components/src/reflex_components/radix/themes/layout/container.py similarity index 90% rename from reflex/components/radix/themes/layout/container.py rename to packages/reflex-components/src/reflex_components/radix/themes/layout/container.py index e44b583d2c9..b7b591fceba 100644 --- a/reflex/components/radix/themes/layout/container.py +++ b/packages/reflex-components/src/reflex_components/radix/themes/layout/container.py @@ -5,9 +5,9 @@ from typing import Literal from reflex.components.component import field -from reflex.components.core.breakpoints import Responsive -from reflex.components.el import elements -from reflex.components.radix.themes.base import RadixThemesComponent +from reflex_components.core.breakpoints import Responsive +from reflex_components.el import elements +from reflex_components.radix.themes.base import RadixThemesComponent from reflex.style import STACK_CHILDREN_FULL_WIDTH from reflex.vars.base import LiteralVar, Var diff --git a/reflex/components/radix/themes/layout/flex.py b/packages/reflex-components/src/reflex_components/radix/themes/layout/flex.py similarity index 91% rename from reflex/components/radix/themes/layout/flex.py rename to packages/reflex-components/src/reflex_components/radix/themes/layout/flex.py index 80101d27e47..964698434c7 100644 --- a/reflex/components/radix/themes/layout/flex.py +++ b/packages/reflex-components/src/reflex_components/radix/themes/layout/flex.py @@ -5,9 +5,9 @@ from typing import ClassVar, Literal from reflex.components.component import field -from reflex.components.core.breakpoints import Responsive -from reflex.components.el import elements -from reflex.components.radix.themes.base import ( +from reflex_components.core.breakpoints import Responsive +from reflex_components.el import elements +from reflex_components.radix.themes.base import ( LiteralAlign, LiteralJustify, LiteralSpacing, diff --git a/reflex/components/radix/themes/layout/grid.py b/packages/reflex-components/src/reflex_components/radix/themes/layout/grid.py similarity index 92% rename from reflex/components/radix/themes/layout/grid.py rename to packages/reflex-components/src/reflex_components/radix/themes/layout/grid.py index 186fa3c11a7..9b5b4168ed4 100644 --- a/reflex/components/radix/themes/layout/grid.py +++ b/packages/reflex-components/src/reflex_components/radix/themes/layout/grid.py @@ -5,9 +5,9 @@ from typing import ClassVar, Literal from reflex.components.component import field -from reflex.components.core.breakpoints import Responsive -from reflex.components.el import elements -from reflex.components.radix.themes.base import ( +from reflex_components.core.breakpoints import Responsive +from reflex_components.el import elements +from reflex_components.radix.themes.base import ( LiteralAlign, LiteralJustify, LiteralSpacing, diff --git a/reflex/components/radix/themes/layout/list.py b/packages/reflex-components/src/reflex_components/radix/themes/layout/list.py similarity index 93% rename from reflex/components/radix/themes/layout/list.py rename to packages/reflex-components/src/reflex_components/radix/themes/layout/list.py index b39a56b2e0d..8848c806136 100644 --- a/reflex/components/radix/themes/layout/list.py +++ b/packages/reflex-components/src/reflex_components/radix/themes/layout/list.py @@ -6,12 +6,12 @@ from typing import Any, Literal from reflex.components.component import ComponentNamespace, field -from reflex.components.core.foreach import Foreach -from reflex.components.el.elements.base import BaseHTML -from reflex.components.el.elements.typography import Li, Ol, Ul -from reflex.components.lucide.icon import Icon -from reflex.components.markdown.markdown import MarkdownComponentMap -from reflex.components.radix.themes.typography.text import Text +from reflex_components.core.foreach import Foreach +from reflex_components.el.elements.base import BaseHTML +from reflex_components.el.elements.typography import Li, Ol, Ul +from reflex_components.lucide.icon import Icon +from reflex_components.markdown.markdown import MarkdownComponentMap +from reflex_components.radix.themes.typography.text import Text from reflex.vars.base import Var LiteralListStyleTypeUnordered = Literal[ diff --git a/reflex/components/radix/themes/layout/section.py b/packages/reflex-components/src/reflex_components/radix/themes/layout/section.py similarity index 77% rename from reflex/components/radix/themes/layout/section.py rename to packages/reflex-components/src/reflex_components/radix/themes/layout/section.py index 88fe24cbad9..12543502c09 100644 --- a/reflex/components/radix/themes/layout/section.py +++ b/packages/reflex-components/src/reflex_components/radix/themes/layout/section.py @@ -5,9 +5,9 @@ from typing import Literal from reflex.components.component import field -from reflex.components.core.breakpoints import Responsive -from reflex.components.el import elements -from reflex.components.radix.themes.base import RadixThemesComponent +from reflex_components.core.breakpoints import Responsive +from reflex_components.el import elements +from reflex_components.radix.themes.base import RadixThemesComponent from reflex.vars.base import LiteralVar, Var LiteralSectionSize = Literal["1", "2", "3"] diff --git a/reflex/components/radix/themes/layout/spacer.py b/packages/reflex-components/src/reflex_components/radix/themes/layout/spacer.py similarity index 100% rename from reflex/components/radix/themes/layout/spacer.py rename to packages/reflex-components/src/reflex_components/radix/themes/layout/spacer.py diff --git a/reflex/components/radix/themes/layout/stack.py b/packages/reflex-components/src/reflex_components/radix/themes/layout/stack.py similarity index 93% rename from reflex/components/radix/themes/layout/stack.py rename to packages/reflex-components/src/reflex_components/radix/themes/layout/stack.py index 98f9c2f9170..b86c8940a5a 100644 --- a/reflex/components/radix/themes/layout/stack.py +++ b/packages/reflex-components/src/reflex_components/radix/themes/layout/stack.py @@ -3,8 +3,8 @@ from __future__ import annotations from reflex.components.component import Component, field -from reflex.components.core.breakpoints import Responsive -from reflex.components.radix.themes.base import LiteralAlign, LiteralSpacing +from reflex_components.core.breakpoints import Responsive +from reflex_components.radix.themes.base import LiteralAlign, LiteralSpacing from reflex.vars.base import Var from .flex import Flex, LiteralFlexDirection diff --git a/reflex/components/radix/themes/typography/__init__.py b/packages/reflex-components/src/reflex_components/radix/themes/typography/__init__.py similarity index 100% rename from reflex/components/radix/themes/typography/__init__.py rename to packages/reflex-components/src/reflex_components/radix/themes/typography/__init__.py diff --git a/reflex/components/radix/themes/typography/base.py b/packages/reflex-components/src/reflex_components/radix/themes/typography/base.py similarity index 100% rename from reflex/components/radix/themes/typography/base.py rename to packages/reflex-components/src/reflex_components/radix/themes/typography/base.py diff --git a/reflex/components/radix/themes/typography/blockquote.py b/packages/reflex-components/src/reflex_components/radix/themes/typography/blockquote.py similarity index 84% rename from reflex/components/radix/themes/typography/blockquote.py rename to packages/reflex-components/src/reflex_components/radix/themes/typography/blockquote.py index 836b8286bd7..ecbd7875e2a 100644 --- a/reflex/components/radix/themes/typography/blockquote.py +++ b/packages/reflex-components/src/reflex_components/radix/themes/typography/blockquote.py @@ -6,9 +6,9 @@ from __future__ import annotations from reflex.components.component import field -from reflex.components.core.breakpoints import Responsive -from reflex.components.el import elements -from reflex.components.radix.themes.base import LiteralAccentColor, RadixThemesComponent +from reflex_components.core.breakpoints import Responsive +from reflex_components.el import elements +from reflex_components.radix.themes.base import LiteralAccentColor, RadixThemesComponent from reflex.vars.base import Var from .base import LiteralTextSize, LiteralTextWeight diff --git a/reflex/components/radix/themes/typography/code.py b/packages/reflex-components/src/reflex_components/radix/themes/typography/code.py similarity index 83% rename from reflex/components/radix/themes/typography/code.py rename to packages/reflex-components/src/reflex_components/radix/themes/typography/code.py index ade8c441d1e..69de851d7c9 100644 --- a/reflex/components/radix/themes/typography/code.py +++ b/packages/reflex-components/src/reflex_components/radix/themes/typography/code.py @@ -6,10 +6,10 @@ from __future__ import annotations from reflex.components.component import field -from reflex.components.core.breakpoints import Responsive -from reflex.components.el import elements -from reflex.components.markdown.markdown import MarkdownComponentMap -from reflex.components.radix.themes.base import ( +from reflex_components.core.breakpoints import Responsive +from reflex_components.el import elements +from reflex_components.markdown.markdown import MarkdownComponentMap +from reflex_components.radix.themes.base import ( LiteralAccentColor, LiteralVariant, RadixThemesComponent, diff --git a/reflex/components/radix/themes/typography/heading.py b/packages/reflex-components/src/reflex_components/radix/themes/typography/heading.py similarity index 87% rename from reflex/components/radix/themes/typography/heading.py rename to packages/reflex-components/src/reflex_components/radix/themes/typography/heading.py index d970ad15ad8..f7ec7033c8c 100644 --- a/reflex/components/radix/themes/typography/heading.py +++ b/packages/reflex-components/src/reflex_components/radix/themes/typography/heading.py @@ -6,10 +6,10 @@ from __future__ import annotations from reflex.components.component import field -from reflex.components.core.breakpoints import Responsive -from reflex.components.el import elements -from reflex.components.markdown.markdown import MarkdownComponentMap -from reflex.components.radix.themes.base import LiteralAccentColor, RadixThemesComponent +from reflex_components.core.breakpoints import Responsive +from reflex_components.el import elements +from reflex_components.markdown.markdown import MarkdownComponentMap +from reflex_components.radix.themes.base import LiteralAccentColor, RadixThemesComponent from reflex.vars.base import Var from .base import LiteralTextAlign, LiteralTextSize, LiteralTextTrim, LiteralTextWeight diff --git a/reflex/components/radix/themes/typography/link.py b/packages/reflex-components/src/reflex_components/radix/themes/typography/link.py similarity index 90% rename from reflex/components/radix/themes/typography/link.py rename to packages/reflex-components/src/reflex_components/radix/themes/typography/link.py index 1e20f36942f..b688fc9e3cb 100644 --- a/reflex/components/radix/themes/typography/link.py +++ b/packages/reflex-components/src/reflex_components/radix/themes/typography/link.py @@ -8,13 +8,13 @@ from typing import Literal from reflex.components.component import Component, MemoizationLeaf, field -from reflex.components.core.breakpoints import Responsive -from reflex.components.core.colors import color -from reflex.components.core.cond import cond -from reflex.components.el.elements.inline import A -from reflex.components.markdown.markdown import MarkdownComponentMap -from reflex.components.radix.themes.base import LiteralAccentColor, RadixThemesComponent -from reflex.components.react_router.dom import ReactRouterLink +from reflex_components.core.breakpoints import Responsive +from reflex_components.core.colors import color +from reflex_components.core.cond import cond +from reflex_components.el.elements.inline import A +from reflex_components.markdown.markdown import MarkdownComponentMap +from reflex_components.radix.themes.base import LiteralAccentColor, RadixThemesComponent +from reflex_components.react_router.dom import ReactRouterLink from reflex.utils.imports import ImportDict, ImportVar from reflex.vars.base import Var diff --git a/reflex/components/radix/themes/typography/text.py b/packages/reflex-components/src/reflex_components/radix/themes/typography/text.py similarity index 92% rename from reflex/components/radix/themes/typography/text.py rename to packages/reflex-components/src/reflex_components/radix/themes/typography/text.py index 1c90bd83a32..666cf8d4512 100644 --- a/reflex/components/radix/themes/typography/text.py +++ b/packages/reflex-components/src/reflex_components/radix/themes/typography/text.py @@ -8,10 +8,10 @@ from typing import Literal from reflex.components.component import ComponentNamespace, field -from reflex.components.core.breakpoints import Responsive -from reflex.components.el import elements -from reflex.components.markdown.markdown import MarkdownComponentMap -from reflex.components.radix.themes.base import LiteralAccentColor, RadixThemesComponent +from reflex_components.core.breakpoints import Responsive +from reflex_components.el import elements +from reflex_components.markdown.markdown import MarkdownComponentMap +from reflex_components.radix.themes.base import LiteralAccentColor, RadixThemesComponent from reflex.vars.base import Var from .base import LiteralTextAlign, LiteralTextSize, LiteralTextTrim, LiteralTextWeight diff --git a/reflex/components/react_player/__init__.py b/packages/reflex-components/src/reflex_components/react_player/__init__.py similarity index 100% rename from reflex/components/react_player/__init__.py rename to packages/reflex-components/src/reflex_components/react_player/__init__.py diff --git a/reflex/components/react_player/audio.py b/packages/reflex-components/src/reflex_components/react_player/audio.py similarity index 63% rename from reflex/components/react_player/audio.py rename to packages/reflex-components/src/reflex_components/react_player/audio.py index 2f5cc5b6d8d..7223bc71a6a 100644 --- a/reflex/components/react_player/audio.py +++ b/packages/reflex-components/src/reflex_components/react_player/audio.py @@ -1,6 +1,6 @@ """A audio component.""" -from reflex.components.react_player.react_player import ReactPlayer +from reflex_components.react_player.react_player import ReactPlayer class Audio(ReactPlayer): diff --git a/reflex/components/react_player/react_player.py b/packages/reflex-components/src/reflex_components/react_player/react_player.py similarity index 99% rename from reflex/components/react_player/react_player.py rename to packages/reflex-components/src/reflex_components/react_player/react_player.py index f75779a322a..943d78c904b 100644 --- a/reflex/components/react_player/react_player.py +++ b/packages/reflex-components/src/reflex_components/react_player/react_player.py @@ -5,7 +5,7 @@ from typing import Any, TypedDict from reflex.components.component import Component, field -from reflex.components.core.cond import cond +from reflex_components.core.cond import cond from reflex.event import EventHandler, no_args_event_spec from reflex.utils import console from reflex.vars.base import Var diff --git a/reflex/components/react_player/video.py b/packages/reflex-components/src/reflex_components/react_player/video.py similarity index 63% rename from reflex/components/react_player/video.py rename to packages/reflex-components/src/reflex_components/react_player/video.py index 70b513195e2..9f0381772b2 100644 --- a/reflex/components/react_player/video.py +++ b/packages/reflex-components/src/reflex_components/react_player/video.py @@ -1,6 +1,6 @@ """A video component.""" -from reflex.components.react_player.react_player import ReactPlayer +from reflex_components.react_player.react_player import ReactPlayer class Video(ReactPlayer): diff --git a/reflex/components/react_router/__init__.py b/packages/reflex-components/src/reflex_components/react_router/__init__.py similarity index 100% rename from reflex/components/react_router/__init__.py rename to packages/reflex-components/src/reflex_components/react_router/__init__.py diff --git a/reflex/components/react_router/dom.py b/packages/reflex-components/src/reflex_components/react_router/dom.py similarity index 97% rename from reflex/components/react_router/dom.py rename to packages/reflex-components/src/reflex_components/react_router/dom.py index 5654c829a89..d2e759ad9fd 100644 --- a/reflex/components/react_router/dom.py +++ b/packages/reflex-components/src/reflex_components/react_router/dom.py @@ -5,7 +5,7 @@ from typing import ClassVar, Literal, TypedDict from reflex.components.component import field -from reflex.components.el.elements.inline import A +from reflex_components.el.elements.inline import A from reflex.vars.base import Var LiteralLinkDiscover = Literal["none", "render"] diff --git a/reflex/components/recharts/__init__.py b/packages/reflex-components/src/reflex_components/recharts/__init__.py similarity index 100% rename from reflex/components/recharts/__init__.py rename to packages/reflex-components/src/reflex_components/recharts/__init__.py diff --git a/reflex/components/recharts/cartesian.py b/packages/reflex-components/src/reflex_components/recharts/cartesian.py similarity index 100% rename from reflex/components/recharts/cartesian.py rename to packages/reflex-components/src/reflex_components/recharts/cartesian.py diff --git a/reflex/components/recharts/charts.py b/packages/reflex-components/src/reflex_components/recharts/charts.py similarity index 99% rename from reflex/components/recharts/charts.py rename to packages/reflex-components/src/reflex_components/recharts/charts.py index 5a62c4d6dce..83a83355fdb 100644 --- a/reflex/components/recharts/charts.py +++ b/packages/reflex-components/src/reflex_components/recharts/charts.py @@ -6,7 +6,7 @@ from typing import Any, ClassVar from reflex.components.component import Component, field -from reflex.components.recharts.general import ResponsiveContainer +from reflex_components.recharts.general import ResponsiveContainer from reflex.constants import EventTriggers from reflex.constants.colors import Color from reflex.event import EventHandler, no_args_event_spec diff --git a/reflex/components/recharts/general.py b/packages/reflex-components/src/reflex_components/recharts/general.py similarity index 100% rename from reflex/components/recharts/general.py rename to packages/reflex-components/src/reflex_components/recharts/general.py diff --git a/reflex/components/recharts/polar.py b/packages/reflex-components/src/reflex_components/recharts/polar.py similarity index 100% rename from reflex/components/recharts/polar.py rename to packages/reflex-components/src/reflex_components/recharts/polar.py diff --git a/reflex/components/recharts/recharts.py b/packages/reflex-components/src/reflex_components/recharts/recharts.py similarity index 100% rename from reflex/components/recharts/recharts.py rename to packages/reflex-components/src/reflex_components/recharts/recharts.py diff --git a/reflex/components/sonner/__init__.py b/packages/reflex-components/src/reflex_components/sonner/__init__.py similarity index 100% rename from reflex/components/sonner/__init__.py rename to packages/reflex-components/src/reflex_components/sonner/__init__.py diff --git a/reflex/components/sonner/toast.py b/packages/reflex-components/src/reflex_components/sonner/toast.py similarity index 99% rename from reflex/components/sonner/toast.py rename to packages/reflex-components/src/reflex_components/sonner/toast.py index 330a24478c5..b4d7566de36 100644 --- a/reflex/components/sonner/toast.py +++ b/packages/reflex-components/src/reflex_components/sonner/toast.py @@ -6,7 +6,7 @@ from typing import Any, Literal from reflex.components.component import Component, ComponentNamespace, field -from reflex.components.lucide.icon import Icon +from reflex_components.lucide.icon import Icon from reflex.components.props import NoExtrasAllowedProps from reflex.constants.base import Dirs from reflex.event import EventSpec, run_script diff --git a/pyi_hashes.json b/pyi_hashes.json index 03d20e2342b..2f366c25e87 100644 --- a/pyi_hashes.json +++ b/pyi_hashes.json @@ -1,122 +1,123 @@ { - "reflex/__init__.pyi": "0a3ae880e256b9fd3b960e12a2cb51a7", - "reflex/components/__init__.pyi": "ac05995852baa81062ba3d18fbc489fb", - "reflex/components/base/__init__.pyi": "16e47bf19e0d62835a605baa3d039c5a", - "reflex/components/base/app_wrap.pyi": "22e94feaa9fe675bcae51c412f5b67f1", - "reflex/components/base/body.pyi": "fa8e4343880c7b3ac09b056bd3c253ee", - "reflex/components/base/document.pyi": "1c90cb8f1981a0f7f6087c702feb23a3", - "reflex/components/base/error_boundary.pyi": "53deea0de4b36a6e0af7067e3c05e695", - "reflex/components/base/fragment.pyi": "d3da5a5bff969ea682a86eaa6658b7ec", - "reflex/components/base/link.pyi": "7ffdf9c70724da5781284641f3ce8370", - "reflex/components/base/meta.pyi": "ceb1b79d42e7b3e115e1c3ca6f7121c2", - "reflex/components/base/script.pyi": "c136448d69727928443f8ced053c7210", - "reflex/components/base/strict_mode.pyi": "e7e7358393ff81e9283e2e67f7c2aaa8", - "reflex/components/core/__init__.pyi": "007170b97e58bdf28b2aee381d91c0c7", - "reflex/components/core/auto_scroll.pyi": "a4db8095e145926992a8272379d27257", - "reflex/components/core/banner.pyi": "66f4fd0cd78a9d6071caa31c51394a49", - "reflex/components/core/clipboard.pyi": "c8a7834ea5f6202c760065b2534ea59d", - "reflex/components/core/debounce.pyi": "1722c092d6e17406a9bf047353d05ea6", - "reflex/components/core/helmet.pyi": "cb5ac1be02c6f82fcc78ba74651be593", - "reflex/components/core/html.pyi": "4ebe946f3fc097fc2e31dddf7040ec1c", - "reflex/components/core/sticky.pyi": "cb763b986a9b0654d1a3f33440dfcf60", - "reflex/components/core/upload.pyi": "c90782be1b63276b428bce3fd4ce0af2", - "reflex/components/core/window_events.pyi": "e7af4bf5341c4afaf60c4a534660f68f", - "reflex/components/datadisplay/__init__.pyi": "52755871369acbfd3a96b46b9a11d32e", - "reflex/components/datadisplay/code.pyi": "1d123d19ef08f085422f3023540e7bb1", - "reflex/components/datadisplay/dataeditor.pyi": "93309b17a4c12b2216e2d863d325a107", - "reflex/components/datadisplay/shiki_code_block.pyi": "570c1a03ad509da982b90de42c69fd47", - "reflex/components/el/__init__.pyi": "0adfd001a926a2a40aee94f6fa725ecc", - "reflex/components/el/element.pyi": "62431eed73f5a2b0536036ce05fb84bd", - "reflex/components/el/elements/__init__.pyi": "29512d7a6b29c6dc5ff68d3b31f26528", - "reflex/components/el/elements/base.pyi": "705e5555b86b2fff64cc77baf6ed02f1", - "reflex/components/el/elements/forms.pyi": "5a1fde0f8fee4d1bec938577781d6e53", - "reflex/components/el/elements/inline.pyi": "c7340e5c60344c61aa7a1c30b3e1b92f", - "reflex/components/el/elements/media.pyi": "fa92cf81b560466b310ad9f5795e9fcf", - "reflex/components/el/elements/metadata.pyi": "67ac22ad50139f545bc0e2e26fe31c04", - "reflex/components/el/elements/other.pyi": "b4bc739f1338acd430263b5bc284b6ab", - "reflex/components/el/elements/scripts.pyi": "00731f16568572131dcc3e02d2a7aa66", - "reflex/components/el/elements/sectioning.pyi": "c8bc889c30bcb54a270fb07ff1d2c7cc", - "reflex/components/el/elements/tables.pyi": "7c76a00f319dbbc27ea1a4d81695e4be", - "reflex/components/el/elements/typography.pyi": "b6a5b6a0a23a03cd81a96ddb4769076a", - "reflex/components/gridjs/datatable.pyi": "761a2472cef297005f8d7ce63a06ee27", - "reflex/components/lucide/icon.pyi": "1f0449a8dc8ea7016334f4d51a42ce1a", - "reflex/components/markdown/markdown.pyi": "9e7316a36a36409d718700609652e570", - "reflex/components/moment/moment.pyi": "b63ea2a7e91f4caf8db86e438caead5a", - "reflex/components/plotly/plotly.pyi": "af31e1963b6788f3dec3238310269b7c", - "reflex/components/radix/__init__.pyi": "5d8e3579912473e563676bfc71f29191", - "reflex/components/radix/primitives/__init__.pyi": "01c388fe7a1f5426a16676404344edf6", - "reflex/components/radix/primitives/accordion.pyi": "a0b2f56c5a726057596b77b336879bd9", - "reflex/components/radix/primitives/base.pyi": "e64ef8c92e94e02baa2193c6b3da8300", - "reflex/components/radix/primitives/dialog.pyi": "2995a7323d8a016db9945955db2d2070", - "reflex/components/radix/primitives/drawer.pyi": "0041ba408b1171d6a733335d5dfb2f5d", - "reflex/components/radix/primitives/form.pyi": "7eb90bcdb45a9de263eea722733d7704", - "reflex/components/radix/primitives/progress.pyi": "959e0540affc4967c457da7f6642b7ae", - "reflex/components/radix/primitives/slider.pyi": "3892b9e10cd424a4ea40f166737eeaf8", - "reflex/components/radix/themes/__init__.pyi": "582b4a7ead62b2ae8605e17fa084c063", - "reflex/components/radix/themes/base.pyi": "fa9d8fa28255b259b91cebe363881b6c", - "reflex/components/radix/themes/color_mode.pyi": "dfbe926e30f4f1b013086c59def6a298", - "reflex/components/radix/themes/components/__init__.pyi": "efa279ee05479d7bb8a64d49da808d03", - "reflex/components/radix/themes/components/alert_dialog.pyi": "fd831007e357a398de254a7a67569394", - "reflex/components/radix/themes/components/aspect_ratio.pyi": "7087ecde3b3484a5e3cc77388228fc67", - "reflex/components/radix/themes/components/avatar.pyi": "093195106ff1a94b086fc98845d827e5", - "reflex/components/radix/themes/components/badge.pyi": "1ff3402150492bdbc1667a06f78de274", - "reflex/components/radix/themes/components/button.pyi": "1b919088eedbf5c5bae929c52b05fbd4", - "reflex/components/radix/themes/components/callout.pyi": "1b96bd57f15b28d6f371548452befde4", - "reflex/components/radix/themes/components/card.pyi": "c19d311c41f547b25afb6a224c3f885b", - "reflex/components/radix/themes/components/checkbox.pyi": "f7835e065a1c2961290320b5bb766d22", - "reflex/components/radix/themes/components/checkbox_cards.pyi": "339b3ed08900e4cb5e9786de6aa17fb8", - "reflex/components/radix/themes/components/checkbox_group.pyi": "1bdd96f70bcf9289db2a1f210de14b8b", - "reflex/components/radix/themes/components/context_menu.pyi": "84008b434ef1d349db155966e58c5f5a", - "reflex/components/radix/themes/components/data_list.pyi": "37df138ba88fe43f61648646f9d94616", - "reflex/components/radix/themes/components/dialog.pyi": "0d414930a1768b80cdbbf4c0b51c296a", - "reflex/components/radix/themes/components/dropdown_menu.pyi": "3aee89946b2ac137e536b9eb88c335bc", - "reflex/components/radix/themes/components/hover_card.pyi": "3ddc1bdf79f98d291c86553e275207aa", - "reflex/components/radix/themes/components/icon_button.pyi": "09f16042ed75fb605ba89df069f439de", - "reflex/components/radix/themes/components/inset.pyi": "2088b78fafc9256809b245d2544bb35a", - "reflex/components/radix/themes/components/popover.pyi": "80b410f0fcdfdd5815b06d17e8feff94", - "reflex/components/radix/themes/components/progress.pyi": "e4043f18f1eb33c0f675b827318d45ac", - "reflex/components/radix/themes/components/radio.pyi": "afc737e2f4c34eb9520361b1677c889f", - "reflex/components/radix/themes/components/radio_cards.pyi": "18775b80e37bfb7da81457f8068a83c3", - "reflex/components/radix/themes/components/radio_group.pyi": "b2b1491d0461acdd1980bf1b0ff9d0bc", - "reflex/components/radix/themes/components/scroll_area.pyi": "9aec5400dc3795f2430010bc288e9c03", - "reflex/components/radix/themes/components/segmented_control.pyi": "ed256e9e239c3378372dc5e25efe37e4", - "reflex/components/radix/themes/components/select.pyi": "58ca47be618155d427dbadefd5d72471", - "reflex/components/radix/themes/components/separator.pyi": "c84161d5924b4038289a3419f516582a", - "reflex/components/radix/themes/components/skeleton.pyi": "bde931806e05805957f06a618c675fc9", - "reflex/components/radix/themes/components/slider.pyi": "aa45010f4674390f055da20edd2008bb", - "reflex/components/radix/themes/components/spinner.pyi": "6080e9ba414077f78e52fd01c9061da6", - "reflex/components/radix/themes/components/switch.pyi": "fcb3767bda070ef0af4ee664f2f068a4", - "reflex/components/radix/themes/components/table.pyi": "1c5aca792b0acb1a79d8365f64e76e59", - "reflex/components/radix/themes/components/tabs.pyi": "94029d5505bc14a7059633472a4908e0", - "reflex/components/radix/themes/components/text_area.pyi": "ccc7f3f38218ee3189baf1350ec64b40", - "reflex/components/radix/themes/components/text_field.pyi": "87526c9942ec517140bd4fbb86f89600", - "reflex/components/radix/themes/components/tooltip.pyi": "6ada2d6bc3abc9aa5e55d66f81ae95de", - "reflex/components/radix/themes/layout/__init__.pyi": "73eefc509a49215b1797b5b5d28d035e", - "reflex/components/radix/themes/layout/base.pyi": "091d8353514b73ef0dc4a5a9347b0fdf", - "reflex/components/radix/themes/layout/box.pyi": "1467e1ef4b6722b728596f42806d693b", - "reflex/components/radix/themes/layout/center.pyi": "a0c20b842b2fb7407b7a3d079aaa1dae", - "reflex/components/radix/themes/layout/container.pyi": "6a8a5581f2bad13bbac35d6e0999074f", - "reflex/components/radix/themes/layout/flex.pyi": "446e10f5fb7c55855eb240d6e3f73dfa", - "reflex/components/radix/themes/layout/grid.pyi": "f2979297d7e4263b578dab47b71aa5c2", - "reflex/components/radix/themes/layout/list.pyi": "d14e480c72efe8c64d37408b789b9472", - "reflex/components/radix/themes/layout/section.pyi": "23ae5def80148fed4b5e729c9d931326", - "reflex/components/radix/themes/layout/spacer.pyi": "ce2020374844384dac1b7c18e00fd0a5", - "reflex/components/radix/themes/layout/stack.pyi": "6271d05ef431208d8db0418f04c6987c", - "reflex/components/radix/themes/typography/__init__.pyi": "b8ef970530397e9984004961f3aaee62", - "reflex/components/radix/themes/typography/blockquote.pyi": "dbdf3fdf7cdca84447a8007dd43b9d78", - "reflex/components/radix/themes/typography/code.pyi": "c5b95158b8328a199120e4795bf27f44", - "reflex/components/radix/themes/typography/heading.pyi": "a5cf5efcf485fd8a6f1b38a64e27a52d", - "reflex/components/radix/themes/typography/link.pyi": "64357025c9d5bae85ea206dcd3ebfa2d", - "reflex/components/radix/themes/typography/text.pyi": "0e1434318bb482157b8d1b276550019b", - "reflex/components/react_player/audio.pyi": "0e1690ff1f1f39bc748278d292238350", - "reflex/components/react_player/react_player.pyi": "5289c57db568ee3ae01f97789c445f37", - "reflex/components/react_player/video.pyi": "998671c06103d797c554d9278eb3b2a0", - "reflex/components/react_router/dom.pyi": "2198359c2d8f3d1856f4391978a4e2de", - "reflex/components/recharts/__init__.pyi": "6ee7f1ca2c0912f389ba6f3251a74d99", - "reflex/components/recharts/cartesian.pyi": "4dc01da3195f80b9408d84373b874c41", - "reflex/components/recharts/charts.pyi": "16cd435d77f06f0315b595b7e62cf44b", - "reflex/components/recharts/general.pyi": "9abf71810a5405fd45b13804c0a7fd1a", - "reflex/components/recharts/polar.pyi": "ea4743e8903365ba95bc4b653c47cc4a", - "reflex/components/recharts/recharts.pyi": "b3d93d085d51053bbb8f65326f34a299", - "reflex/components/sonner/toast.pyi": "636050fcc919f8ab0903c30dceaa18f1" + "packages/reflex-components/src/reflex_components/__init__.pyi": "760d3249cd2e9c55d1e8963054515231", + "packages/reflex-components/src/reflex_components/base/__init__.pyi": "d2303a7a1a18f9390ffe0d885b20c9d5", + "packages/reflex-components/src/reflex_components/base/app_wrap.pyi": "9672898d1e6c4e0ae6bfa281d41b2389", + "packages/reflex-components/src/reflex_components/base/body.pyi": "4b7b090ca81fa9913ba208a8a2dc3fd4", + "packages/reflex-components/src/reflex_components/base/document.pyi": "0ec240e365318f181aa9fdaa4e0ce7f0", + "packages/reflex-components/src/reflex_components/base/error_boundary.pyi": "b40fe30f7d29ff467710a86879542070", + "packages/reflex-components/src/reflex_components/base/fragment.pyi": "0e709d1366b59f68a4ba811544ef9650", + "packages/reflex-components/src/reflex_components/base/link.pyi": "f4fb3b6e437a7e12279c9c720c163fed", + "packages/reflex-components/src/reflex_components/base/meta.pyi": "d7b2df571c0909abcab81edc6779d6da", + "packages/reflex-components/src/reflex_components/base/script.pyi": "8a498a6df7b104e6d612cd880ad65203", + "packages/reflex-components/src/reflex_components/base/strict_mode.pyi": "b3bc948878137f371f0464163ab82a66", + "packages/reflex-components/src/reflex_components/core/__init__.pyi": "d47e3059a7531c2eadc0ec0c4283bd30", + "packages/reflex-components/src/reflex_components/core/auto_scroll.pyi": "71366c7018110dca86fe689b332aa524", + "packages/reflex-components/src/reflex_components/core/banner.pyi": "ffc008bf6f001495e11076aeaf942ef0", + "packages/reflex-components/src/reflex_components/core/clipboard.pyi": "6d4990ec436bf1d3ccea5a8bb91c7875", + "packages/reflex-components/src/reflex_components/core/debounce.pyi": "bce792c1b6fc05f2449b46a5e4580d97", + "packages/reflex-components/src/reflex_components/core/helmet.pyi": "9dda69cc8f6563a7062be75a5a9a1766", + "packages/reflex-components/src/reflex_components/core/html.pyi": "3ad4a88e2062d4e5470ff8f6836bbc4a", + "packages/reflex-components/src/reflex_components/core/sticky.pyi": "16656267c47cb13692679d4a6551adf7", + "packages/reflex-components/src/reflex_components/core/upload.pyi": "371f5891d35430ae4f9afea6e97c0fe3", + "packages/reflex-components/src/reflex_components/core/window_events.pyi": "c520860e6cedb6c018319e3f99b0db07", + "packages/reflex-components/src/reflex_components/datadisplay/__init__.pyi": "8844db1b47917daa949ed5ac2073c7b8", + "packages/reflex-components/src/reflex_components/datadisplay/code.pyi": "5a6a9f439b34a695af61b8b21b996584", + "packages/reflex-components/src/reflex_components/datadisplay/dataeditor.pyi": "4719c4de53d79d219053fb870d3cabc6", + "packages/reflex-components/src/reflex_components/datadisplay/shiki_code_block.pyi": "5a08e435625a1f5b34231faf8d46984b", + "packages/reflex-components/src/reflex_components/el/__init__.pyi": "477afb183b4ac1baebc91a2c0a0544fd", + "packages/reflex-components/src/reflex_components/el/element.pyi": "55604caea0edce331f7672a8f8e82f4a", + "packages/reflex-components/src/reflex_components/el/elements/__init__.pyi": "a9c6b03e1a2a73e6278a6ebfc3127429", + "packages/reflex-components/src/reflex_components/el/elements/base.pyi": "4c8d5d21f1ccb927cf03efb9a8350a8e", + "packages/reflex-components/src/reflex_components/el/elements/forms.pyi": "941846dcb64ff471ebd03e604979f982", + "packages/reflex-components/src/reflex_components/el/elements/inline.pyi": "f96166755444d611daa933581c66e955", + "packages/reflex-components/src/reflex_components/el/elements/media.pyi": "14870b03495566fb709cb80658a7bd61", + "packages/reflex-components/src/reflex_components/el/elements/metadata.pyi": "b69d0ea2bae2e9c7fcf27589127f0d36", + "packages/reflex-components/src/reflex_components/el/elements/other.pyi": "2a03bd4e607ce7a90f65f0488eab8ca8", + "packages/reflex-components/src/reflex_components/el/elements/scripts.pyi": "422a4e8adce548a3cf3c09184945f455", + "packages/reflex-components/src/reflex_components/el/elements/sectioning.pyi": "e44354124a23f88e31d5c158430a6341", + "packages/reflex-components/src/reflex_components/el/elements/tables.pyi": "e3b27ff3699fe612ac0ab81689452195", + "packages/reflex-components/src/reflex_components/el/elements/typography.pyi": "2987ced0f04c729751a09dbafdff558d", + "packages/reflex-components/src/reflex_components/gridjs/datatable.pyi": "7f85bb4e93c355ec0d19b3bb17f0d2cf", + "packages/reflex-components/src/reflex_components/lucide/icon.pyi": "8b48d954a6c9f1825c5123893ca2bf7f", + "packages/reflex-components/src/reflex_components/markdown/markdown.pyi": "0865d8c3903158c66796c6ef0966bc40", + "packages/reflex-components/src/reflex_components/moment/moment.pyi": "619390dc6611c0de70b26eabbe73fb19", + "packages/reflex-components/src/reflex_components/plotly/plotly.pyi": "a4e04cb2cbc44fb7514167eea12aca4c", + "packages/reflex-components/src/reflex_components/radix/__init__.pyi": "233a92ee0848860f23c43d22a5975a37", + "packages/reflex-components/src/reflex_components/radix/primitives/__init__.pyi": "3995eab0f47e72fdac088ac18113b345", + "packages/reflex-components/src/reflex_components/radix/primitives/accordion.pyi": "270ba5ff161fc071d8586641861ce0d1", + "packages/reflex-components/src/reflex_components/radix/primitives/base.pyi": "c0b3ccba236cdbcf865246ef12b4bc47", + "packages/reflex-components/src/reflex_components/radix/primitives/dialog.pyi": "5eb60fcef948b0a9e140da505ff179b9", + "packages/reflex-components/src/reflex_components/radix/primitives/drawer.pyi": "80e5b25d28f359a546d5c420868a78c0", + "packages/reflex-components/src/reflex_components/radix/primitives/form.pyi": "e674d866ab762f7981c37d7496e7d8f5", + "packages/reflex-components/src/reflex_components/radix/primitives/progress.pyi": "090918a1012a8ca167b393d607d5eaca", + "packages/reflex-components/src/reflex_components/radix/primitives/slider.pyi": "11b2fd7ec2ea672f1d1b09720041040d", + "packages/reflex-components/src/reflex_components/radix/themes/__init__.pyi": "7e1a012bc6f715ba409545df7e8dfc98", + "packages/reflex-components/src/reflex_components/radix/themes/base.pyi": "882fa26c3cda78a97d74ea42e9b81cab", + "packages/reflex-components/src/reflex_components/radix/themes/color_mode.pyi": "58e027cf96c7b61098b2e5fbe74d5286", + "packages/reflex-components/src/reflex_components/radix/themes/components/__init__.pyi": "a72c44dcf366b3cf6f1223ee446d12f8", + "packages/reflex-components/src/reflex_components/radix/themes/components/alert_dialog.pyi": "bbf3135eff6cba018ff1c05829c31fae", + "packages/reflex-components/src/reflex_components/radix/themes/components/aspect_ratio.pyi": "ed081f844343c6f36bb1967dca7bbf4e", + "packages/reflex-components/src/reflex_components/radix/themes/components/avatar.pyi": "8d889ca28e0a5f53e7e7798371570ddb", + "packages/reflex-components/src/reflex_components/radix/themes/components/badge.pyi": "33d72707a0e30595c31a1a0c39f3eebd", + "packages/reflex-components/src/reflex_components/radix/themes/components/button.pyi": "e0738952f361f17b24506153fb4e953d", + "packages/reflex-components/src/reflex_components/radix/themes/components/callout.pyi": "7cd794c97556fdf8a775bf7ead533b4d", + "packages/reflex-components/src/reflex_components/radix/themes/components/card.pyi": "1773ab976ccd025e7a4b21b5eca36db9", + "packages/reflex-components/src/reflex_components/radix/themes/components/checkbox.pyi": "346f5b9b7d3cba41d97e1f623b4fe2d3", + "packages/reflex-components/src/reflex_components/radix/themes/components/checkbox_cards.pyi": "b9a8f4b0fd88e04991d0e7aa5bae9fbb", + "packages/reflex-components/src/reflex_components/radix/themes/components/checkbox_group.pyi": "f1f19e9d997c6965fe73b699ea969ac1", + "packages/reflex-components/src/reflex_components/radix/themes/components/context_menu.pyi": "859ff8a0293ed99cfac6238791d4675c", + "packages/reflex-components/src/reflex_components/radix/themes/components/data_list.pyi": "0863370c6d9309e933b0bfddfe815469", + "packages/reflex-components/src/reflex_components/radix/themes/components/dialog.pyi": "b37bf94b6f09fff735a7d0ded8c98a09", + "packages/reflex-components/src/reflex_components/radix/themes/components/dropdown_menu.pyi": "e447c5fa580d5a2d0cfa073281800b1a", + "packages/reflex-components/src/reflex_components/radix/themes/components/hover_card.pyi": "3fca7c21b6055c04580140b1b938ab72", + "packages/reflex-components/src/reflex_components/radix/themes/components/icon_button.pyi": "3af538b407731f27f8a4fb6cc94adde5", + "packages/reflex-components/src/reflex_components/radix/themes/components/inset.pyi": "cd3690ddfa29e9bfcaba6afa13a915bf", + "packages/reflex-components/src/reflex_components/radix/themes/components/popover.pyi": "d57149b28c9ac873897f9f9afb2e9bc1", + "packages/reflex-components/src/reflex_components/radix/themes/components/progress.pyi": "39b23b217ed8fc29376d337892540d50", + "packages/reflex-components/src/reflex_components/radix/themes/components/radio.pyi": "b9fa6171755eb2345584c34b4976457d", + "packages/reflex-components/src/reflex_components/radix/themes/components/radio_cards.pyi": "b21275ab34ac18d75cb0bbb18c074f24", + "packages/reflex-components/src/reflex_components/radix/themes/components/radio_group.pyi": "b91a679b9aa7d823f2a15dbab10448b5", + "packages/reflex-components/src/reflex_components/radix/themes/components/scroll_area.pyi": "0e161bfc88b42ff3d05a20bd3f74c545", + "packages/reflex-components/src/reflex_components/radix/themes/components/segmented_control.pyi": "b8b8ebc506280ebb7993c5d6b9e43a5b", + "packages/reflex-components/src/reflex_components/radix/themes/components/select.pyi": "560f412a0983632a2e3f2ed9f5565c7f", + "packages/reflex-components/src/reflex_components/radix/themes/components/separator.pyi": "97f7e832d55a6900194037b70a140f6f", + "packages/reflex-components/src/reflex_components/radix/themes/components/skeleton.pyi": "b9b8a3ef80b8b9b0d9ed6b9130f73f7c", + "packages/reflex-components/src/reflex_components/radix/themes/components/slider.pyi": "1e92cf88c6bcdb048a809710dfee3ed4", + "packages/reflex-components/src/reflex_components/radix/themes/components/spinner.pyi": "99297f40b3129b5f3aec45f35578a820", + "packages/reflex-components/src/reflex_components/radix/themes/components/switch.pyi": "bc114c006f0b9b9085e0a259dc9e358f", + "packages/reflex-components/src/reflex_components/radix/themes/components/table.pyi": "4305a28fc4ab98a9cac1985f5465eeaa", + "packages/reflex-components/src/reflex_components/radix/themes/components/tabs.pyi": "ca93a5feb35a9c16a2ca53fd221f9788", + "packages/reflex-components/src/reflex_components/radix/themes/components/text_area.pyi": "57d52555cf412f4e6731f13f7df925c6", + "packages/reflex-components/src/reflex_components/radix/themes/components/text_field.pyi": "05d4352f53895f3d0e00e2723f0532b1", + "packages/reflex-components/src/reflex_components/radix/themes/components/tooltip.pyi": "d6c8411990befde7d1e9bd8d10e97b18", + "packages/reflex-components/src/reflex_components/radix/themes/layout/__init__.pyi": "d73df4cb5d900261c2c43de8a302c229", + "packages/reflex-components/src/reflex_components/radix/themes/layout/base.pyi": "d2aece3ac78dfe10893d5257417974b2", + "packages/reflex-components/src/reflex_components/radix/themes/layout/box.pyi": "4ccaa1fabaa232a37dd77c58fbbb3064", + "packages/reflex-components/src/reflex_components/radix/themes/layout/center.pyi": "6bde3fb0b73d6b0bb1b48a1753ccc03e", + "packages/reflex-components/src/reflex_components/radix/themes/layout/container.pyi": "501a965041ee74c0e300f22e78f56dfe", + "packages/reflex-components/src/reflex_components/radix/themes/layout/flex.pyi": "af282030c25d04b59b9308f806e60084", + "packages/reflex-components/src/reflex_components/radix/themes/layout/grid.pyi": "56b4e25ea1dae0714409d393946ca94f", + "packages/reflex-components/src/reflex_components/radix/themes/layout/list.pyi": "69ae715f678b932ccc6b81195bfff8b5", + "packages/reflex-components/src/reflex_components/radix/themes/layout/section.pyi": "353645d8f6dc36c55ad330aee98c50f4", + "packages/reflex-components/src/reflex_components/radix/themes/layout/spacer.pyi": "c81cb167c7ad003177f535e3508dfb9e", + "packages/reflex-components/src/reflex_components/radix/themes/layout/stack.pyi": "6e283abbce7ed43973ed22edc9c8fd08", + "packages/reflex-components/src/reflex_components/radix/themes/typography/__init__.pyi": "b8fa254569fbe3bd2c0ca425f8c02b7d", + "packages/reflex-components/src/reflex_components/radix/themes/typography/blockquote.pyi": "d9e01352563e26ed8fd36ace06c1308f", + "packages/reflex-components/src/reflex_components/radix/themes/typography/code.pyi": "c3545061bcb84da208346d167b55b2e4", + "packages/reflex-components/src/reflex_components/radix/themes/typography/heading.pyi": "d14fbb1252d824eb190903eb99e3a3ca", + "packages/reflex-components/src/reflex_components/radix/themes/typography/link.pyi": "adbba1d868722f0c9a324fbafc90d250", + "packages/reflex-components/src/reflex_components/radix/themes/typography/text.pyi": "f8a6d3e852660eff9ea43bb740d29a7a", + "packages/reflex-components/src/reflex_components/react_player/audio.pyi": "e78e94d28fe752bc3cf72bf63671c606", + "packages/reflex-components/src/reflex_components/react_player/react_player.pyi": "0b737bb8bc2feca702125b29c21741b7", + "packages/reflex-components/src/reflex_components/react_player/video.pyi": "28deeb97e5b4ee4da7c0b8f819dc9b89", + "packages/reflex-components/src/reflex_components/react_router/dom.pyi": "936c045b8adbc0e0f96ce66a380d4cdc", + "packages/reflex-components/src/reflex_components/recharts/__init__.pyi": "8813aaa710352e4bafb68f70ca18d73f", + "packages/reflex-components/src/reflex_components/recharts/cartesian.pyi": "ce549ad2157ad986607bc3eb361c2830", + "packages/reflex-components/src/reflex_components/recharts/charts.pyi": "2e64ecdbaac453423e6d915149e2c3ba", + "packages/reflex-components/src/reflex_components/recharts/general.pyi": "eefaa54bdeca02457e2dc7dc95a1a28c", + "packages/reflex-components/src/reflex_components/recharts/polar.pyi": "8e3633223a4c7d47fcc361931a2c713f", + "packages/reflex-components/src/reflex_components/recharts/recharts.pyi": "294907ff114fb120adda4647c99e0b95", + "packages/reflex-components/src/reflex_components/sonner/toast.pyi": "2a6f460088763ceff43427d999fc6369", + "reflex/__init__.pyi": "d3e2ea7cb94b6bd45f43a7b8a4848588", + "reflex/components/__init__.pyi": "d620d52015f908cda828d231c1064236" } diff --git a/pyproject.toml b/pyproject.toml index 4efe251951b..87ef506a9ee 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -37,6 +37,7 @@ dependencies = [ "starlette >=0.47.0", "typing_extensions >=4.13.0", "wrapt >=1.17.0,<3.0", + "reflex-components", ] classifiers = [ @@ -116,6 +117,7 @@ dependencies = ["plotly", "ruff", "pre_commit", "toml"] require-runtime-dependencies = true [tool.pyright] +extraPaths = ["packages/reflex-components/src"] reportIncompatibleMethodOverride = false [tool.ruff] @@ -294,7 +296,7 @@ hooks = [ [tool.uv] required-version = ">=0.7.0" -sources = { reflex-docgen = { workspace = true } } +sources = { reflex-docgen = { workspace = true }, reflex-components = { workspace = true } } [tool.uv.workspace] members = ["packages/*"] diff --git a/reflex/app.py b/reflex/app.py index 39fd6478797..9bee5e96c83 100644 --- a/reflex/app.py +++ b/reflex/app.py @@ -31,6 +31,19 @@ from types import SimpleNamespace from typing import TYPE_CHECKING, Any, BinaryIO, ParamSpec, get_args, get_type_hints +from reflex_components.base.app_wrap import AppWrap +from reflex_components.base.error_boundary import ErrorBoundary +from reflex_components.base.fragment import Fragment +from reflex_components.base.strict_mode import StrictMode +from reflex_components.core.banner import ( + backend_disabled, + connection_pulser, + connection_toaster, +) +from reflex_components.core.breakpoints import set_breakpoints +from reflex_components.core.sticky import sticky +from reflex_components.radix import themes +from reflex_components.sonner.toast import toast from rich.progress import MofNCompleteColumn, Progress, TimeElapsedColumn from socketio import ASGIApp as EngineIOApp from socketio import AsyncNamespace, AsyncServer @@ -54,25 +67,12 @@ compile_theme, readable_name_from_component, ) -from reflex.components.base.app_wrap import AppWrap -from reflex.components.base.error_boundary import ErrorBoundary -from reflex.components.base.fragment import Fragment -from reflex.components.base.strict_mode import StrictMode from reflex.components.component import ( CUSTOM_COMPONENTS, Component, ComponentStyle, evaluate_style_namespaces, ) -from reflex.components.core.banner import ( - backend_disabled, - connection_pulser, - connection_toaster, -) -from reflex.components.core.breakpoints import set_breakpoints -from reflex.components.core.sticky import sticky -from reflex.components.radix import themes -from reflex.components.sonner.toast import toast from reflex.config import get_config from reflex.environment import ExecutorType, environment from reflex.event import ( @@ -156,7 +156,7 @@ def default_backend_exception_handler(exception: Exception) -> EventSpec: EventSpec: The window alert event. """ - from reflex.components.sonner.toast import toast + from reflex_components.sonner.toast import toast error = traceback.format_exc() @@ -696,7 +696,7 @@ def _add_default_endpoints(self): def _add_optional_endpoints(self): """Add optional api endpoints (_upload).""" - from reflex.components.core.upload import Upload, get_upload_dir + from reflex_components.core.upload import Upload, get_upload_dir if not self._api: return @@ -815,7 +815,7 @@ def add_page( if route == constants.Page404.SLUG: if component is None: - from reflex.components.el.elements import span + from reflex_components.el.elements import span component = span("404: Page not found") component = self._generate_component(component) diff --git a/reflex/compiler/compiler.py b/reflex/compiler/compiler.py index a097b2d99f2..7e88077bcc3 100644 --- a/reflex/compiler/compiler.py +++ b/reflex/compiler/compiler.py @@ -8,9 +8,10 @@ from pathlib import Path from typing import TYPE_CHECKING, Any +from reflex_components.base.fragment import Fragment + from reflex import constants from reflex.compiler import templates, utils -from reflex.components.base.fragment import Fragment from reflex.components.component import ( BaseComponent, Component, diff --git a/reflex/compiler/utils.py b/reflex/compiler/utils.py index f02edacbd71..c515b7c527a 100644 --- a/reflex/compiler/utils.py +++ b/reflex/compiler/utils.py @@ -12,14 +12,15 @@ from typing import Any, TypedDict from urllib.parse import urlparse +from reflex_components.base import Description, Image, Scripts +from reflex_components.base.document import Links, ScrollRestoration +from reflex_components.base.document import Meta as ReactMeta +from reflex_components.el.elements.metadata import Head, Link, Meta, Title +from reflex_components.el.elements.other import Html +from reflex_components.el.elements.sectioning import Body + from reflex import constants -from reflex.components.base import Description, Image, Scripts -from reflex.components.base.document import Links, ScrollRestoration -from reflex.components.base.document import Meta as ReactMeta from reflex.components.component import Component, ComponentStyle, CustomComponent -from reflex.components.el.elements.metadata import Head, Link, Meta, Title -from reflex.components.el.elements.other import Html -from reflex.components.el.elements.sectioning import Body from reflex.constants.state import FIELD_MARKER from reflex.istate.storage import Cookie, LocalStorage, SessionStorage from reflex.state import BaseState, _resolve_delta diff --git a/reflex/components/__init__.py b/reflex/components/__init__.py index 36364a3b851..70bb0d4ea20 100644 --- a/reflex/components/__init__.py +++ b/reflex/components/__init__.py @@ -1,34 +1,122 @@ -"""Import all the components.""" +"""Import all the components. + +Components have been moved to the ``reflex_components`` package. +This module re-exports them for backwards compatibility and installs +an import redirect hook so that ``from reflex.components. import X`` +continues to work by delegating to ``reflex_components.``. +""" from __future__ import annotations +import importlib +import importlib.abc +import importlib.machinery +import sys +from types import ModuleType + from reflex.utils import lazy_loader -_SUBMODULES: set[str] = { - "lucide", +# Subpackages that have moved to the reflex_components package. +_MOVED_SUBMODULES: frozenset[str] = frozenset({ + "base", "core", "datadisplay", + "el", "gridjs", + "lucide", "markdown", "moment", "plotly", "radix", "react_player", "react_router", - "sonner", - "el", - "base", "recharts", -} + "sonner", +}) + + +class _AliasLoader(importlib.abc.Loader): + """Loader that aliases one module name to another.""" + + def __init__(self, target_name: str): + self.target_name = target_name + def create_module(self, spec: importlib.machinery.ModuleSpec) -> ModuleType | None: + return None + + def exec_module(self, module: ModuleType) -> None: + target = importlib.import_module(self.target_name) + # Make the alias point to the real module. + module.__dict__.update(target.__dict__) + module.__path__ = getattr(target, "__path__", []) + module.__file__ = getattr(target, "__file__", None) + module.__loader__ = self + # Register the target module under the alias name so subsequent + # imports resolve immediately. + sys.modules[module.__name__] = target + + +class _ComponentsRedirect(importlib.abc.MetaPathFinder): + """Import hook: redirects ``reflex.components.`` to ``reflex_components.``.""" + + def find_spec( + self, + fullname: str, + path: object = None, + target: object = None, + ) -> importlib.machinery.ModuleSpec | None: + parts = fullname.split(".") + if ( + len(parts) >= 3 + and parts[0] == "reflex" + and parts[1] == "components" + and parts[2] in _MOVED_SUBMODULES + ): + target_name = "reflex_components." + ".".join(parts[2:]) + return importlib.machinery.ModuleSpec( + fullname, + _AliasLoader(target_name), + is_package=True, + ) + return None + + +# Install the import redirect hook. +if not any(isinstance(f, _ComponentsRedirect) for f in sys.meta_path): + sys.meta_path.insert(0, _ComponentsRedirect()) + + +# Submodules that still live in reflex.components (infrastructure). _SUBMOD_ATTRS: dict[str, list[str]] = { "component": [ "Component", "NoSSRComponent", ], } -__getattr__, __dir__, __all__ = lazy_loader.attach( + +_lazy_getattr, __dir__, __all__ = lazy_loader.attach( __name__, - submodules=_SUBMODULES, + submodules=set(), submod_attrs=_SUBMOD_ATTRS, ) + + +def __getattr__(name: str) -> object: + """Resolve attributes: first try local lazy loader, then delegate to reflex_components. + + Returns: + The requested attribute from this module or reflex_components. + + Raises: + AttributeError: If the attribute is not found in either this module or reflex_components. + """ + try: + return _lazy_getattr(name) + except AttributeError: + pass + if name in _MOVED_SUBMODULES: + import reflex_components + + return getattr(reflex_components, name) + msg = f"module {__name__!r} has no attribute {name!r}" + raise AttributeError(msg) diff --git a/reflex/components/component.py b/reflex/components/component.py index fbdc1decfda..116cf2f467a 100644 --- a/reflex/components/component.py +++ b/reflex/components/component.py @@ -17,13 +17,13 @@ from types import SimpleNamespace from typing import TYPE_CHECKING, Any, ClassVar, TypeVar, cast, get_args, get_origin +from reflex_components.core.breakpoints import Breakpoints from rich.markup import escape from typing_extensions import dataclass_transform import reflex.state from reflex import constants from reflex.compiler.templates import stateful_component_template -from reflex.components.core.breakpoints import Breakpoints from reflex.components.dynamic import load_dynamic_serializer from reflex.components.field import BaseField, FieldBasedMeta from reflex.components.tags import Tag @@ -1140,8 +1140,8 @@ def create(cls: type[T], *children, **props) -> T: The component. """ # Import here to avoid circular imports. - from reflex.components.base.bare import Bare - from reflex.components.base.fragment import Fragment + from reflex_components.base.bare import Bare + from reflex_components.base.fragment import Fragment # Filter out None props props = {key: value for key, value in props.items() if value is not None} @@ -1350,10 +1350,10 @@ def _validate_component_children(self, children: list[Component]): children: The children of the component. """ - from reflex.components.base.fragment import Fragment - from reflex.components.core.cond import Cond - from reflex.components.core.foreach import Foreach - from reflex.components.core.match import Match + from reflex_components.base.fragment import Fragment + from reflex_components.core.cond import Cond + from reflex_components.core.foreach import Foreach + from reflex_components.core.match import Match no_valid_parents_defined = all(child._valid_parents == [] for child in children) if ( @@ -2417,7 +2417,7 @@ def create(cls, component: Component) -> StatefulComponent | None: Returns: The stateful component or None if the component should not be memoized. """ - from reflex.components.core.foreach import Foreach + from reflex_components.core.foreach import Foreach if component._memoization_mode.disposition == MemoizationDisposition.NEVER: # Never memoize this component. @@ -2498,10 +2498,10 @@ def _child_var(child: Component) -> Var | Component: Returns: The Var from the child component or the child itself (for regular cases). """ - from reflex.components.base.bare import Bare - from reflex.components.core.cond import Cond - from reflex.components.core.foreach import Foreach - from reflex.components.core.match import Match + from reflex_components.base.bare import Bare + from reflex_components.core.cond import Cond + from reflex_components.core.foreach import Foreach + from reflex_components.core.match import Match if isinstance(child, Bare): return child.contents @@ -2851,7 +2851,7 @@ def empty_component() -> Component: Returns: An empty component. """ - from reflex.components.base.bare import Bare + from reflex_components.base.bare import Bare return Bare.create("") diff --git a/reflex/components/dynamic.py b/reflex/components/dynamic.py index 0b98a8844b0..03c85430ace 100644 --- a/reflex/components/dynamic.py +++ b/reflex/components/dynamic.py @@ -70,8 +70,9 @@ def make_component(component: Component) -> str: The generated code """ # Causes a circular import, so we import here. + from reflex_components.base.bare import Bare + from reflex.compiler import compiler, templates, utils - from reflex.components.base.bare import Bare component = Bare.create(Var.create(component)) diff --git a/reflex/components/tags/iter_tag.py b/reflex/components/tags/iter_tag.py index 64b7e1e9ad2..2f4db2ebd82 100644 --- a/reflex/components/tags/iter_tag.py +++ b/reflex/components/tags/iter_tag.py @@ -79,10 +79,11 @@ def render_component(self) -> Component: ValueError: If the render function doesn't return a component. """ # Import here to avoid circular imports. + from reflex_components.base.fragment import Fragment + from reflex_components.core.cond import Cond + from reflex_components.core.foreach import Foreach + from reflex.compiler.compiler import _into_component_once - from reflex.components.base.fragment import Fragment - from reflex.components.core.cond import Cond - from reflex.components.core.foreach import Foreach # Get the render function arguments. args = inspect.getfullargspec(self.render_fn).args diff --git a/reflex/config.py b/reflex/config.py index 56e23fd13a0..8a65d4d0a17 100644 --- a/reflex/config.py +++ b/reflex/config.py @@ -176,7 +176,7 @@ class BaseConfig: state_auto_setters: Whether to automatically create setters for state base vars. show_built_with_reflex: Whether to display the sticky "Built with Reflex" badge on all pages. is_reflex_cloud: Whether the app is running in the reflex cloud environment. - extra_overlay_function: Extra overlay function to run after the app is built. Formatted such that `from path_0.path_1... import path[-1]`, and calling it with no arguments would work. For example, "reflex.components.moment.moment". + extra_overlay_function: Extra overlay function to run after the app is built. Formatted such that `from path_0.path_1... import path[-1]`, and calling it with no arguments would work. For example, "reflex_components.moment.moment". plugins: List of plugins to use in the app. disable_plugins: List of plugin types to disable in the app. transport: The transport method for client-server communication. diff --git a/reflex/constants/colors.py b/reflex/constants/colors.py index f769f8099b6..bad9edc11a1 100644 --- a/reflex/constants/colors.py +++ b/reflex/constants/colors.py @@ -68,7 +68,7 @@ def format_color( if isinstance(alpha, bool): return f"var(--{color}-{'a' if alpha else ''}{shade})" - from reflex.components.core import cond + from reflex_components.core import cond alpha_var = cond(alpha, "a", "") return f"var(--{color}-{alpha_var}{shade})" diff --git a/reflex/event.py b/reflex/event.py index 959c57aa11e..50f7afa65fc 100644 --- a/reflex/event.py +++ b/reflex/event.py @@ -880,7 +880,7 @@ def as_event_spec(self, handler: EventHandler) -> EventSpec: Raises: ValueError: If the on_upload_progress is not a valid event handler. """ - from reflex.components.core.upload import ( + from reflex_components.core.upload import ( DEFAULT_UPLOAD_ID, upload_files_context_var_data, ) @@ -1258,7 +1258,7 @@ def download( ValueError: If the URL provided is invalid, both URL and data are provided, or the data is not an expected type. """ - from reflex.components.core.cond import cond + from reflex_components.core.cond import cond if isinstance(url, str): if not url.startswith("/"): diff --git a/reflex/experimental/__init__.py b/reflex/experimental/__init__.py index 2734da19112..260c6c2c194 100644 --- a/reflex/experimental/__init__.py +++ b/reflex/experimental/__init__.py @@ -2,7 +2,8 @@ from types import SimpleNamespace -from reflex.components.datadisplay.shiki_code_block import code_block as code_block +from reflex_components.datadisplay.shiki_code_block import code_block as code_block + from reflex.utils.console import warn from reflex.utils.misc import run_in_thread diff --git a/reflex/state.py b/reflex/state.py index a105f9039d4..83840975581 100644 --- a/reflex/state.py +++ b/reflex/state.py @@ -2554,7 +2554,7 @@ def dynamic(func: Callable[[T], Component]): state_class: type[T] = values[0] def wrapper() -> Component: - from reflex.components.base.fragment import fragment + from reflex_components.base.fragment import fragment return fragment(state_class._evaluate(lambda state: func(state))) diff --git a/reflex/style.py b/reflex/style.py index 26cc4955f85..e3964dc4740 100644 --- a/reflex/style.py +++ b/reflex/style.py @@ -5,8 +5,9 @@ from collections.abc import Mapping from typing import Any, Literal +from reflex_components.core.breakpoints import Breakpoints, breakpoints_values + from reflex import constants -from reflex.components.core.breakpoints import Breakpoints, breakpoints_values from reflex.event import EventChain, EventHandler, EventSpec, run_script from reflex.utils import format from reflex.utils.exceptions import ReflexError diff --git a/reflex/utils/codespaces.py b/reflex/utils/codespaces.py index 03954911952..b339b1796de 100644 --- a/reflex/utils/codespaces.py +++ b/reflex/utils/codespaces.py @@ -4,13 +4,13 @@ import os +from reflex_components.base.script import Script +from reflex_components.core.banner import has_connection_errors +from reflex_components.core.cond import cond from starlette.requests import Request from starlette.responses import HTMLResponse -from reflex.components.base.script import Script from reflex.components.component import Component -from reflex.components.core.banner import has_connection_errors -from reflex.components.core.cond import cond from reflex.constants import Endpoint from reflex.utils.decorator import once diff --git a/reflex/utils/misc.py b/reflex/utils/misc.py index 0eb4d245771..d0a84e79338 100644 --- a/reflex/utils/misc.py +++ b/reflex/utils/misc.py @@ -103,7 +103,7 @@ def preload_color_theme(): Returns: Script: A script component to add to App.head_components """ - from reflex.components.el.elements.scripts import Script + from reflex_components.el.elements.scripts import Script # Create direct inline script content (like next-themes dangerouslySetInnerHTML) script_content = """ diff --git a/reflex/utils/pyi_generator.py b/reflex/utils/pyi_generator.py index 3dae7ff4c62..d442462c804 100644 --- a/reflex/utils/pyi_generator.py +++ b/reflex/utils/pyi_generator.py @@ -83,7 +83,7 @@ # TODO: fix import ordering and unused imports with ruff later DEFAULT_IMPORTS = { "typing": sorted(DEFAULT_TYPING_IMPORTS), - "reflex.components.core.breakpoints": ["Breakpoints"], + "reflex_components.core.breakpoints": ["Breakpoints"], "reflex.event": [ "EventChain", "EventHandler", @@ -1240,6 +1240,45 @@ def _write_pyi_file(module_path: Path, source: str) -> str: return md5(pyi_content.encode()).hexdigest() +# Component subpackages that have moved to the reflex_components package. +_MOVED_COMPONENT_SUBMODULES: frozenset[str] = frozenset({ + "base", + "core", + "datadisplay", + "el", + "gridjs", + "lucide", + "markdown", + "moment", + "plotly", + "radix", + "react_player", + "react_router", + "recharts", + "sonner", +}) + + +def _rewrite_component_import(module: str) -> str: + """Rewrite a lazy-loader module path to an absolute ``reflex_components`` import when needed. + + Args: + module: The module path from ``_SUBMOD_ATTRS`` (e.g. ``"components.radix.themes.base"``). + + Returns: + An absolute import path (``"reflex_components.radix.themes.base"``) for moved + components, or a relative path (``".components.component"``) for everything else. + """ + if module == "components": + return "reflex_components" + if module.startswith("components."): + rest = module[len("components.") :] + first_part = rest.split(".")[0] + if first_part in _MOVED_COMPONENT_SUBMODULES: + return f"reflex_components.{rest}" + return f".{module}" + + def _get_init_lazy_imports(mod: tuple | ModuleType, new_tree: ast.AST): # retrieve the _SUBMODULES and _SUBMOD_ATTRS from an init file if present. sub_mods: set[str] | None = getattr(mod, "_SUBMODULES", None) @@ -1266,7 +1305,7 @@ def _get_init_lazy_imports(mod: tuple | ModuleType, new_tree: ast.AST): } # construct the import statement and handle special cases for aliases sub_mod_attrs_imports = [ - f"from .{module} import " + f"from {_rewrite_component_import(module)} import " + ( ( (imported[0] + " as " + imported[1]) diff --git a/reflex/utils/types.py b/reflex/utils/types.py index b375157d556..dc4480d1ca1 100644 --- a/reflex/utils/types.py +++ b/reflex/utils/types.py @@ -34,13 +34,13 @@ from typing import get_origin as get_origin_og from typing import get_type_hints as get_type_hints_og +from reflex_components.core.breakpoints import Breakpoints from typing_extensions import Self as Self from typing_extensions import override as override import reflex from reflex import constants from reflex.base import Base -from reflex.components.core.breakpoints import Breakpoints from reflex.utils import console # Potential GenericAlias types for isinstance checks. diff --git a/reflex/vars/object.py b/reflex/vars/object.py index 1ecb09394ba..c044efd5546 100644 --- a/reflex/vars/object.py +++ b/reflex/vars/object.py @@ -251,7 +251,7 @@ def get(self, key: Var | Any, default: Var | Any | None = None) -> Var: Returns: The item from the object. """ - from reflex.components.core.cond import cond + from reflex_components.core.cond import cond if default is None: default = Var.create(None) diff --git a/scripts/make_pyi.py b/scripts/make_pyi.py index f4121d755be..e8014f92bb4 100644 --- a/scripts/make_pyi.py +++ b/scripts/make_pyi.py @@ -12,7 +12,12 @@ LAST_RUN_COMMIT_SHA_FILE = Path(".pyi_generator_last_run").resolve() GENERATOR_FILE = Path(__file__).resolve() GENERATOR_DIFF_FILE = Path(".pyi_generator_diff").resolve() -DEFAULT_TARGETS = ["reflex/components", "reflex/experimental", "reflex/__init__.py"] +DEFAULT_TARGETS = [ + "reflex/components", + "reflex/experimental", + "reflex/__init__.py", + "packages/reflex-components/src/reflex_components", +] def _git_diff(args: list[str]) -> str: diff --git a/tests/integration/test_extra_overlay_function.py b/tests/integration/test_extra_overlay_function.py index 95dcae3e2b4..f8323aa84aa 100644 --- a/tests/integration/test_extra_overlay_function.py +++ b/tests/integration/test_extra_overlay_function.py @@ -25,7 +25,7 @@ def index(): app = rx.App() rx.config.get_config().extra_overlay_function = ( - "reflex.components.radix.themes.components.button" + "reflex_components.radix.themes.components.button" ) app.add_page(index) diff --git a/tests/integration/test_icon.py b/tests/integration/test_icon.py index 43313532a62..047b05fee4f 100644 --- a/tests/integration/test_icon.py +++ b/tests/integration/test_icon.py @@ -3,15 +3,16 @@ from collections.abc import Generator import pytest +from reflex_components.lucide.icon import LUCIDE_ICON_LIST from selenium.webdriver.common.by import By -from reflex.components.lucide.icon import LUCIDE_ICON_LIST from reflex.testing import AppHarness, WebDriver def Icons(): + from reflex_components.lucide.icon import LUCIDE_ICON_LIST + import reflex as rx - from reflex.components.lucide.icon import LUCIDE_ICON_LIST app = rx.App() diff --git a/tests/units/compiler/test_compiler.py b/tests/units/compiler/test_compiler.py index 1a0f87b89a3..7baff787971 100644 --- a/tests/units/compiler/test_compiler.py +++ b/tests/units/compiler/test_compiler.py @@ -4,11 +4,11 @@ import pytest from pytest_mock import MockerFixture +from reflex_components.base import document +from reflex_components.el.elements.metadata import Link from reflex import constants from reflex.compiler import compiler, utils -from reflex.components.base import document -from reflex.components.el.elements.metadata import Link from reflex.constants.compiler import PageNames from reflex.utils.imports import ImportVar, ParsedImportDict from reflex.vars.base import Var diff --git a/tests/units/components/base/test_bare.py b/tests/units/components/base/test_bare.py index d36813badf2..ada6c9505cf 100644 --- a/tests/units/components/base/test_bare.py +++ b/tests/units/components/base/test_bare.py @@ -1,6 +1,6 @@ import pytest +from reflex_components.base.bare import Bare -from reflex.components.base.bare import Bare from reflex.vars.base import Var STATE_VAR = Var(_js_expr="default_state.name") diff --git a/tests/units/components/base/test_link.py b/tests/units/components/base/test_link.py index 3c1260e1293..1a6c2a171af 100644 --- a/tests/units/components/base/test_link.py +++ b/tests/units/components/base/test_link.py @@ -1,4 +1,4 @@ -from reflex.components.base.link import RawLink, ScriptTag +from reflex_components.base.link import RawLink, ScriptTag def test_raw_link(): diff --git a/tests/units/components/base/test_script.py b/tests/units/components/base/test_script.py index a279dfefc1e..e41ba701367 100644 --- a/tests/units/components/base/test_script.py +++ b/tests/units/components/base/test_script.py @@ -1,8 +1,7 @@ """Test that element script renders correctly.""" import pytest - -from reflex.components.base.script import Script +from reflex_components.base.script import Script def test_script_inline(): diff --git a/tests/units/components/core/test_banner.py b/tests/units/components/core/test_banner.py index 78646447d6d..6080f4d9425 100644 --- a/tests/units/components/core/test_banner.py +++ b/tests/units/components/core/test_banner.py @@ -1,11 +1,11 @@ -from reflex.components.core.banner import ( +from reflex_components.core.banner import ( ConnectionBanner, ConnectionModal, ConnectionPulser, WebsocketTargetURL, ) -from reflex.components.radix.themes.base import RadixThemesComponent -from reflex.components.radix.themes.typography.text import Text +from reflex_components.radix.themes.base import RadixThemesComponent +from reflex_components.radix.themes.typography.text import Text def test_websocket_target_url(): diff --git a/tests/units/components/core/test_colors.py b/tests/units/components/core/test_colors.py index aaa82959c92..e1bcf722eb7 100644 --- a/tests/units/components/core/test_colors.py +++ b/tests/units/components/core/test_colors.py @@ -1,7 +1,7 @@ import pytest +from reflex_components.datadisplay.code import CodeBlock import reflex as rx -from reflex.components.datadisplay.code import CodeBlock from reflex.constants.colors import Color from reflex.constants.state import FIELD_MARKER from reflex.vars.base import LiteralVar diff --git a/tests/units/components/core/test_cond.py b/tests/units/components/core/test_cond.py index 0e1df51d067..c1dcb4d0137 100644 --- a/tests/units/components/core/test_cond.py +++ b/tests/units/components/core/test_cond.py @@ -2,10 +2,10 @@ from typing import Any import pytest +from reflex_components.base.fragment import Fragment +from reflex_components.core.cond import Cond, cond +from reflex_components.radix.themes.typography.text import Text -from reflex.components.base.fragment import Fragment -from reflex.components.core.cond import Cond, cond -from reflex.components.radix.themes.typography.text import Text from reflex.constants.state import FIELD_MARKER from reflex.state import BaseState from reflex.utils.format import format_state_name diff --git a/tests/units/components/core/test_debounce.py b/tests/units/components/core/test_debounce.py index 43cca255c70..f5b7247d4f0 100644 --- a/tests/units/components/core/test_debounce.py +++ b/tests/units/components/core/test_debounce.py @@ -1,9 +1,9 @@ """Test that DebounceInput collapses nested forms.""" import pytest +from reflex_components.core.debounce import DEFAULT_DEBOUNCE_TIMEOUT import reflex as rx -from reflex.components.core.debounce import DEFAULT_DEBOUNCE_TIMEOUT from reflex.state import BaseState from reflex.vars.base import LiteralVar, Var diff --git a/tests/units/components/core/test_foreach.py b/tests/units/components/core/test_foreach.py index 01b84bdaef6..c3946d6ec7e 100644 --- a/tests/units/components/core/test_foreach.py +++ b/tests/units/components/core/test_foreach.py @@ -1,17 +1,17 @@ import pytest - -import reflex as rx -from reflex import el -from reflex.base import Base -from reflex.components.component import Component -from reflex.components.core.foreach import ( +from reflex_components.core.foreach import ( Foreach, ForeachRenderError, ForeachVarError, foreach, ) -from reflex.components.radix.themes.layout.box import box -from reflex.components.radix.themes.typography.text import text +from reflex_components.radix.themes.layout.box import box +from reflex_components.radix.themes.typography.text import text + +import reflex as rx +from reflex import el +from reflex.base import Base +from reflex.components.component import Component from reflex.constants.state import FIELD_MARKER from reflex.state import BaseState, ComponentState from reflex.vars.number import NumberVar diff --git a/tests/units/components/core/test_html.py b/tests/units/components/core/test_html.py index 8ab466a4e33..27780481508 100644 --- a/tests/units/components/core/test_html.py +++ b/tests/units/components/core/test_html.py @@ -1,6 +1,6 @@ import pytest +from reflex_components.core.html import Html -from reflex.components.core.html import Html from reflex.state import State diff --git a/tests/units/components/core/test_match.py b/tests/units/components/core/test_match.py index 8fd1865fde6..98b3f4228ee 100644 --- a/tests/units/components/core/test_match.py +++ b/tests/units/components/core/test_match.py @@ -1,10 +1,10 @@ import re import pytest +from reflex_components.core.match import Match import reflex as rx from reflex.components.component import Component -from reflex.components.core.match import Match from reflex.constants.state import FIELD_MARKER from reflex.state import BaseState from reflex.utils.exceptions import MatchTypeError @@ -141,7 +141,7 @@ def test_match_on_component_without_default(): """Test that matching cases with return values as components returns a Fragment as the default case if not provided. """ - from reflex.components.base.fragment import Fragment + from reflex_components.base.fragment import Fragment match_case_tuples = ( (1, rx.text("first value")), @@ -264,7 +264,7 @@ def test_match_case_tuple_elements(match_case): ), ( 'Match cases should have the same return types. Case 3 with return value `jsx(RadixThemesText,{as:"p"},"first value")` ' - "of type is not " + "of type is not " ), ), ], diff --git a/tests/units/components/core/test_responsive.py b/tests/units/components/core/test_responsive.py index 6424ed3c3d9..03b50116abe 100644 --- a/tests/units/components/core/test_responsive.py +++ b/tests/units/components/core/test_responsive.py @@ -1,11 +1,11 @@ -from reflex.components.core.responsive import ( +from reflex_components.core.responsive import ( desktop_only, mobile_and_tablet, mobile_only, tablet_and_desktop, tablet_only, ) -from reflex.components.radix.themes.layout.box import Box +from reflex_components.radix.themes.layout.box import Box def test_mobile_only(): diff --git a/tests/units/components/core/test_upload.py b/tests/units/components/core/test_upload.py index 3b03362d6e4..cbf201b2f08 100644 --- a/tests/units/components/core/test_upload.py +++ b/tests/units/components/core/test_upload.py @@ -1,7 +1,6 @@ from typing import Any -from reflex import event -from reflex.components.core.upload import ( +from reflex_components.core.upload import ( StyledUpload, Upload, UploadNamespace, @@ -9,6 +8,8 @@ cancel_upload, get_upload_url, ) + +from reflex import event from reflex.event import EventSpec from reflex.state import State from reflex.vars.base import LiteralVar, Var diff --git a/tests/units/components/datadisplay/test_code.py b/tests/units/components/datadisplay/test_code.py index 85d60cf60b6..1bf1a19bb78 100644 --- a/tests/units/components/datadisplay/test_code.py +++ b/tests/units/components/datadisplay/test_code.py @@ -1,6 +1,5 @@ import pytest - -from reflex.components.datadisplay.code import CodeBlock, Theme +from reflex_components.datadisplay.code import CodeBlock, Theme @pytest.mark.parametrize( diff --git a/tests/units/components/datadisplay/test_dataeditor.py b/tests/units/components/datadisplay/test_dataeditor.py index 6b0d3ac6103..212a52922c7 100644 --- a/tests/units/components/datadisplay/test_dataeditor.py +++ b/tests/units/components/datadisplay/test_dataeditor.py @@ -1,4 +1,4 @@ -from reflex.components.datadisplay.dataeditor import DataEditor +from reflex_components.datadisplay.dataeditor import DataEditor def test_dataeditor(): diff --git a/tests/units/components/datadisplay/test_datatable.py b/tests/units/components/datadisplay/test_datatable.py index 8142870e465..308bcf1f85f 100644 --- a/tests/units/components/datadisplay/test_datatable.py +++ b/tests/units/components/datadisplay/test_datatable.py @@ -1,8 +1,8 @@ import pandas as pd import pytest +from reflex_components.gridjs.datatable import DataTable import reflex as rx -from reflex.components.gridjs.datatable import DataTable from reflex.constants.state import FIELD_MARKER from reflex.utils import types from reflex.utils.exceptions import UntypedComputedVarError diff --git a/tests/units/components/datadisplay/test_shiki_code.py b/tests/units/components/datadisplay/test_shiki_code.py index 05553815409..9fe2c494c61 100644 --- a/tests/units/components/datadisplay/test_shiki_code.py +++ b/tests/units/components/datadisplay/test_shiki_code.py @@ -1,14 +1,14 @@ import pytest - -from reflex.components.datadisplay.shiki_code_block import ( +from reflex_components.datadisplay.shiki_code_block import ( ShikiBaseTransformers, ShikiCodeBlock, ShikiHighLevelCodeBlock, ShikiJsTransformer, ) -from reflex.components.el.elements.forms import Button -from reflex.components.lucide.icon import Icon -from reflex.components.radix.themes.layout.box import Box +from reflex_components.el.elements.forms import Button +from reflex_components.lucide.icon import Icon +from reflex_components.radix.themes.layout.box import Box + from reflex.style import Style from reflex.vars import Var from reflex.vars.base import LiteralVar diff --git a/tests/units/components/el/test_svg.py b/tests/units/components/el/test_svg.py index d7d46898508..f409b80cce9 100644 --- a/tests/units/components/el/test_svg.py +++ b/tests/units/components/el/test_svg.py @@ -1,4 +1,4 @@ -from reflex.components.el.elements.media import ( +from reflex_components.el.elements.media import ( Circle, Defs, Ellipse, diff --git a/tests/units/components/forms/test_form.py b/tests/units/components/forms/test_form.py index 6b3d4a06001..f29e4b4382c 100644 --- a/tests/units/components/forms/test_form.py +++ b/tests/units/components/forms/test_form.py @@ -1,4 +1,5 @@ -from reflex.components.radix.primitives.form import Form +from reflex_components.radix.primitives.form import Form + from reflex.event import EventChain, prevent_default from reflex.vars.base import Var diff --git a/tests/units/components/graphing/test_recharts.py b/tests/units/components/graphing/test_recharts.py index c1b986dfda5..52dd57ec306 100644 --- a/tests/units/components/graphing/test_recharts.py +++ b/tests/units/components/graphing/test_recharts.py @@ -1,4 +1,4 @@ -from reflex.components.recharts.charts import ( +from reflex_components.recharts.charts import ( AreaChart, BarChart, LineChart, @@ -7,7 +7,7 @@ RadialBarChart, ScatterChart, ) -from reflex.components.recharts.general import ResponsiveContainer +from reflex_components.recharts.general import ResponsiveContainer def test_area_chart(): diff --git a/tests/units/components/lucide/test_icon.py b/tests/units/components/lucide/test_icon.py index 0f183abc7ae..da9442275f8 100644 --- a/tests/units/components/lucide/test_icon.py +++ b/tests/units/components/lucide/test_icon.py @@ -1,10 +1,10 @@ import pytest - -from reflex.components.lucide.icon import ( +from reflex_components.lucide.icon import ( LUCIDE_ICON_LIST, LUCIDE_ICON_MAPPING_OVERRIDE, Icon, ) + from reflex.utils import format diff --git a/tests/units/components/markdown/test_markdown.py b/tests/units/components/markdown/test_markdown.py index b91084a09fa..91b83ad3844 100644 --- a/tests/units/components/markdown/test_markdown.py +++ b/tests/units/components/markdown/test_markdown.py @@ -1,11 +1,11 @@ import pytest +from reflex_components.datadisplay.code import CodeBlock +from reflex_components.datadisplay.shiki_code_block import ShikiHighLevelCodeBlock +from reflex_components.markdown.markdown import Markdown, MarkdownComponentMap +from reflex_components.radix.themes.layout.box import Box +from reflex_components.radix.themes.typography.heading import Heading from reflex.components.component import Component, memo -from reflex.components.datadisplay.code import CodeBlock -from reflex.components.datadisplay.shiki_code_block import ShikiHighLevelCodeBlock -from reflex.components.markdown.markdown import Markdown, MarkdownComponentMap -from reflex.components.radix.themes.layout.box import Box -from reflex.components.radix.themes.typography.heading import Heading from reflex.vars.base import Var diff --git a/tests/units/components/radix/test_icon_button.py b/tests/units/components/radix/test_icon_button.py index 8047cf7b2d9..cfe61ab6d67 100644 --- a/tests/units/components/radix/test_icon_button.py +++ b/tests/units/components/radix/test_icon_button.py @@ -1,7 +1,7 @@ import pytest +from reflex_components.lucide.icon import Icon +from reflex_components.radix.themes.components.icon_button import IconButton -from reflex.components.lucide.icon import Icon -from reflex.components.radix.themes.components.icon_button import IconButton from reflex.style import Style from reflex.vars.base import LiteralVar diff --git a/tests/units/components/radix/test_layout.py b/tests/units/components/radix/test_layout.py index 73fcde2a87a..be8e31ead54 100644 --- a/tests/units/components/radix/test_layout.py +++ b/tests/units/components/radix/test_layout.py @@ -1,4 +1,4 @@ -from reflex.components.radix.themes.layout.base import LayoutComponent +from reflex_components.radix.themes.layout.base import LayoutComponent def test_layout_component(): diff --git a/tests/units/components/recharts/test_cartesian.py b/tests/units/components/recharts/test_cartesian.py index 3337427bd13..fbbe70d6308 100644 --- a/tests/units/components/recharts/test_cartesian.py +++ b/tests/units/components/recharts/test_cartesian.py @@ -1,4 +1,4 @@ -from reflex.components.recharts import ( +from reflex_components.recharts import ( Area, Bar, Brush, diff --git a/tests/units/components/recharts/test_polar.py b/tests/units/components/recharts/test_polar.py index 4e4af0f498c..c2918e1b909 100644 --- a/tests/units/components/recharts/test_polar.py +++ b/tests/units/components/recharts/test_polar.py @@ -1,4 +1,4 @@ -from reflex.components.recharts import ( +from reflex_components.recharts import ( Pie, PolarAngleAxis, PolarGrid, diff --git a/tests/units/components/test_component.py b/tests/units/components/test_component.py index 959c81c34bb..a4368e08a67 100644 --- a/tests/units/components/test_component.py +++ b/tests/units/components/test_component.py @@ -2,12 +2,13 @@ from typing import Any, ClassVar import pytest +from reflex_components.base.bare import Bare +from reflex_components.base.fragment import Fragment +from reflex_components.radix.themes.layout.box import Box import reflex as rx from reflex.base import Base from reflex.compiler.utils import compile_custom_component -from reflex.components.base.bare import Bare -from reflex.components.base.fragment import Fragment from reflex.components.component import ( CUSTOM_COMPONENTS, Component, @@ -15,7 +16,6 @@ StatefulComponent, custom_component, ) -from reflex.components.radix.themes.layout.box import Box from reflex.constants import EventTriggers from reflex.constants.state import FIELD_MARKER from reflex.event import ( @@ -861,7 +861,7 @@ def my_component(width: Var[int], color: Var[str]): color=color, ) - from reflex.components.radix.themes.typography.text import Text + from reflex_components.radix.themes.typography.text import Text ccomponent = my_component( rx.text("child"), width=LiteralVar.create(1), color=LiteralVar.create("red") diff --git a/tests/units/components/test_component_state.py b/tests/units/components/test_component_state.py index 1b62e35c81d..b27d86d05ae 100644 --- a/tests/units/components/test_component_state.py +++ b/tests/units/components/test_component_state.py @@ -1,9 +1,9 @@ """Ensure that Components returned by ComponentState.create have independent State classes.""" import pytest +from reflex_components.base.bare import Bare import reflex as rx -from reflex.components.base.bare import Bare from reflex.utils.exceptions import ReflexRuntimeError diff --git a/tests/units/components/typography/test_markdown.py b/tests/units/components/typography/test_markdown.py index d104d4c3812..e084c52e995 100644 --- a/tests/units/components/typography/test_markdown.py +++ b/tests/units/components/typography/test_markdown.py @@ -1,7 +1,7 @@ import pytest +from reflex_components.markdown.markdown import Markdown import reflex as rx -from reflex.components.markdown.markdown import Markdown @pytest.mark.parametrize( diff --git a/tests/units/test_app.py b/tests/units/test_app.py index 25c71c0d17e..2d9248124a4 100644 --- a/tests/units/test_app.py +++ b/tests/units/test_app.py @@ -15,6 +15,10 @@ import pytest from pytest_mock import MockerFixture +from reflex_components.base.bare import Bare +from reflex_components.base.fragment import Fragment +from reflex_components.core.cond import Cond +from reflex_components.radix.themes.typography.text import Text from starlette.applications import Starlette from starlette.datastructures import FormData, UploadFile from starlette.responses import StreamingResponse @@ -29,10 +33,6 @@ upload, ) from reflex.components import Component -from reflex.components.base.bare import Bare -from reflex.components.base.fragment import Fragment -from reflex.components.core.cond import Cond -from reflex.components.radix.themes.typography.text import Text from reflex.constants.state import FIELD_MARKER from reflex.environment import environment from reflex.event import Event @@ -1842,7 +1842,7 @@ def test_call_app(): def test_app_with_optional_endpoints(): - from reflex.components.core.upload import Upload + from reflex_components.core.upload import Upload app = App() Upload.is_used = True diff --git a/tests/units/test_event.py b/tests/units/test_event.py index c413a1f225e..34e111dd843 100644 --- a/tests/units/test_event.py +++ b/tests/units/test_event.py @@ -640,7 +640,7 @@ async def handle_old_background(self): def test_event_var_in_rx_cond(): """Test that EventVar and EventChainVar cannot be used in rx.cond().""" - from reflex.components.core.cond import cond as rx_cond + from reflex_components.core.cond import cond as rx_cond class S(BaseState): @event diff --git a/tests/units/test_var.py b/tests/units/test_var.py index 402505c0543..51a0195eae2 100644 --- a/tests/units/test_var.py +++ b/tests/units/test_var.py @@ -1896,7 +1896,7 @@ class StateWithVar(rx.State): field: int = 1 mocker.patch( - "reflex.components.base.bare.get_performance_mode", + "reflex_components.base.bare.get_performance_mode", return_value=PerformanceMode.RAISE, ) @@ -1906,7 +1906,7 @@ class StateWithVar(rx.State): ) mocker.patch( - "reflex.components.base.bare.get_performance_mode", + "reflex_components.base.bare.get_performance_mode", return_value=PerformanceMode.OFF, ) diff --git a/tests/units/utils/test_serializers.py b/tests/units/utils/test_serializers.py index 6c085c4ceef..3370fa79c5a 100644 --- a/tests/units/utils/test_serializers.py +++ b/tests/units/utils/test_serializers.py @@ -6,9 +6,9 @@ from typing import Any import pytest +from reflex_components.core.colors import Color from reflex.base import Base -from reflex.components.core.colors import Color from reflex.utils import serializers from reflex.utils.format import json_dumps from reflex.vars.base import LiteralVar diff --git a/uv.lock b/uv.lock index 36ddb410075..cfa4401e06d 100644 --- a/uv.lock +++ b/uv.lock @@ -14,6 +14,7 @@ resolution-markers = [ [manifest] members = [ "reflex", + "reflex-components", "reflex-docgen", ] @@ -2041,6 +2042,7 @@ dependencies = [ { name = "python-multipart" }, { name = "python-socketio" }, { name = "redis" }, + { name = "reflex-components" }, { name = "reflex-hosting-cli" }, { name = "rich" }, { name = "sqlmodel" }, @@ -2108,6 +2110,7 @@ requires-dist = [ { name = "python-multipart", specifier = ">=0.0.20,<1.0" }, { name = "python-socketio", specifier = ">=5.12.0,<6.0" }, { name = "redis", specifier = ">=5.2.1,<8.0" }, + { name = "reflex-components", editable = "packages/reflex-components" }, { name = "reflex-hosting-cli", specifier = ">=0.1.61" }, { name = "rich", specifier = ">=13,<15" }, { name = "sqlmodel", specifier = ">=0.0.27,<0.1" }, @@ -2153,6 +2156,11 @@ dev = [ { name = "uvicorn" }, ] +[[package]] +name = "reflex-components" +version = "0.0.1" +source = { editable = "packages/reflex-components" } + [[package]] name = "reflex-docgen" version = "0.0.1" From 300c6d9e8c07558c361eea2b5eac6251bb31ecf9 Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Tue, 24 Mar 2026 16:45:40 -0700 Subject: [PATCH 011/113] so mucn --- .../src/reflex_components/__init__.py | 9 +- .../src/reflex_components/base/app_wrap.py | 2 +- .../reflex_components/base/error_boundary.py | 4 +- .../src/reflex_components/base/link.py | 2 +- .../src/reflex_components/base/meta.py | 4 +- .../src/reflex_components/base/script.py | 4 +- .../src/reflex_components/core/auto_scroll.py | 2 +- .../src/reflex_components/core/banner.py | 21 +- .../src/reflex_components/core/clipboard.py | 2 +- .../src/reflex_components/core/cond.py | 2 +- .../src/reflex_components/core/foreach.py | 4 +- .../src/reflex_components/core/html.py | 2 +- .../src/reflex_components/core/match.py | 2 +- .../src/reflex_components/core/responsive.py | 2 +- .../src/reflex_components/core/sticky.py | 5 +- .../src/reflex_components/core/upload.py | 11 +- .../reflex_components/core/window_events.py | 2 +- .../src/reflex_components/datadisplay/code.py | 11 +- .../datadisplay/shiki_code_block.py | 13 +- .../src/reflex_components/el/__init__.py | 2 +- .../src/reflex_components/el/elements/base.py | 2 +- .../reflex_components/el/elements/forms.py | 2 +- .../reflex_components/el/elements/media.py | 2 +- .../reflex_components/el/elements/metadata.py | 2 +- .../reflex_components/el/elements/scripts.py | 2 +- packages/reflex-markdown/README.md | 1 + packages/reflex-markdown/pyproject.toml | 13 ++ .../src/reflex_markdown}/__init__.py | 0 .../src/reflex_markdown}/markdown.py | 27 +-- packages/reflex-plotly/README.md | 1 + packages/reflex-plotly/pyproject.toml | 13 ++ .../src/reflex_plotly}/__init__.py | 0 .../src/reflex_plotly}/plotly.py | 3 +- packages/reflex-radix/README.md | 1 + packages/reflex-radix/pyproject.toml | 13 ++ .../src/reflex_radix}/__init__.py | 0 .../src/reflex_radix}/primitives/__init__.py | 0 .../src/reflex_radix}/primitives/accordion.py | 7 +- .../src/reflex_radix}/primitives/base.py | 0 .../src/reflex_radix}/primitives/dialog.py | 3 +- .../src/reflex_radix}/primitives/drawer.py | 6 +- .../src/reflex_radix}/primitives/form.py | 5 +- .../src/reflex_radix}/primitives/progress.py | 9 +- .../src/reflex_radix}/primitives/slider.py | 2 +- .../src/reflex_radix}/themes/__init__.py | 0 .../src/reflex_radix}/themes/base.py | 3 +- .../src/reflex_radix}/themes/color_mode.py | 7 +- .../themes/components/__init__.py | 0 .../themes/components/alert_dialog.py | 8 +- .../themes/components/aspect_ratio.py | 2 +- .../reflex_radix}/themes/components/avatar.py | 7 +- .../reflex_radix}/themes/components/badge.py | 7 +- .../reflex_radix}/themes/components/button.py | 7 +- .../themes/components/callout.py | 7 +- .../reflex_radix}/themes/components/card.py | 5 +- .../themes/components/checkbox.py | 13 +- .../themes/components/checkbox_cards.py | 5 +- .../themes/components/checkbox_group.py | 5 +- .../themes/components/context_menu.py | 5 +- .../themes/components/data_list.py | 5 +- .../reflex_radix}/themes/components/dialog.py | 8 +- .../themes/components/dropdown_menu.py | 11 +- .../themes/components/hover_card.py | 8 +- .../themes/components/icon_button.py | 9 +- .../reflex_radix}/themes/components/inset.py | 5 +- .../themes/components/popover.py | 8 +- .../themes/components/progress.py | 5 +- .../reflex_radix}/themes/components/radio.py | 5 +- .../themes/components/radio_cards.py | 5 +- .../themes/components/radio_group.py | 17 +- .../themes/components/scroll_area.py | 2 +- .../themes/components/segmented_control.py | 5 +- .../reflex_radix}/themes/components/select.py | 11 +- .../themes/components/separator.py | 5 +- .../themes/components/skeleton.py | 5 +- .../reflex_radix}/themes/components/slider.py | 5 +- .../themes/components/spinner.py | 5 +- .../reflex_radix}/themes/components/switch.py | 5 +- .../reflex_radix}/themes/components/table.py | 5 +- .../reflex_radix}/themes/components/tabs.py | 5 +- .../themes/components/text_area.py | 7 +- .../themes/components/text_field.py | 13 +- .../themes/components/tooltip.py | 2 +- .../reflex_radix}/themes/layout/__init__.py | 0 .../src/reflex_radix}/themes/layout/base.py | 7 +- .../src/reflex_radix}/themes/layout/box.py | 3 +- .../src/reflex_radix}/themes/layout/center.py | 0 .../reflex_radix}/themes/layout/container.py | 5 +- .../src/reflex_radix}/themes/layout/flex.py | 7 +- .../src/reflex_radix}/themes/layout/grid.py | 7 +- .../src/reflex_radix}/themes/layout/list.py | 7 +- .../reflex_radix}/themes/layout/section.py | 5 +- .../src/reflex_radix}/themes/layout/spacer.py | 0 .../src/reflex_radix}/themes/layout/stack.py | 5 +- .../themes/typography/__init__.py | 0 .../reflex_radix}/themes/typography/base.py | 0 .../themes/typography/blockquote.py | 5 +- .../reflex_radix}/themes/typography/code.py | 9 +- .../themes/typography/heading.py | 7 +- .../reflex_radix}/themes/typography/link.py | 9 +- .../reflex_radix}/themes/typography/text.py | 7 +- packages/reflex-react-player/README.md | 1 + packages/reflex-react-player/pyproject.toml | 13 ++ .../src/reflex_react_player}/__init__.py | 0 .../src/reflex_react_player}/audio.py | 2 +- .../src/reflex_react_player}/react_player.py | 3 +- .../src/reflex_react_player}/video.py | 2 +- packages/reflex-react-router/README.md | 1 + packages/reflex-react-router/pyproject.toml | 13 ++ .../src/reflex_react_router}/__init__.py | 0 .../src/reflex_react_router}/dom.py | 3 +- packages/reflex-recharts/README.md | 1 + packages/reflex-recharts/pyproject.toml | 13 ++ .../src/reflex_recharts}/__init__.py | 0 .../src/reflex_recharts}/cartesian.py | 0 .../src/reflex_recharts}/charts.py | 2 +- .../src/reflex_recharts}/general.py | 0 .../src/reflex_recharts}/polar.py | 0 .../src/reflex_recharts}/recharts.py | 0 packages/reflex-sonner/README.md | 1 + packages/reflex-sonner/pyproject.toml | 13 ++ .../src/reflex_sonner}/__init__.py | 0 .../src/reflex_sonner}/toast.py | 3 +- pyi_hashes.json | 200 +++++++++--------- pyproject.toml | 20 +- reflex/app.py | 6 +- reflex/components/__init__.py | 70 +++--- reflex/utils/pyi_generator.py | 74 ++++--- scripts/make_pyi.py | 7 + .../test_extra_overlay_function.py | 2 +- tests/units/components/core/test_banner.py | 4 +- tests/units/components/core/test_cond.py | 2 +- tests/units/components/core/test_foreach.py | 4 +- tests/units/components/core/test_match.py | 2 +- .../units/components/core/test_responsive.py | 2 +- .../components/datadisplay/test_shiki_code.py | 2 +- tests/units/components/forms/test_form.py | 2 +- .../components/graphing/test_recharts.py | 4 +- .../components/markdown/test_markdown.py | 6 +- .../components/radix/test_icon_button.py | 2 +- tests/units/components/radix/test_layout.py | 2 +- .../components/recharts/test_cartesian.py | 11 +- tests/units/components/recharts/test_polar.py | 2 +- tests/units/components/test_component.py | 4 +- .../components/typography/test_markdown.py | 2 +- tests/units/test_app.py | 2 +- uv.lock | 56 +++++ 147 files changed, 666 insertions(+), 438 deletions(-) create mode 100644 packages/reflex-markdown/README.md create mode 100644 packages/reflex-markdown/pyproject.toml rename packages/{reflex-components/src/reflex_components/markdown => reflex-markdown/src/reflex_markdown}/__init__.py (100%) rename packages/{reflex-components/src/reflex_components/markdown => reflex-markdown/src/reflex_markdown}/markdown.py (95%) create mode 100644 packages/reflex-plotly/README.md create mode 100644 packages/reflex-plotly/pyproject.toml rename packages/{reflex-components/src/reflex_components/plotly => reflex-plotly/src/reflex_plotly}/__init__.py (100%) rename packages/{reflex-components/src/reflex_components/plotly => reflex-plotly/src/reflex_plotly}/plotly.py (99%) create mode 100644 packages/reflex-radix/README.md create mode 100644 packages/reflex-radix/pyproject.toml rename packages/{reflex-components/src/reflex_components/radix => reflex-radix/src/reflex_radix}/__init__.py (100%) rename packages/{reflex-components/src/reflex_components/radix => reflex-radix/src/reflex_radix}/primitives/__init__.py (100%) rename packages/{reflex-components/src/reflex_components/radix => reflex-radix/src/reflex_radix}/primitives/accordion.py (99%) rename packages/{reflex-components/src/reflex_components/radix => reflex-radix/src/reflex_radix}/primitives/base.py (100%) rename packages/{reflex-components/src/reflex_components/radix => reflex-radix/src/reflex_radix}/primitives/dialog.py (99%) rename packages/{reflex-components/src/reflex_components/radix => reflex-radix/src/reflex_radix}/primitives/drawer.py (97%) rename packages/{reflex-components/src/reflex_components/radix => reflex-radix/src/reflex_radix}/primitives/form.py (98%) rename packages/{reflex-components/src/reflex_components/radix => reflex-radix/src/reflex_radix}/primitives/progress.py (93%) rename packages/{reflex-components/src/reflex_components/radix => reflex-radix/src/reflex_radix}/primitives/slider.py (98%) rename packages/{reflex-components/src/reflex_components/radix => reflex-radix/src/reflex_radix}/themes/__init__.py (100%) rename packages/{reflex-components/src/reflex_components/radix => reflex-radix/src/reflex_radix}/themes/base.py (99%) rename packages/{reflex-components/src/reflex_components/radix => reflex-radix/src/reflex_radix}/themes/color_mode.py (97%) rename packages/{reflex-components/src/reflex_components/radix => reflex-radix/src/reflex_radix}/themes/components/__init__.py (100%) rename packages/{reflex-components/src/reflex_components/radix => reflex-radix/src/reflex_radix}/themes/components/alert_dialog.py (96%) rename packages/{reflex-components/src/reflex_components/radix => reflex-radix/src/reflex_radix}/themes/components/aspect_ratio.py (85%) rename packages/{reflex-components/src/reflex_components/radix => reflex-radix/src/reflex_radix}/themes/components/avatar.py (95%) rename packages/{reflex-components/src/reflex_components/radix => reflex-radix/src/reflex_radix}/themes/components/badge.py (95%) rename packages/{reflex-components/src/reflex_components/radix => reflex-radix/src/reflex_radix}/themes/components/button.py (96%) rename packages/{reflex-components/src/reflex_components/radix => reflex-radix/src/reflex_radix}/themes/components/callout.py (96%) rename packages/{reflex-components/src/reflex_components/radix => reflex-radix/src/reflex_radix}/themes/components/card.py (92%) rename packages/{reflex-components/src/reflex_components/radix => reflex-radix/src/reflex_radix}/themes/components/checkbox.py (96%) rename packages/{reflex-components/src/reflex_components/radix => reflex-radix/src/reflex_radix}/themes/components/checkbox_cards.py (94%) rename packages/{reflex-components/src/reflex_components/radix => reflex-radix/src/reflex_radix}/themes/components/checkbox_group.py (95%) rename packages/{reflex-components/src/reflex_components/radix => reflex-radix/src/reflex_radix}/themes/components/context_menu.py (99%) rename packages/{reflex-components/src/reflex_components/radix => reflex-radix/src/reflex_radix}/themes/components/data_list.py (95%) rename packages/{reflex-components/src/reflex_components/radix => reflex-radix/src/reflex_radix}/themes/components/dialog.py (96%) rename packages/{reflex-components/src/reflex_components/radix => reflex-radix/src/reflex_radix}/themes/components/dropdown_menu.py (99%) rename packages/{reflex-components/src/reflex_components/radix => reflex-radix/src/reflex_radix}/themes/components/hover_card.py (96%) rename packages/{reflex-components/src/reflex_components/radix => reflex-radix/src/reflex_radix}/themes/components/icon_button.py (98%) rename packages/{reflex-components/src/reflex_components/radix => reflex-radix/src/reflex_radix}/themes/components/inset.py (94%) rename packages/{reflex-components/src/reflex_components/radix => reflex-radix/src/reflex_radix}/themes/components/popover.py (97%) rename packages/{reflex-components/src/reflex_components/radix => reflex-radix/src/reflex_radix}/themes/components/progress.py (96%) rename packages/{reflex-components/src/reflex_components/radix => reflex-radix/src/reflex_radix}/themes/components/radio.py (91%) rename packages/{reflex-components/src/reflex_components/radix => reflex-radix/src/reflex_radix}/themes/components/radio_cards.py (97%) rename packages/{reflex-components/src/reflex_components/radix => reflex-radix/src/reflex_radix}/themes/components/radio_group.py (97%) rename packages/{reflex-components/src/reflex_components/radix => reflex-radix/src/reflex_radix}/themes/components/scroll_area.py (93%) rename packages/{reflex-components/src/reflex_components/radix => reflex-radix/src/reflex_radix}/themes/components/segmented_control.py (96%) rename packages/{reflex-components/src/reflex_components/radix => reflex-radix/src/reflex_radix}/themes/components/select.py (99%) rename packages/{reflex-components/src/reflex_components/radix => reflex-radix/src/reflex_radix}/themes/components/separator.py (92%) rename packages/{reflex-components/src/reflex_components/radix => reflex-radix/src/reflex_radix}/themes/components/skeleton.py (91%) rename packages/{reflex-components/src/reflex_components/radix => reflex-radix/src/reflex_radix}/themes/components/slider.py (97%) rename packages/{reflex-components/src/reflex_components/radix => reflex-radix/src/reflex_radix}/themes/components/spinner.py (84%) rename packages/{reflex-components/src/reflex_components/radix => reflex-radix/src/reflex_radix}/themes/components/switch.py (95%) rename packages/{reflex-components/src/reflex_components/radix => reflex-radix/src/reflex_radix}/themes/components/table.py (97%) rename packages/{reflex-components/src/reflex_components/radix => reflex-radix/src/reflex_radix}/themes/components/tabs.py (98%) rename packages/{reflex-components/src/reflex_components/radix => reflex-radix/src/reflex_radix}/themes/components/text_area.py (98%) rename packages/{reflex-components/src/reflex_components/radix => reflex-radix/src/reflex_radix}/themes/components/text_field.py (98%) rename packages/{reflex-components/src/reflex_components/radix => reflex-radix/src/reflex_radix}/themes/components/tooltip.py (98%) rename packages/{reflex-components/src/reflex_components/radix => reflex-radix/src/reflex_radix}/themes/layout/__init__.py (100%) rename packages/{reflex-components/src/reflex_components/radix => reflex-radix/src/reflex_radix}/themes/layout/base.py (95%) rename packages/{reflex-components/src/reflex_components/radix => reflex-radix/src/reflex_radix}/themes/layout/box.py (80%) rename packages/{reflex-components/src/reflex_components/radix => reflex-radix/src/reflex_radix}/themes/layout/center.py (100%) rename packages/{reflex-components/src/reflex_components/radix => reflex-radix/src/reflex_radix}/themes/layout/container.py (95%) rename packages/{reflex-components/src/reflex_components/radix => reflex-radix/src/reflex_radix}/themes/layout/flex.py (97%) rename packages/{reflex-components/src/reflex_components/radix => reflex-radix/src/reflex_radix}/themes/layout/grid.py (97%) rename packages/{reflex-components/src/reflex_components/radix => reflex-radix/src/reflex_radix}/themes/layout/list.py (97%) rename packages/{reflex-components/src/reflex_components/radix => reflex-radix/src/reflex_radix}/themes/layout/section.py (90%) rename packages/{reflex-components/src/reflex_components/radix => reflex-radix/src/reflex_radix}/themes/layout/spacer.py (100%) rename packages/{reflex-components/src/reflex_components/radix => reflex-radix/src/reflex_radix}/themes/layout/stack.py (95%) rename packages/{reflex-components/src/reflex_components/radix => reflex-radix/src/reflex_radix}/themes/typography/__init__.py (100%) rename packages/{reflex-components/src/reflex_components/radix => reflex-radix/src/reflex_radix}/themes/typography/base.py (100%) rename packages/{reflex-components/src/reflex_components/radix => reflex-radix/src/reflex_radix}/themes/typography/blockquote.py (91%) rename packages/{reflex-components/src/reflex_components/radix => reflex-radix/src/reflex_radix}/themes/typography/code.py (90%) rename packages/{reflex-components/src/reflex_components/radix => reflex-radix/src/reflex_radix}/themes/typography/heading.py (91%) rename packages/{reflex-components/src/reflex_components/radix => reflex-radix/src/reflex_radix}/themes/typography/link.py (94%) rename packages/{reflex-components/src/reflex_components/radix => reflex-radix/src/reflex_radix}/themes/typography/text.py (94%) create mode 100644 packages/reflex-react-player/README.md create mode 100644 packages/reflex-react-player/pyproject.toml rename packages/{reflex-components/src/reflex_components/react_player => reflex-react-player/src/reflex_react_player}/__init__.py (100%) rename packages/{reflex-components/src/reflex_components/react_player => reflex-react-player/src/reflex_react_player}/audio.py (61%) rename packages/{reflex-components/src/reflex_components/react_player => reflex-react-player/src/reflex_react_player}/react_player.py (99%) rename packages/{reflex-components/src/reflex_components/react_player => reflex-react-player/src/reflex_react_player}/video.py (61%) create mode 100644 packages/reflex-react-router/README.md create mode 100644 packages/reflex-react-router/pyproject.toml rename packages/{reflex-components/src/reflex_components/react_router => reflex-react-router/src/reflex_react_router}/__init__.py (100%) rename packages/{reflex-components/src/reflex_components/react_router => reflex-react-router/src/reflex_react_router}/dom.py (99%) create mode 100644 packages/reflex-recharts/README.md create mode 100644 packages/reflex-recharts/pyproject.toml rename packages/{reflex-components/src/reflex_components/recharts => reflex-recharts/src/reflex_recharts}/__init__.py (100%) rename packages/{reflex-components/src/reflex_components/recharts => reflex-recharts/src/reflex_recharts}/cartesian.py (100%) rename packages/{reflex-components/src/reflex_components/recharts => reflex-recharts/src/reflex_recharts}/charts.py (99%) rename packages/{reflex-components/src/reflex_components/recharts => reflex-recharts/src/reflex_recharts}/general.py (100%) rename packages/{reflex-components/src/reflex_components/recharts => reflex-recharts/src/reflex_recharts}/polar.py (100%) rename packages/{reflex-components/src/reflex_components/recharts => reflex-recharts/src/reflex_recharts}/recharts.py (100%) create mode 100644 packages/reflex-sonner/README.md create mode 100644 packages/reflex-sonner/pyproject.toml rename packages/{reflex-components/src/reflex_components/sonner => reflex-sonner/src/reflex_sonner}/__init__.py (100%) rename packages/{reflex-components/src/reflex_components/sonner => reflex-sonner/src/reflex_sonner}/toast.py (99%) diff --git a/packages/reflex-components/src/reflex_components/__init__.py b/packages/reflex-components/src/reflex_components/__init__.py index 6d1d787cc2e..24c86c12c50 100644 --- a/packages/reflex-components/src/reflex_components/__init__.py +++ b/packages/reflex-components/src/reflex_components/__init__.py @@ -1,4 +1,4 @@ -"""Reflex UI components package.""" +"""Reflex base UI components package.""" from __future__ import annotations @@ -11,14 +11,7 @@ "el", "gridjs", "lucide", - "markdown", "moment", - "plotly", - "radix", - "react_player", - "react_router", - "recharts", - "sonner", } _SUBMOD_ATTRS: dict[str, list[str]] = {} diff --git a/packages/reflex-components/src/reflex_components/base/app_wrap.py b/packages/reflex-components/src/reflex_components/base/app_wrap.py index de868b6efac..c4e1a0b43bc 100644 --- a/packages/reflex-components/src/reflex_components/base/app_wrap.py +++ b/packages/reflex-components/src/reflex_components/base/app_wrap.py @@ -1,8 +1,8 @@ """Top-level component that wraps the entire app.""" -from reflex_components.base.fragment import Fragment from reflex.components.component import Component from reflex.vars.base import Var +from reflex_components.base.fragment import Fragment class AppWrap(Fragment): diff --git a/packages/reflex-components/src/reflex_components/base/error_boundary.py b/packages/reflex-components/src/reflex_components/base/error_boundary.py index 6f608357529..d257fa06eed 100644 --- a/packages/reflex-components/src/reflex_components/base/error_boundary.py +++ b/packages/reflex-components/src/reflex_components/base/error_boundary.py @@ -3,13 +3,13 @@ from __future__ import annotations from reflex.components.component import Component, field -from reflex_components.datadisplay.logo import svg_logo -from reflex_components.el import a, button, div, h2, hr, p, pre, svg from reflex.event import EventHandler, set_clipboard from reflex.state import FrontendEventExceptionState from reflex.vars.base import Var from reflex.vars.function import ArgsFunctionOperation from reflex.vars.object import ObjectVar +from reflex_components.datadisplay.logo import svg_logo +from reflex_components.el import a, button, div, h2, hr, p, pre, svg def on_error_spec( diff --git a/packages/reflex-components/src/reflex_components/base/link.py b/packages/reflex-components/src/reflex_components/base/link.py index 9e532b67ab9..c0b77be9fb8 100644 --- a/packages/reflex-components/src/reflex_components/base/link.py +++ b/packages/reflex-components/src/reflex_components/base/link.py @@ -1,8 +1,8 @@ """Display the title of the current page.""" from reflex.components.component import field -from reflex_components.el.elements.base import BaseHTML from reflex.vars.base import Var +from reflex_components.el.elements.base import BaseHTML class RawLink(BaseHTML): diff --git a/packages/reflex-components/src/reflex_components/base/meta.py b/packages/reflex-components/src/reflex_components/base/meta.py index 557ff12ea52..f01a01a1b63 100644 --- a/packages/reflex-components/src/reflex_components/base/meta.py +++ b/packages/reflex-components/src/reflex_components/base/meta.py @@ -2,11 +2,11 @@ from __future__ import annotations -from reflex_components.base.bare import Bare from reflex.components.component import field +from reflex.vars.base import Var +from reflex_components.base.bare import Bare from reflex_components.el import elements from reflex_components.el.elements.metadata import Meta as Meta # for compatibility -from reflex.vars.base import Var class Title(elements.Title): diff --git a/packages/reflex-components/src/reflex_components/base/script.py b/packages/reflex-components/src/reflex_components/base/script.py index 1cf0df47ce0..039fc0b339e 100644 --- a/packages/reflex-components/src/reflex_components/base/script.py +++ b/packages/reflex-components/src/reflex_components/base/script.py @@ -2,9 +2,9 @@ from __future__ import annotations -from reflex.components import el as elements -from reflex_components.core.helmet import helmet from reflex.utils import console +from reflex_components import el as elements +from reflex_components.core.helmet import helmet class Script(elements.Script): diff --git a/packages/reflex-components/src/reflex_components/core/auto_scroll.py b/packages/reflex-components/src/reflex_components/core/auto_scroll.py index 9a606140ecd..8b948e7f2b6 100644 --- a/packages/reflex-components/src/reflex_components/core/auto_scroll.py +++ b/packages/reflex-components/src/reflex_components/core/auto_scroll.py @@ -4,10 +4,10 @@ import dataclasses -from reflex_components.el.elements.typography import Div from reflex.constants.compiler import MemoizationDisposition, MemoizationMode from reflex.utils.imports import ImportDict from reflex.vars.base import Var, get_unique_variable_name +from reflex_components.el.elements.typography import Div class AutoScroll(Div): diff --git a/packages/reflex-components/src/reflex_components/core/banner.py b/packages/reflex-components/src/reflex_components/core/banner.py index 90bb36d422c..7760698f0e3 100644 --- a/packages/reflex-components/src/reflex_components/core/banner.py +++ b/packages/reflex-components/src/reflex_components/core/banner.py @@ -2,20 +2,13 @@ from __future__ import annotations +from reflex_radix.themes.components.dialog import DialogContent, DialogRoot, DialogTitle +from reflex_radix.themes.layout.flex import Flex +from reflex_radix.themes.typography.text import Text +from reflex_sonner.toast import ToastProps, toast_ref + from reflex import constants -from reflex_components.base.fragment import Fragment from reflex.components.component import Component -from reflex_components.core.cond import cond -from reflex_components.el.elements.typography import Div -from reflex_components.lucide.icon import Icon -from reflex_components.radix.themes.components.dialog import ( - DialogContent, - DialogRoot, - DialogTitle, -) -from reflex_components.radix.themes.layout.flex import Flex -from reflex_components.radix.themes.typography.text import Text -from reflex_components.sonner.toast import ToastProps, toast_ref from reflex.constants import Dirs, Hooks, Imports from reflex.constants.compiler import CompileVars from reflex.environment import environment @@ -25,6 +18,10 @@ from reflex.vars.function import FunctionStringVar from reflex.vars.number import BooleanVar from reflex.vars.sequence import LiteralArrayVar +from reflex_components.base.fragment import Fragment +from reflex_components.core.cond import cond +from reflex_components.el.elements.typography import Div +from reflex_components.lucide.icon import Icon connect_error_var_data: VarData = VarData( imports=Imports.EVENTS, diff --git a/packages/reflex-components/src/reflex_components/core/clipboard.py b/packages/reflex-components/src/reflex_components/core/clipboard.py index 7cd5ebb6ca2..a4eb48a3c89 100644 --- a/packages/reflex-components/src/reflex_components/core/clipboard.py +++ b/packages/reflex-components/src/reflex_components/core/clipboard.py @@ -4,7 +4,6 @@ from collections.abc import Sequence -from reflex_components.base.fragment import Fragment from reflex.components.component import field from reflex.components.tags.tag import Tag from reflex.constants.compiler import Hooks @@ -13,6 +12,7 @@ from reflex.utils.imports import ImportVar from reflex.vars import get_unique_variable_name from reflex.vars.base import Var, VarData +from reflex_components.base.fragment import Fragment class Clipboard(Fragment): diff --git a/packages/reflex-components/src/reflex_components/core/cond.py b/packages/reflex-components/src/reflex_components/core/cond.py index 61a87f93969..007cf20eafe 100644 --- a/packages/reflex-components/src/reflex_components/core/cond.py +++ b/packages/reflex-components/src/reflex_components/core/cond.py @@ -4,7 +4,6 @@ from typing import Any, overload -from reflex_components.base.fragment import Fragment from reflex.components.component import BaseComponent, Component, field from reflex.components.tags import CondTag, Tag from reflex.constants import Dirs @@ -14,6 +13,7 @@ from reflex.vars import VarData from reflex.vars.base import LiteralVar, Var from reflex.vars.number import ternary_operation +from reflex_components.base.fragment import Fragment _IS_TRUE_IMPORT: ImportDict = { f"$/{Dirs.STATE_PATH}": [ImportVar(tag="isTrue")], diff --git a/packages/reflex-components/src/reflex_components/core/foreach.py b/packages/reflex-components/src/reflex_components/core/foreach.py index 18dcec42527..0743dce96b7 100644 --- a/packages/reflex-components/src/reflex_components/core/foreach.py +++ b/packages/reflex-components/src/reflex_components/core/foreach.py @@ -8,9 +8,7 @@ from hashlib import md5 from typing import Any -from reflex_components.base.fragment import Fragment from reflex.components.component import Component, field -from reflex_components.core.cond import cond from reflex.components.tags import IterTag from reflex.constants import MemoizationMode from reflex.constants.state import FIELD_MARKER @@ -18,6 +16,8 @@ from reflex.utils import types from reflex.utils.exceptions import UntypedVarError from reflex.vars.base import LiteralVar, Var +from reflex_components.base.fragment import Fragment +from reflex_components.core.cond import cond class ForeachVarError(TypeError): diff --git a/packages/reflex-components/src/reflex_components/core/html.py b/packages/reflex-components/src/reflex_components/core/html.py index 0db92633448..31790b1eed3 100644 --- a/packages/reflex-components/src/reflex_components/core/html.py +++ b/packages/reflex-components/src/reflex_components/core/html.py @@ -1,8 +1,8 @@ """A html component.""" from reflex.components.component import field -from reflex_components.el.elements.typography import Div from reflex.vars.base import Var +from reflex_components.el.elements.typography import Div class Html(Div): diff --git a/packages/reflex-components/src/reflex_components/core/match.py b/packages/reflex-components/src/reflex_components/core/match.py index 38fd9b1b4b6..e92649d49e6 100644 --- a/packages/reflex-components/src/reflex_components/core/match.py +++ b/packages/reflex-components/src/reflex_components/core/match.py @@ -3,7 +3,6 @@ import textwrap from typing import Any, cast -from reflex_components.base import Fragment from reflex.components.component import BaseComponent, Component, MemoizationLeaf, field from reflex.components.tags import Tag from reflex.components.tags.match_tag import MatchTag @@ -13,6 +12,7 @@ from reflex.utils.imports import ImportDict from reflex.vars import VarData from reflex.vars.base import LiteralVar, Var +from reflex_components.base import Fragment class Match(MemoizationLeaf): diff --git a/packages/reflex-components/src/reflex_components/core/responsive.py b/packages/reflex-components/src/reflex_components/core/responsive.py index a1251fc548b..34edc042493 100644 --- a/packages/reflex-components/src/reflex_components/core/responsive.py +++ b/packages/reflex-components/src/reflex_components/core/responsive.py @@ -1,6 +1,6 @@ """Responsive components.""" -from reflex_components.radix.themes.layout.box import Box +from reflex_radix.themes.layout.box import Box # Add responsive styles shortcuts. diff --git a/packages/reflex-components/src/reflex_components/core/sticky.py b/packages/reflex-components/src/reflex_components/core/sticky.py index 59d90238911..cf9977c80bb 100644 --- a/packages/reflex-components/src/reflex_components/core/sticky.py +++ b/packages/reflex-components/src/reflex_components/core/sticky.py @@ -1,13 +1,14 @@ """Components for displaying the Reflex sticky logo.""" +from reflex_radix.themes.typography.text import Text + from reflex.components.component import ComponentNamespace +from reflex.style import Style from reflex_components.core.colors import color from reflex_components.core.cond import color_mode_cond from reflex_components.core.responsive import desktop_only from reflex_components.el.elements.inline import A from reflex_components.el.elements.media import Path, Rect, Svg -from reflex_components.radix.themes.typography.text import Text -from reflex.style import Style class StickyLogo(Svg): diff --git a/packages/reflex-components/src/reflex_components/core/upload.py b/packages/reflex-components/src/reflex_components/core/upload.py index 69fa2c05d93..d3c91e06474 100644 --- a/packages/reflex-components/src/reflex_components/core/upload.py +++ b/packages/reflex-components/src/reflex_components/core/upload.py @@ -6,8 +6,10 @@ from pathlib import Path from typing import Any, ClassVar +from reflex_radix.themes.layout.box import Box +from reflex_sonner.toast import toast + from reflex.app import UploadFile -from reflex_components.base.fragment import Fragment from reflex.components.component import ( Component, ComponentNamespace, @@ -15,10 +17,6 @@ StatefulComponent, field, ) -from reflex_components.core.cond import cond -from reflex_components.el.elements.forms import Input -from reflex_components.radix.themes.layout.box import Box -from reflex_components.sonner.toast import toast from reflex.constants import Dirs from reflex.constants.compiler import Hooks, Imports from reflex.environment import environment @@ -42,6 +40,9 @@ from reflex.vars.function import FunctionVar from reflex.vars.object import ObjectVar from reflex.vars.sequence import ArrayVar, LiteralStringVar +from reflex_components.base.fragment import Fragment +from reflex_components.core.cond import cond +from reflex_components.el.elements.forms import Input DEFAULT_UPLOAD_ID: str = "default" diff --git a/packages/reflex-components/src/reflex_components/core/window_events.py b/packages/reflex-components/src/reflex_components/core/window_events.py index 5b70f355a2e..31f322465a4 100644 --- a/packages/reflex-components/src/reflex_components/core/window_events.py +++ b/packages/reflex-components/src/reflex_components/core/window_events.py @@ -5,12 +5,12 @@ from typing import Any, cast import reflex as rx -from reflex_components.base.fragment import Fragment from reflex.components.component import StatefulComponent, field from reflex.constants.compiler import Hooks from reflex.event import key_event, no_args_event_spec from reflex.vars.base import Var, VarData from reflex.vars.object import ObjectVar +from reflex_components.base.fragment import Fragment def _on_resize_spec() -> tuple[Var[int], Var[int]]: diff --git a/packages/reflex-components/src/reflex_components/datadisplay/code.py b/packages/reflex-components/src/reflex_components/datadisplay/code.py index f7dede64cbf..d6fffe3bfd6 100644 --- a/packages/reflex-components/src/reflex_components/datadisplay/code.py +++ b/packages/reflex-components/src/reflex_components/datadisplay/code.py @@ -5,18 +5,19 @@ import dataclasses from typing import ClassVar, Literal +from reflex_markdown.markdown import MarkdownComponentMap +from reflex_radix.themes.components.button import Button +from reflex_radix.themes.layout.box import Box + from reflex.components.component import Component, ComponentNamespace, field -from reflex_components.core.cond import color_mode_cond -from reflex_components.lucide.icon import Icon -from reflex_components.markdown.markdown import MarkdownComponentMap -from reflex_components.radix.themes.components.button import Button -from reflex_components.radix.themes.layout.box import Box from reflex.constants.colors import Color from reflex.event import set_clipboard from reflex.style import Style from reflex.utils import format from reflex.utils.imports import ImportVar from reflex.vars.base import LiteralVar, Var, VarData +from reflex_components.core.cond import color_mode_cond +from reflex_components.lucide.icon import Icon LiteralCodeLanguage = Literal[ "abap", diff --git a/packages/reflex-components/src/reflex_components/datadisplay/shiki_code_block.py b/packages/reflex-components/src/reflex_components/datadisplay/shiki_code_block.py index a8410228360..e23cef04e98 100644 --- a/packages/reflex-components/src/reflex_components/datadisplay/shiki_code_block.py +++ b/packages/reflex-components/src/reflex_components/datadisplay/shiki_code_block.py @@ -8,14 +8,11 @@ from dataclasses import dataclass from typing import Any, Literal +from reflex_markdown.markdown import MarkdownComponentMap +from reflex_radix.themes.layout.box import Box + from reflex.components.component import Component, ComponentNamespace, field -from reflex_components.core.colors import color -from reflex_components.core.cond import color_mode_cond -from reflex_components.el.elements.forms import Button -from reflex_components.lucide.icon import Icon -from reflex_components.markdown.markdown import MarkdownComponentMap from reflex.components.props import NoExtrasAllowedProps -from reflex_components.radix.themes.layout.box import Box from reflex.event import run_script, set_clipboard from reflex.style import Style from reflex.utils.exceptions import VarTypeError @@ -23,6 +20,10 @@ from reflex.vars.base import LiteralVar, Var from reflex.vars.function import FunctionStringVar from reflex.vars.sequence import StringVar, string_replace_operation +from reflex_components.core.colors import color +from reflex_components.core.cond import color_mode_cond +from reflex_components.el.elements.forms import Button +from reflex_components.lucide.icon import Icon def copy_script() -> Any: diff --git a/packages/reflex-components/src/reflex_components/el/__init__.py b/packages/reflex-components/src/reflex_components/el/__init__.py index 7db37e751a5..c3073f557ec 100644 --- a/packages/reflex-components/src/reflex_components/el/__init__.py +++ b/packages/reflex-components/src/reflex_components/el/__init__.py @@ -13,7 +13,7 @@ for k, attrs in elements._MAPPING.items() } _EXTRA_MAPPINGS: dict[str, str] = { - "a": "reflex_components.react_router.link", + "a": "reflex_react_router.link", } __getattr__, __dir__, __all__ = lazy_loader.attach( diff --git a/packages/reflex-components/src/reflex_components/el/elements/base.py b/packages/reflex-components/src/reflex_components/el/elements/base.py index 0f431bae41d..6387fd5b987 100644 --- a/packages/reflex-components/src/reflex_components/el/elements/base.py +++ b/packages/reflex-components/src/reflex_components/el/elements/base.py @@ -3,8 +3,8 @@ from typing import Literal from reflex.components.component import field -from reflex_components.el.element import Element from reflex.vars.base import Var +from reflex_components.el.element import Element AutoCapitalize = Literal["off", "none", "on", "sentences", "words", "characters"] ContentEditable = Literal["inherit", "plaintext-only"] | bool diff --git a/packages/reflex-components/src/reflex_components/el/elements/forms.py b/packages/reflex-components/src/reflex_components/el/elements/forms.py index e0765c1d832..131f1d634a9 100644 --- a/packages/reflex-components/src/reflex_components/el/elements/forms.py +++ b/packages/reflex-components/src/reflex_components/el/elements/forms.py @@ -7,7 +7,6 @@ from typing import Any, ClassVar, Literal from reflex.components.component import field -from reflex_components.el.element import Element from reflex.components.tags.tag import Tag from reflex.constants import Dirs, EventTriggers from reflex.event import ( @@ -27,6 +26,7 @@ from reflex.vars import VarData from reflex.vars.base import LiteralVar, Var from reflex.vars.number import ternary_operation +from reflex_components.el.element import Element from .base import BaseHTML diff --git a/packages/reflex-components/src/reflex_components/el/elements/media.py b/packages/reflex-components/src/reflex_components/el/elements/media.py index 335c21a7fac..729800362fa 100644 --- a/packages/reflex-components/src/reflex_components/el/elements/media.py +++ b/packages/reflex-components/src/reflex_components/el/elements/media.py @@ -4,9 +4,9 @@ from reflex import Component, ComponentNamespace from reflex.components.component import field -from reflex_components.el.elements.inline import ReferrerPolicy from reflex.constants.colors import Color from reflex.vars.base import Var +from reflex_components.el.elements.inline import ReferrerPolicy from .base import BaseHTML diff --git a/packages/reflex-components/src/reflex_components/el/elements/metadata.py b/packages/reflex-components/src/reflex_components/el/elements/metadata.py index bd3a490e7e4..248dabcf54e 100644 --- a/packages/reflex-components/src/reflex_components/el/elements/metadata.py +++ b/packages/reflex-components/src/reflex_components/el/elements/metadata.py @@ -1,10 +1,10 @@ """Metadata classes.""" from reflex.components.component import field +from reflex.vars.base import Var from reflex_components.el.element import Element from reflex_components.el.elements.inline import ReferrerPolicy from reflex_components.el.elements.media import CrossOrigin -from reflex.vars.base import Var from .base import BaseHTML diff --git a/packages/reflex-components/src/reflex_components/el/elements/scripts.py b/packages/reflex-components/src/reflex_components/el/elements/scripts.py index 4527282117f..45151631747 100644 --- a/packages/reflex-components/src/reflex_components/el/elements/scripts.py +++ b/packages/reflex-components/src/reflex_components/el/elements/scripts.py @@ -1,9 +1,9 @@ """Scripts classes.""" from reflex.components.component import field +from reflex.vars.base import Var from reflex_components.el.elements.inline import ReferrerPolicy from reflex_components.el.elements.media import CrossOrigin -from reflex.vars.base import Var from .base import BaseHTML diff --git a/packages/reflex-markdown/README.md b/packages/reflex-markdown/README.md new file mode 100644 index 00000000000..155a0e1c73c --- /dev/null +++ b/packages/reflex-markdown/README.md @@ -0,0 +1 @@ +# Reflex markdown diff --git a/packages/reflex-markdown/pyproject.toml b/packages/reflex-markdown/pyproject.toml new file mode 100644 index 00000000000..cb5114d8784 --- /dev/null +++ b/packages/reflex-markdown/pyproject.toml @@ -0,0 +1,13 @@ +[project] +name = "reflex-markdown" +version = "0.0.1" +description = "Reflex markdown components." +readme = "README.md" +authors = [{ name = "Khaleel Al-Adhami", email = "khaleel@reflex.dev" }] +maintainers = [{ name = "Khaleel Al-Adhami", email = "khaleel@reflex.dev" }] +requires-python = ">=3.10" +dependencies = [] + +[build-system] +requires = ["uv_build>=0.10.11,<0.12.0"] +build-backend = "uv_build" diff --git a/packages/reflex-components/src/reflex_components/markdown/__init__.py b/packages/reflex-markdown/src/reflex_markdown/__init__.py similarity index 100% rename from packages/reflex-components/src/reflex_components/markdown/__init__.py rename to packages/reflex-markdown/src/reflex_markdown/__init__.py diff --git a/packages/reflex-components/src/reflex_components/markdown/markdown.py b/packages/reflex-markdown/src/reflex_markdown/markdown.py similarity index 95% rename from packages/reflex-components/src/reflex_components/markdown/markdown.py rename to packages/reflex-markdown/src/reflex_markdown/markdown.py index 4a460a09550..796ff7b2341 100644 --- a/packages/reflex-components/src/reflex_components/markdown/markdown.py +++ b/packages/reflex-markdown/src/reflex_markdown/markdown.py @@ -10,6 +10,8 @@ from types import SimpleNamespace from typing import Any +from reflex_components.el.elements.typography import Div + from reflex.components.component import ( BaseComponent, Component, @@ -17,7 +19,6 @@ CustomComponent, field, ) -from reflex_components.el.elements.typography import Div from reflex.components.tags.tag import Tag from reflex.utils import console from reflex.utils.imports import ImportDict, ImportTypes, ImportVar @@ -88,73 +89,73 @@ def create( def _h1(value: object): - from reflex_components.radix.themes.typography.heading import Heading + from reflex_radix.themes.typography.heading import Heading return Heading.create(value, as_="h1", size="6", margin_y="0.5em") def _h2(value: object): - from reflex_components.radix.themes.typography.heading import Heading + from reflex_radix.themes.typography.heading import Heading return Heading.create(value, as_="h2", size="5", margin_y="0.5em") def _h3(value: object): - from reflex_components.radix.themes.typography.heading import Heading + from reflex_radix.themes.typography.heading import Heading return Heading.create(value, as_="h3", size="4", margin_y="0.5em") def _h4(value: object): - from reflex_components.radix.themes.typography.heading import Heading + from reflex_radix.themes.typography.heading import Heading return Heading.create(value, as_="h4", size="3", margin_y="0.5em") def _h5(value: object): - from reflex_components.radix.themes.typography.heading import Heading + from reflex_radix.themes.typography.heading import Heading return Heading.create(value, as_="h5", size="2", margin_y="0.5em") def _h6(value: object): - from reflex_components.radix.themes.typography.heading import Heading + from reflex_radix.themes.typography.heading import Heading return Heading.create(value, as_="h6", size="1", margin_y="0.5em") def _p(value: object): - from reflex_components.radix.themes.typography.text import Text + from reflex_radix.themes.typography.text import Text return Text.create(value, margin_y="1em") def _ul(value: object): - from reflex_components.radix.themes.layout.list import UnorderedList + from reflex_radix.themes.layout.list import UnorderedList return UnorderedList.create(value, margin_y="1em") def _ol(value: object): - from reflex_components.radix.themes.layout.list import OrderedList + from reflex_radix.themes.layout.list import OrderedList return OrderedList.create(value, margin_y="1em") def _li(value: object): - from reflex_components.radix.themes.layout.list import ListItem + from reflex_radix.themes.layout.list import ListItem return ListItem.create(value, margin_y="0.5em") def _a(value: object): - from reflex_components.radix.themes.typography.link import Link + from reflex_radix.themes.typography.link import Link return Link.create(value) def _code(value: object): - from reflex_components.radix.themes.typography.code import Code + from reflex_radix.themes.typography.code import Code return Code.create(value) diff --git a/packages/reflex-plotly/README.md b/packages/reflex-plotly/README.md new file mode 100644 index 00000000000..758ebd0d3ab --- /dev/null +++ b/packages/reflex-plotly/README.md @@ -0,0 +1 @@ +# Reflex plotly diff --git a/packages/reflex-plotly/pyproject.toml b/packages/reflex-plotly/pyproject.toml new file mode 100644 index 00000000000..e62191ca964 --- /dev/null +++ b/packages/reflex-plotly/pyproject.toml @@ -0,0 +1,13 @@ +[project] +name = "reflex-plotly" +version = "0.0.1" +description = "Reflex plotly components." +readme = "README.md" +authors = [{ name = "Khaleel Al-Adhami", email = "khaleel@reflex.dev" }] +maintainers = [{ name = "Khaleel Al-Adhami", email = "khaleel@reflex.dev" }] +requires-python = ">=3.10" +dependencies = [] + +[build-system] +requires = ["uv_build>=0.10.11,<0.12.0"] +build-backend = "uv_build" diff --git a/packages/reflex-components/src/reflex_components/plotly/__init__.py b/packages/reflex-plotly/src/reflex_plotly/__init__.py similarity index 100% rename from packages/reflex-components/src/reflex_components/plotly/__init__.py rename to packages/reflex-plotly/src/reflex_plotly/__init__.py diff --git a/packages/reflex-components/src/reflex_components/plotly/plotly.py b/packages/reflex-plotly/src/reflex_plotly/plotly.py similarity index 99% rename from packages/reflex-components/src/reflex_components/plotly/plotly.py rename to packages/reflex-plotly/src/reflex_plotly/plotly.py index 03d2fcd5510..8be79d3a693 100644 --- a/packages/reflex-components/src/reflex_components/plotly/plotly.py +++ b/packages/reflex-plotly/src/reflex_plotly/plotly.py @@ -4,8 +4,9 @@ from typing import TYPE_CHECKING, Any, TypedDict, TypeVar -from reflex.components.component import Component, NoSSRComponent, field from reflex_components.core.cond import color_mode_cond + +from reflex.components.component import Component, NoSSRComponent, field from reflex.event import EventHandler, no_args_event_spec from reflex.utils import console from reflex.utils.imports import ImportDict, ImportVar diff --git a/packages/reflex-radix/README.md b/packages/reflex-radix/README.md new file mode 100644 index 00000000000..e49f890d64c --- /dev/null +++ b/packages/reflex-radix/README.md @@ -0,0 +1 @@ +# Reflex radix diff --git a/packages/reflex-radix/pyproject.toml b/packages/reflex-radix/pyproject.toml new file mode 100644 index 00000000000..90b515ff687 --- /dev/null +++ b/packages/reflex-radix/pyproject.toml @@ -0,0 +1,13 @@ +[project] +name = "reflex-radix" +version = "0.0.1" +description = "Reflex radix components." +readme = "README.md" +authors = [{ name = "Khaleel Al-Adhami", email = "khaleel@reflex.dev" }] +maintainers = [{ name = "Khaleel Al-Adhami", email = "khaleel@reflex.dev" }] +requires-python = ">=3.10" +dependencies = [] + +[build-system] +requires = ["uv_build>=0.10.11,<0.12.0"] +build-backend = "uv_build" diff --git a/packages/reflex-components/src/reflex_components/radix/__init__.py b/packages/reflex-radix/src/reflex_radix/__init__.py similarity index 100% rename from packages/reflex-components/src/reflex_components/radix/__init__.py rename to packages/reflex-radix/src/reflex_radix/__init__.py diff --git a/packages/reflex-components/src/reflex_components/radix/primitives/__init__.py b/packages/reflex-radix/src/reflex_radix/primitives/__init__.py similarity index 100% rename from packages/reflex-components/src/reflex_components/radix/primitives/__init__.py rename to packages/reflex-radix/src/reflex_radix/primitives/__init__.py diff --git a/packages/reflex-components/src/reflex_components/radix/primitives/accordion.py b/packages/reflex-radix/src/reflex_radix/primitives/accordion.py similarity index 99% rename from packages/reflex-components/src/reflex_components/radix/primitives/accordion.py rename to packages/reflex-radix/src/reflex_radix/primitives/accordion.py index ec0a095c99b..93aa63969e2 100644 --- a/packages/reflex-components/src/reflex_components/radix/primitives/accordion.py +++ b/packages/reflex-radix/src/reflex_radix/primitives/accordion.py @@ -5,17 +5,18 @@ from collections.abc import Sequence from typing import Any, ClassVar, Literal -from reflex.components.component import Component, ComponentNamespace, field from reflex_components.core.colors import color from reflex_components.core.cond import cond from reflex_components.lucide.icon import Icon -from reflex_components.radix.primitives.base import RadixPrimitiveComponent -from reflex_components.radix.themes.base import LiteralAccentColor, LiteralRadius + +from reflex.components.component import Component, ComponentNamespace, field from reflex.constants.compiler import MemoizationMode from reflex.event import EventHandler from reflex.style import Style from reflex.vars import get_uuid_string_var from reflex.vars.base import LiteralVar, Var +from reflex_radix.primitives.base import RadixPrimitiveComponent +from reflex_radix.themes.base import LiteralAccentColor, LiteralRadius LiteralAccordionType = Literal["single", "multiple"] LiteralAccordionDir = Literal["ltr", "rtl"] diff --git a/packages/reflex-components/src/reflex_components/radix/primitives/base.py b/packages/reflex-radix/src/reflex_radix/primitives/base.py similarity index 100% rename from packages/reflex-components/src/reflex_components/radix/primitives/base.py rename to packages/reflex-radix/src/reflex_radix/primitives/base.py diff --git a/packages/reflex-components/src/reflex_components/radix/primitives/dialog.py b/packages/reflex-radix/src/reflex_radix/primitives/dialog.py similarity index 99% rename from packages/reflex-components/src/reflex_components/radix/primitives/dialog.py rename to packages/reflex-radix/src/reflex_radix/primitives/dialog.py index 716a2742c42..7bc3023fe5e 100644 --- a/packages/reflex-components/src/reflex_components/radix/primitives/dialog.py +++ b/packages/reflex-radix/src/reflex_radix/primitives/dialog.py @@ -2,8 +2,9 @@ from typing import Any, ClassVar -from reflex.components.component import ComponentNamespace, field from reflex_components.el import elements + +from reflex.components.component import ComponentNamespace, field from reflex.constants.compiler import MemoizationMode from reflex.event import EventHandler, no_args_event_spec, passthrough_event_spec from reflex.vars.base import Var diff --git a/packages/reflex-components/src/reflex_components/radix/primitives/drawer.py b/packages/reflex-radix/src/reflex_radix/primitives/drawer.py similarity index 97% rename from packages/reflex-components/src/reflex_components/radix/primitives/drawer.py rename to packages/reflex-radix/src/reflex_radix/primitives/drawer.py index 1a98ad158e6..050c0f84840 100644 --- a/packages/reflex-components/src/reflex_components/radix/primitives/drawer.py +++ b/packages/reflex-radix/src/reflex_radix/primitives/drawer.py @@ -8,12 +8,12 @@ from typing import Any, Literal from reflex.components.component import Component, ComponentNamespace, field -from reflex_components.radix.primitives.base import RadixPrimitiveComponent -from reflex_components.radix.themes.base import Theme -from reflex_components.radix.themes.layout.flex import Flex from reflex.constants.compiler import MemoizationMode from reflex.event import EventHandler, no_args_event_spec, passthrough_event_spec from reflex.vars.base import Var +from reflex_radix.primitives.base import RadixPrimitiveComponent +from reflex_radix.themes.base import Theme +from reflex_radix.themes.layout.flex import Flex class DrawerComponent(RadixPrimitiveComponent): diff --git a/packages/reflex-components/src/reflex_components/radix/primitives/form.py b/packages/reflex-radix/src/reflex_radix/primitives/form.py similarity index 98% rename from packages/reflex-components/src/reflex_components/radix/primitives/form.py rename to packages/reflex-radix/src/reflex_radix/primitives/form.py index 5bc60419f74..d81ac902189 100644 --- a/packages/reflex-components/src/reflex_components/radix/primitives/form.py +++ b/packages/reflex-radix/src/reflex_radix/primitives/form.py @@ -4,12 +4,13 @@ from typing import Any, Literal -from reflex.components.component import ComponentNamespace, field from reflex_components.core.debounce import DebounceInput from reflex_components.el.elements.forms import Form as HTMLForm -from reflex_components.radix.themes.components.text_field import TextFieldRoot + +from reflex.components.component import ComponentNamespace, field from reflex.event import EventHandler, no_args_event_spec from reflex.vars.base import Var +from reflex_radix.themes.components.text_field import TextFieldRoot from .base import RadixPrimitiveComponentWithClassName diff --git a/packages/reflex-components/src/reflex_components/radix/primitives/progress.py b/packages/reflex-radix/src/reflex_radix/primitives/progress.py similarity index 93% rename from packages/reflex-components/src/reflex_components/radix/primitives/progress.py rename to packages/reflex-radix/src/reflex_radix/primitives/progress.py index fc86e5622c7..1f158ec89cc 100644 --- a/packages/reflex-components/src/reflex_components/radix/primitives/progress.py +++ b/packages/reflex-radix/src/reflex_radix/primitives/progress.py @@ -4,12 +4,13 @@ from typing import Any -from reflex.components.component import Component, ComponentNamespace, field from reflex_components.core.colors import color -from reflex_components.radix.primitives.accordion import DEFAULT_ANIMATION_DURATION -from reflex_components.radix.primitives.base import RadixPrimitiveComponentWithClassName -from reflex_components.radix.themes.base import LiteralAccentColor, LiteralRadius + +from reflex.components.component import Component, ComponentNamespace, field from reflex.vars.base import Var +from reflex_radix.primitives.accordion import DEFAULT_ANIMATION_DURATION +from reflex_radix.primitives.base import RadixPrimitiveComponentWithClassName +from reflex_radix.themes.base import LiteralAccentColor, LiteralRadius class ProgressComponent(RadixPrimitiveComponentWithClassName): diff --git a/packages/reflex-components/src/reflex_components/radix/primitives/slider.py b/packages/reflex-radix/src/reflex_radix/primitives/slider.py similarity index 98% rename from packages/reflex-components/src/reflex_components/radix/primitives/slider.py rename to packages/reflex-radix/src/reflex_radix/primitives/slider.py index 7bae34540e4..a14585de97c 100644 --- a/packages/reflex-components/src/reflex_components/radix/primitives/slider.py +++ b/packages/reflex-radix/src/reflex_radix/primitives/slider.py @@ -6,9 +6,9 @@ from typing import Any, Literal from reflex.components.component import Component, ComponentNamespace, field -from reflex_components.radix.primitives.base import RadixPrimitiveComponentWithClassName from reflex.event import EventHandler, passthrough_event_spec from reflex.vars.base import Var +from reflex_radix.primitives.base import RadixPrimitiveComponentWithClassName LiteralSliderOrientation = Literal["horizontal", "vertical"] LiteralSliderDir = Literal["ltr", "rtl"] diff --git a/packages/reflex-components/src/reflex_components/radix/themes/__init__.py b/packages/reflex-radix/src/reflex_radix/themes/__init__.py similarity index 100% rename from packages/reflex-components/src/reflex_components/radix/themes/__init__.py rename to packages/reflex-radix/src/reflex_radix/themes/__init__.py diff --git a/packages/reflex-components/src/reflex_components/radix/themes/base.py b/packages/reflex-radix/src/reflex_radix/themes/base.py similarity index 99% rename from packages/reflex-components/src/reflex_components/radix/themes/base.py rename to packages/reflex-radix/src/reflex_radix/themes/base.py index 77d9036d5e8..cd814ecc394 100644 --- a/packages/reflex-components/src/reflex_components/radix/themes/base.py +++ b/packages/reflex-radix/src/reflex_radix/themes/base.py @@ -4,9 +4,10 @@ from typing import Any, ClassVar, Literal +from reflex_components.core.breakpoints import Responsive + from reflex.components import Component from reflex.components.component import field -from reflex_components.core.breakpoints import Responsive from reflex.components.tags import Tag from reflex.utils.imports import ImportDict, ImportVar from reflex.vars.base import Var diff --git a/packages/reflex-components/src/reflex_components/radix/themes/color_mode.py b/packages/reflex-radix/src/reflex_radix/themes/color_mode.py similarity index 97% rename from packages/reflex-components/src/reflex_components/radix/themes/color_mode.py rename to packages/reflex-radix/src/reflex_radix/themes/color_mode.py index 36ef88f61e6..e2ac94d936f 100644 --- a/packages/reflex-components/src/reflex_components/radix/themes/color_mode.py +++ b/packages/reflex-radix/src/reflex_radix/themes/color_mode.py @@ -19,11 +19,10 @@ from typing import Any, Literal, get_args -from reflex.components.component import BaseComponent, field from reflex_components.core.cond import Cond, color_mode_cond, cond from reflex_components.lucide.icon import Icon -from reflex_components.radix.themes.components.dropdown_menu import dropdown_menu -from reflex_components.radix.themes.components.switch import Switch + +from reflex.components.component import BaseComponent, field from reflex.style import ( LIGHT_COLOR_MODE, color_mode, @@ -33,6 +32,8 @@ ) from reflex.vars.base import Var from reflex.vars.sequence import LiteralArrayVar +from reflex_radix.themes.components.dropdown_menu import dropdown_menu +from reflex_radix.themes.components.switch import Switch from .components.icon_button import IconButton diff --git a/packages/reflex-components/src/reflex_components/radix/themes/components/__init__.py b/packages/reflex-radix/src/reflex_radix/themes/components/__init__.py similarity index 100% rename from packages/reflex-components/src/reflex_components/radix/themes/components/__init__.py rename to packages/reflex-radix/src/reflex_radix/themes/components/__init__.py diff --git a/packages/reflex-components/src/reflex_components/radix/themes/components/alert_dialog.py b/packages/reflex-radix/src/reflex_radix/themes/components/alert_dialog.py similarity index 96% rename from packages/reflex-components/src/reflex_components/radix/themes/components/alert_dialog.py rename to packages/reflex-radix/src/reflex_radix/themes/components/alert_dialog.py index 879a5fa4a17..7db9ac831d2 100644 --- a/packages/reflex-components/src/reflex_components/radix/themes/components/alert_dialog.py +++ b/packages/reflex-radix/src/reflex_radix/themes/components/alert_dialog.py @@ -2,16 +2,14 @@ from typing import Literal -from reflex.components.component import ComponentNamespace, field from reflex_components.core.breakpoints import Responsive from reflex_components.el import elements -from reflex_components.radix.themes.base import ( - RadixThemesComponent, - RadixThemesTriggerComponent, -) + +from reflex.components.component import ComponentNamespace, field from reflex.constants.compiler import MemoizationMode from reflex.event import EventHandler, no_args_event_spec, passthrough_event_spec from reflex.vars.base import Var +from reflex_radix.themes.base import RadixThemesComponent, RadixThemesTriggerComponent LiteralContentSize = Literal["1", "2", "3", "4"] diff --git a/packages/reflex-components/src/reflex_components/radix/themes/components/aspect_ratio.py b/packages/reflex-radix/src/reflex_radix/themes/components/aspect_ratio.py similarity index 85% rename from packages/reflex-components/src/reflex_components/radix/themes/components/aspect_ratio.py rename to packages/reflex-radix/src/reflex_radix/themes/components/aspect_ratio.py index 6e94a5e5bba..1c1f56b3565 100644 --- a/packages/reflex-components/src/reflex_components/radix/themes/components/aspect_ratio.py +++ b/packages/reflex-radix/src/reflex_radix/themes/components/aspect_ratio.py @@ -1,8 +1,8 @@ """Interactive components provided by @radix-ui/themes.""" from reflex.components.component import field -from reflex_components.radix.themes.base import RadixThemesComponent from reflex.vars.base import Var +from reflex_radix.themes.base import RadixThemesComponent class AspectRatio(RadixThemesComponent): diff --git a/packages/reflex-components/src/reflex_components/radix/themes/components/avatar.py b/packages/reflex-radix/src/reflex_radix/themes/components/avatar.py similarity index 95% rename from packages/reflex-components/src/reflex_components/radix/themes/components/avatar.py rename to packages/reflex-radix/src/reflex_radix/themes/components/avatar.py index ad4b1033df8..e7403759d97 100644 --- a/packages/reflex-components/src/reflex_components/radix/themes/components/avatar.py +++ b/packages/reflex-radix/src/reflex_radix/themes/components/avatar.py @@ -2,14 +2,15 @@ from typing import Literal -from reflex.components.component import field from reflex_components.core.breakpoints import Responsive -from reflex_components.radix.themes.base import ( + +from reflex.components.component import field +from reflex.vars.base import Var +from reflex_radix.themes.base import ( LiteralAccentColor, LiteralRadius, RadixThemesComponent, ) -from reflex.vars.base import Var LiteralSize = Literal["1", "2", "3", "4", "5", "6", "7", "8", "9"] diff --git a/packages/reflex-components/src/reflex_components/radix/themes/components/badge.py b/packages/reflex-radix/src/reflex_radix/themes/components/badge.py similarity index 95% rename from packages/reflex-components/src/reflex_components/radix/themes/components/badge.py rename to packages/reflex-radix/src/reflex_radix/themes/components/badge.py index f1efb6157f9..b415b0e1ce3 100644 --- a/packages/reflex-components/src/reflex_components/radix/themes/components/badge.py +++ b/packages/reflex-radix/src/reflex_radix/themes/components/badge.py @@ -2,15 +2,16 @@ from typing import Literal -from reflex.components.component import field from reflex_components.core.breakpoints import Responsive from reflex_components.el import elements -from reflex_components.radix.themes.base import ( + +from reflex.components.component import field +from reflex.vars.base import Var +from reflex_radix.themes.base import ( LiteralAccentColor, LiteralRadius, RadixThemesComponent, ) -from reflex.vars.base import Var class Badge(elements.Span, RadixThemesComponent): diff --git a/packages/reflex-components/src/reflex_components/radix/themes/components/button.py b/packages/reflex-radix/src/reflex_radix/themes/components/button.py similarity index 96% rename from packages/reflex-components/src/reflex_components/radix/themes/components/button.py rename to packages/reflex-radix/src/reflex_radix/themes/components/button.py index 10d713e70d3..03acf2230f1 100644 --- a/packages/reflex-components/src/reflex_components/radix/themes/components/button.py +++ b/packages/reflex-radix/src/reflex_radix/themes/components/button.py @@ -2,17 +2,18 @@ from typing import Literal -from reflex.components.component import field from reflex_components.core.breakpoints import Responsive from reflex_components.el import elements -from reflex_components.radix.themes.base import ( + +from reflex.components.component import field +from reflex.vars.base import Var +from reflex_radix.themes.base import ( LiteralAccentColor, LiteralRadius, LiteralVariant, RadixLoadingProp, RadixThemesComponent, ) -from reflex.vars.base import Var LiteralButtonSize = Literal["1", "2", "3", "4"] diff --git a/packages/reflex-components/src/reflex_components/radix/themes/components/callout.py b/packages/reflex-radix/src/reflex_radix/themes/components/callout.py similarity index 96% rename from packages/reflex-components/src/reflex_components/radix/themes/components/callout.py rename to packages/reflex-radix/src/reflex_radix/themes/components/callout.py index b86cf86f1ac..25d6a79a966 100644 --- a/packages/reflex-components/src/reflex_components/radix/themes/components/callout.py +++ b/packages/reflex-radix/src/reflex_radix/themes/components/callout.py @@ -2,13 +2,14 @@ from typing import Literal -import reflex as rx -from reflex.components.component import Component, ComponentNamespace, field from reflex_components.core.breakpoints import Responsive from reflex_components.el import elements from reflex_components.lucide.icon import Icon -from reflex_components.radix.themes.base import LiteralAccentColor, RadixThemesComponent + +import reflex as rx +from reflex.components.component import Component, ComponentNamespace, field from reflex.vars.base import Var +from reflex_radix.themes.base import LiteralAccentColor, RadixThemesComponent CalloutVariant = Literal["soft", "surface", "outline"] diff --git a/packages/reflex-components/src/reflex_components/radix/themes/components/card.py b/packages/reflex-radix/src/reflex_radix/themes/components/card.py similarity index 92% rename from packages/reflex-components/src/reflex_components/radix/themes/components/card.py rename to packages/reflex-radix/src/reflex_radix/themes/components/card.py index f588924ba5d..acafa575692 100644 --- a/packages/reflex-components/src/reflex_components/radix/themes/components/card.py +++ b/packages/reflex-radix/src/reflex_radix/themes/components/card.py @@ -2,11 +2,12 @@ from typing import Literal -from reflex.components.component import field from reflex_components.core.breakpoints import Responsive from reflex_components.el import elements -from reflex_components.radix.themes.base import RadixThemesComponent + +from reflex.components.component import field from reflex.vars.base import Var +from reflex_radix.themes.base import RadixThemesComponent class Card(elements.Div, RadixThemesComponent): diff --git a/packages/reflex-components/src/reflex_components/radix/themes/components/checkbox.py b/packages/reflex-radix/src/reflex_radix/themes/components/checkbox.py similarity index 96% rename from packages/reflex-components/src/reflex_components/radix/themes/components/checkbox.py rename to packages/reflex-radix/src/reflex_radix/themes/components/checkbox.py index 09d2a03b9b9..8e7103152be 100644 --- a/packages/reflex-components/src/reflex_components/radix/themes/components/checkbox.py +++ b/packages/reflex-radix/src/reflex_radix/themes/components/checkbox.py @@ -2,17 +2,18 @@ from typing import Literal -from reflex.components.component import Component, ComponentNamespace, field from reflex_components.core.breakpoints import Responsive -from reflex_components.radix.themes.base import ( + +from reflex.components.component import Component, ComponentNamespace, field +from reflex.event import EventHandler, passthrough_event_spec +from reflex.vars.base import Var +from reflex_radix.themes.base import ( LiteralAccentColor, LiteralSpacing, RadixThemesComponent, ) -from reflex_components.radix.themes.layout.flex import Flex -from reflex_components.radix.themes.typography.text import Text -from reflex.event import EventHandler, passthrough_event_spec -from reflex.vars.base import Var +from reflex_radix.themes.layout.flex import Flex +from reflex_radix.themes.typography.text import Text LiteralCheckboxSize = Literal["1", "2", "3"] LiteralCheckboxVariant = Literal["classic", "surface", "soft"] diff --git a/packages/reflex-components/src/reflex_components/radix/themes/components/checkbox_cards.py b/packages/reflex-radix/src/reflex_radix/themes/components/checkbox_cards.py similarity index 94% rename from packages/reflex-components/src/reflex_components/radix/themes/components/checkbox_cards.py rename to packages/reflex-radix/src/reflex_radix/themes/components/checkbox_cards.py index 3dd06314739..b5a5746eeb5 100644 --- a/packages/reflex-components/src/reflex_components/radix/themes/components/checkbox_cards.py +++ b/packages/reflex-radix/src/reflex_radix/themes/components/checkbox_cards.py @@ -3,10 +3,11 @@ from types import SimpleNamespace from typing import Literal -from reflex.components.component import field from reflex_components.core.breakpoints import Responsive -from reflex_components.radix.themes.base import LiteralAccentColor, RadixThemesComponent + +from reflex.components.component import field from reflex.vars.base import Var +from reflex_radix.themes.base import LiteralAccentColor, RadixThemesComponent class CheckboxCardsRoot(RadixThemesComponent): diff --git a/packages/reflex-components/src/reflex_components/radix/themes/components/checkbox_group.py b/packages/reflex-radix/src/reflex_radix/themes/components/checkbox_group.py similarity index 95% rename from packages/reflex-components/src/reflex_components/radix/themes/components/checkbox_group.py rename to packages/reflex-radix/src/reflex_radix/themes/components/checkbox_group.py index 723c84a4489..b8409c82281 100644 --- a/packages/reflex-components/src/reflex_components/radix/themes/components/checkbox_group.py +++ b/packages/reflex-radix/src/reflex_radix/themes/components/checkbox_group.py @@ -4,10 +4,11 @@ from types import SimpleNamespace from typing import Literal -from reflex.components.component import field from reflex_components.core.breakpoints import Responsive -from reflex_components.radix.themes.base import LiteralAccentColor, RadixThemesComponent + +from reflex.components.component import field from reflex.vars.base import Var +from reflex_radix.themes.base import LiteralAccentColor, RadixThemesComponent class CheckboxGroupRoot(RadixThemesComponent): diff --git a/packages/reflex-components/src/reflex_components/radix/themes/components/context_menu.py b/packages/reflex-radix/src/reflex_radix/themes/components/context_menu.py similarity index 99% rename from packages/reflex-components/src/reflex_components/radix/themes/components/context_menu.py rename to packages/reflex-radix/src/reflex_radix/themes/components/context_menu.py index 140a60ececa..013fe700de8 100644 --- a/packages/reflex-components/src/reflex_components/radix/themes/components/context_menu.py +++ b/packages/reflex-radix/src/reflex_radix/themes/components/context_menu.py @@ -2,12 +2,13 @@ from typing import ClassVar, Literal -from reflex.components.component import ComponentNamespace, field from reflex_components.core.breakpoints import Responsive -from reflex_components.radix.themes.base import LiteralAccentColor, RadixThemesComponent + +from reflex.components.component import ComponentNamespace, field from reflex.constants.compiler import MemoizationMode from reflex.event import EventHandler, no_args_event_spec, passthrough_event_spec from reflex.vars.base import Var +from reflex_radix.themes.base import LiteralAccentColor, RadixThemesComponent from .checkbox import Checkbox from .radio_group import HighLevelRadioGroup diff --git a/packages/reflex-components/src/reflex_components/radix/themes/components/data_list.py b/packages/reflex-radix/src/reflex_radix/themes/components/data_list.py similarity index 95% rename from packages/reflex-components/src/reflex_components/radix/themes/components/data_list.py rename to packages/reflex-radix/src/reflex_radix/themes/components/data_list.py index d235abf552e..f9a6655f843 100644 --- a/packages/reflex-components/src/reflex_components/radix/themes/components/data_list.py +++ b/packages/reflex-radix/src/reflex_radix/themes/components/data_list.py @@ -3,10 +3,11 @@ from types import SimpleNamespace from typing import Literal -from reflex.components.component import field from reflex_components.core.breakpoints import Responsive -from reflex_components.radix.themes.base import LiteralAccentColor, RadixThemesComponent + +from reflex.components.component import field from reflex.vars.base import Var +from reflex_radix.themes.base import LiteralAccentColor, RadixThemesComponent class DataListRoot(RadixThemesComponent): diff --git a/packages/reflex-components/src/reflex_components/radix/themes/components/dialog.py b/packages/reflex-radix/src/reflex_radix/themes/components/dialog.py similarity index 96% rename from packages/reflex-components/src/reflex_components/radix/themes/components/dialog.py rename to packages/reflex-radix/src/reflex_radix/themes/components/dialog.py index 3c90dbd5179..b5439c652b9 100644 --- a/packages/reflex-components/src/reflex_components/radix/themes/components/dialog.py +++ b/packages/reflex-radix/src/reflex_radix/themes/components/dialog.py @@ -2,16 +2,14 @@ from typing import Literal -from reflex.components.component import ComponentNamespace, field from reflex_components.core.breakpoints import Responsive from reflex_components.el import elements -from reflex_components.radix.themes.base import ( - RadixThemesComponent, - RadixThemesTriggerComponent, -) + +from reflex.components.component import ComponentNamespace, field from reflex.constants.compiler import MemoizationMode from reflex.event import EventHandler, no_args_event_spec, passthrough_event_spec from reflex.vars.base import Var +from reflex_radix.themes.base import RadixThemesComponent, RadixThemesTriggerComponent class DialogRoot(RadixThemesComponent): diff --git a/packages/reflex-components/src/reflex_components/radix/themes/components/dropdown_menu.py b/packages/reflex-radix/src/reflex_radix/themes/components/dropdown_menu.py similarity index 99% rename from packages/reflex-components/src/reflex_components/radix/themes/components/dropdown_menu.py rename to packages/reflex-radix/src/reflex_radix/themes/components/dropdown_menu.py index b0ef82321a4..ff4c773f526 100644 --- a/packages/reflex-components/src/reflex_components/radix/themes/components/dropdown_menu.py +++ b/packages/reflex-radix/src/reflex_radix/themes/components/dropdown_menu.py @@ -2,16 +2,17 @@ from typing import ClassVar, Literal -from reflex.components.component import ComponentNamespace, field from reflex_components.core.breakpoints import Responsive -from reflex_components.radix.themes.base import ( + +from reflex.components.component import ComponentNamespace, field +from reflex.constants.compiler import MemoizationMode +from reflex.event import EventHandler, no_args_event_spec, passthrough_event_spec +from reflex.vars.base import Var +from reflex_radix.themes.base import ( LiteralAccentColor, RadixThemesComponent, RadixThemesTriggerComponent, ) -from reflex.constants.compiler import MemoizationMode -from reflex.event import EventHandler, no_args_event_spec, passthrough_event_spec -from reflex.vars.base import Var LiteralDirType = Literal["ltr", "rtl"] diff --git a/packages/reflex-components/src/reflex_components/radix/themes/components/hover_card.py b/packages/reflex-radix/src/reflex_radix/themes/components/hover_card.py similarity index 96% rename from packages/reflex-components/src/reflex_components/radix/themes/components/hover_card.py rename to packages/reflex-radix/src/reflex_radix/themes/components/hover_card.py index f42ac9dcc46..8e3971a5e29 100644 --- a/packages/reflex-components/src/reflex_components/radix/themes/components/hover_card.py +++ b/packages/reflex-radix/src/reflex_radix/themes/components/hover_card.py @@ -2,16 +2,14 @@ from typing import Literal -from reflex.components.component import ComponentNamespace, field from reflex_components.core.breakpoints import Responsive from reflex_components.el import elements -from reflex_components.radix.themes.base import ( - RadixThemesComponent, - RadixThemesTriggerComponent, -) + +from reflex.components.component import ComponentNamespace, field from reflex.constants.compiler import MemoizationMode from reflex.event import EventHandler, passthrough_event_spec from reflex.vars.base import Var +from reflex_radix.themes.base import RadixThemesComponent, RadixThemesTriggerComponent class HoverCardRoot(RadixThemesComponent): diff --git a/packages/reflex-components/src/reflex_components/radix/themes/components/icon_button.py b/packages/reflex-radix/src/reflex_radix/themes/components/icon_button.py similarity index 98% rename from packages/reflex-components/src/reflex_components/radix/themes/components/icon_button.py rename to packages/reflex-radix/src/reflex_radix/themes/components/icon_button.py index 0abf8aa331a..7157035d480 100644 --- a/packages/reflex-components/src/reflex_components/radix/themes/components/icon_button.py +++ b/packages/reflex-radix/src/reflex_radix/themes/components/icon_button.py @@ -4,20 +4,21 @@ from typing import Literal -from reflex.components.component import Component, field from reflex_components.core.breakpoints import Responsive from reflex_components.core.match import Match from reflex_components.el import elements from reflex_components.lucide import Icon -from reflex_components.radix.themes.base import ( + +from reflex.components.component import Component, field +from reflex.style import Style +from reflex.vars.base import Var +from reflex_radix.themes.base import ( LiteralAccentColor, LiteralRadius, LiteralVariant, RadixLoadingProp, RadixThemesComponent, ) -from reflex.style import Style -from reflex.vars.base import Var LiteralButtonSize = Literal["1", "2", "3", "4"] diff --git a/packages/reflex-components/src/reflex_components/radix/themes/components/inset.py b/packages/reflex-radix/src/reflex_radix/themes/components/inset.py similarity index 94% rename from packages/reflex-components/src/reflex_components/radix/themes/components/inset.py rename to packages/reflex-radix/src/reflex_radix/themes/components/inset.py index ca77854cee5..a1c5657c3d9 100644 --- a/packages/reflex-components/src/reflex_components/radix/themes/components/inset.py +++ b/packages/reflex-radix/src/reflex_radix/themes/components/inset.py @@ -2,11 +2,12 @@ from typing import Literal -from reflex.components.component import field from reflex_components.core.breakpoints import Responsive from reflex_components.el import elements -from reflex_components.radix.themes.base import RadixThemesComponent + +from reflex.components.component import field from reflex.vars.base import Var +from reflex_radix.themes.base import RadixThemesComponent LiteralButtonSize = Literal["1", "2", "3", "4"] diff --git a/packages/reflex-components/src/reflex_components/radix/themes/components/popover.py b/packages/reflex-radix/src/reflex_radix/themes/components/popover.py similarity index 97% rename from packages/reflex-components/src/reflex_components/radix/themes/components/popover.py rename to packages/reflex-radix/src/reflex_radix/themes/components/popover.py index c5eb06f72b0..b7898496dc6 100644 --- a/packages/reflex-components/src/reflex_components/radix/themes/components/popover.py +++ b/packages/reflex-radix/src/reflex_radix/themes/components/popover.py @@ -2,16 +2,14 @@ from typing import Literal -from reflex.components.component import ComponentNamespace, field from reflex_components.core.breakpoints import Responsive from reflex_components.el import elements -from reflex_components.radix.themes.base import ( - RadixThemesComponent, - RadixThemesTriggerComponent, -) + +from reflex.components.component import ComponentNamespace, field from reflex.constants.compiler import MemoizationMode from reflex.event import EventHandler, no_args_event_spec, passthrough_event_spec from reflex.vars.base import Var +from reflex_radix.themes.base import RadixThemesComponent, RadixThemesTriggerComponent class PopoverRoot(RadixThemesComponent): diff --git a/packages/reflex-components/src/reflex_components/radix/themes/components/progress.py b/packages/reflex-radix/src/reflex_radix/themes/components/progress.py similarity index 96% rename from packages/reflex-components/src/reflex_components/radix/themes/components/progress.py rename to packages/reflex-radix/src/reflex_radix/themes/components/progress.py index a3b9b23b8b3..06e1067feef 100644 --- a/packages/reflex-components/src/reflex_components/radix/themes/components/progress.py +++ b/packages/reflex-radix/src/reflex_radix/themes/components/progress.py @@ -2,11 +2,12 @@ from typing import Literal -from reflex.components.component import Component, field from reflex_components.core.breakpoints import Responsive -from reflex_components.radix.themes.base import LiteralAccentColor, RadixThemesComponent + +from reflex.components.component import Component, field from reflex.style import Style from reflex.vars.base import Var +from reflex_radix.themes.base import LiteralAccentColor, RadixThemesComponent class Progress(RadixThemesComponent): diff --git a/packages/reflex-components/src/reflex_components/radix/themes/components/radio.py b/packages/reflex-radix/src/reflex_radix/themes/components/radio.py similarity index 91% rename from packages/reflex-components/src/reflex_components/radix/themes/components/radio.py rename to packages/reflex-radix/src/reflex_radix/themes/components/radio.py index 3e9556736e9..a882a514ec9 100644 --- a/packages/reflex-components/src/reflex_components/radix/themes/components/radio.py +++ b/packages/reflex-radix/src/reflex_radix/themes/components/radio.py @@ -2,10 +2,11 @@ from typing import Literal -from reflex.components.component import field from reflex_components.core.breakpoints import Responsive -from reflex_components.radix.themes.base import LiteralAccentColor, RadixThemesComponent + +from reflex.components.component import field from reflex.vars.base import Var +from reflex_radix.themes.base import LiteralAccentColor, RadixThemesComponent class Radio(RadixThemesComponent): diff --git a/packages/reflex-components/src/reflex_components/radix/themes/components/radio_cards.py b/packages/reflex-radix/src/reflex_radix/themes/components/radio_cards.py similarity index 97% rename from packages/reflex-components/src/reflex_components/radix/themes/components/radio_cards.py rename to packages/reflex-radix/src/reflex_radix/themes/components/radio_cards.py index 404af9e1355..f6fd2d31f8b 100644 --- a/packages/reflex-components/src/reflex_components/radix/themes/components/radio_cards.py +++ b/packages/reflex-radix/src/reflex_radix/themes/components/radio_cards.py @@ -3,11 +3,12 @@ from types import SimpleNamespace from typing import ClassVar, Literal -from reflex.components.component import field from reflex_components.core.breakpoints import Responsive -from reflex_components.radix.themes.base import LiteralAccentColor, RadixThemesComponent + +from reflex.components.component import field from reflex.event import EventHandler, passthrough_event_spec from reflex.vars.base import Var +from reflex_radix.themes.base import LiteralAccentColor, RadixThemesComponent class RadioCardsRoot(RadixThemesComponent): diff --git a/packages/reflex-components/src/reflex_components/radix/themes/components/radio_group.py b/packages/reflex-radix/src/reflex_radix/themes/components/radio_group.py similarity index 97% rename from packages/reflex-components/src/reflex_components/radix/themes/components/radio_group.py rename to packages/reflex-radix/src/reflex_radix/themes/components/radio_group.py index bd65f565d94..7408268c6a7 100644 --- a/packages/reflex-components/src/reflex_components/radix/themes/components/radio_group.py +++ b/packages/reflex-radix/src/reflex_radix/themes/components/radio_group.py @@ -5,20 +5,21 @@ from collections.abc import Sequence from typing import Literal +from reflex_components.core.breakpoints import Responsive + import reflex as rx from reflex.components.component import Component, ComponentNamespace, field -from reflex_components.core.breakpoints import Responsive -from reflex_components.radix.themes.base import ( - LiteralAccentColor, - LiteralSpacing, - RadixThemesComponent, -) -from reflex_components.radix.themes.layout.flex import Flex -from reflex_components.radix.themes.typography.text import Text from reflex.event import EventHandler, passthrough_event_spec from reflex.utils import types from reflex.vars.base import LiteralVar, Var from reflex.vars.sequence import StringVar +from reflex_radix.themes.base import ( + LiteralAccentColor, + LiteralSpacing, + RadixThemesComponent, +) +from reflex_radix.themes.layout.flex import Flex +from reflex_radix.themes.typography.text import Text LiteralFlexDirection = Literal["row", "column", "row-reverse", "column-reverse"] diff --git a/packages/reflex-components/src/reflex_components/radix/themes/components/scroll_area.py b/packages/reflex-radix/src/reflex_radix/themes/components/scroll_area.py similarity index 93% rename from packages/reflex-components/src/reflex_components/radix/themes/components/scroll_area.py rename to packages/reflex-radix/src/reflex_radix/themes/components/scroll_area.py index b80804f4d53..300093e1bc1 100644 --- a/packages/reflex-components/src/reflex_components/radix/themes/components/scroll_area.py +++ b/packages/reflex-radix/src/reflex_radix/themes/components/scroll_area.py @@ -3,8 +3,8 @@ from typing import Literal from reflex.components.component import field -from reflex_components.radix.themes.base import RadixThemesComponent from reflex.vars.base import Var +from reflex_radix.themes.base import RadixThemesComponent class ScrollArea(RadixThemesComponent): diff --git a/packages/reflex-components/src/reflex_components/radix/themes/components/segmented_control.py b/packages/reflex-radix/src/reflex_radix/themes/components/segmented_control.py similarity index 96% rename from packages/reflex-components/src/reflex_components/radix/themes/components/segmented_control.py rename to packages/reflex-radix/src/reflex_radix/themes/components/segmented_control.py index 773c6154b0c..189b64719e5 100644 --- a/packages/reflex-components/src/reflex_components/radix/themes/components/segmented_control.py +++ b/packages/reflex-radix/src/reflex_radix/themes/components/segmented_control.py @@ -6,11 +6,12 @@ from types import SimpleNamespace from typing import ClassVar, Literal -from reflex.components.component import field from reflex_components.core.breakpoints import Responsive -from reflex_components.radix.themes.base import LiteralAccentColor, RadixThemesComponent + +from reflex.components.component import field from reflex.event import EventHandler from reflex.vars.base import Var +from reflex_radix.themes.base import LiteralAccentColor, RadixThemesComponent def on_value_change( diff --git a/packages/reflex-components/src/reflex_components/radix/themes/components/select.py b/packages/reflex-radix/src/reflex_radix/themes/components/select.py similarity index 99% rename from packages/reflex-components/src/reflex_components/radix/themes/components/select.py rename to packages/reflex-radix/src/reflex_radix/themes/components/select.py index 9f2dc494266..d2fed4b7833 100644 --- a/packages/reflex-components/src/reflex_components/radix/themes/components/select.py +++ b/packages/reflex-radix/src/reflex_radix/themes/components/select.py @@ -3,17 +3,18 @@ from collections.abc import Sequence from typing import ClassVar, Literal +from reflex_components.core.breakpoints import Responsive + import reflex as rx from reflex.components.component import Component, ComponentNamespace, field -from reflex_components.core.breakpoints import Responsive -from reflex_components.radix.themes.base import ( +from reflex.constants.compiler import MemoizationMode +from reflex.event import no_args_event_spec, passthrough_event_spec +from reflex.vars.base import Var +from reflex_radix.themes.base import ( LiteralAccentColor, LiteralRadius, RadixThemesComponent, ) -from reflex.constants.compiler import MemoizationMode -from reflex.event import no_args_event_spec, passthrough_event_spec -from reflex.vars.base import Var class SelectRoot(RadixThemesComponent): diff --git a/packages/reflex-components/src/reflex_components/radix/themes/components/separator.py b/packages/reflex-radix/src/reflex_radix/themes/components/separator.py similarity index 92% rename from packages/reflex-components/src/reflex_components/radix/themes/components/separator.py rename to packages/reflex-radix/src/reflex_radix/themes/components/separator.py index a3912d2b349..76d784e91f5 100644 --- a/packages/reflex-components/src/reflex_components/radix/themes/components/separator.py +++ b/packages/reflex-radix/src/reflex_radix/themes/components/separator.py @@ -2,10 +2,11 @@ from typing import Literal -from reflex.components.component import field from reflex_components.core.breakpoints import Responsive -from reflex_components.radix.themes.base import LiteralAccentColor, RadixThemesComponent + +from reflex.components.component import field from reflex.vars.base import LiteralVar, Var +from reflex_radix.themes.base import LiteralAccentColor, RadixThemesComponent LiteralSeparatorSize = Literal["1", "2", "3", "4"] diff --git a/packages/reflex-components/src/reflex_components/radix/themes/components/skeleton.py b/packages/reflex-radix/src/reflex_radix/themes/components/skeleton.py similarity index 91% rename from packages/reflex-components/src/reflex_components/radix/themes/components/skeleton.py rename to packages/reflex-radix/src/reflex_radix/themes/components/skeleton.py index 15f4e1875d1..6d6f7340bad 100644 --- a/packages/reflex-components/src/reflex_components/radix/themes/components/skeleton.py +++ b/packages/reflex-radix/src/reflex_radix/themes/components/skeleton.py @@ -1,10 +1,11 @@ """Skeleton theme from Radix components.""" -from reflex.components.component import field from reflex_components.core.breakpoints import Responsive -from reflex_components.radix.themes.base import RadixLoadingProp, RadixThemesComponent + +from reflex.components.component import field from reflex.constants.compiler import MemoizationMode from reflex.vars.base import Var +from reflex_radix.themes.base import RadixLoadingProp, RadixThemesComponent class Skeleton(RadixLoadingProp, RadixThemesComponent): diff --git a/packages/reflex-components/src/reflex_components/radix/themes/components/slider.py b/packages/reflex-radix/src/reflex_radix/themes/components/slider.py similarity index 97% rename from packages/reflex-components/src/reflex_components/radix/themes/components/slider.py rename to packages/reflex-radix/src/reflex_radix/themes/components/slider.py index 14c067b5e97..7a8c9a42fa8 100644 --- a/packages/reflex-components/src/reflex_components/radix/themes/components/slider.py +++ b/packages/reflex-radix/src/reflex_radix/themes/components/slider.py @@ -5,12 +5,13 @@ from collections.abc import Sequence from typing import Literal -from reflex.components.component import Component, field from reflex_components.core.breakpoints import Responsive -from reflex_components.radix.themes.base import LiteralAccentColor, RadixThemesComponent + +from reflex.components.component import Component, field from reflex.event import EventHandler, passthrough_event_spec from reflex.utils.types import typehint_issubclass from reflex.vars.base import Var +from reflex_radix.themes.base import LiteralAccentColor, RadixThemesComponent on_value_event_spec = ( passthrough_event_spec(list[float]), diff --git a/packages/reflex-components/src/reflex_components/radix/themes/components/spinner.py b/packages/reflex-radix/src/reflex_radix/themes/components/spinner.py similarity index 84% rename from packages/reflex-components/src/reflex_components/radix/themes/components/spinner.py rename to packages/reflex-radix/src/reflex_radix/themes/components/spinner.py index e3b0e41b8e1..24777c811cf 100644 --- a/packages/reflex-components/src/reflex_components/radix/themes/components/spinner.py +++ b/packages/reflex-radix/src/reflex_radix/themes/components/spinner.py @@ -2,10 +2,11 @@ from typing import Literal -from reflex.components.component import field from reflex_components.core.breakpoints import Responsive -from reflex_components.radix.themes.base import RadixLoadingProp, RadixThemesComponent + +from reflex.components.component import field from reflex.vars.base import Var +from reflex_radix.themes.base import RadixLoadingProp, RadixThemesComponent LiteralSpinnerSize = Literal["1", "2", "3"] diff --git a/packages/reflex-components/src/reflex_components/radix/themes/components/switch.py b/packages/reflex-radix/src/reflex_radix/themes/components/switch.py similarity index 95% rename from packages/reflex-components/src/reflex_components/radix/themes/components/switch.py rename to packages/reflex-radix/src/reflex_radix/themes/components/switch.py index c8f0f43e5df..0d43ffbdc2d 100644 --- a/packages/reflex-components/src/reflex_components/radix/themes/components/switch.py +++ b/packages/reflex-radix/src/reflex_radix/themes/components/switch.py @@ -2,11 +2,12 @@ from typing import Literal -from reflex.components.component import field from reflex_components.core.breakpoints import Responsive -from reflex_components.radix.themes.base import LiteralAccentColor, RadixThemesComponent + +from reflex.components.component import field from reflex.event import EventHandler, passthrough_event_spec from reflex.vars.base import Var +from reflex_radix.themes.base import LiteralAccentColor, RadixThemesComponent LiteralSwitchSize = Literal["1", "2", "3"] diff --git a/packages/reflex-components/src/reflex_components/radix/themes/components/table.py b/packages/reflex-radix/src/reflex_radix/themes/components/table.py similarity index 97% rename from packages/reflex-components/src/reflex_components/radix/themes/components/table.py rename to packages/reflex-radix/src/reflex_radix/themes/components/table.py index 3ba8a26e21e..cdbe2902137 100644 --- a/packages/reflex-components/src/reflex_components/radix/themes/components/table.py +++ b/packages/reflex-radix/src/reflex_radix/themes/components/table.py @@ -2,11 +2,12 @@ from typing import ClassVar, Literal -from reflex.components.component import ComponentNamespace, field from reflex_components.core.breakpoints import Responsive from reflex_components.el import elements -from reflex_components.radix.themes.base import CommonPaddingProps, RadixThemesComponent + +from reflex.components.component import ComponentNamespace, field from reflex.vars.base import Var +from reflex_radix.themes.base import CommonPaddingProps, RadixThemesComponent class TableRoot(elements.Table, RadixThemesComponent): diff --git a/packages/reflex-components/src/reflex_components/radix/themes/components/tabs.py b/packages/reflex-radix/src/reflex_radix/themes/components/tabs.py similarity index 98% rename from packages/reflex-components/src/reflex_components/radix/themes/components/tabs.py rename to packages/reflex-radix/src/reflex_radix/themes/components/tabs.py index 3f15cad786a..26cd487025a 100644 --- a/packages/reflex-components/src/reflex_components/radix/themes/components/tabs.py +++ b/packages/reflex-radix/src/reflex_radix/themes/components/tabs.py @@ -4,13 +4,14 @@ from typing import Any, ClassVar, Literal -from reflex.components.component import Component, ComponentNamespace, field from reflex_components.core.breakpoints import Responsive from reflex_components.core.colors import color -from reflex_components.radix.themes.base import LiteralAccentColor, RadixThemesComponent + +from reflex.components.component import Component, ComponentNamespace, field from reflex.constants.compiler import MemoizationMode from reflex.event import EventHandler, passthrough_event_spec from reflex.vars.base import Var +from reflex_radix.themes.base import LiteralAccentColor, RadixThemesComponent vertical_orientation_css = "&[data-orientation='vertical']" diff --git a/packages/reflex-components/src/reflex_components/radix/themes/components/text_area.py b/packages/reflex-radix/src/reflex_radix/themes/components/text_area.py similarity index 98% rename from packages/reflex-components/src/reflex_components/radix/themes/components/text_area.py rename to packages/reflex-radix/src/reflex_radix/themes/components/text_area.py index 9bd83b9abf8..5a27b3ae7ef 100644 --- a/packages/reflex-components/src/reflex_components/radix/themes/components/text_area.py +++ b/packages/reflex-radix/src/reflex_radix/themes/components/text_area.py @@ -2,16 +2,17 @@ from typing import Literal -from reflex.components.component import Component, field from reflex_components.core.breakpoints import Responsive from reflex_components.core.debounce import DebounceInput from reflex_components.el import elements -from reflex_components.radix.themes.base import ( + +from reflex.components.component import Component, field +from reflex.vars.base import Var +from reflex_radix.themes.base import ( LiteralAccentColor, LiteralRadius, RadixThemesComponent, ) -from reflex.vars.base import Var LiteralTextAreaSize = Literal["1", "2", "3"] diff --git a/packages/reflex-components/src/reflex_components/radix/themes/components/text_field.py b/packages/reflex-radix/src/reflex_radix/themes/components/text_field.py similarity index 98% rename from packages/reflex-components/src/reflex_components/radix/themes/components/text_field.py rename to packages/reflex-radix/src/reflex_radix/themes/components/text_field.py index 248941d81fa..4bdb3000f62 100644 --- a/packages/reflex-components/src/reflex_components/radix/themes/components/text_field.py +++ b/packages/reflex-radix/src/reflex_radix/themes/components/text_field.py @@ -4,19 +4,20 @@ from typing import Literal -from reflex.components.component import Component, ComponentNamespace, field from reflex_components.core.breakpoints import Responsive from reflex_components.core.debounce import DebounceInput from reflex_components.el import elements -from reflex_components.radix.themes.base import ( - LiteralAccentColor, - LiteralRadius, - RadixThemesComponent, -) + +from reflex.components.component import Component, ComponentNamespace, field from reflex.event import EventHandler, input_event, key_event from reflex.utils.types import is_optional from reflex.vars.base import Var from reflex.vars.number import ternary_operation +from reflex_radix.themes.base import ( + LiteralAccentColor, + LiteralRadius, + RadixThemesComponent, +) LiteralTextFieldSize = Literal["1", "2", "3"] LiteralTextFieldVariant = Literal["classic", "surface", "soft"] diff --git a/packages/reflex-components/src/reflex_components/radix/themes/components/tooltip.py b/packages/reflex-radix/src/reflex_radix/themes/components/tooltip.py similarity index 98% rename from packages/reflex-components/src/reflex_components/radix/themes/components/tooltip.py rename to packages/reflex-radix/src/reflex_radix/themes/components/tooltip.py index 9f4cd2f5184..9cd6e23907e 100644 --- a/packages/reflex-components/src/reflex_components/radix/themes/components/tooltip.py +++ b/packages/reflex-radix/src/reflex_radix/themes/components/tooltip.py @@ -3,11 +3,11 @@ from typing import Literal from reflex.components.component import Component, field -from reflex_components.radix.themes.base import RadixThemesComponent from reflex.constants.compiler import MemoizationMode from reflex.event import EventHandler, no_args_event_spec, passthrough_event_spec from reflex.utils import format from reflex.vars.base import Var +from reflex_radix.themes.base import RadixThemesComponent LiteralSideType = Literal[ "top", diff --git a/packages/reflex-components/src/reflex_components/radix/themes/layout/__init__.py b/packages/reflex-radix/src/reflex_radix/themes/layout/__init__.py similarity index 100% rename from packages/reflex-components/src/reflex_components/radix/themes/layout/__init__.py rename to packages/reflex-radix/src/reflex_radix/themes/layout/__init__.py diff --git a/packages/reflex-components/src/reflex_components/radix/themes/layout/base.py b/packages/reflex-radix/src/reflex_radix/themes/layout/base.py similarity index 95% rename from packages/reflex-components/src/reflex_components/radix/themes/layout/base.py rename to packages/reflex-radix/src/reflex_radix/themes/layout/base.py index 5f881d0ce49..4ccc21a0dbc 100644 --- a/packages/reflex-components/src/reflex_components/radix/themes/layout/base.py +++ b/packages/reflex-radix/src/reflex_radix/themes/layout/base.py @@ -4,14 +4,15 @@ from typing import Literal -from reflex.components.component import field from reflex_components.core.breakpoints import Responsive -from reflex_components.radix.themes.base import ( + +from reflex.components.component import field +from reflex.vars.base import Var +from reflex_radix.themes.base import ( CommonMarginProps, CommonPaddingProps, RadixThemesComponent, ) -from reflex.vars.base import Var LiteralBoolNumber = Literal["0", "1"] diff --git a/packages/reflex-components/src/reflex_components/radix/themes/layout/box.py b/packages/reflex-radix/src/reflex_radix/themes/layout/box.py similarity index 80% rename from packages/reflex-components/src/reflex_components/radix/themes/layout/box.py rename to packages/reflex-radix/src/reflex_radix/themes/layout/box.py index 0303b0d56e2..54452de74ad 100644 --- a/packages/reflex-components/src/reflex_components/radix/themes/layout/box.py +++ b/packages/reflex-radix/src/reflex_radix/themes/layout/box.py @@ -3,7 +3,8 @@ from __future__ import annotations from reflex_components.el import elements -from reflex_components.radix.themes.base import RadixThemesComponent + +from reflex_radix.themes.base import RadixThemesComponent class Box(elements.Div, RadixThemesComponent): diff --git a/packages/reflex-components/src/reflex_components/radix/themes/layout/center.py b/packages/reflex-radix/src/reflex_radix/themes/layout/center.py similarity index 100% rename from packages/reflex-components/src/reflex_components/radix/themes/layout/center.py rename to packages/reflex-radix/src/reflex_radix/themes/layout/center.py diff --git a/packages/reflex-components/src/reflex_components/radix/themes/layout/container.py b/packages/reflex-radix/src/reflex_radix/themes/layout/container.py similarity index 95% rename from packages/reflex-components/src/reflex_components/radix/themes/layout/container.py rename to packages/reflex-radix/src/reflex_radix/themes/layout/container.py index b7b591fceba..fcfc7e0e255 100644 --- a/packages/reflex-components/src/reflex_components/radix/themes/layout/container.py +++ b/packages/reflex-radix/src/reflex_radix/themes/layout/container.py @@ -4,12 +4,13 @@ from typing import Literal -from reflex.components.component import field from reflex_components.core.breakpoints import Responsive from reflex_components.el import elements -from reflex_components.radix.themes.base import RadixThemesComponent + +from reflex.components.component import field from reflex.style import STACK_CHILDREN_FULL_WIDTH from reflex.vars.base import LiteralVar, Var +from reflex_radix.themes.base import RadixThemesComponent LiteralContainerSize = Literal["1", "2", "3", "4"] diff --git a/packages/reflex-components/src/reflex_components/radix/themes/layout/flex.py b/packages/reflex-radix/src/reflex_radix/themes/layout/flex.py similarity index 97% rename from packages/reflex-components/src/reflex_components/radix/themes/layout/flex.py rename to packages/reflex-radix/src/reflex_radix/themes/layout/flex.py index 964698434c7..4ea2492e7f5 100644 --- a/packages/reflex-components/src/reflex_components/radix/themes/layout/flex.py +++ b/packages/reflex-radix/src/reflex_radix/themes/layout/flex.py @@ -4,16 +4,17 @@ from typing import ClassVar, Literal -from reflex.components.component import field from reflex_components.core.breakpoints import Responsive from reflex_components.el import elements -from reflex_components.radix.themes.base import ( + +from reflex.components.component import field +from reflex.vars.base import Var +from reflex_radix.themes.base import ( LiteralAlign, LiteralJustify, LiteralSpacing, RadixThemesComponent, ) -from reflex.vars.base import Var LiteralFlexDirection = Literal["row", "column", "row-reverse", "column-reverse"] LiteralFlexWrap = Literal["nowrap", "wrap", "wrap-reverse"] diff --git a/packages/reflex-components/src/reflex_components/radix/themes/layout/grid.py b/packages/reflex-radix/src/reflex_radix/themes/layout/grid.py similarity index 97% rename from packages/reflex-components/src/reflex_components/radix/themes/layout/grid.py rename to packages/reflex-radix/src/reflex_radix/themes/layout/grid.py index 9b5b4168ed4..c2480f63e39 100644 --- a/packages/reflex-components/src/reflex_components/radix/themes/layout/grid.py +++ b/packages/reflex-radix/src/reflex_radix/themes/layout/grid.py @@ -4,16 +4,17 @@ from typing import ClassVar, Literal -from reflex.components.component import field from reflex_components.core.breakpoints import Responsive from reflex_components.el import elements -from reflex_components.radix.themes.base import ( + +from reflex.components.component import field +from reflex.vars.base import Var +from reflex_radix.themes.base import ( LiteralAlign, LiteralJustify, LiteralSpacing, RadixThemesComponent, ) -from reflex.vars.base import Var LiteralGridFlow = Literal["row", "column", "dense", "row-dense", "column-dense"] diff --git a/packages/reflex-components/src/reflex_components/radix/themes/layout/list.py b/packages/reflex-radix/src/reflex_radix/themes/layout/list.py similarity index 97% rename from packages/reflex-components/src/reflex_components/radix/themes/layout/list.py rename to packages/reflex-radix/src/reflex_radix/themes/layout/list.py index 8848c806136..0cf47b662e2 100644 --- a/packages/reflex-components/src/reflex_components/radix/themes/layout/list.py +++ b/packages/reflex-radix/src/reflex_radix/themes/layout/list.py @@ -5,14 +5,15 @@ from collections.abc import Iterable from typing import Any, Literal -from reflex.components.component import ComponentNamespace, field from reflex_components.core.foreach import Foreach from reflex_components.el.elements.base import BaseHTML from reflex_components.el.elements.typography import Li, Ol, Ul from reflex_components.lucide.icon import Icon -from reflex_components.markdown.markdown import MarkdownComponentMap -from reflex_components.radix.themes.typography.text import Text +from reflex_markdown.markdown import MarkdownComponentMap + +from reflex.components.component import ComponentNamespace, field from reflex.vars.base import Var +from reflex_radix.themes.typography.text import Text LiteralListStyleTypeUnordered = Literal[ "none", diff --git a/packages/reflex-components/src/reflex_components/radix/themes/layout/section.py b/packages/reflex-radix/src/reflex_radix/themes/layout/section.py similarity index 90% rename from packages/reflex-components/src/reflex_components/radix/themes/layout/section.py rename to packages/reflex-radix/src/reflex_radix/themes/layout/section.py index 12543502c09..31507465f0f 100644 --- a/packages/reflex-components/src/reflex_components/radix/themes/layout/section.py +++ b/packages/reflex-radix/src/reflex_radix/themes/layout/section.py @@ -4,11 +4,12 @@ from typing import Literal -from reflex.components.component import field from reflex_components.core.breakpoints import Responsive from reflex_components.el import elements -from reflex_components.radix.themes.base import RadixThemesComponent + +from reflex.components.component import field from reflex.vars.base import LiteralVar, Var +from reflex_radix.themes.base import RadixThemesComponent LiteralSectionSize = Literal["1", "2", "3"] diff --git a/packages/reflex-components/src/reflex_components/radix/themes/layout/spacer.py b/packages/reflex-radix/src/reflex_radix/themes/layout/spacer.py similarity index 100% rename from packages/reflex-components/src/reflex_components/radix/themes/layout/spacer.py rename to packages/reflex-radix/src/reflex_radix/themes/layout/spacer.py diff --git a/packages/reflex-components/src/reflex_components/radix/themes/layout/stack.py b/packages/reflex-radix/src/reflex_radix/themes/layout/stack.py similarity index 95% rename from packages/reflex-components/src/reflex_components/radix/themes/layout/stack.py rename to packages/reflex-radix/src/reflex_radix/themes/layout/stack.py index b86c8940a5a..d3bfa7b66f1 100644 --- a/packages/reflex-components/src/reflex_components/radix/themes/layout/stack.py +++ b/packages/reflex-radix/src/reflex_radix/themes/layout/stack.py @@ -2,10 +2,11 @@ from __future__ import annotations -from reflex.components.component import Component, field from reflex_components.core.breakpoints import Responsive -from reflex_components.radix.themes.base import LiteralAlign, LiteralSpacing + +from reflex.components.component import Component, field from reflex.vars.base import Var +from reflex_radix.themes.base import LiteralAlign, LiteralSpacing from .flex import Flex, LiteralFlexDirection diff --git a/packages/reflex-components/src/reflex_components/radix/themes/typography/__init__.py b/packages/reflex-radix/src/reflex_radix/themes/typography/__init__.py similarity index 100% rename from packages/reflex-components/src/reflex_components/radix/themes/typography/__init__.py rename to packages/reflex-radix/src/reflex_radix/themes/typography/__init__.py diff --git a/packages/reflex-components/src/reflex_components/radix/themes/typography/base.py b/packages/reflex-radix/src/reflex_radix/themes/typography/base.py similarity index 100% rename from packages/reflex-components/src/reflex_components/radix/themes/typography/base.py rename to packages/reflex-radix/src/reflex_radix/themes/typography/base.py diff --git a/packages/reflex-components/src/reflex_components/radix/themes/typography/blockquote.py b/packages/reflex-radix/src/reflex_radix/themes/typography/blockquote.py similarity index 91% rename from packages/reflex-components/src/reflex_components/radix/themes/typography/blockquote.py rename to packages/reflex-radix/src/reflex_radix/themes/typography/blockquote.py index ecbd7875e2a..b2291307179 100644 --- a/packages/reflex-components/src/reflex_components/radix/themes/typography/blockquote.py +++ b/packages/reflex-radix/src/reflex_radix/themes/typography/blockquote.py @@ -5,11 +5,12 @@ from __future__ import annotations -from reflex.components.component import field from reflex_components.core.breakpoints import Responsive from reflex_components.el import elements -from reflex_components.radix.themes.base import LiteralAccentColor, RadixThemesComponent + +from reflex.components.component import field from reflex.vars.base import Var +from reflex_radix.themes.base import LiteralAccentColor, RadixThemesComponent from .base import LiteralTextSize, LiteralTextWeight diff --git a/packages/reflex-components/src/reflex_components/radix/themes/typography/code.py b/packages/reflex-radix/src/reflex_radix/themes/typography/code.py similarity index 90% rename from packages/reflex-components/src/reflex_components/radix/themes/typography/code.py rename to packages/reflex-radix/src/reflex_radix/themes/typography/code.py index 69de851d7c9..75136d18170 100644 --- a/packages/reflex-components/src/reflex_components/radix/themes/typography/code.py +++ b/packages/reflex-radix/src/reflex_radix/themes/typography/code.py @@ -5,16 +5,17 @@ from __future__ import annotations -from reflex.components.component import field from reflex_components.core.breakpoints import Responsive from reflex_components.el import elements -from reflex_components.markdown.markdown import MarkdownComponentMap -from reflex_components.radix.themes.base import ( +from reflex_markdown.markdown import MarkdownComponentMap + +from reflex.components.component import field +from reflex.vars.base import Var +from reflex_radix.themes.base import ( LiteralAccentColor, LiteralVariant, RadixThemesComponent, ) -from reflex.vars.base import Var from .base import LiteralTextSize, LiteralTextWeight diff --git a/packages/reflex-components/src/reflex_components/radix/themes/typography/heading.py b/packages/reflex-radix/src/reflex_radix/themes/typography/heading.py similarity index 91% rename from packages/reflex-components/src/reflex_components/radix/themes/typography/heading.py rename to packages/reflex-radix/src/reflex_radix/themes/typography/heading.py index f7ec7033c8c..2b8a448cefb 100644 --- a/packages/reflex-components/src/reflex_components/radix/themes/typography/heading.py +++ b/packages/reflex-radix/src/reflex_radix/themes/typography/heading.py @@ -5,12 +5,13 @@ from __future__ import annotations -from reflex.components.component import field from reflex_components.core.breakpoints import Responsive from reflex_components.el import elements -from reflex_components.markdown.markdown import MarkdownComponentMap -from reflex_components.radix.themes.base import LiteralAccentColor, RadixThemesComponent +from reflex_markdown.markdown import MarkdownComponentMap + +from reflex.components.component import field from reflex.vars.base import Var +from reflex_radix.themes.base import LiteralAccentColor, RadixThemesComponent from .base import LiteralTextAlign, LiteralTextSize, LiteralTextTrim, LiteralTextWeight diff --git a/packages/reflex-components/src/reflex_components/radix/themes/typography/link.py b/packages/reflex-radix/src/reflex_radix/themes/typography/link.py similarity index 94% rename from packages/reflex-components/src/reflex_components/radix/themes/typography/link.py rename to packages/reflex-radix/src/reflex_radix/themes/typography/link.py index b688fc9e3cb..5684563252e 100644 --- a/packages/reflex-components/src/reflex_components/radix/themes/typography/link.py +++ b/packages/reflex-radix/src/reflex_radix/themes/typography/link.py @@ -7,16 +7,17 @@ from typing import Literal -from reflex.components.component import Component, MemoizationLeaf, field from reflex_components.core.breakpoints import Responsive from reflex_components.core.colors import color from reflex_components.core.cond import cond from reflex_components.el.elements.inline import A -from reflex_components.markdown.markdown import MarkdownComponentMap -from reflex_components.radix.themes.base import LiteralAccentColor, RadixThemesComponent -from reflex_components.react_router.dom import ReactRouterLink +from reflex_markdown.markdown import MarkdownComponentMap +from reflex_react_router.dom import ReactRouterLink + +from reflex.components.component import Component, MemoizationLeaf, field from reflex.utils.imports import ImportDict, ImportVar from reflex.vars.base import Var +from reflex_radix.themes.base import LiteralAccentColor, RadixThemesComponent from .base import LiteralTextSize, LiteralTextTrim, LiteralTextWeight diff --git a/packages/reflex-components/src/reflex_components/radix/themes/typography/text.py b/packages/reflex-radix/src/reflex_radix/themes/typography/text.py similarity index 94% rename from packages/reflex-components/src/reflex_components/radix/themes/typography/text.py rename to packages/reflex-radix/src/reflex_radix/themes/typography/text.py index 666cf8d4512..e95932c8d22 100644 --- a/packages/reflex-components/src/reflex_components/radix/themes/typography/text.py +++ b/packages/reflex-radix/src/reflex_radix/themes/typography/text.py @@ -7,12 +7,13 @@ from typing import Literal -from reflex.components.component import ComponentNamespace, field from reflex_components.core.breakpoints import Responsive from reflex_components.el import elements -from reflex_components.markdown.markdown import MarkdownComponentMap -from reflex_components.radix.themes.base import LiteralAccentColor, RadixThemesComponent +from reflex_markdown.markdown import MarkdownComponentMap + +from reflex.components.component import ComponentNamespace, field from reflex.vars.base import Var +from reflex_radix.themes.base import LiteralAccentColor, RadixThemesComponent from .base import LiteralTextAlign, LiteralTextSize, LiteralTextTrim, LiteralTextWeight diff --git a/packages/reflex-react-player/README.md b/packages/reflex-react-player/README.md new file mode 100644 index 00000000000..b0e28d519ae --- /dev/null +++ b/packages/reflex-react-player/README.md @@ -0,0 +1 @@ +# Reflex react-player diff --git a/packages/reflex-react-player/pyproject.toml b/packages/reflex-react-player/pyproject.toml new file mode 100644 index 00000000000..84931013428 --- /dev/null +++ b/packages/reflex-react-player/pyproject.toml @@ -0,0 +1,13 @@ +[project] +name = "reflex-react-player" +version = "0.0.1" +description = "Reflex react-player components." +readme = "README.md" +authors = [{ name = "Khaleel Al-Adhami", email = "khaleel@reflex.dev" }] +maintainers = [{ name = "Khaleel Al-Adhami", email = "khaleel@reflex.dev" }] +requires-python = ">=3.10" +dependencies = [] + +[build-system] +requires = ["uv_build>=0.10.11,<0.12.0"] +build-backend = "uv_build" diff --git a/packages/reflex-components/src/reflex_components/react_player/__init__.py b/packages/reflex-react-player/src/reflex_react_player/__init__.py similarity index 100% rename from packages/reflex-components/src/reflex_components/react_player/__init__.py rename to packages/reflex-react-player/src/reflex_react_player/__init__.py diff --git a/packages/reflex-components/src/reflex_components/react_player/audio.py b/packages/reflex-react-player/src/reflex_react_player/audio.py similarity index 61% rename from packages/reflex-components/src/reflex_components/react_player/audio.py rename to packages/reflex-react-player/src/reflex_react_player/audio.py index 7223bc71a6a..ca0bf16c8e8 100644 --- a/packages/reflex-components/src/reflex_components/react_player/audio.py +++ b/packages/reflex-react-player/src/reflex_react_player/audio.py @@ -1,6 +1,6 @@ """A audio component.""" -from reflex_components.react_player.react_player import ReactPlayer +from reflex_react_player.react_player import ReactPlayer class Audio(ReactPlayer): diff --git a/packages/reflex-components/src/reflex_components/react_player/react_player.py b/packages/reflex-react-player/src/reflex_react_player/react_player.py similarity index 99% rename from packages/reflex-components/src/reflex_components/react_player/react_player.py rename to packages/reflex-react-player/src/reflex_react_player/react_player.py index 943d78c904b..2513c3a4756 100644 --- a/packages/reflex-components/src/reflex_components/react_player/react_player.py +++ b/packages/reflex-react-player/src/reflex_react_player/react_player.py @@ -4,8 +4,9 @@ from typing import Any, TypedDict -from reflex.components.component import Component, field from reflex_components.core.cond import cond + +from reflex.components.component import Component, field from reflex.event import EventHandler, no_args_event_spec from reflex.utils import console from reflex.vars.base import Var diff --git a/packages/reflex-components/src/reflex_components/react_player/video.py b/packages/reflex-react-player/src/reflex_react_player/video.py similarity index 61% rename from packages/reflex-components/src/reflex_components/react_player/video.py rename to packages/reflex-react-player/src/reflex_react_player/video.py index 9f0381772b2..e63e3037946 100644 --- a/packages/reflex-components/src/reflex_components/react_player/video.py +++ b/packages/reflex-react-player/src/reflex_react_player/video.py @@ -1,6 +1,6 @@ """A video component.""" -from reflex_components.react_player.react_player import ReactPlayer +from reflex_react_player.react_player import ReactPlayer class Video(ReactPlayer): diff --git a/packages/reflex-react-router/README.md b/packages/reflex-react-router/README.md new file mode 100644 index 00000000000..6aa783f98bd --- /dev/null +++ b/packages/reflex-react-router/README.md @@ -0,0 +1 @@ +# Reflex react-router diff --git a/packages/reflex-react-router/pyproject.toml b/packages/reflex-react-router/pyproject.toml new file mode 100644 index 00000000000..e02f826ac8d --- /dev/null +++ b/packages/reflex-react-router/pyproject.toml @@ -0,0 +1,13 @@ +[project] +name = "reflex-react-router" +version = "0.0.1" +description = "Reflex react-router components." +readme = "README.md" +authors = [{ name = "Khaleel Al-Adhami", email = "khaleel@reflex.dev" }] +maintainers = [{ name = "Khaleel Al-Adhami", email = "khaleel@reflex.dev" }] +requires-python = ">=3.10" +dependencies = [] + +[build-system] +requires = ["uv_build>=0.10.11,<0.12.0"] +build-backend = "uv_build" diff --git a/packages/reflex-components/src/reflex_components/react_router/__init__.py b/packages/reflex-react-router/src/reflex_react_router/__init__.py similarity index 100% rename from packages/reflex-components/src/reflex_components/react_router/__init__.py rename to packages/reflex-react-router/src/reflex_react_router/__init__.py diff --git a/packages/reflex-components/src/reflex_components/react_router/dom.py b/packages/reflex-react-router/src/reflex_react_router/dom.py similarity index 99% rename from packages/reflex-components/src/reflex_components/react_router/dom.py rename to packages/reflex-react-router/src/reflex_react_router/dom.py index d2e759ad9fd..683ebdcd348 100644 --- a/packages/reflex-components/src/reflex_components/react_router/dom.py +++ b/packages/reflex-react-router/src/reflex_react_router/dom.py @@ -4,8 +4,9 @@ from typing import ClassVar, Literal, TypedDict -from reflex.components.component import field from reflex_components.el.elements.inline import A + +from reflex.components.component import field from reflex.vars.base import Var LiteralLinkDiscover = Literal["none", "render"] diff --git a/packages/reflex-recharts/README.md b/packages/reflex-recharts/README.md new file mode 100644 index 00000000000..f706ed69105 --- /dev/null +++ b/packages/reflex-recharts/README.md @@ -0,0 +1 @@ +# Reflex recharts diff --git a/packages/reflex-recharts/pyproject.toml b/packages/reflex-recharts/pyproject.toml new file mode 100644 index 00000000000..348118467b6 --- /dev/null +++ b/packages/reflex-recharts/pyproject.toml @@ -0,0 +1,13 @@ +[project] +name = "reflex-recharts" +version = "0.0.1" +description = "Reflex recharts components." +readme = "README.md" +authors = [{ name = "Khaleel Al-Adhami", email = "khaleel@reflex.dev" }] +maintainers = [{ name = "Khaleel Al-Adhami", email = "khaleel@reflex.dev" }] +requires-python = ">=3.10" +dependencies = [] + +[build-system] +requires = ["uv_build>=0.10.11,<0.12.0"] +build-backend = "uv_build" diff --git a/packages/reflex-components/src/reflex_components/recharts/__init__.py b/packages/reflex-recharts/src/reflex_recharts/__init__.py similarity index 100% rename from packages/reflex-components/src/reflex_components/recharts/__init__.py rename to packages/reflex-recharts/src/reflex_recharts/__init__.py diff --git a/packages/reflex-components/src/reflex_components/recharts/cartesian.py b/packages/reflex-recharts/src/reflex_recharts/cartesian.py similarity index 100% rename from packages/reflex-components/src/reflex_components/recharts/cartesian.py rename to packages/reflex-recharts/src/reflex_recharts/cartesian.py diff --git a/packages/reflex-components/src/reflex_components/recharts/charts.py b/packages/reflex-recharts/src/reflex_recharts/charts.py similarity index 99% rename from packages/reflex-components/src/reflex_components/recharts/charts.py rename to packages/reflex-recharts/src/reflex_recharts/charts.py index 83a83355fdb..26d53d38225 100644 --- a/packages/reflex-components/src/reflex_components/recharts/charts.py +++ b/packages/reflex-recharts/src/reflex_recharts/charts.py @@ -6,11 +6,11 @@ from typing import Any, ClassVar from reflex.components.component import Component, field -from reflex_components.recharts.general import ResponsiveContainer from reflex.constants import EventTriggers from reflex.constants.colors import Color from reflex.event import EventHandler, no_args_event_spec from reflex.vars.base import Var +from reflex_recharts.general import ResponsiveContainer from .recharts import ( LiteralAnimationEasing, diff --git a/packages/reflex-components/src/reflex_components/recharts/general.py b/packages/reflex-recharts/src/reflex_recharts/general.py similarity index 100% rename from packages/reflex-components/src/reflex_components/recharts/general.py rename to packages/reflex-recharts/src/reflex_recharts/general.py diff --git a/packages/reflex-components/src/reflex_components/recharts/polar.py b/packages/reflex-recharts/src/reflex_recharts/polar.py similarity index 100% rename from packages/reflex-components/src/reflex_components/recharts/polar.py rename to packages/reflex-recharts/src/reflex_recharts/polar.py diff --git a/packages/reflex-components/src/reflex_components/recharts/recharts.py b/packages/reflex-recharts/src/reflex_recharts/recharts.py similarity index 100% rename from packages/reflex-components/src/reflex_components/recharts/recharts.py rename to packages/reflex-recharts/src/reflex_recharts/recharts.py diff --git a/packages/reflex-sonner/README.md b/packages/reflex-sonner/README.md new file mode 100644 index 00000000000..086bf641a10 --- /dev/null +++ b/packages/reflex-sonner/README.md @@ -0,0 +1 @@ +# Reflex sonner diff --git a/packages/reflex-sonner/pyproject.toml b/packages/reflex-sonner/pyproject.toml new file mode 100644 index 00000000000..60c983b5942 --- /dev/null +++ b/packages/reflex-sonner/pyproject.toml @@ -0,0 +1,13 @@ +[project] +name = "reflex-sonner" +version = "0.0.1" +description = "Reflex sonner components." +readme = "README.md" +authors = [{ name = "Khaleel Al-Adhami", email = "khaleel@reflex.dev" }] +maintainers = [{ name = "Khaleel Al-Adhami", email = "khaleel@reflex.dev" }] +requires-python = ">=3.10" +dependencies = [] + +[build-system] +requires = ["uv_build>=0.10.11,<0.12.0"] +build-backend = "uv_build" diff --git a/packages/reflex-components/src/reflex_components/sonner/__init__.py b/packages/reflex-sonner/src/reflex_sonner/__init__.py similarity index 100% rename from packages/reflex-components/src/reflex_components/sonner/__init__.py rename to packages/reflex-sonner/src/reflex_sonner/__init__.py diff --git a/packages/reflex-components/src/reflex_components/sonner/toast.py b/packages/reflex-sonner/src/reflex_sonner/toast.py similarity index 99% rename from packages/reflex-components/src/reflex_components/sonner/toast.py rename to packages/reflex-sonner/src/reflex_sonner/toast.py index b4d7566de36..f85591dec0f 100644 --- a/packages/reflex-components/src/reflex_components/sonner/toast.py +++ b/packages/reflex-sonner/src/reflex_sonner/toast.py @@ -5,8 +5,9 @@ import dataclasses from typing import Any, Literal -from reflex.components.component import Component, ComponentNamespace, field from reflex_components.lucide.icon import Icon + +from reflex.components.component import Component, ComponentNamespace, field from reflex.components.props import NoExtrasAllowedProps from reflex.constants.base import Dirs from reflex.event import EventSpec, run_script diff --git a/pyi_hashes.json b/pyi_hashes.json index 2f366c25e87..0c7904dea38 100644 --- a/pyi_hashes.json +++ b/pyi_hashes.json @@ -1,123 +1,123 @@ { - "packages/reflex-components/src/reflex_components/__init__.pyi": "760d3249cd2e9c55d1e8963054515231", + "packages/reflex-components/src/reflex_components/__init__.pyi": "240d03f6b24d4d6c76b5d7ed5f47b6bc", "packages/reflex-components/src/reflex_components/base/__init__.pyi": "d2303a7a1a18f9390ffe0d885b20c9d5", - "packages/reflex-components/src/reflex_components/base/app_wrap.pyi": "9672898d1e6c4e0ae6bfa281d41b2389", + "packages/reflex-components/src/reflex_components/base/app_wrap.pyi": "b21c49560793be0a2dd87f65fcd02243", "packages/reflex-components/src/reflex_components/base/body.pyi": "4b7b090ca81fa9913ba208a8a2dc3fd4", "packages/reflex-components/src/reflex_components/base/document.pyi": "0ec240e365318f181aa9fdaa4e0ce7f0", - "packages/reflex-components/src/reflex_components/base/error_boundary.pyi": "b40fe30f7d29ff467710a86879542070", + "packages/reflex-components/src/reflex_components/base/error_boundary.pyi": "81462e3ebf56153fbc1841dc47b0ebba", "packages/reflex-components/src/reflex_components/base/fragment.pyi": "0e709d1366b59f68a4ba811544ef9650", - "packages/reflex-components/src/reflex_components/base/link.pyi": "f4fb3b6e437a7e12279c9c720c163fed", - "packages/reflex-components/src/reflex_components/base/meta.pyi": "d7b2df571c0909abcab81edc6779d6da", - "packages/reflex-components/src/reflex_components/base/script.pyi": "8a498a6df7b104e6d612cd880ad65203", + "packages/reflex-components/src/reflex_components/base/link.pyi": "29845b3aea542bc9882c17952486c480", + "packages/reflex-components/src/reflex_components/base/meta.pyi": "566bdf49f29297eb3067c248e3971bc2", + "packages/reflex-components/src/reflex_components/base/script.pyi": "7ccc6fba10a33722212e8a6d472d5ef9", "packages/reflex-components/src/reflex_components/base/strict_mode.pyi": "b3bc948878137f371f0464163ab82a66", "packages/reflex-components/src/reflex_components/core/__init__.pyi": "d47e3059a7531c2eadc0ec0c4283bd30", - "packages/reflex-components/src/reflex_components/core/auto_scroll.pyi": "71366c7018110dca86fe689b332aa524", - "packages/reflex-components/src/reflex_components/core/banner.pyi": "ffc008bf6f001495e11076aeaf942ef0", - "packages/reflex-components/src/reflex_components/core/clipboard.pyi": "6d4990ec436bf1d3ccea5a8bb91c7875", + "packages/reflex-components/src/reflex_components/core/auto_scroll.pyi": "4d670d66c3f51a512841e7602cae245e", + "packages/reflex-components/src/reflex_components/core/banner.pyi": "6c5accb0329d67554a8e9d137d3b5e98", + "packages/reflex-components/src/reflex_components/core/clipboard.pyi": "ca379d3a1afaf4da438ac3eaf2cf8e3f", "packages/reflex-components/src/reflex_components/core/debounce.pyi": "bce792c1b6fc05f2449b46a5e4580d97", "packages/reflex-components/src/reflex_components/core/helmet.pyi": "9dda69cc8f6563a7062be75a5a9a1766", - "packages/reflex-components/src/reflex_components/core/html.pyi": "3ad4a88e2062d4e5470ff8f6836bbc4a", - "packages/reflex-components/src/reflex_components/core/sticky.pyi": "16656267c47cb13692679d4a6551adf7", - "packages/reflex-components/src/reflex_components/core/upload.pyi": "371f5891d35430ae4f9afea6e97c0fe3", - "packages/reflex-components/src/reflex_components/core/window_events.pyi": "c520860e6cedb6c018319e3f99b0db07", + "packages/reflex-components/src/reflex_components/core/html.pyi": "3a6e4412987660a94618141d5d9781f3", + "packages/reflex-components/src/reflex_components/core/sticky.pyi": "ced21fad06e510145f5c19500b15eb43", + "packages/reflex-components/src/reflex_components/core/upload.pyi": "bfdcf703ea1e8eb5b07e03dfe1756caa", + "packages/reflex-components/src/reflex_components/core/window_events.pyi": "ad784ba099a49b095f5f7e65511649cf", "packages/reflex-components/src/reflex_components/datadisplay/__init__.pyi": "8844db1b47917daa949ed5ac2073c7b8", - "packages/reflex-components/src/reflex_components/datadisplay/code.pyi": "5a6a9f439b34a695af61b8b21b996584", + "packages/reflex-components/src/reflex_components/datadisplay/code.pyi": "1a25a5f0c10c5af04517531f037f876d", "packages/reflex-components/src/reflex_components/datadisplay/dataeditor.pyi": "4719c4de53d79d219053fb870d3cabc6", - "packages/reflex-components/src/reflex_components/datadisplay/shiki_code_block.pyi": "5a08e435625a1f5b34231faf8d46984b", - "packages/reflex-components/src/reflex_components/el/__init__.pyi": "477afb183b4ac1baebc91a2c0a0544fd", + "packages/reflex-components/src/reflex_components/datadisplay/shiki_code_block.pyi": "b19254d2c37b167230790a1c0c93869a", + "packages/reflex-components/src/reflex_components/el/__init__.pyi": "7702649d2a9304bc2d9345aa40c2c039", "packages/reflex-components/src/reflex_components/el/element.pyi": "55604caea0edce331f7672a8f8e82f4a", "packages/reflex-components/src/reflex_components/el/elements/__init__.pyi": "a9c6b03e1a2a73e6278a6ebfc3127429", - "packages/reflex-components/src/reflex_components/el/elements/base.pyi": "4c8d5d21f1ccb927cf03efb9a8350a8e", - "packages/reflex-components/src/reflex_components/el/elements/forms.pyi": "941846dcb64ff471ebd03e604979f982", + "packages/reflex-components/src/reflex_components/el/elements/base.pyi": "237c56feee3a1269c7bc7aa77421d57d", + "packages/reflex-components/src/reflex_components/el/elements/forms.pyi": "d8419d197afd40479e97486188527d7d", "packages/reflex-components/src/reflex_components/el/elements/inline.pyi": "f96166755444d611daa933581c66e955", - "packages/reflex-components/src/reflex_components/el/elements/media.pyi": "14870b03495566fb709cb80658a7bd61", - "packages/reflex-components/src/reflex_components/el/elements/metadata.pyi": "b69d0ea2bae2e9c7fcf27589127f0d36", + "packages/reflex-components/src/reflex_components/el/elements/media.pyi": "98f02f19526fbb4e9f0935ca4fc2f6a7", + "packages/reflex-components/src/reflex_components/el/elements/metadata.pyi": "fed6c39500a30b88a5dd4fd9af0a604e", "packages/reflex-components/src/reflex_components/el/elements/other.pyi": "2a03bd4e607ce7a90f65f0488eab8ca8", - "packages/reflex-components/src/reflex_components/el/elements/scripts.pyi": "422a4e8adce548a3cf3c09184945f455", + "packages/reflex-components/src/reflex_components/el/elements/scripts.pyi": "2703d60187f624481247293870bb1a24", "packages/reflex-components/src/reflex_components/el/elements/sectioning.pyi": "e44354124a23f88e31d5c158430a6341", "packages/reflex-components/src/reflex_components/el/elements/tables.pyi": "e3b27ff3699fe612ac0ab81689452195", "packages/reflex-components/src/reflex_components/el/elements/typography.pyi": "2987ced0f04c729751a09dbafdff558d", "packages/reflex-components/src/reflex_components/gridjs/datatable.pyi": "7f85bb4e93c355ec0d19b3bb17f0d2cf", "packages/reflex-components/src/reflex_components/lucide/icon.pyi": "8b48d954a6c9f1825c5123893ca2bf7f", - "packages/reflex-components/src/reflex_components/markdown/markdown.pyi": "0865d8c3903158c66796c6ef0966bc40", "packages/reflex-components/src/reflex_components/moment/moment.pyi": "619390dc6611c0de70b26eabbe73fb19", - "packages/reflex-components/src/reflex_components/plotly/plotly.pyi": "a4e04cb2cbc44fb7514167eea12aca4c", - "packages/reflex-components/src/reflex_components/radix/__init__.pyi": "233a92ee0848860f23c43d22a5975a37", - "packages/reflex-components/src/reflex_components/radix/primitives/__init__.pyi": "3995eab0f47e72fdac088ac18113b345", - "packages/reflex-components/src/reflex_components/radix/primitives/accordion.pyi": "270ba5ff161fc071d8586641861ce0d1", - "packages/reflex-components/src/reflex_components/radix/primitives/base.pyi": "c0b3ccba236cdbcf865246ef12b4bc47", - "packages/reflex-components/src/reflex_components/radix/primitives/dialog.pyi": "5eb60fcef948b0a9e140da505ff179b9", - "packages/reflex-components/src/reflex_components/radix/primitives/drawer.pyi": "80e5b25d28f359a546d5c420868a78c0", - "packages/reflex-components/src/reflex_components/radix/primitives/form.pyi": "e674d866ab762f7981c37d7496e7d8f5", - "packages/reflex-components/src/reflex_components/radix/primitives/progress.pyi": "090918a1012a8ca167b393d607d5eaca", - "packages/reflex-components/src/reflex_components/radix/primitives/slider.pyi": "11b2fd7ec2ea672f1d1b09720041040d", - "packages/reflex-components/src/reflex_components/radix/themes/__init__.pyi": "7e1a012bc6f715ba409545df7e8dfc98", - "packages/reflex-components/src/reflex_components/radix/themes/base.pyi": "882fa26c3cda78a97d74ea42e9b81cab", - "packages/reflex-components/src/reflex_components/radix/themes/color_mode.pyi": "58e027cf96c7b61098b2e5fbe74d5286", - "packages/reflex-components/src/reflex_components/radix/themes/components/__init__.pyi": "a72c44dcf366b3cf6f1223ee446d12f8", - "packages/reflex-components/src/reflex_components/radix/themes/components/alert_dialog.pyi": "bbf3135eff6cba018ff1c05829c31fae", - "packages/reflex-components/src/reflex_components/radix/themes/components/aspect_ratio.pyi": "ed081f844343c6f36bb1967dca7bbf4e", - "packages/reflex-components/src/reflex_components/radix/themes/components/avatar.pyi": "8d889ca28e0a5f53e7e7798371570ddb", - "packages/reflex-components/src/reflex_components/radix/themes/components/badge.pyi": "33d72707a0e30595c31a1a0c39f3eebd", - "packages/reflex-components/src/reflex_components/radix/themes/components/button.pyi": "e0738952f361f17b24506153fb4e953d", - "packages/reflex-components/src/reflex_components/radix/themes/components/callout.pyi": "7cd794c97556fdf8a775bf7ead533b4d", - "packages/reflex-components/src/reflex_components/radix/themes/components/card.pyi": "1773ab976ccd025e7a4b21b5eca36db9", - "packages/reflex-components/src/reflex_components/radix/themes/components/checkbox.pyi": "346f5b9b7d3cba41d97e1f623b4fe2d3", - "packages/reflex-components/src/reflex_components/radix/themes/components/checkbox_cards.pyi": "b9a8f4b0fd88e04991d0e7aa5bae9fbb", - "packages/reflex-components/src/reflex_components/radix/themes/components/checkbox_group.pyi": "f1f19e9d997c6965fe73b699ea969ac1", - "packages/reflex-components/src/reflex_components/radix/themes/components/context_menu.pyi": "859ff8a0293ed99cfac6238791d4675c", - "packages/reflex-components/src/reflex_components/radix/themes/components/data_list.pyi": "0863370c6d9309e933b0bfddfe815469", - "packages/reflex-components/src/reflex_components/radix/themes/components/dialog.pyi": "b37bf94b6f09fff735a7d0ded8c98a09", - "packages/reflex-components/src/reflex_components/radix/themes/components/dropdown_menu.pyi": "e447c5fa580d5a2d0cfa073281800b1a", - "packages/reflex-components/src/reflex_components/radix/themes/components/hover_card.pyi": "3fca7c21b6055c04580140b1b938ab72", - "packages/reflex-components/src/reflex_components/radix/themes/components/icon_button.pyi": "3af538b407731f27f8a4fb6cc94adde5", - "packages/reflex-components/src/reflex_components/radix/themes/components/inset.pyi": "cd3690ddfa29e9bfcaba6afa13a915bf", - "packages/reflex-components/src/reflex_components/radix/themes/components/popover.pyi": "d57149b28c9ac873897f9f9afb2e9bc1", - "packages/reflex-components/src/reflex_components/radix/themes/components/progress.pyi": "39b23b217ed8fc29376d337892540d50", - "packages/reflex-components/src/reflex_components/radix/themes/components/radio.pyi": "b9fa6171755eb2345584c34b4976457d", - "packages/reflex-components/src/reflex_components/radix/themes/components/radio_cards.pyi": "b21275ab34ac18d75cb0bbb18c074f24", - "packages/reflex-components/src/reflex_components/radix/themes/components/radio_group.pyi": "b91a679b9aa7d823f2a15dbab10448b5", - "packages/reflex-components/src/reflex_components/radix/themes/components/scroll_area.pyi": "0e161bfc88b42ff3d05a20bd3f74c545", - "packages/reflex-components/src/reflex_components/radix/themes/components/segmented_control.pyi": "b8b8ebc506280ebb7993c5d6b9e43a5b", - "packages/reflex-components/src/reflex_components/radix/themes/components/select.pyi": "560f412a0983632a2e3f2ed9f5565c7f", - "packages/reflex-components/src/reflex_components/radix/themes/components/separator.pyi": "97f7e832d55a6900194037b70a140f6f", - "packages/reflex-components/src/reflex_components/radix/themes/components/skeleton.pyi": "b9b8a3ef80b8b9b0d9ed6b9130f73f7c", - "packages/reflex-components/src/reflex_components/radix/themes/components/slider.pyi": "1e92cf88c6bcdb048a809710dfee3ed4", - "packages/reflex-components/src/reflex_components/radix/themes/components/spinner.pyi": "99297f40b3129b5f3aec45f35578a820", - "packages/reflex-components/src/reflex_components/radix/themes/components/switch.pyi": "bc114c006f0b9b9085e0a259dc9e358f", - "packages/reflex-components/src/reflex_components/radix/themes/components/table.pyi": "4305a28fc4ab98a9cac1985f5465eeaa", - "packages/reflex-components/src/reflex_components/radix/themes/components/tabs.pyi": "ca93a5feb35a9c16a2ca53fd221f9788", - "packages/reflex-components/src/reflex_components/radix/themes/components/text_area.pyi": "57d52555cf412f4e6731f13f7df925c6", - "packages/reflex-components/src/reflex_components/radix/themes/components/text_field.pyi": "05d4352f53895f3d0e00e2723f0532b1", - "packages/reflex-components/src/reflex_components/radix/themes/components/tooltip.pyi": "d6c8411990befde7d1e9bd8d10e97b18", - "packages/reflex-components/src/reflex_components/radix/themes/layout/__init__.pyi": "d73df4cb5d900261c2c43de8a302c229", - "packages/reflex-components/src/reflex_components/radix/themes/layout/base.pyi": "d2aece3ac78dfe10893d5257417974b2", - "packages/reflex-components/src/reflex_components/radix/themes/layout/box.pyi": "4ccaa1fabaa232a37dd77c58fbbb3064", - "packages/reflex-components/src/reflex_components/radix/themes/layout/center.pyi": "6bde3fb0b73d6b0bb1b48a1753ccc03e", - "packages/reflex-components/src/reflex_components/radix/themes/layout/container.pyi": "501a965041ee74c0e300f22e78f56dfe", - "packages/reflex-components/src/reflex_components/radix/themes/layout/flex.pyi": "af282030c25d04b59b9308f806e60084", - "packages/reflex-components/src/reflex_components/radix/themes/layout/grid.pyi": "56b4e25ea1dae0714409d393946ca94f", - "packages/reflex-components/src/reflex_components/radix/themes/layout/list.pyi": "69ae715f678b932ccc6b81195bfff8b5", - "packages/reflex-components/src/reflex_components/radix/themes/layout/section.pyi": "353645d8f6dc36c55ad330aee98c50f4", - "packages/reflex-components/src/reflex_components/radix/themes/layout/spacer.pyi": "c81cb167c7ad003177f535e3508dfb9e", - "packages/reflex-components/src/reflex_components/radix/themes/layout/stack.pyi": "6e283abbce7ed43973ed22edc9c8fd08", - "packages/reflex-components/src/reflex_components/radix/themes/typography/__init__.pyi": "b8fa254569fbe3bd2c0ca425f8c02b7d", - "packages/reflex-components/src/reflex_components/radix/themes/typography/blockquote.pyi": "d9e01352563e26ed8fd36ace06c1308f", - "packages/reflex-components/src/reflex_components/radix/themes/typography/code.pyi": "c3545061bcb84da208346d167b55b2e4", - "packages/reflex-components/src/reflex_components/radix/themes/typography/heading.pyi": "d14fbb1252d824eb190903eb99e3a3ca", - "packages/reflex-components/src/reflex_components/radix/themes/typography/link.pyi": "adbba1d868722f0c9a324fbafc90d250", - "packages/reflex-components/src/reflex_components/radix/themes/typography/text.pyi": "f8a6d3e852660eff9ea43bb740d29a7a", - "packages/reflex-components/src/reflex_components/react_player/audio.pyi": "e78e94d28fe752bc3cf72bf63671c606", - "packages/reflex-components/src/reflex_components/react_player/react_player.pyi": "0b737bb8bc2feca702125b29c21741b7", - "packages/reflex-components/src/reflex_components/react_player/video.pyi": "28deeb97e5b4ee4da7c0b8f819dc9b89", - "packages/reflex-components/src/reflex_components/react_router/dom.pyi": "936c045b8adbc0e0f96ce66a380d4cdc", - "packages/reflex-components/src/reflex_components/recharts/__init__.pyi": "8813aaa710352e4bafb68f70ca18d73f", - "packages/reflex-components/src/reflex_components/recharts/cartesian.pyi": "ce549ad2157ad986607bc3eb361c2830", - "packages/reflex-components/src/reflex_components/recharts/charts.pyi": "2e64ecdbaac453423e6d915149e2c3ba", - "packages/reflex-components/src/reflex_components/recharts/general.pyi": "eefaa54bdeca02457e2dc7dc95a1a28c", - "packages/reflex-components/src/reflex_components/recharts/polar.pyi": "8e3633223a4c7d47fcc361931a2c713f", - "packages/reflex-components/src/reflex_components/recharts/recharts.pyi": "294907ff114fb120adda4647c99e0b95", - "packages/reflex-components/src/reflex_components/sonner/toast.pyi": "2a6f460088763ceff43427d999fc6369", - "reflex/__init__.pyi": "d3e2ea7cb94b6bd45f43a7b8a4848588", + "packages/reflex-markdown/src/reflex_markdown/markdown.pyi": "850554a9091ac20a12eb5eb483a1a251", + "packages/reflex-plotly/src/reflex_plotly/plotly.pyi": "44a46aaa75f1fe5f6e41d883b0802f6f", + "packages/reflex-radix/src/reflex_radix/__init__.pyi": "75cd2277d0f97081d32e19213906ad09", + "packages/reflex-radix/src/reflex_radix/primitives/__init__.pyi": "4c8a8df181c3f560d9a2608dac33dba7", + "packages/reflex-radix/src/reflex_radix/primitives/accordion.pyi": "437228ba3a78ae0bf9623087aa94be47", + "packages/reflex-radix/src/reflex_radix/primitives/base.pyi": "95d45d707fd11b29cb9072c46c68dce8", + "packages/reflex-radix/src/reflex_radix/primitives/dialog.pyi": "355d4c52ca984eef4271a99e6036ae9e", + "packages/reflex-radix/src/reflex_radix/primitives/drawer.pyi": "20cab3cb30285d0b146def51f89cd189", + "packages/reflex-radix/src/reflex_radix/primitives/form.pyi": "e6d69600381a201dd5e2a2166cc2d18c", + "packages/reflex-radix/src/reflex_radix/primitives/progress.pyi": "8adcee24351c54ea8c5cbf9cf5a7c962", + "packages/reflex-radix/src/reflex_radix/primitives/slider.pyi": "7f3145da33f3584bed2382d5308d3d71", + "packages/reflex-radix/src/reflex_radix/themes/__init__.pyi": "70deb6963522fc835109fe8286b5830e", + "packages/reflex-radix/src/reflex_radix/themes/base.pyi": "447b2e074290552480b20e68d42f0af6", + "packages/reflex-radix/src/reflex_radix/themes/color_mode.pyi": "b88ea84de588dc50913f085cc274c750", + "packages/reflex-radix/src/reflex_radix/themes/components/__init__.pyi": "0b669b9a5800852098f4d8fdcd47e3df", + "packages/reflex-radix/src/reflex_radix/themes/components/alert_dialog.pyi": "b9746eeda7ed9d851e75fb257fbec758", + "packages/reflex-radix/src/reflex_radix/themes/components/aspect_ratio.pyi": "3a422f6fb832ce0d0a2a6cfb47020beb", + "packages/reflex-radix/src/reflex_radix/themes/components/avatar.pyi": "4b49b975cfe78b051a9693ad0a3f4a0f", + "packages/reflex-radix/src/reflex_radix/themes/components/badge.pyi": "d4e992419d705607063a479550f19ad3", + "packages/reflex-radix/src/reflex_radix/themes/components/button.pyi": "1f6959ca0e00e6a75af244c90198b4c1", + "packages/reflex-radix/src/reflex_radix/themes/components/callout.pyi": "d0311447991bd873fb748189b3f52a7d", + "packages/reflex-radix/src/reflex_radix/themes/components/card.pyi": "d4c9fe9d681f3d4e5116d2156119e762", + "packages/reflex-radix/src/reflex_radix/themes/components/checkbox.pyi": "6f6fb294939ada2c51863fb10ac8aece", + "packages/reflex-radix/src/reflex_radix/themes/components/checkbox_cards.pyi": "1ebd42045339b490bbacb42d7a91249b", + "packages/reflex-radix/src/reflex_radix/themes/components/checkbox_group.pyi": "39418af266dd65beb0f9ed4d8b931128", + "packages/reflex-radix/src/reflex_radix/themes/components/context_menu.pyi": "8e75c53758726158b0c03bacdd70a7eb", + "packages/reflex-radix/src/reflex_radix/themes/components/data_list.pyi": "ac4c62ec039dc270db06e9b893d9e5d3", + "packages/reflex-radix/src/reflex_radix/themes/components/dialog.pyi": "8a6229b1769347cb2cce40241ed3c18b", + "packages/reflex-radix/src/reflex_radix/themes/components/dropdown_menu.pyi": "df7987ca847489ae7e26c2e177266a52", + "packages/reflex-radix/src/reflex_radix/themes/components/hover_card.pyi": "38268d27b6b645fc8da68ce992f97608", + "packages/reflex-radix/src/reflex_radix/themes/components/icon_button.pyi": "0039679ad1fe4944492eba67d90caa92", + "packages/reflex-radix/src/reflex_radix/themes/components/inset.pyi": "cf9aebf77708e6eb3d8fce5fc04693c1", + "packages/reflex-radix/src/reflex_radix/themes/components/popover.pyi": "2bf10a5e2d065d08332bb29a7e6f16d6", + "packages/reflex-radix/src/reflex_radix/themes/components/progress.pyi": "946f59142bb4145ddc57a76ae04f6470", + "packages/reflex-radix/src/reflex_radix/themes/components/radio.pyi": "e9c243f5c3b01805cfd7ad8189f14c5c", + "packages/reflex-radix/src/reflex_radix/themes/components/radio_cards.pyi": "af415db2dddb6fc4a2b99c326f9a4d0f", + "packages/reflex-radix/src/reflex_radix/themes/components/radio_group.pyi": "0761733c708e1d5f5bd58b6da4bfc853", + "packages/reflex-radix/src/reflex_radix/themes/components/scroll_area.pyi": "d7ef8d8e9b62e469bf0465019f3a49a5", + "packages/reflex-radix/src/reflex_radix/themes/components/segmented_control.pyi": "c280f75e5ac3877ba5b9722cdc562c53", + "packages/reflex-radix/src/reflex_radix/themes/components/select.pyi": "e7609d7df3f656a07652b8b872818e40", + "packages/reflex-radix/src/reflex_radix/themes/components/separator.pyi": "e2249dd4c3eafcd1b9f49c260db5eb3a", + "packages/reflex-radix/src/reflex_radix/themes/components/skeleton.pyi": "cae2b219cce7a1ec5ae05b9f7d1a1b76", + "packages/reflex-radix/src/reflex_radix/themes/components/slider.pyi": "87062150b2ffb63ecab59bf8b70e3769", + "packages/reflex-radix/src/reflex_radix/themes/components/spinner.pyi": "4611c927874be43ebd0069ee2b1c387e", + "packages/reflex-radix/src/reflex_radix/themes/components/switch.pyi": "15945496b266c663e8b35611883399b4", + "packages/reflex-radix/src/reflex_radix/themes/components/table.pyi": "253a0a1e9671aa9842d5bb40ccd122c8", + "packages/reflex-radix/src/reflex_radix/themes/components/tabs.pyi": "0ea3cb6aee40c2ad0632e605881872a9", + "packages/reflex-radix/src/reflex_radix/themes/components/text_area.pyi": "24df7e2528cadbdd010e6d84d8c811b2", + "packages/reflex-radix/src/reflex_radix/themes/components/text_field.pyi": "0154a98317b41feb9a541c1743730122", + "packages/reflex-radix/src/reflex_radix/themes/components/tooltip.pyi": "9fd2d97333c070482527106aaddb33df", + "packages/reflex-radix/src/reflex_radix/themes/layout/__init__.pyi": "2ee0097537b335fac09c78c8cdb18403", + "packages/reflex-radix/src/reflex_radix/themes/layout/base.pyi": "d6992c0dc01cdf1c0c08fe4ea08add2d", + "packages/reflex-radix/src/reflex_radix/themes/layout/box.pyi": "035bed4f0b70f4f058f71ade2df76b65", + "packages/reflex-radix/src/reflex_radix/themes/layout/center.pyi": "56f886020620ce0bf34a2147a75fca07", + "packages/reflex-radix/src/reflex_radix/themes/layout/container.pyi": "b50b26c4a9f105a5b691e749f19f380d", + "packages/reflex-radix/src/reflex_radix/themes/layout/flex.pyi": "88143f231c8f41ad461bfa233d41c59b", + "packages/reflex-radix/src/reflex_radix/themes/layout/grid.pyi": "addd8b6da6ab012bedbfc212567945ec", + "packages/reflex-radix/src/reflex_radix/themes/layout/list.pyi": "01ebeeda19193d0bea1a4eecbce3f322", + "packages/reflex-radix/src/reflex_radix/themes/layout/section.pyi": "4ff65602b88ab3748ff1bcf75e3dd5a6", + "packages/reflex-radix/src/reflex_radix/themes/layout/spacer.pyi": "94154bbb47e32b7258ab4e355f0b152c", + "packages/reflex-radix/src/reflex_radix/themes/layout/stack.pyi": "814df3a8c4ab2dfca32ee5f5c1d7bd94", + "packages/reflex-radix/src/reflex_radix/themes/typography/__init__.pyi": "a124b5b991b9f703f0eec9266cbec3a4", + "packages/reflex-radix/src/reflex_radix/themes/typography/blockquote.pyi": "74fd7a55d5832b3ac17ee1c4b3522a2e", + "packages/reflex-radix/src/reflex_radix/themes/typography/code.pyi": "d36379b5eefe20a6c8536a7ee7c359a0", + "packages/reflex-radix/src/reflex_radix/themes/typography/heading.pyi": "575e1aa1b082cb99e16fbb76eeef5ff1", + "packages/reflex-radix/src/reflex_radix/themes/typography/link.pyi": "78270efdcb331f689013c041f1be6ebe", + "packages/reflex-radix/src/reflex_radix/themes/typography/text.pyi": "d764729c9e6eb6fd7f48642647ae0c79", + "packages/reflex-react-player/src/reflex_react_player/audio.pyi": "3a5e72ce19c44fa6435d7de39bf09ee9", + "packages/reflex-react-player/src/reflex_react_player/react_player.pyi": "e6b4676044a713f64998a73941858e88", + "packages/reflex-react-player/src/reflex_react_player/video.pyi": "4e88c0e52b81f02e354f689cde029d7c", + "packages/reflex-react-router/src/reflex_react_router/dom.pyi": "4f869685727b609a277536688172f68e", + "packages/reflex-recharts/src/reflex_recharts/__init__.pyi": "10d16ea120467dabf8e59aa1e80d56ad", + "packages/reflex-recharts/src/reflex_recharts/cartesian.pyi": "86fd21642953dde3ed3f98b6e0d25bc1", + "packages/reflex-recharts/src/reflex_recharts/charts.pyi": "130f5ba805b7963fc171b310d3755c7a", + "packages/reflex-recharts/src/reflex_recharts/general.pyi": "95e9d9fbc6ad1ada0fdf34f75bb09b68", + "packages/reflex-recharts/src/reflex_recharts/polar.pyi": "5e73f07de061c963118d329cb6f71f18", + "packages/reflex-recharts/src/reflex_recharts/recharts.pyi": "84b2c8e054a52d12ced9dfeb88e6221b", + "packages/reflex-sonner/src/reflex_sonner/toast.pyi": "91d2d80518fdf5fcb29e13b483b761fd", + "reflex/__init__.pyi": "b6347ee6db827478a7e4a236039b3592", "reflex/components/__init__.pyi": "d620d52015f908cda828d231c1064236" } diff --git a/pyproject.toml b/pyproject.toml index 87ef506a9ee..adafcd19b77 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -38,6 +38,13 @@ dependencies = [ "typing_extensions >=4.13.0", "wrapt >=1.17.0,<3.0", "reflex-components", + "reflex-markdown", + "reflex-plotly", + "reflex-radix", + "reflex-react-player", + "reflex-react-router", + "reflex-recharts", + "reflex-sonner", ] classifiers = [ @@ -117,7 +124,16 @@ dependencies = ["plotly", "ruff", "pre_commit", "toml"] require-runtime-dependencies = true [tool.pyright] -extraPaths = ["packages/reflex-components/src"] +extraPaths = [ + "packages/reflex-components/src", + "packages/reflex-markdown/src", + "packages/reflex-plotly/src", + "packages/reflex-radix/src", + "packages/reflex-react-player/src", + "packages/reflex-react-router/src", + "packages/reflex-recharts/src", + "packages/reflex-sonner/src", +] reportIncompatibleMethodOverride = false [tool.ruff] @@ -296,7 +312,7 @@ hooks = [ [tool.uv] required-version = ">=0.7.0" -sources = { reflex-docgen = { workspace = true }, reflex-components = { workspace = true } } +sources = { reflex-docgen = { workspace = true }, reflex-components = { workspace = true }, reflex-markdown = { workspace = true }, reflex-plotly = { workspace = true }, reflex-radix = { workspace = true }, reflex-react-player = { workspace = true }, reflex-react-router = { workspace = true }, reflex-recharts = { workspace = true }, reflex-sonner = { workspace = true } } [tool.uv.workspace] members = ["packages/*"] diff --git a/reflex/app.py b/reflex/app.py index 9bee5e96c83..07cff7e12f0 100644 --- a/reflex/app.py +++ b/reflex/app.py @@ -42,8 +42,8 @@ ) from reflex_components.core.breakpoints import set_breakpoints from reflex_components.core.sticky import sticky -from reflex_components.radix import themes -from reflex_components.sonner.toast import toast +from reflex_radix import themes +from reflex_sonner.toast import toast from rich.progress import MofNCompleteColumn, Progress, TimeElapsedColumn from socketio import ASGIApp as EngineIOApp from socketio import AsyncNamespace, AsyncServer @@ -156,7 +156,7 @@ def default_backend_exception_handler(exception: Exception) -> EventSpec: EventSpec: The window alert event. """ - from reflex_components.sonner.toast import toast + from reflex_sonner.toast import toast error = traceback.format_exc() diff --git a/reflex/components/__init__.py b/reflex/components/__init__.py index 70bb0d4ea20..520ec8d1ea7 100644 --- a/reflex/components/__init__.py +++ b/reflex/components/__init__.py @@ -1,9 +1,9 @@ """Import all the components. -Components have been moved to the ``reflex_components`` package. -This module re-exports them for backwards compatibility and installs -an import redirect hook so that ``from reflex.components. import X`` -continues to work by delegating to ``reflex_components.``. +Components have been split across multiple packages. +This module installs an import redirect hook so that +``from reflex.components. import X`` continues to work +by delegating to the appropriate package. """ from __future__ import annotations @@ -16,23 +16,31 @@ from reflex.utils import lazy_loader -# Subpackages that have moved to the reflex_components package. -_MOVED_SUBMODULES: frozenset[str] = frozenset({ - "base", - "core", - "datadisplay", - "el", - "gridjs", - "lucide", - "markdown", - "moment", - "plotly", - "radix", - "react_player", - "react_router", - "recharts", - "sonner", -}) +# Mapping from subpackage name to the target top-level package. +_SUBPACKAGE_TARGETS: dict[str, str] = { + # reflex-components (base package) + "base": "reflex_components.base", + "core": "reflex_components.core", + "datadisplay": "reflex_components.datadisplay", + "el": "reflex_components.el", + "gridjs": "reflex_components.gridjs", + "lucide": "reflex_components.lucide", + "moment": "reflex_components.moment", + # reflex-radix + "radix": "reflex_radix", + # reflex-markdown + "markdown": "reflex_markdown", + # reflex-plotly + "plotly": "reflex_plotly", + # reflex-react-player + "react_player": "reflex_react_player", + # reflex-react-router + "react_router": "reflex_react_router", + # reflex-recharts + "recharts": "reflex_recharts", + # reflex-sonner + "sonner": "reflex_sonner", +} class _AliasLoader(importlib.abc.Loader): @@ -57,7 +65,7 @@ def exec_module(self, module: ModuleType) -> None: class _ComponentsRedirect(importlib.abc.MetaPathFinder): - """Import hook: redirects ``reflex.components.`` to ``reflex_components.``.""" + """Import hook: redirects ``reflex.components.`` to the real package.""" def find_spec( self, @@ -70,9 +78,11 @@ def find_spec( len(parts) >= 3 and parts[0] == "reflex" and parts[1] == "components" - and parts[2] in _MOVED_SUBMODULES + and parts[2] in _SUBPACKAGE_TARGETS ): - target_name = "reflex_components." + ".".join(parts[2:]) + base = _SUBPACKAGE_TARGETS[parts[2]] + rest = ".".join(parts[3:]) + target_name = f"{base}.{rest}" if rest else base return importlib.machinery.ModuleSpec( fullname, _AliasLoader(target_name), @@ -102,21 +112,19 @@ def find_spec( def __getattr__(name: str) -> object: - """Resolve attributes: first try local lazy loader, then delegate to reflex_components. + """Resolve attributes: first try local lazy loader, then delegate to component packages. Returns: - The requested attribute from this module or reflex_components. + The requested attribute from this module or a component package. Raises: - AttributeError: If the attribute is not found in either this module or reflex_components. + AttributeError: If the attribute is not found. """ try: return _lazy_getattr(name) except AttributeError: pass - if name in _MOVED_SUBMODULES: - import reflex_components - - return getattr(reflex_components, name) + if name in _SUBPACKAGE_TARGETS: + return importlib.import_module(_SUBPACKAGE_TARGETS[name]) msg = f"module {__name__!r} has no attribute {name!r}" raise AttributeError(msg) diff --git a/reflex/utils/pyi_generator.py b/reflex/utils/pyi_generator.py index d442462c804..80bb81d8ffd 100644 --- a/reflex/utils/pyi_generator.py +++ b/reflex/utils/pyi_generator.py @@ -1240,42 +1240,48 @@ def _write_pyi_file(module_path: Path, source: str) -> str: return md5(pyi_content.encode()).hexdigest() -# Component subpackages that have moved to the reflex_components package. -_MOVED_COMPONENT_SUBMODULES: frozenset[str] = frozenset({ - "base", - "core", - "datadisplay", - "el", - "gridjs", - "lucide", - "markdown", - "moment", - "plotly", - "radix", - "react_player", - "react_router", - "recharts", - "sonner", -}) +# Mapping from component subpackage name to its target Python package. +_COMPONENT_SUBPACKAGE_TARGETS: dict[str, str] = { + # reflex-components (base package) + "base": "reflex_components.base", + "core": "reflex_components.core", + "datadisplay": "reflex_components.datadisplay", + "el": "reflex_components.el", + "gridjs": "reflex_components.gridjs", + "lucide": "reflex_components.lucide", + "moment": "reflex_components.moment", + # Standalone packages + "markdown": "reflex_markdown", + "plotly": "reflex_plotly", + "radix": "reflex_radix", + "react_player": "reflex_react_player", + "react_router": "reflex_react_router", + "recharts": "reflex_recharts", + "sonner": "reflex_sonner", +} def _rewrite_component_import(module: str) -> str: - """Rewrite a lazy-loader module path to an absolute ``reflex_components`` import when needed. + """Rewrite a lazy-loader module path to the correct absolute package import. Args: module: The module path from ``_SUBMOD_ATTRS`` (e.g. ``"components.radix.themes.base"``). Returns: - An absolute import path (``"reflex_components.radix.themes.base"``) for moved + An absolute import path (``"reflex_radix.themes.base"``) for moved components, or a relative path (``".components.component"``) for everything else. """ if module == "components": + # "components": ["el", "radix", ...] — these are re-exported submodules. + # Can't map to a single package, but the pyi generator handles each attr individually. return "reflex_components" if module.startswith("components."): rest = module[len("components.") :] first_part = rest.split(".")[0] - if first_part in _MOVED_COMPONENT_SUBMODULES: - return f"reflex_components.{rest}" + target = _COMPONENT_SUBPACKAGE_TARGETS.get(first_part) + if target is not None: + remainder = rest[len(first_part) :] + return f"{target}{remainder}" return f".{module}" @@ -1304,19 +1310,27 @@ def _get_init_lazy_imports(mod: tuple | ModuleType, new_tree: ast.AST): for imported in attrs } # construct the import statement and handle special cases for aliases - sub_mod_attrs_imports = [ - f"from {_rewrite_component_import(module)} import " - + ( - ( + for imported, module in flattened_sub_mod_attrs.items(): + # For "components": ["el", "radix", ...], resolve each attr to its package. + if ( + module == "components" + and isinstance(imported, str) + and imported in _COMPONENT_SUBPACKAGE_TARGETS + ): + target = _COMPONENT_SUBPACKAGE_TARGETS[imported] + sub_mod_attrs_imports.append(f"import {target} as {imported}") + continue + + rewritten = _rewrite_component_import(module) + if isinstance(imported, tuple): + suffix = ( (imported[0] + " as " + imported[1]) if imported[0] != imported[1] else imported[0] ) - if isinstance(imported, tuple) - else imported - ) - for imported, module in flattened_sub_mod_attrs.items() - ] + else: + suffix = imported + sub_mod_attrs_imports.append(f"from {rewritten} import {suffix}") sub_mod_attrs_imports.append("") if extra_mappings: diff --git a/scripts/make_pyi.py b/scripts/make_pyi.py index e8014f92bb4..076bc7a4bce 100644 --- a/scripts/make_pyi.py +++ b/scripts/make_pyi.py @@ -17,6 +17,13 @@ "reflex/experimental", "reflex/__init__.py", "packages/reflex-components/src/reflex_components", + "packages/reflex-markdown/src/reflex_markdown", + "packages/reflex-plotly/src/reflex_plotly", + "packages/reflex-radix/src/reflex_radix", + "packages/reflex-react-player/src/reflex_react_player", + "packages/reflex-react-router/src/reflex_react_router", + "packages/reflex-recharts/src/reflex_recharts", + "packages/reflex-sonner/src/reflex_sonner", ] diff --git a/tests/integration/test_extra_overlay_function.py b/tests/integration/test_extra_overlay_function.py index f8323aa84aa..f0b8511b39f 100644 --- a/tests/integration/test_extra_overlay_function.py +++ b/tests/integration/test_extra_overlay_function.py @@ -25,7 +25,7 @@ def index(): app = rx.App() rx.config.get_config().extra_overlay_function = ( - "reflex_components.radix.themes.components.button" + "reflex_radix.themes.components.button" ) app.add_page(index) diff --git a/tests/units/components/core/test_banner.py b/tests/units/components/core/test_banner.py index 6080f4d9425..d2600bf6ef2 100644 --- a/tests/units/components/core/test_banner.py +++ b/tests/units/components/core/test_banner.py @@ -4,8 +4,8 @@ ConnectionPulser, WebsocketTargetURL, ) -from reflex_components.radix.themes.base import RadixThemesComponent -from reflex_components.radix.themes.typography.text import Text +from reflex_radix.themes.base import RadixThemesComponent +from reflex_radix.themes.typography.text import Text def test_websocket_target_url(): diff --git a/tests/units/components/core/test_cond.py b/tests/units/components/core/test_cond.py index c1dcb4d0137..13858269894 100644 --- a/tests/units/components/core/test_cond.py +++ b/tests/units/components/core/test_cond.py @@ -4,7 +4,7 @@ import pytest from reflex_components.base.fragment import Fragment from reflex_components.core.cond import Cond, cond -from reflex_components.radix.themes.typography.text import Text +from reflex_radix.themes.typography.text import Text from reflex.constants.state import FIELD_MARKER from reflex.state import BaseState diff --git a/tests/units/components/core/test_foreach.py b/tests/units/components/core/test_foreach.py index c3946d6ec7e..aead018d41a 100644 --- a/tests/units/components/core/test_foreach.py +++ b/tests/units/components/core/test_foreach.py @@ -5,8 +5,8 @@ ForeachVarError, foreach, ) -from reflex_components.radix.themes.layout.box import box -from reflex_components.radix.themes.typography.text import text +from reflex_radix.themes.layout.box import box +from reflex_radix.themes.typography.text import text import reflex as rx from reflex import el diff --git a/tests/units/components/core/test_match.py b/tests/units/components/core/test_match.py index 98b3f4228ee..055c8717155 100644 --- a/tests/units/components/core/test_match.py +++ b/tests/units/components/core/test_match.py @@ -264,7 +264,7 @@ def test_match_case_tuple_elements(match_case): ), ( 'Match cases should have the same return types. Case 3 with return value `jsx(RadixThemesText,{as:"p"},"first value")` ' - "of type is not " + "of type is not " ), ), ], diff --git a/tests/units/components/core/test_responsive.py b/tests/units/components/core/test_responsive.py index 03b50116abe..6d5392bfc18 100644 --- a/tests/units/components/core/test_responsive.py +++ b/tests/units/components/core/test_responsive.py @@ -5,7 +5,7 @@ tablet_and_desktop, tablet_only, ) -from reflex_components.radix.themes.layout.box import Box +from reflex_radix.themes.layout.box import Box def test_mobile_only(): diff --git a/tests/units/components/datadisplay/test_shiki_code.py b/tests/units/components/datadisplay/test_shiki_code.py index 9fe2c494c61..14a902192b3 100644 --- a/tests/units/components/datadisplay/test_shiki_code.py +++ b/tests/units/components/datadisplay/test_shiki_code.py @@ -7,7 +7,7 @@ ) from reflex_components.el.elements.forms import Button from reflex_components.lucide.icon import Icon -from reflex_components.radix.themes.layout.box import Box +from reflex_radix.themes.layout.box import Box from reflex.style import Style from reflex.vars import Var diff --git a/tests/units/components/forms/test_form.py b/tests/units/components/forms/test_form.py index f29e4b4382c..999038674b9 100644 --- a/tests/units/components/forms/test_form.py +++ b/tests/units/components/forms/test_form.py @@ -1,4 +1,4 @@ -from reflex_components.radix.primitives.form import Form +from reflex_radix.primitives.form import Form from reflex.event import EventChain, prevent_default from reflex.vars.base import Var diff --git a/tests/units/components/graphing/test_recharts.py b/tests/units/components/graphing/test_recharts.py index 52dd57ec306..bc551d517f6 100644 --- a/tests/units/components/graphing/test_recharts.py +++ b/tests/units/components/graphing/test_recharts.py @@ -1,4 +1,4 @@ -from reflex_components.recharts.charts import ( +from reflex_recharts.charts import ( AreaChart, BarChart, LineChart, @@ -7,7 +7,7 @@ RadialBarChart, ScatterChart, ) -from reflex_components.recharts.general import ResponsiveContainer +from reflex_recharts.general import ResponsiveContainer def test_area_chart(): diff --git a/tests/units/components/markdown/test_markdown.py b/tests/units/components/markdown/test_markdown.py index 91b83ad3844..f211d6e49f1 100644 --- a/tests/units/components/markdown/test_markdown.py +++ b/tests/units/components/markdown/test_markdown.py @@ -1,9 +1,9 @@ import pytest from reflex_components.datadisplay.code import CodeBlock from reflex_components.datadisplay.shiki_code_block import ShikiHighLevelCodeBlock -from reflex_components.markdown.markdown import Markdown, MarkdownComponentMap -from reflex_components.radix.themes.layout.box import Box -from reflex_components.radix.themes.typography.heading import Heading +from reflex_markdown.markdown import Markdown, MarkdownComponentMap +from reflex_radix.themes.layout.box import Box +from reflex_radix.themes.typography.heading import Heading from reflex.components.component import Component, memo from reflex.vars.base import Var diff --git a/tests/units/components/radix/test_icon_button.py b/tests/units/components/radix/test_icon_button.py index cfe61ab6d67..3b4040068ac 100644 --- a/tests/units/components/radix/test_icon_button.py +++ b/tests/units/components/radix/test_icon_button.py @@ -1,6 +1,6 @@ import pytest from reflex_components.lucide.icon import Icon -from reflex_components.radix.themes.components.icon_button import IconButton +from reflex_radix.themes.components.icon_button import IconButton from reflex.style import Style from reflex.vars.base import LiteralVar diff --git a/tests/units/components/radix/test_layout.py b/tests/units/components/radix/test_layout.py index be8e31ead54..87803df9d7b 100644 --- a/tests/units/components/radix/test_layout.py +++ b/tests/units/components/radix/test_layout.py @@ -1,4 +1,4 @@ -from reflex_components.radix.themes.layout.base import LayoutComponent +from reflex_radix.themes.layout.base import LayoutComponent def test_layout_component(): diff --git a/tests/units/components/recharts/test_cartesian.py b/tests/units/components/recharts/test_cartesian.py index fbbe70d6308..17191186d5c 100644 --- a/tests/units/components/recharts/test_cartesian.py +++ b/tests/units/components/recharts/test_cartesian.py @@ -1,13 +1,4 @@ -from reflex_components.recharts import ( - Area, - Bar, - Brush, - Line, - Scatter, - XAxis, - YAxis, - ZAxis, -) +from reflex_recharts import Area, Bar, Brush, Line, Scatter, XAxis, YAxis, ZAxis def test_xaxis(): diff --git a/tests/units/components/recharts/test_polar.py b/tests/units/components/recharts/test_polar.py index c2918e1b909..e85c912df6f 100644 --- a/tests/units/components/recharts/test_polar.py +++ b/tests/units/components/recharts/test_polar.py @@ -1,4 +1,4 @@ -from reflex_components.recharts import ( +from reflex_recharts import ( Pie, PolarAngleAxis, PolarGrid, diff --git a/tests/units/components/test_component.py b/tests/units/components/test_component.py index a4368e08a67..025237c082f 100644 --- a/tests/units/components/test_component.py +++ b/tests/units/components/test_component.py @@ -4,7 +4,7 @@ import pytest from reflex_components.base.bare import Bare from reflex_components.base.fragment import Fragment -from reflex_components.radix.themes.layout.box import Box +from reflex_radix.themes.layout.box import Box import reflex as rx from reflex.base import Base @@ -861,7 +861,7 @@ def my_component(width: Var[int], color: Var[str]): color=color, ) - from reflex_components.radix.themes.typography.text import Text + from reflex_radix.themes.typography.text import Text ccomponent = my_component( rx.text("child"), width=LiteralVar.create(1), color=LiteralVar.create("red") diff --git a/tests/units/components/typography/test_markdown.py b/tests/units/components/typography/test_markdown.py index e084c52e995..4e13e51291c 100644 --- a/tests/units/components/typography/test_markdown.py +++ b/tests/units/components/typography/test_markdown.py @@ -1,5 +1,5 @@ import pytest -from reflex_components.markdown.markdown import Markdown +from reflex_markdown.markdown import Markdown import reflex as rx diff --git a/tests/units/test_app.py b/tests/units/test_app.py index 2d9248124a4..7e479ff1ebd 100644 --- a/tests/units/test_app.py +++ b/tests/units/test_app.py @@ -18,7 +18,7 @@ from reflex_components.base.bare import Bare from reflex_components.base.fragment import Fragment from reflex_components.core.cond import Cond -from reflex_components.radix.themes.typography.text import Text +from reflex_radix.themes.typography.text import Text from starlette.applications import Starlette from starlette.datastructures import FormData, UploadFile from starlette.responses import StreamingResponse diff --git a/uv.lock b/uv.lock index cfa4401e06d..db13889c306 100644 --- a/uv.lock +++ b/uv.lock @@ -16,6 +16,13 @@ members = [ "reflex", "reflex-components", "reflex-docgen", + "reflex-markdown", + "reflex-plotly", + "reflex-radix", + "reflex-react-player", + "reflex-react-router", + "reflex-recharts", + "reflex-sonner", ] [[package]] @@ -2044,6 +2051,13 @@ dependencies = [ { name = "redis" }, { name = "reflex-components" }, { name = "reflex-hosting-cli" }, + { name = "reflex-markdown" }, + { name = "reflex-plotly" }, + { name = "reflex-radix" }, + { name = "reflex-react-player" }, + { name = "reflex-react-router" }, + { name = "reflex-recharts" }, + { name = "reflex-sonner" }, { name = "rich" }, { name = "sqlmodel" }, { name = "starlette" }, @@ -2112,6 +2126,13 @@ requires-dist = [ { name = "redis", specifier = ">=5.2.1,<8.0" }, { name = "reflex-components", editable = "packages/reflex-components" }, { name = "reflex-hosting-cli", specifier = ">=0.1.61" }, + { name = "reflex-markdown", editable = "packages/reflex-markdown" }, + { name = "reflex-plotly", editable = "packages/reflex-plotly" }, + { name = "reflex-radix", editable = "packages/reflex-radix" }, + { name = "reflex-react-player", editable = "packages/reflex-react-player" }, + { name = "reflex-react-router", editable = "packages/reflex-react-router" }, + { name = "reflex-recharts", editable = "packages/reflex-recharts" }, + { name = "reflex-sonner", editable = "packages/reflex-sonner" }, { name = "rich", specifier = ">=13,<15" }, { name = "sqlmodel", specifier = ">=0.0.27,<0.1" }, { name = "sqlmodel", marker = "extra == 'db'", specifier = ">=0.0.24,<0.1" }, @@ -2200,6 +2221,41 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f7/67/415e1f7fe09e77027e9e0063f39053d74f1299e671e837c9a7745453676d/reflex_hosting_cli-0.1.62-py3-none-any.whl", hash = "sha256:73d517fa827b1d52dcb81ba9024671acfd4889015a436ed223d2eda3c07eab89", size = 45049, upload-time = "2026-03-10T01:12:15.033Z" }, ] +[[package]] +name = "reflex-markdown" +version = "0.0.1" +source = { editable = "packages/reflex-markdown" } + +[[package]] +name = "reflex-plotly" +version = "0.0.1" +source = { editable = "packages/reflex-plotly" } + +[[package]] +name = "reflex-radix" +version = "0.0.1" +source = { editable = "packages/reflex-radix" } + +[[package]] +name = "reflex-react-player" +version = "0.0.1" +source = { editable = "packages/reflex-react-player" } + +[[package]] +name = "reflex-react-router" +version = "0.0.1" +source = { editable = "packages/reflex-react-router" } + +[[package]] +name = "reflex-recharts" +version = "0.0.1" +source = { editable = "packages/reflex-recharts" } + +[[package]] +name = "reflex-sonner" +version = "0.0.1" +source = { editable = "packages/reflex-sonner" } + [[package]] name = "requests" version = "2.32.5" From e4b0f46698d3039f40f766e55eaca553a3fb7663 Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Tue, 24 Mar 2026 17:03:08 -0700 Subject: [PATCH 012/113] god --- packages/reflex-code/README.md | 1 + packages/reflex-code/pyproject.toml | 13 ++++ .../reflex-code/src/reflex_code/__init__.py | 1 + .../src/reflex_code}/code.py | 4 +- .../src/reflex_code}/shiki_code_block.py | 8 +-- .../src/reflex_components/__init__.py | 3 - .../src/reflex_components/core/banner.py | 2 +- packages/reflex-dataeditor/README.md | 1 + packages/reflex-dataeditor/pyproject.toml | 13 ++++ .../src/reflex_dataeditor/__init__.py | 1 + .../src/reflex_dataeditor}/dataeditor.py | 0 packages/reflex-gridjs/README.md | 1 + packages/reflex-gridjs/pyproject.toml | 13 ++++ .../src/reflex_gridjs}/__init__.py | 0 .../src/reflex_gridjs}/datatable.py | 0 packages/reflex-lucide/README.md | 1 + packages/reflex-lucide/pyproject.toml | 13 ++++ .../src/reflex_lucide}/__init__.py | 0 .../src/reflex_lucide}/icon.py | 0 .../src/reflex_markdown/markdown.py | 2 +- packages/reflex-moment/README.md | 1 + packages/reflex-moment/pyproject.toml | 13 ++++ .../src/reflex_moment}/__init__.py | 0 .../src/reflex_moment}/moment.py | 0 .../src/reflex_radix/primitives/accordion.py | 2 +- .../src/reflex_radix/themes/color_mode.py | 2 +- .../reflex_radix/themes/components/callout.py | 2 +- .../themes/components/icon_button.py | 2 +- .../src/reflex_radix/themes/layout/list.py | 2 +- .../reflex-sonner/src/reflex_sonner/toast.py | 2 +- pyi_hashes.json | 30 ++++----- pyproject.toml | 12 +++- reflex/components/__init__.py | 65 ++++++++++++------- reflex/config.py | 2 +- reflex/experimental/__init__.py | 2 +- reflex/utils/pyi_generator.py | 23 ++++--- scripts/make_pyi.py | 5 ++ tests/integration/test_icon.py | 4 +- tests/units/components/core/test_colors.py | 2 +- .../units/components/datadisplay/test_code.py | 2 +- .../components/datadisplay/test_dataeditor.py | 2 +- .../components/datadisplay/test_datatable.py | 2 +- .../components/datadisplay/test_shiki_code.py | 4 +- tests/units/components/lucide/test_icon.py | 6 +- .../components/markdown/test_markdown.py | 4 +- .../components/radix/test_icon_button.py | 2 +- uv.lock | 40 ++++++++++++ 47 files changed, 226 insertions(+), 84 deletions(-) create mode 100644 packages/reflex-code/README.md create mode 100644 packages/reflex-code/pyproject.toml create mode 100644 packages/reflex-code/src/reflex_code/__init__.py rename packages/{reflex-components/src/reflex_components/datadisplay => reflex-code/src/reflex_code}/code.py (99%) rename packages/{reflex-components/src/reflex_components/datadisplay => reflex-code/src/reflex_code}/shiki_code_block.py (99%) create mode 100644 packages/reflex-dataeditor/README.md create mode 100644 packages/reflex-dataeditor/pyproject.toml create mode 100644 packages/reflex-dataeditor/src/reflex_dataeditor/__init__.py rename packages/{reflex-components/src/reflex_components/datadisplay => reflex-dataeditor/src/reflex_dataeditor}/dataeditor.py (100%) create mode 100644 packages/reflex-gridjs/README.md create mode 100644 packages/reflex-gridjs/pyproject.toml rename packages/{reflex-components/src/reflex_components/gridjs => reflex-gridjs/src/reflex_gridjs}/__init__.py (100%) rename packages/{reflex-components/src/reflex_components/gridjs => reflex-gridjs/src/reflex_gridjs}/datatable.py (100%) create mode 100644 packages/reflex-lucide/README.md create mode 100644 packages/reflex-lucide/pyproject.toml rename packages/{reflex-components/src/reflex_components/lucide => reflex-lucide/src/reflex_lucide}/__init__.py (100%) rename packages/{reflex-components/src/reflex_components/lucide => reflex-lucide/src/reflex_lucide}/icon.py (100%) create mode 100644 packages/reflex-moment/README.md create mode 100644 packages/reflex-moment/pyproject.toml rename packages/{reflex-components/src/reflex_components/moment => reflex-moment/src/reflex_moment}/__init__.py (100%) rename packages/{reflex-components/src/reflex_components/moment => reflex-moment/src/reflex_moment}/moment.py (100%) diff --git a/packages/reflex-code/README.md b/packages/reflex-code/README.md new file mode 100644 index 00000000000..6d668189196 --- /dev/null +++ b/packages/reflex-code/README.md @@ -0,0 +1 @@ +# Reflex code diff --git a/packages/reflex-code/pyproject.toml b/packages/reflex-code/pyproject.toml new file mode 100644 index 00000000000..9f93a4d89c3 --- /dev/null +++ b/packages/reflex-code/pyproject.toml @@ -0,0 +1,13 @@ +[project] +name = "reflex-code" +version = "0.0.1" +description = "Reflex code display components." +readme = "README.md" +authors = [{ name = "Khaleel Al-Adhami", email = "khaleel@reflex.dev" }] +maintainers = [{ name = "Khaleel Al-Adhami", email = "khaleel@reflex.dev" }] +requires-python = ">=3.10" +dependencies = [] + +[build-system] +requires = ["uv_build>=0.10.11,<0.12.0"] +build-backend = "uv_build" diff --git a/packages/reflex-code/src/reflex_code/__init__.py b/packages/reflex-code/src/reflex_code/__init__.py new file mode 100644 index 00000000000..e3e7851bee9 --- /dev/null +++ b/packages/reflex-code/src/reflex_code/__init__.py @@ -0,0 +1 @@ +"""Reflex code display components.""" diff --git a/packages/reflex-components/src/reflex_components/datadisplay/code.py b/packages/reflex-code/src/reflex_code/code.py similarity index 99% rename from packages/reflex-components/src/reflex_components/datadisplay/code.py rename to packages/reflex-code/src/reflex_code/code.py index d6fffe3bfd6..a1ca1154f33 100644 --- a/packages/reflex-components/src/reflex_components/datadisplay/code.py +++ b/packages/reflex-code/src/reflex_code/code.py @@ -5,6 +5,8 @@ import dataclasses from typing import ClassVar, Literal +from reflex_components.core.cond import color_mode_cond +from reflex_lucide.icon import Icon from reflex_markdown.markdown import MarkdownComponentMap from reflex_radix.themes.components.button import Button from reflex_radix.themes.layout.box import Box @@ -16,8 +18,6 @@ from reflex.utils import format from reflex.utils.imports import ImportVar from reflex.vars.base import LiteralVar, Var, VarData -from reflex_components.core.cond import color_mode_cond -from reflex_components.lucide.icon import Icon LiteralCodeLanguage = Literal[ "abap", diff --git a/packages/reflex-components/src/reflex_components/datadisplay/shiki_code_block.py b/packages/reflex-code/src/reflex_code/shiki_code_block.py similarity index 99% rename from packages/reflex-components/src/reflex_components/datadisplay/shiki_code_block.py rename to packages/reflex-code/src/reflex_code/shiki_code_block.py index e23cef04e98..32671b5605d 100644 --- a/packages/reflex-components/src/reflex_components/datadisplay/shiki_code_block.py +++ b/packages/reflex-code/src/reflex_code/shiki_code_block.py @@ -8,6 +8,10 @@ from dataclasses import dataclass from typing import Any, Literal +from reflex_components.core.colors import color +from reflex_components.core.cond import color_mode_cond +from reflex_components.el.elements.forms import Button +from reflex_lucide.icon import Icon from reflex_markdown.markdown import MarkdownComponentMap from reflex_radix.themes.layout.box import Box @@ -20,10 +24,6 @@ from reflex.vars.base import LiteralVar, Var from reflex.vars.function import FunctionStringVar from reflex.vars.sequence import StringVar, string_replace_operation -from reflex_components.core.colors import color -from reflex_components.core.cond import color_mode_cond -from reflex_components.el.elements.forms import Button -from reflex_components.lucide.icon import Icon def copy_script() -> Any: diff --git a/packages/reflex-components/src/reflex_components/__init__.py b/packages/reflex-components/src/reflex_components/__init__.py index 24c86c12c50..87727b9eb05 100644 --- a/packages/reflex-components/src/reflex_components/__init__.py +++ b/packages/reflex-components/src/reflex_components/__init__.py @@ -9,9 +9,6 @@ "core", "datadisplay", "el", - "gridjs", - "lucide", - "moment", } _SUBMOD_ATTRS: dict[str, list[str]] = {} diff --git a/packages/reflex-components/src/reflex_components/core/banner.py b/packages/reflex-components/src/reflex_components/core/banner.py index 7760698f0e3..433148d781c 100644 --- a/packages/reflex-components/src/reflex_components/core/banner.py +++ b/packages/reflex-components/src/reflex_components/core/banner.py @@ -2,6 +2,7 @@ from __future__ import annotations +from reflex_lucide.icon import Icon from reflex_radix.themes.components.dialog import DialogContent, DialogRoot, DialogTitle from reflex_radix.themes.layout.flex import Flex from reflex_radix.themes.typography.text import Text @@ -21,7 +22,6 @@ from reflex_components.base.fragment import Fragment from reflex_components.core.cond import cond from reflex_components.el.elements.typography import Div -from reflex_components.lucide.icon import Icon connect_error_var_data: VarData = VarData( imports=Imports.EVENTS, diff --git a/packages/reflex-dataeditor/README.md b/packages/reflex-dataeditor/README.md new file mode 100644 index 00000000000..3104034611b --- /dev/null +++ b/packages/reflex-dataeditor/README.md @@ -0,0 +1 @@ +# Reflex dataeditor diff --git a/packages/reflex-dataeditor/pyproject.toml b/packages/reflex-dataeditor/pyproject.toml new file mode 100644 index 00000000000..778127d1e0a --- /dev/null +++ b/packages/reflex-dataeditor/pyproject.toml @@ -0,0 +1,13 @@ +[project] +name = "reflex-dataeditor" +version = "0.0.1" +description = "Reflex dataeditor components." +readme = "README.md" +authors = [{ name = "Khaleel Al-Adhami", email = "khaleel@reflex.dev" }] +maintainers = [{ name = "Khaleel Al-Adhami", email = "khaleel@reflex.dev" }] +requires-python = ">=3.10" +dependencies = [] + +[build-system] +requires = ["uv_build>=0.10.11,<0.12.0"] +build-backend = "uv_build" diff --git a/packages/reflex-dataeditor/src/reflex_dataeditor/__init__.py b/packages/reflex-dataeditor/src/reflex_dataeditor/__init__.py new file mode 100644 index 00000000000..86d12c5542c --- /dev/null +++ b/packages/reflex-dataeditor/src/reflex_dataeditor/__init__.py @@ -0,0 +1 @@ +"""Reflex DataEditor component.""" diff --git a/packages/reflex-components/src/reflex_components/datadisplay/dataeditor.py b/packages/reflex-dataeditor/src/reflex_dataeditor/dataeditor.py similarity index 100% rename from packages/reflex-components/src/reflex_components/datadisplay/dataeditor.py rename to packages/reflex-dataeditor/src/reflex_dataeditor/dataeditor.py diff --git a/packages/reflex-gridjs/README.md b/packages/reflex-gridjs/README.md new file mode 100644 index 00000000000..da4b78c28d6 --- /dev/null +++ b/packages/reflex-gridjs/README.md @@ -0,0 +1 @@ +# Reflex gridjs diff --git a/packages/reflex-gridjs/pyproject.toml b/packages/reflex-gridjs/pyproject.toml new file mode 100644 index 00000000000..fe17c9a82c7 --- /dev/null +++ b/packages/reflex-gridjs/pyproject.toml @@ -0,0 +1,13 @@ +[project] +name = "reflex-gridjs" +version = "0.0.1" +description = "Reflex gridjs components." +readme = "README.md" +authors = [{ name = "Khaleel Al-Adhami", email = "khaleel@reflex.dev" }] +maintainers = [{ name = "Khaleel Al-Adhami", email = "khaleel@reflex.dev" }] +requires-python = ">=3.10" +dependencies = [] + +[build-system] +requires = ["uv_build>=0.10.11,<0.12.0"] +build-backend = "uv_build" diff --git a/packages/reflex-components/src/reflex_components/gridjs/__init__.py b/packages/reflex-gridjs/src/reflex_gridjs/__init__.py similarity index 100% rename from packages/reflex-components/src/reflex_components/gridjs/__init__.py rename to packages/reflex-gridjs/src/reflex_gridjs/__init__.py diff --git a/packages/reflex-components/src/reflex_components/gridjs/datatable.py b/packages/reflex-gridjs/src/reflex_gridjs/datatable.py similarity index 100% rename from packages/reflex-components/src/reflex_components/gridjs/datatable.py rename to packages/reflex-gridjs/src/reflex_gridjs/datatable.py diff --git a/packages/reflex-lucide/README.md b/packages/reflex-lucide/README.md new file mode 100644 index 00000000000..3b55d0e3531 --- /dev/null +++ b/packages/reflex-lucide/README.md @@ -0,0 +1 @@ +# Reflex lucide diff --git a/packages/reflex-lucide/pyproject.toml b/packages/reflex-lucide/pyproject.toml new file mode 100644 index 00000000000..99d34c569f6 --- /dev/null +++ b/packages/reflex-lucide/pyproject.toml @@ -0,0 +1,13 @@ +[project] +name = "reflex-lucide" +version = "0.0.1" +description = "Reflex lucide components." +readme = "README.md" +authors = [{ name = "Khaleel Al-Adhami", email = "khaleel@reflex.dev" }] +maintainers = [{ name = "Khaleel Al-Adhami", email = "khaleel@reflex.dev" }] +requires-python = ">=3.10" +dependencies = [] + +[build-system] +requires = ["uv_build>=0.10.11,<0.12.0"] +build-backend = "uv_build" diff --git a/packages/reflex-components/src/reflex_components/lucide/__init__.py b/packages/reflex-lucide/src/reflex_lucide/__init__.py similarity index 100% rename from packages/reflex-components/src/reflex_components/lucide/__init__.py rename to packages/reflex-lucide/src/reflex_lucide/__init__.py diff --git a/packages/reflex-components/src/reflex_components/lucide/icon.py b/packages/reflex-lucide/src/reflex_lucide/icon.py similarity index 100% rename from packages/reflex-components/src/reflex_components/lucide/icon.py rename to packages/reflex-lucide/src/reflex_lucide/icon.py diff --git a/packages/reflex-markdown/src/reflex_markdown/markdown.py b/packages/reflex-markdown/src/reflex_markdown/markdown.py index 796ff7b2341..1eec0d644b9 100644 --- a/packages/reflex-markdown/src/reflex_markdown/markdown.py +++ b/packages/reflex-markdown/src/reflex_markdown/markdown.py @@ -161,7 +161,7 @@ def _code(value: object): def _codeblock(value: object, **props): - from reflex_components.datadisplay.code import CodeBlock + from reflex_code.code import CodeBlock return CodeBlock.create(value, margin_y="1em", wrap_long_lines=True, **props) diff --git a/packages/reflex-moment/README.md b/packages/reflex-moment/README.md new file mode 100644 index 00000000000..df964807846 --- /dev/null +++ b/packages/reflex-moment/README.md @@ -0,0 +1 @@ +# Reflex moment diff --git a/packages/reflex-moment/pyproject.toml b/packages/reflex-moment/pyproject.toml new file mode 100644 index 00000000000..1b9c5e726c2 --- /dev/null +++ b/packages/reflex-moment/pyproject.toml @@ -0,0 +1,13 @@ +[project] +name = "reflex-moment" +version = "0.0.1" +description = "Reflex moment components." +readme = "README.md" +authors = [{ name = "Khaleel Al-Adhami", email = "khaleel@reflex.dev" }] +maintainers = [{ name = "Khaleel Al-Adhami", email = "khaleel@reflex.dev" }] +requires-python = ">=3.10" +dependencies = [] + +[build-system] +requires = ["uv_build>=0.10.11,<0.12.0"] +build-backend = "uv_build" diff --git a/packages/reflex-components/src/reflex_components/moment/__init__.py b/packages/reflex-moment/src/reflex_moment/__init__.py similarity index 100% rename from packages/reflex-components/src/reflex_components/moment/__init__.py rename to packages/reflex-moment/src/reflex_moment/__init__.py diff --git a/packages/reflex-components/src/reflex_components/moment/moment.py b/packages/reflex-moment/src/reflex_moment/moment.py similarity index 100% rename from packages/reflex-components/src/reflex_components/moment/moment.py rename to packages/reflex-moment/src/reflex_moment/moment.py diff --git a/packages/reflex-radix/src/reflex_radix/primitives/accordion.py b/packages/reflex-radix/src/reflex_radix/primitives/accordion.py index 93aa63969e2..cbf3d167b77 100644 --- a/packages/reflex-radix/src/reflex_radix/primitives/accordion.py +++ b/packages/reflex-radix/src/reflex_radix/primitives/accordion.py @@ -7,7 +7,7 @@ from reflex_components.core.colors import color from reflex_components.core.cond import cond -from reflex_components.lucide.icon import Icon +from reflex_lucide.icon import Icon from reflex.components.component import Component, ComponentNamespace, field from reflex.constants.compiler import MemoizationMode diff --git a/packages/reflex-radix/src/reflex_radix/themes/color_mode.py b/packages/reflex-radix/src/reflex_radix/themes/color_mode.py index e2ac94d936f..4eabf63f066 100644 --- a/packages/reflex-radix/src/reflex_radix/themes/color_mode.py +++ b/packages/reflex-radix/src/reflex_radix/themes/color_mode.py @@ -20,7 +20,7 @@ from typing import Any, Literal, get_args from reflex_components.core.cond import Cond, color_mode_cond, cond -from reflex_components.lucide.icon import Icon +from reflex_lucide.icon import Icon from reflex.components.component import BaseComponent, field from reflex.style import ( diff --git a/packages/reflex-radix/src/reflex_radix/themes/components/callout.py b/packages/reflex-radix/src/reflex_radix/themes/components/callout.py index 25d6a79a966..632decafed1 100644 --- a/packages/reflex-radix/src/reflex_radix/themes/components/callout.py +++ b/packages/reflex-radix/src/reflex_radix/themes/components/callout.py @@ -4,7 +4,7 @@ from reflex_components.core.breakpoints import Responsive from reflex_components.el import elements -from reflex_components.lucide.icon import Icon +from reflex_lucide.icon import Icon import reflex as rx from reflex.components.component import Component, ComponentNamespace, field diff --git a/packages/reflex-radix/src/reflex_radix/themes/components/icon_button.py b/packages/reflex-radix/src/reflex_radix/themes/components/icon_button.py index 7157035d480..94df0b62ab7 100644 --- a/packages/reflex-radix/src/reflex_radix/themes/components/icon_button.py +++ b/packages/reflex-radix/src/reflex_radix/themes/components/icon_button.py @@ -7,7 +7,7 @@ from reflex_components.core.breakpoints import Responsive from reflex_components.core.match import Match from reflex_components.el import elements -from reflex_components.lucide import Icon +from reflex_lucide import Icon from reflex.components.component import Component, field from reflex.style import Style diff --git a/packages/reflex-radix/src/reflex_radix/themes/layout/list.py b/packages/reflex-radix/src/reflex_radix/themes/layout/list.py index 0cf47b662e2..a7fb7d6a622 100644 --- a/packages/reflex-radix/src/reflex_radix/themes/layout/list.py +++ b/packages/reflex-radix/src/reflex_radix/themes/layout/list.py @@ -8,7 +8,7 @@ from reflex_components.core.foreach import Foreach from reflex_components.el.elements.base import BaseHTML from reflex_components.el.elements.typography import Li, Ol, Ul -from reflex_components.lucide.icon import Icon +from reflex_lucide.icon import Icon from reflex_markdown.markdown import MarkdownComponentMap from reflex.components.component import ComponentNamespace, field diff --git a/packages/reflex-sonner/src/reflex_sonner/toast.py b/packages/reflex-sonner/src/reflex_sonner/toast.py index f85591dec0f..5b87d741c2f 100644 --- a/packages/reflex-sonner/src/reflex_sonner/toast.py +++ b/packages/reflex-sonner/src/reflex_sonner/toast.py @@ -5,7 +5,7 @@ import dataclasses from typing import Any, Literal -from reflex_components.lucide.icon import Icon +from reflex_lucide.icon import Icon from reflex.components.component import Component, ComponentNamespace, field from reflex.components.props import NoExtrasAllowedProps diff --git a/pyi_hashes.json b/pyi_hashes.json index 0c7904dea38..21b6081c3d4 100644 --- a/pyi_hashes.json +++ b/pyi_hashes.json @@ -1,5 +1,7 @@ { - "packages/reflex-components/src/reflex_components/__init__.pyi": "240d03f6b24d4d6c76b5d7ed5f47b6bc", + "packages/reflex-code/src/reflex_code/code.pyi": "deaf8aead1880d3c14b189e9817b5b75", + "packages/reflex-code/src/reflex_code/shiki_code_block.pyi": "87e08e6e649e4a43e879cc85dceb7ab0", + "packages/reflex-components/src/reflex_components/__init__.pyi": "44e8ecd8bc946d2a666d96eba51accdd", "packages/reflex-components/src/reflex_components/base/__init__.pyi": "d2303a7a1a18f9390ffe0d885b20c9d5", "packages/reflex-components/src/reflex_components/base/app_wrap.pyi": "b21c49560793be0a2dd87f65fcd02243", "packages/reflex-components/src/reflex_components/base/body.pyi": "4b7b090ca81fa9913ba208a8a2dc3fd4", @@ -12,7 +14,7 @@ "packages/reflex-components/src/reflex_components/base/strict_mode.pyi": "b3bc948878137f371f0464163ab82a66", "packages/reflex-components/src/reflex_components/core/__init__.pyi": "d47e3059a7531c2eadc0ec0c4283bd30", "packages/reflex-components/src/reflex_components/core/auto_scroll.pyi": "4d670d66c3f51a512841e7602cae245e", - "packages/reflex-components/src/reflex_components/core/banner.pyi": "6c5accb0329d67554a8e9d137d3b5e98", + "packages/reflex-components/src/reflex_components/core/banner.pyi": "932f39b35449d97b37f868cdd552b00c", "packages/reflex-components/src/reflex_components/core/clipboard.pyi": "ca379d3a1afaf4da438ac3eaf2cf8e3f", "packages/reflex-components/src/reflex_components/core/debounce.pyi": "bce792c1b6fc05f2449b46a5e4580d97", "packages/reflex-components/src/reflex_components/core/helmet.pyi": "9dda69cc8f6563a7062be75a5a9a1766", @@ -21,9 +23,6 @@ "packages/reflex-components/src/reflex_components/core/upload.pyi": "bfdcf703ea1e8eb5b07e03dfe1756caa", "packages/reflex-components/src/reflex_components/core/window_events.pyi": "ad784ba099a49b095f5f7e65511649cf", "packages/reflex-components/src/reflex_components/datadisplay/__init__.pyi": "8844db1b47917daa949ed5ac2073c7b8", - "packages/reflex-components/src/reflex_components/datadisplay/code.pyi": "1a25a5f0c10c5af04517531f037f876d", - "packages/reflex-components/src/reflex_components/datadisplay/dataeditor.pyi": "4719c4de53d79d219053fb870d3cabc6", - "packages/reflex-components/src/reflex_components/datadisplay/shiki_code_block.pyi": "b19254d2c37b167230790a1c0c93869a", "packages/reflex-components/src/reflex_components/el/__init__.pyi": "7702649d2a9304bc2d9345aa40c2c039", "packages/reflex-components/src/reflex_components/el/element.pyi": "55604caea0edce331f7672a8f8e82f4a", "packages/reflex-components/src/reflex_components/el/elements/__init__.pyi": "a9c6b03e1a2a73e6278a6ebfc3127429", @@ -37,14 +36,15 @@ "packages/reflex-components/src/reflex_components/el/elements/sectioning.pyi": "e44354124a23f88e31d5c158430a6341", "packages/reflex-components/src/reflex_components/el/elements/tables.pyi": "e3b27ff3699fe612ac0ab81689452195", "packages/reflex-components/src/reflex_components/el/elements/typography.pyi": "2987ced0f04c729751a09dbafdff558d", - "packages/reflex-components/src/reflex_components/gridjs/datatable.pyi": "7f85bb4e93c355ec0d19b3bb17f0d2cf", - "packages/reflex-components/src/reflex_components/lucide/icon.pyi": "8b48d954a6c9f1825c5123893ca2bf7f", - "packages/reflex-components/src/reflex_components/moment/moment.pyi": "619390dc6611c0de70b26eabbe73fb19", + "packages/reflex-dataeditor/src/reflex_dataeditor/dataeditor.pyi": "55921260dcc7be07ef80ac7a52d0c197", + "packages/reflex-gridjs/src/reflex_gridjs/datatable.pyi": "6d97e071b20bc42b96529e4fb639cfb8", + "packages/reflex-lucide/src/reflex_lucide/icon.pyi": "f9706da589d2fb75d46afdd44a020926", "packages/reflex-markdown/src/reflex_markdown/markdown.pyi": "850554a9091ac20a12eb5eb483a1a251", + "packages/reflex-moment/src/reflex_moment/moment.pyi": "ffad10f828272ab33c74c26d8cb4a67e", "packages/reflex-plotly/src/reflex_plotly/plotly.pyi": "44a46aaa75f1fe5f6e41d883b0802f6f", "packages/reflex-radix/src/reflex_radix/__init__.pyi": "75cd2277d0f97081d32e19213906ad09", "packages/reflex-radix/src/reflex_radix/primitives/__init__.pyi": "4c8a8df181c3f560d9a2608dac33dba7", - "packages/reflex-radix/src/reflex_radix/primitives/accordion.pyi": "437228ba3a78ae0bf9623087aa94be47", + "packages/reflex-radix/src/reflex_radix/primitives/accordion.pyi": "6aa08325bdec956d5c2243e39639e123", "packages/reflex-radix/src/reflex_radix/primitives/base.pyi": "95d45d707fd11b29cb9072c46c68dce8", "packages/reflex-radix/src/reflex_radix/primitives/dialog.pyi": "355d4c52ca984eef4271a99e6036ae9e", "packages/reflex-radix/src/reflex_radix/primitives/drawer.pyi": "20cab3cb30285d0b146def51f89cd189", @@ -53,14 +53,14 @@ "packages/reflex-radix/src/reflex_radix/primitives/slider.pyi": "7f3145da33f3584bed2382d5308d3d71", "packages/reflex-radix/src/reflex_radix/themes/__init__.pyi": "70deb6963522fc835109fe8286b5830e", "packages/reflex-radix/src/reflex_radix/themes/base.pyi": "447b2e074290552480b20e68d42f0af6", - "packages/reflex-radix/src/reflex_radix/themes/color_mode.pyi": "b88ea84de588dc50913f085cc274c750", + "packages/reflex-radix/src/reflex_radix/themes/color_mode.pyi": "7c22d80a9517f67ed2fb5a268df97eec", "packages/reflex-radix/src/reflex_radix/themes/components/__init__.pyi": "0b669b9a5800852098f4d8fdcd47e3df", "packages/reflex-radix/src/reflex_radix/themes/components/alert_dialog.pyi": "b9746eeda7ed9d851e75fb257fbec758", "packages/reflex-radix/src/reflex_radix/themes/components/aspect_ratio.pyi": "3a422f6fb832ce0d0a2a6cfb47020beb", "packages/reflex-radix/src/reflex_radix/themes/components/avatar.pyi": "4b49b975cfe78b051a9693ad0a3f4a0f", "packages/reflex-radix/src/reflex_radix/themes/components/badge.pyi": "d4e992419d705607063a479550f19ad3", "packages/reflex-radix/src/reflex_radix/themes/components/button.pyi": "1f6959ca0e00e6a75af244c90198b4c1", - "packages/reflex-radix/src/reflex_radix/themes/components/callout.pyi": "d0311447991bd873fb748189b3f52a7d", + "packages/reflex-radix/src/reflex_radix/themes/components/callout.pyi": "905b5d6ad98ff1ac41ac4ff21a49aba6", "packages/reflex-radix/src/reflex_radix/themes/components/card.pyi": "d4c9fe9d681f3d4e5116d2156119e762", "packages/reflex-radix/src/reflex_radix/themes/components/checkbox.pyi": "6f6fb294939ada2c51863fb10ac8aece", "packages/reflex-radix/src/reflex_radix/themes/components/checkbox_cards.pyi": "1ebd42045339b490bbacb42d7a91249b", @@ -70,7 +70,7 @@ "packages/reflex-radix/src/reflex_radix/themes/components/dialog.pyi": "8a6229b1769347cb2cce40241ed3c18b", "packages/reflex-radix/src/reflex_radix/themes/components/dropdown_menu.pyi": "df7987ca847489ae7e26c2e177266a52", "packages/reflex-radix/src/reflex_radix/themes/components/hover_card.pyi": "38268d27b6b645fc8da68ce992f97608", - "packages/reflex-radix/src/reflex_radix/themes/components/icon_button.pyi": "0039679ad1fe4944492eba67d90caa92", + "packages/reflex-radix/src/reflex_radix/themes/components/icon_button.pyi": "086c9c0e0a2c366f31e86057ab4e360e", "packages/reflex-radix/src/reflex_radix/themes/components/inset.pyi": "cf9aebf77708e6eb3d8fce5fc04693c1", "packages/reflex-radix/src/reflex_radix/themes/components/popover.pyi": "2bf10a5e2d065d08332bb29a7e6f16d6", "packages/reflex-radix/src/reflex_radix/themes/components/progress.pyi": "946f59142bb4145ddc57a76ae04f6470", @@ -97,7 +97,7 @@ "packages/reflex-radix/src/reflex_radix/themes/layout/container.pyi": "b50b26c4a9f105a5b691e749f19f380d", "packages/reflex-radix/src/reflex_radix/themes/layout/flex.pyi": "88143f231c8f41ad461bfa233d41c59b", "packages/reflex-radix/src/reflex_radix/themes/layout/grid.pyi": "addd8b6da6ab012bedbfc212567945ec", - "packages/reflex-radix/src/reflex_radix/themes/layout/list.pyi": "01ebeeda19193d0bea1a4eecbce3f322", + "packages/reflex-radix/src/reflex_radix/themes/layout/list.pyi": "f33b5afc5acc2a8d3875b6fd724c23af", "packages/reflex-radix/src/reflex_radix/themes/layout/section.pyi": "4ff65602b88ab3748ff1bcf75e3dd5a6", "packages/reflex-radix/src/reflex_radix/themes/layout/spacer.pyi": "94154bbb47e32b7258ab4e355f0b152c", "packages/reflex-radix/src/reflex_radix/themes/layout/stack.pyi": "814df3a8c4ab2dfca32ee5f5c1d7bd94", @@ -117,7 +117,7 @@ "packages/reflex-recharts/src/reflex_recharts/general.pyi": "95e9d9fbc6ad1ada0fdf34f75bb09b68", "packages/reflex-recharts/src/reflex_recharts/polar.pyi": "5e73f07de061c963118d329cb6f71f18", "packages/reflex-recharts/src/reflex_recharts/recharts.pyi": "84b2c8e054a52d12ced9dfeb88e6221b", - "packages/reflex-sonner/src/reflex_sonner/toast.pyi": "91d2d80518fdf5fcb29e13b483b761fd", - "reflex/__init__.pyi": "b6347ee6db827478a7e4a236039b3592", + "packages/reflex-sonner/src/reflex_sonner/toast.pyi": "5013a0c87953ea4896cb37b76ef988e9", + "reflex/__init__.pyi": "047892cecd4f922aedbc6c0ad7368503", "reflex/components/__init__.pyi": "d620d52015f908cda828d231c1064236" } diff --git a/pyproject.toml b/pyproject.toml index adafcd19b77..04556efbd6e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -37,8 +37,13 @@ dependencies = [ "starlette >=0.47.0", "typing_extensions >=4.13.0", "wrapt >=1.17.0,<3.0", + "reflex-code", "reflex-components", + "reflex-dataeditor", + "reflex-gridjs", + "reflex-lucide", "reflex-markdown", + "reflex-moment", "reflex-plotly", "reflex-radix", "reflex-react-player", @@ -125,8 +130,13 @@ require-runtime-dependencies = true [tool.pyright] extraPaths = [ + "packages/reflex-code/src", "packages/reflex-components/src", + "packages/reflex-dataeditor/src", + "packages/reflex-gridjs/src", + "packages/reflex-lucide/src", "packages/reflex-markdown/src", + "packages/reflex-moment/src", "packages/reflex-plotly/src", "packages/reflex-radix/src", "packages/reflex-react-player/src", @@ -312,7 +322,7 @@ hooks = [ [tool.uv] required-version = ">=0.7.0" -sources = { reflex-docgen = { workspace = true }, reflex-components = { workspace = true }, reflex-markdown = { workspace = true }, reflex-plotly = { workspace = true }, reflex-radix = { workspace = true }, reflex-react-player = { workspace = true }, reflex-react-router = { workspace = true }, reflex-recharts = { workspace = true }, reflex-sonner = { workspace = true } } +sources = { reflex-code = { workspace = true }, reflex-components = { workspace = true }, reflex-dataeditor = { workspace = true }, reflex-docgen = { workspace = true }, reflex-gridjs = { workspace = true }, reflex-lucide = { workspace = true }, reflex-markdown = { workspace = true }, reflex-moment = { workspace = true }, reflex-plotly = { workspace = true }, reflex-radix = { workspace = true }, reflex-react-player = { workspace = true }, reflex-react-router = { workspace = true }, reflex-recharts = { workspace = true }, reflex-sonner = { workspace = true } } [tool.uv.workspace] members = ["packages/*"] diff --git a/reflex/components/__init__.py b/reflex/components/__init__.py index 520ec8d1ea7..eaab63334f0 100644 --- a/reflex/components/__init__.py +++ b/reflex/components/__init__.py @@ -23,25 +23,27 @@ "core": "reflex_components.core", "datadisplay": "reflex_components.datadisplay", "el": "reflex_components.el", - "gridjs": "reflex_components.gridjs", - "lucide": "reflex_components.lucide", - "moment": "reflex_components.moment", - # reflex-radix - "radix": "reflex_radix", - # reflex-markdown + # Standalone packages + "gridjs": "reflex_gridjs", + "lucide": "reflex_lucide", "markdown": "reflex_markdown", - # reflex-plotly + "moment": "reflex_moment", "plotly": "reflex_plotly", - # reflex-react-player + "radix": "reflex_radix", "react_player": "reflex_react_player", - # reflex-react-router "react_router": "reflex_react_router", - # reflex-recharts "recharts": "reflex_recharts", - # reflex-sonner "sonner": "reflex_sonner", } +# Deeper overrides for subpackages that were split from datadisplay. +# Checked before the general _SUBPACKAGE_TARGETS mapping. +_DEEP_OVERRIDES: dict[str, str] = { + "datadisplay.code": "reflex_code.code", + "datadisplay.shiki_code_block": "reflex_code.shiki_code_block", + "datadisplay.dataeditor": "reflex_dataeditor.dataeditor", +} + class _AliasLoader(importlib.abc.Loader): """Loader that aliases one module name to another.""" @@ -74,20 +76,33 @@ def find_spec( target: object = None, ) -> importlib.machinery.ModuleSpec | None: parts = fullname.split(".") - if ( - len(parts) >= 3 - and parts[0] == "reflex" - and parts[1] == "components" - and parts[2] in _SUBPACKAGE_TARGETS - ): - base = _SUBPACKAGE_TARGETS[parts[2]] - rest = ".".join(parts[3:]) - target_name = f"{base}.{rest}" if rest else base - return importlib.machinery.ModuleSpec( - fullname, - _AliasLoader(target_name), - is_package=True, - ) + if len(parts) >= 3 and parts[0] == "reflex" and parts[1] == "components": + subpkg = parts[2] + rest_parts = parts[3:] + + # Check deep overrides first (e.g. datadisplay.code -> reflex_code.code). + if rest_parts: + deep_key = f"{subpkg}.{rest_parts[0]}" + override = _DEEP_OVERRIDES.get(deep_key) + if override is not None: + extra = ".".join(rest_parts[1:]) + target_name = f"{override}.{extra}" if extra else override + return importlib.machinery.ModuleSpec( + fullname, + _AliasLoader(target_name), + is_package=True, + ) + + # General subpackage mapping. + if subpkg in _SUBPACKAGE_TARGETS: + base = _SUBPACKAGE_TARGETS[subpkg] + rest = ".".join(rest_parts) + target_name = f"{base}.{rest}" if rest else base + return importlib.machinery.ModuleSpec( + fullname, + _AliasLoader(target_name), + is_package=True, + ) return None diff --git a/reflex/config.py b/reflex/config.py index 8a65d4d0a17..38a2b871455 100644 --- a/reflex/config.py +++ b/reflex/config.py @@ -176,7 +176,7 @@ class BaseConfig: state_auto_setters: Whether to automatically create setters for state base vars. show_built_with_reflex: Whether to display the sticky "Built with Reflex" badge on all pages. is_reflex_cloud: Whether the app is running in the reflex cloud environment. - extra_overlay_function: Extra overlay function to run after the app is built. Formatted such that `from path_0.path_1... import path[-1]`, and calling it with no arguments would work. For example, "reflex_components.moment.moment". + extra_overlay_function: Extra overlay function to run after the app is built. Formatted such that `from path_0.path_1... import path[-1]`, and calling it with no arguments would work. For example, "reflex_moment.moment". plugins: List of plugins to use in the app. disable_plugins: List of plugin types to disable in the app. transport: The transport method for client-server communication. diff --git a/reflex/experimental/__init__.py b/reflex/experimental/__init__.py index 260c6c2c194..5ed1a413f23 100644 --- a/reflex/experimental/__init__.py +++ b/reflex/experimental/__init__.py @@ -2,7 +2,7 @@ from types import SimpleNamespace -from reflex_components.datadisplay.shiki_code_block import code_block as code_block +from reflex_code.shiki_code_block import code_block as code_block from reflex.utils.console import warn from reflex.utils.misc import run_in_thread diff --git a/reflex/utils/pyi_generator.py b/reflex/utils/pyi_generator.py index 80bb81d8ffd..858743050c7 100644 --- a/reflex/utils/pyi_generator.py +++ b/reflex/utils/pyi_generator.py @@ -1247,9 +1247,13 @@ def _write_pyi_file(module_path: Path, source: str) -> str: "core": "reflex_components.core", "datadisplay": "reflex_components.datadisplay", "el": "reflex_components.el", - "gridjs": "reflex_components.gridjs", - "lucide": "reflex_components.lucide", - "moment": "reflex_components.moment", + "gridjs": "reflex_gridjs", + "lucide": "reflex_lucide", + "moment": "reflex_moment", + # Deep overrides (datadisplay split) + "datadisplay.code": "reflex_code.code", + "datadisplay.shiki_code_block": "reflex_code.shiki_code_block", + "datadisplay.dataeditor": "reflex_dataeditor.dataeditor", # Standalone packages "markdown": "reflex_markdown", "plotly": "reflex_plotly", @@ -1277,11 +1281,14 @@ def _rewrite_component_import(module: str) -> str: return "reflex_components" if module.startswith("components."): rest = module[len("components.") :] - first_part = rest.split(".")[0] - target = _COMPONENT_SUBPACKAGE_TARGETS.get(first_part) - if target is not None: - remainder = rest[len(first_part) :] - return f"{target}{remainder}" + # Try progressively deeper matches (e.g. "datadisplay.code" before "datadisplay"). + parts = rest.split(".") + for depth in range(min(len(parts), 2), 0, -1): + key = ".".join(parts[:depth]) + target = _COMPONENT_SUBPACKAGE_TARGETS.get(key) + if target is not None: + remainder = ".".join(parts[depth:]) + return f"{target}.{remainder}" if remainder else target return f".{module}" diff --git a/scripts/make_pyi.py b/scripts/make_pyi.py index 076bc7a4bce..318acc7347b 100644 --- a/scripts/make_pyi.py +++ b/scripts/make_pyi.py @@ -16,8 +16,13 @@ "reflex/components", "reflex/experimental", "reflex/__init__.py", + "packages/reflex-code/src/reflex_code", "packages/reflex-components/src/reflex_components", + "packages/reflex-dataeditor/src/reflex_dataeditor", + "packages/reflex-gridjs/src/reflex_gridjs", + "packages/reflex-lucide/src/reflex_lucide", "packages/reflex-markdown/src/reflex_markdown", + "packages/reflex-moment/src/reflex_moment", "packages/reflex-plotly/src/reflex_plotly", "packages/reflex-radix/src/reflex_radix", "packages/reflex-react-player/src/reflex_react_player", diff --git a/tests/integration/test_icon.py b/tests/integration/test_icon.py index 047b05fee4f..2fff2426389 100644 --- a/tests/integration/test_icon.py +++ b/tests/integration/test_icon.py @@ -3,14 +3,14 @@ from collections.abc import Generator import pytest -from reflex_components.lucide.icon import LUCIDE_ICON_LIST +from reflex_lucide.icon import LUCIDE_ICON_LIST from selenium.webdriver.common.by import By from reflex.testing import AppHarness, WebDriver def Icons(): - from reflex_components.lucide.icon import LUCIDE_ICON_LIST + from reflex_lucide.icon import LUCIDE_ICON_LIST import reflex as rx diff --git a/tests/units/components/core/test_colors.py b/tests/units/components/core/test_colors.py index e1bcf722eb7..08e07f24f09 100644 --- a/tests/units/components/core/test_colors.py +++ b/tests/units/components/core/test_colors.py @@ -1,5 +1,5 @@ import pytest -from reflex_components.datadisplay.code import CodeBlock +from reflex_code.code import CodeBlock import reflex as rx from reflex.constants.colors import Color diff --git a/tests/units/components/datadisplay/test_code.py b/tests/units/components/datadisplay/test_code.py index 1bf1a19bb78..79558c8d317 100644 --- a/tests/units/components/datadisplay/test_code.py +++ b/tests/units/components/datadisplay/test_code.py @@ -1,5 +1,5 @@ import pytest -from reflex_components.datadisplay.code import CodeBlock, Theme +from reflex_code.code import CodeBlock, Theme @pytest.mark.parametrize( diff --git a/tests/units/components/datadisplay/test_dataeditor.py b/tests/units/components/datadisplay/test_dataeditor.py index 212a52922c7..4359865264e 100644 --- a/tests/units/components/datadisplay/test_dataeditor.py +++ b/tests/units/components/datadisplay/test_dataeditor.py @@ -1,4 +1,4 @@ -from reflex_components.datadisplay.dataeditor import DataEditor +from reflex_dataeditor.dataeditor import DataEditor def test_dataeditor(): diff --git a/tests/units/components/datadisplay/test_datatable.py b/tests/units/components/datadisplay/test_datatable.py index 308bcf1f85f..1e7ccb00eb5 100644 --- a/tests/units/components/datadisplay/test_datatable.py +++ b/tests/units/components/datadisplay/test_datatable.py @@ -1,6 +1,6 @@ import pandas as pd import pytest -from reflex_components.gridjs.datatable import DataTable +from reflex_gridjs.datatable import DataTable import reflex as rx from reflex.constants.state import FIELD_MARKER diff --git a/tests/units/components/datadisplay/test_shiki_code.py b/tests/units/components/datadisplay/test_shiki_code.py index 14a902192b3..4444a50f6b1 100644 --- a/tests/units/components/datadisplay/test_shiki_code.py +++ b/tests/units/components/datadisplay/test_shiki_code.py @@ -1,12 +1,12 @@ import pytest -from reflex_components.datadisplay.shiki_code_block import ( +from reflex_code.shiki_code_block import ( ShikiBaseTransformers, ShikiCodeBlock, ShikiHighLevelCodeBlock, ShikiJsTransformer, ) from reflex_components.el.elements.forms import Button -from reflex_components.lucide.icon import Icon +from reflex_lucide.icon import Icon from reflex_radix.themes.layout.box import Box from reflex.style import Style diff --git a/tests/units/components/lucide/test_icon.py b/tests/units/components/lucide/test_icon.py index da9442275f8..b80f84cf453 100644 --- a/tests/units/components/lucide/test_icon.py +++ b/tests/units/components/lucide/test_icon.py @@ -1,9 +1,5 @@ import pytest -from reflex_components.lucide.icon import ( - LUCIDE_ICON_LIST, - LUCIDE_ICON_MAPPING_OVERRIDE, - Icon, -) +from reflex_lucide.icon import LUCIDE_ICON_LIST, LUCIDE_ICON_MAPPING_OVERRIDE, Icon from reflex.utils import format diff --git a/tests/units/components/markdown/test_markdown.py b/tests/units/components/markdown/test_markdown.py index f211d6e49f1..4666f2b9fc4 100644 --- a/tests/units/components/markdown/test_markdown.py +++ b/tests/units/components/markdown/test_markdown.py @@ -1,6 +1,6 @@ import pytest -from reflex_components.datadisplay.code import CodeBlock -from reflex_components.datadisplay.shiki_code_block import ShikiHighLevelCodeBlock +from reflex_code.code import CodeBlock +from reflex_code.shiki_code_block import ShikiHighLevelCodeBlock from reflex_markdown.markdown import Markdown, MarkdownComponentMap from reflex_radix.themes.layout.box import Box from reflex_radix.themes.typography.heading import Heading diff --git a/tests/units/components/radix/test_icon_button.py b/tests/units/components/radix/test_icon_button.py index 3b4040068ac..a6aa9937db5 100644 --- a/tests/units/components/radix/test_icon_button.py +++ b/tests/units/components/radix/test_icon_button.py @@ -1,5 +1,5 @@ import pytest -from reflex_components.lucide.icon import Icon +from reflex_lucide.icon import Icon from reflex_radix.themes.components.icon_button import IconButton from reflex.style import Style diff --git a/uv.lock b/uv.lock index db13889c306..1b148febeaa 100644 --- a/uv.lock +++ b/uv.lock @@ -14,9 +14,14 @@ resolution-markers = [ [manifest] members = [ "reflex", + "reflex-code", "reflex-components", + "reflex-dataeditor", "reflex-docgen", + "reflex-gridjs", + "reflex-lucide", "reflex-markdown", + "reflex-moment", "reflex-plotly", "reflex-radix", "reflex-react-player", @@ -2049,9 +2054,14 @@ dependencies = [ { name = "python-multipart" }, { name = "python-socketio" }, { name = "redis" }, + { name = "reflex-code" }, { name = "reflex-components" }, + { name = "reflex-dataeditor" }, + { name = "reflex-gridjs" }, { name = "reflex-hosting-cli" }, + { name = "reflex-lucide" }, { name = "reflex-markdown" }, + { name = "reflex-moment" }, { name = "reflex-plotly" }, { name = "reflex-radix" }, { name = "reflex-react-player" }, @@ -2124,9 +2134,14 @@ requires-dist = [ { name = "python-multipart", specifier = ">=0.0.20,<1.0" }, { name = "python-socketio", specifier = ">=5.12.0,<6.0" }, { name = "redis", specifier = ">=5.2.1,<8.0" }, + { name = "reflex-code", editable = "packages/reflex-code" }, { name = "reflex-components", editable = "packages/reflex-components" }, + { name = "reflex-dataeditor", editable = "packages/reflex-dataeditor" }, + { name = "reflex-gridjs", editable = "packages/reflex-gridjs" }, { name = "reflex-hosting-cli", specifier = ">=0.1.61" }, + { name = "reflex-lucide", editable = "packages/reflex-lucide" }, { name = "reflex-markdown", editable = "packages/reflex-markdown" }, + { name = "reflex-moment", editable = "packages/reflex-moment" }, { name = "reflex-plotly", editable = "packages/reflex-plotly" }, { name = "reflex-radix", editable = "packages/reflex-radix" }, { name = "reflex-react-player", editable = "packages/reflex-react-player" }, @@ -2177,11 +2192,21 @@ dev = [ { name = "uvicorn" }, ] +[[package]] +name = "reflex-code" +version = "0.0.1" +source = { editable = "packages/reflex-code" } + [[package]] name = "reflex-components" version = "0.0.1" source = { editable = "packages/reflex-components" } +[[package]] +name = "reflex-dataeditor" +version = "0.0.1" +source = { editable = "packages/reflex-dataeditor" } + [[package]] name = "reflex-docgen" version = "0.0.1" @@ -2205,6 +2230,11 @@ requires-dist = [ { name = "typing-inspection", specifier = ">=0.4.2" }, ] +[[package]] +name = "reflex-gridjs" +version = "0.0.1" +source = { editable = "packages/reflex-gridjs" } + [[package]] name = "reflex-hosting-cli" version = "0.1.62" @@ -2221,11 +2251,21 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f7/67/415e1f7fe09e77027e9e0063f39053d74f1299e671e837c9a7745453676d/reflex_hosting_cli-0.1.62-py3-none-any.whl", hash = "sha256:73d517fa827b1d52dcb81ba9024671acfd4889015a436ed223d2eda3c07eab89", size = 45049, upload-time = "2026-03-10T01:12:15.033Z" }, ] +[[package]] +name = "reflex-lucide" +version = "0.0.1" +source = { editable = "packages/reflex-lucide" } + [[package]] name = "reflex-markdown" version = "0.0.1" source = { editable = "packages/reflex-markdown" } +[[package]] +name = "reflex-moment" +version = "0.0.1" +source = { editable = "packages/reflex-moment" } + [[package]] name = "reflex-plotly" version = "0.0.1" From d8d2e619922ac258276dec42d877a1877eb92aa5 Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Tue, 24 Mar 2026 17:13:47 -0700 Subject: [PATCH 013/113] add CI --- .github/workflows/publish.yml | 42 +++++++++++++++++++++++++++++++++-- 1 file changed, 40 insertions(+), 2 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 14908f87bc0..19658f509fe 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -1,7 +1,13 @@ name: Publish to PyPI on: + release: + types: [published] workflow_dispatch: + inputs: + tag: + description: "Release tag (e.g. v1.2.3 or reflex-lucide-v0.1.0)" + required: true jobs: publish: @@ -18,10 +24,42 @@ jobs: - name: Install uv uses: astral-sh/setup-uv@v7 + - name: Parse release tag + id: parse + run: | + TAG="${{ github.event.release.tag_name || inputs.tag }}" + # Tag format: v1.2.3 for reflex, reflex-lucide-v0.1.0 for sub-packages + if [[ "$TAG" =~ ^v([0-9].*)$ ]]; then + echo "package=reflex" >> "$GITHUB_OUTPUT" + echo "version=${BASH_REMATCH[1]}" >> "$GITHUB_OUTPUT" + echo "build_dir=." >> "$GITHUB_OUTPUT" + elif [[ "$TAG" =~ ^(.+)-v([0-9].*)$ ]]; then + PACKAGE="${BASH_REMATCH[1]}" + VERSION="${BASH_REMATCH[2]}" + if [ ! -d "packages/$PACKAGE" ]; then + echo "Error: packages/$PACKAGE does not exist" + exit 1 + fi + echo "package=$PACKAGE" >> "$GITHUB_OUTPUT" + echo "version=$VERSION" >> "$GITHUB_OUTPUT" + echo "build_dir=packages/$PACKAGE" >> "$GITHUB_OUTPUT" + else + echo "Error: Tag '$TAG' does not match expected format (v* or -v*)" + exit 1 + fi + + - name: Set version + run: | + cd "${{ steps.parse.outputs.build_dir }}" + sed -i 's/^version = ".*"/version = "${{ steps.parse.outputs.version }}"/' pyproject.toml + echo "Building ${{ steps.parse.outputs.package }} v${{ steps.parse.outputs.version }}" + grep '^version' pyproject.toml + - name: Build - run: uv build + run: uv build --directory "${{ steps.parse.outputs.build_dir }}" - name: Verify .pyi files in wheel + if: steps.parse.outputs.package == 'reflex' run: | if unzip -l dist/*.whl | grep '\.pyi$'; then echo "✓ .pyi files found in distribution" @@ -31,4 +69,4 @@ jobs: fi - name: Publish - run: uv publish + run: uv publish --directory "${{ steps.parse.outputs.build_dir }}" From 96b9426499c67658a89de5f1b1edf649d0f947f7 Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Tue, 24 Mar 2026 17:22:37 -0700 Subject: [PATCH 014/113] sad --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 04556efbd6e..7bddb9eac58 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -242,7 +242,7 @@ omit = [ [tool.coverage.report] show_missing = true # TODO bump back to 79 -fail_under = 70 +fail_under = 68 precision = 2 ignore_errors = true From 1b75c8f72af57460acf38e80da85e7b99fbc50d7 Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Tue, 24 Mar 2026 17:26:42 -0700 Subject: [PATCH 015/113] what --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 7bddb9eac58..e86373c14b3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -242,7 +242,7 @@ omit = [ [tool.coverage.report] show_missing = true # TODO bump back to 79 -fail_under = 68 +fail_under = 67 precision = 2 ignore_errors = true From 95acdb966cf33f508d94732ea9b77d041792109a Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Tue, 24 Mar 2026 17:41:05 -0700 Subject: [PATCH 016/113] try that --- tests/integration/init-test/in_docker_test_script.sh | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/tests/integration/init-test/in_docker_test_script.sh b/tests/integration/init-test/in_docker_test_script.sh index f17fc3e95fd..78589fae46c 100755 --- a/tests/integration/init-test/in_docker_test_script.sh +++ b/tests/integration/init-test/in_docker_test_script.sh @@ -24,14 +24,15 @@ function do_export () { } echo "Preparing test project dir" -python3 -m venv ~/venv -source ~/venv/bin/activate -pip install -U pip +curl -LsSf https://astral.sh/uv/install.sh | sh +source "$HOME/.local/bin/env" echo "Installing reflex from local repo code" cp -r /reflex-repo ~/reflex-repo -pip install ~/reflex-repo -pip install psutil +uv venv ~/venv +source ~/venv/bin/activate +uv pip install ~/reflex-repo +uv pip install psutil redis-server & From 00fd0ef1ad91f95784837e18f41cb202f19adc53 Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Tue, 24 Mar 2026 18:21:20 -0700 Subject: [PATCH 017/113] done --- .github/workflows/publish.yml | 12 +----- packages/reflex-code/README.md | 1 - packages/reflex-components-code/README.md | 3 ++ .../pyproject.toml | 12 ++++-- .../src/reflex_components_code}/__init__.py | 0 .../src/reflex_components_code}/code.py | 10 ++--- .../shiki_code_block.py | 12 +++--- packages/reflex-components-core/README.md | 3 ++ .../pyproject.toml | 12 ++++-- .../src/reflex_components_core}/__init__.py | 0 .../reflex_components_core}/base/__init__.py | 0 .../reflex_components_core}/base/app_wrap.py | 2 +- .../src/reflex_components_core}/base/bare.py | 0 .../src/reflex_components_core}/base/body.py | 2 +- .../reflex_components_core}/base/document.py | 0 .../base/error_boundary.py | 4 +- .../reflex_components_core}/base/fragment.py | 0 .../src/reflex_components_core}/base/link.py | 2 +- .../src/reflex_components_core}/base/meta.py | 6 +-- .../reflex_components_core}/base/script.py | 4 +- .../base/strict_mode.py | 0 .../reflex_components_core}/core/__init__.py | 0 .../core/auto_scroll.py | 2 +- .../reflex_components_core}/core/banner.py | 16 ++++---- .../core/breakpoints.py | 0 .../reflex_components_core}/core/clipboard.py | 2 +- .../reflex_components_core}/core/colors.py | 0 .../src/reflex_components_core}/core/cond.py | 2 +- .../reflex_components_core}/core/debounce.py | 0 .../reflex_components_core}/core/foreach.py | 4 +- .../reflex_components_core}/core/helmet.py | 0 .../src/reflex_components_core}/core/html.py | 2 +- .../core/layout/__init__.py | 0 .../src/reflex_components_core}/core/match.py | 2 +- .../core/responsive.py | 2 +- .../reflex_components_core}/core/sticky.py | 12 +++--- .../reflex_components_core}/core/upload.py | 10 ++--- .../core/window_events.py | 2 +- .../datadisplay/__init__.py | 0 .../datadisplay/logo.py | 0 .../reflex_components_core}/el/__init__.py | 2 +- .../src/reflex_components_core}/el/element.py | 0 .../el/elements/__init__.py | 0 .../el/elements/base.py | 2 +- .../el/elements/forms.py | 2 +- .../el/elements/inline.py | 0 .../el/elements/media.py | 2 +- .../el/elements/metadata.py | 6 +-- .../el/elements/other.py | 0 .../el/elements/scripts.py | 4 +- .../el/elements/sectioning.py | 0 .../el/elements/tables.py | 0 .../el/elements/typography.py | 0 .../reflex-components-dataeditor/README.md | 3 ++ .../pyproject.toml | 12 ++++-- .../reflex_components_dataeditor}/__init__.py | 0 .../dataeditor.py | 4 +- packages/reflex-components-gridjs/README.md | 3 ++ .../pyproject.toml | 12 ++++-- .../src/reflex_components_gridjs}/__init__.py | 0 .../reflex_components_gridjs}/datatable.py | 0 packages/reflex-components-lucide/README.md | 3 ++ .../pyproject.toml | 12 ++++-- .../src/reflex_components_lucide}/__init__.py | 0 .../src/reflex_components_lucide}/icon.py | 0 packages/reflex-components-markdown/README.md | 3 ++ .../pyproject.toml | 12 ++++-- .../reflex_components_markdown}/__init__.py | 0 .../reflex_components_markdown}/markdown.py | 28 ++++++------- packages/reflex-components-moment/README.md | 3 ++ .../pyproject.toml | 12 ++++-- .../src/reflex_components_moment}/__init__.py | 0 .../src/reflex_components_moment}/moment.py | 0 packages/reflex-components-plotly/README.md | 3 ++ .../pyproject.toml | 12 ++++-- .../src/reflex_components_plotly}/__init__.py | 0 .../src/reflex_components_plotly}/plotly.py | 2 +- packages/reflex-components-radix/README.md | 3 ++ .../pyproject.toml | 12 ++++-- .../src/reflex_components_radix}/__init__.py | 0 .../primitives/__init__.py | 0 .../primitives/accordion.py | 10 ++--- .../primitives/base.py | 2 +- .../primitives/dialog.py | 2 +- .../primitives/drawer.py | 6 +-- .../primitives/form.py | 6 +-- .../primitives/progress.py | 8 ++-- .../primitives/slider.py | 2 +- .../themes/__init__.py | 0 .../reflex_components_radix}/themes/base.py | 2 +- .../themes/color_mode.py | 8 ++-- .../themes/components/__init__.py | 0 .../themes/components/alert_dialog.py | 6 +-- .../themes/components/aspect_ratio.py | 2 +- .../themes/components/avatar.py | 4 +- .../themes/components/badge.py | 6 +-- .../themes/components/button.py | 6 +-- .../themes/components/callout.py | 8 ++-- .../themes/components/card.py | 6 +-- .../themes/components/checkbox.py | 8 ++-- .../themes/components/checkbox_cards.py | 4 +- .../themes/components/checkbox_group.py | 4 +- .../themes/components/context_menu.py | 4 +- .../themes/components/data_list.py | 4 +- .../themes/components/dialog.py | 6 +-- .../themes/components/dropdown_menu.py | 4 +- .../themes/components/hover_card.py | 6 +-- .../themes/components/icon_button.py | 10 ++--- .../themes/components/inset.py | 6 +-- .../themes/components/popover.py | 6 +-- .../themes/components/progress.py | 4 +- .../themes/components/radio.py | 4 +- .../themes/components/radio_cards.py | 4 +- .../themes/components/radio_group.py | 8 ++-- .../themes/components/scroll_area.py | 2 +- .../themes/components/segmented_control.py | 4 +- .../themes/components/select.py | 4 +- .../themes/components/separator.py | 4 +- .../themes/components/skeleton.py | 4 +- .../themes/components/slider.py | 4 +- .../themes/components/spinner.py | 4 +- .../themes/components/switch.py | 4 +- .../themes/components/table.py | 6 +-- .../themes/components/tabs.py | 6 +-- .../themes/components/text_area.py | 8 ++-- .../themes/components/text_field.py | 8 ++-- .../themes/components/tooltip.py | 2 +- .../themes/layout/__init__.py | 0 .../themes/layout/base.py | 4 +- .../themes/layout/box.py | 4 +- .../themes/layout/center.py | 0 .../themes/layout/container.py | 6 +-- .../themes/layout/flex.py | 6 +-- .../themes/layout/grid.py | 6 +-- .../themes/layout/list.py | 12 +++--- .../themes/layout/section.py | 6 +-- .../themes/layout/spacer.py | 0 .../themes/layout/stack.py | 4 +- .../themes/typography/__init__.py | 0 .../themes/typography/base.py | 0 .../themes/typography/blockquote.py | 6 +-- .../themes/typography/code.py | 8 ++-- .../themes/typography/heading.py | 8 ++-- .../themes/typography/link.py | 14 +++---- .../themes/typography/text.py | 8 ++-- .../reflex-components-react-player/README.md | 3 ++ .../pyproject.toml | 12 ++++-- .../__init__.py | 0 .../reflex_components_react_player}/audio.py | 2 +- .../react_player.py | 2 +- .../reflex_components_react_player}/video.py | 2 +- .../reflex-components-react-router/README.md | 3 ++ .../pyproject.toml | 12 ++++-- .../__init__.py | 0 .../reflex_components_react_router}/dom.py | 2 +- packages/reflex-components-recharts/README.md | 3 ++ .../pyproject.toml | 12 ++++-- .../reflex_components_recharts}/__init__.py | 0 .../reflex_components_recharts}/cartesian.py | 0 .../src/reflex_components_recharts}/charts.py | 2 +- .../reflex_components_recharts}/general.py | 0 .../src/reflex_components_recharts}/polar.py | 0 .../reflex_components_recharts}/recharts.py | 0 packages/reflex-components-sonner/README.md | 3 ++ .../pyproject.toml | 12 ++++-- .../src/reflex_components_sonner}/__init__.py | 0 .../src/reflex_components_sonner}/toast.py | 2 +- packages/reflex-components/README.md | 1 - packages/reflex-dataeditor/README.md | 1 - packages/reflex-docgen/README.md | 3 ++ packages/reflex-docgen/pyproject.toml | 10 +++-- packages/reflex-gridjs/README.md | 1 - packages/reflex-lucide/README.md | 1 - packages/reflex-markdown/README.md | 1 - packages/reflex-moment/README.md | 1 - packages/reflex-plotly/README.md | 1 - packages/reflex-radix/README.md | 1 - packages/reflex-react-player/README.md | 1 - packages/reflex-react-router/README.md | 1 - packages/reflex-recharts/README.md | 1 - packages/reflex-sonner/README.md | 1 - pyproject.toml | 35 ++++++++-------- reflex/app.py | 24 +++++------ reflex/compiler/compiler.py | 2 +- reflex/compiler/utils.py | 12 +++--- reflex/components/__init__.py | 36 ++++++++--------- reflex/components/component.py | 26 ++++++------ reflex/components/dynamic.py | 2 +- reflex/components/tags/iter_tag.py | 6 +-- reflex/config.py | 2 +- reflex/constants/colors.py | 2 +- reflex/event.py | 4 +- reflex/experimental/__init__.py | 2 +- reflex/state.py | 2 +- reflex/style.py | 2 +- reflex/utils/codespaces.py | 6 +-- reflex/utils/misc.py | 2 +- reflex/utils/pyi_generator.py | 40 +++++++++---------- reflex/utils/types.py | 2 +- reflex/vars/object.py | 2 +- scripts/make_pyi.py | 26 ++++++------ .../test_extra_overlay_function.py | 2 +- tests/integration/test_icon.py | 4 +- tests/units/compiler/test_compiler.py | 4 +- tests/units/components/base/test_bare.py | 2 +- tests/units/components/base/test_link.py | 2 +- tests/units/components/base/test_script.py | 2 +- tests/units/components/core/test_banner.py | 6 +-- tests/units/components/core/test_colors.py | 2 +- tests/units/components/core/test_cond.py | 6 +-- tests/units/components/core/test_debounce.py | 2 +- tests/units/components/core/test_foreach.py | 6 +-- tests/units/components/core/test_html.py | 2 +- tests/units/components/core/test_match.py | 6 +-- .../units/components/core/test_responsive.py | 4 +- tests/units/components/core/test_upload.py | 2 +- .../units/components/datadisplay/test_code.py | 2 +- .../components/datadisplay/test_dataeditor.py | 2 +- .../components/datadisplay/test_datatable.py | 2 +- .../components/datadisplay/test_shiki_code.py | 8 ++-- tests/units/components/el/test_svg.py | 2 +- tests/units/components/forms/test_form.py | 2 +- .../components/graphing/test_recharts.py | 4 +- tests/units/components/lucide/test_icon.py | 2 +- .../components/markdown/test_markdown.py | 10 ++--- .../components/radix/test_icon_button.py | 4 +- tests/units/components/radix/test_layout.py | 2 +- .../components/recharts/test_cartesian.py | 2 +- tests/units/components/recharts/test_polar.py | 2 +- tests/units/components/test_component.py | 8 ++-- .../units/components/test_component_state.py | 2 +- .../components/typography/test_markdown.py | 2 +- tests/units/test_app.py | 10 ++--- tests/units/test_event.py | 2 +- tests/units/test_var.py | 4 +- tests/units/utils/test_serializers.py | 2 +- 236 files changed, 577 insertions(+), 497 deletions(-) delete mode 100644 packages/reflex-code/README.md create mode 100644 packages/reflex-components-code/README.md rename packages/{reflex-code => reflex-components-code}/pyproject.toml (53%) rename packages/{reflex-code/src/reflex_code => reflex-components-code/src/reflex_components_code}/__init__.py (100%) rename packages/{reflex-code/src/reflex_code => reflex-components-code/src/reflex_components_code}/code.py (97%) rename packages/{reflex-code/src/reflex_code => reflex-components-code/src/reflex_components_code}/shiki_code_block.py (98%) create mode 100644 packages/reflex-components-core/README.md rename packages/{reflex-components => reflex-components-core}/pyproject.toml (52%) rename packages/{reflex-components/src/reflex_components => reflex-components-core/src/reflex_components_core}/__init__.py (100%) rename packages/{reflex-components/src/reflex_components => reflex-components-core/src/reflex_components_core}/base/__init__.py (100%) rename packages/{reflex-components/src/reflex_components => reflex-components-core/src/reflex_components_core}/base/app_wrap.py (88%) rename packages/{reflex-components/src/reflex_components => reflex-components-core/src/reflex_components_core}/base/bare.py (100%) rename packages/{reflex-components/src/reflex_components => reflex-components-core/src/reflex_components_core}/base/body.py (64%) rename packages/{reflex-components/src/reflex_components => reflex-components-core/src/reflex_components_core}/base/document.py (100%) rename packages/{reflex-components/src/reflex_components => reflex-components-core/src/reflex_components_core}/base/error_boundary.py (98%) rename packages/{reflex-components/src/reflex_components => reflex-components-core/src/reflex_components_core}/base/fragment.py (100%) rename packages/{reflex-components/src/reflex_components => reflex-components-core/src/reflex_components_core}/base/link.py (94%) rename packages/{reflex-components/src/reflex_components => reflex-components-core/src/reflex_components_core}/base/meta.py (85%) rename packages/{reflex-components/src/reflex_components => reflex-components-core/src/reflex_components_core}/base/script.py (95%) rename packages/{reflex-components/src/reflex_components => reflex-components-core/src/reflex_components_core}/base/strict_mode.py (100%) rename packages/{reflex-components/src/reflex_components => reflex-components-core/src/reflex_components_core}/core/__init__.py (100%) rename packages/{reflex-components/src/reflex_components => reflex-components-core/src/reflex_components_core}/core/auto_scroll.py (98%) rename packages/{reflex-components/src/reflex_components => reflex-components-core/src/reflex_components_core}/core/banner.py (97%) rename packages/{reflex-components/src/reflex_components => reflex-components-core/src/reflex_components_core}/core/breakpoints.py (100%) rename packages/{reflex-components/src/reflex_components => reflex-components-core/src/reflex_components_core}/core/clipboard.py (98%) rename packages/{reflex-components/src/reflex_components => reflex-components-core/src/reflex_components_core}/core/colors.py (100%) rename packages/{reflex-components/src/reflex_components => reflex-components-core/src/reflex_components_core}/core/cond.py (98%) rename packages/{reflex-components/src/reflex_components => reflex-components-core/src/reflex_components_core}/core/debounce.py (100%) rename packages/{reflex-components/src/reflex_components => reflex-components-core/src/reflex_components_core}/core/foreach.py (98%) rename packages/{reflex-components/src/reflex_components => reflex-components-core/src/reflex_components_core}/core/helmet.py (100%) rename packages/{reflex-components/src/reflex_components => reflex-components-core/src/reflex_components_core}/core/html.py (95%) rename packages/{reflex-components/src/reflex_components => reflex-components-core/src/reflex_components_core}/core/layout/__init__.py (100%) rename packages/{reflex-components/src/reflex_components => reflex-components-core/src/reflex_components_core}/core/match.py (99%) rename packages/{reflex-components/src/reflex_components => reflex-components-core/src/reflex_components_core}/core/responsive.py (96%) rename packages/{reflex-components/src/reflex_components => reflex-components-core/src/reflex_components_core}/core/sticky.py (90%) rename packages/{reflex-components/src/reflex_components => reflex-components-core/src/reflex_components_core}/core/upload.py (98%) rename packages/{reflex-components/src/reflex_components => reflex-components-core/src/reflex_components_core}/core/window_events.py (98%) rename packages/{reflex-components/src/reflex_components => reflex-components-core/src/reflex_components_core}/datadisplay/__init__.py (100%) rename packages/{reflex-components/src/reflex_components => reflex-components-core/src/reflex_components_core}/datadisplay/logo.py (100%) rename packages/{reflex-components/src/reflex_components => reflex-components-core/src/reflex_components_core}/el/__init__.py (92%) rename packages/{reflex-components/src/reflex_components => reflex-components-core/src/reflex_components_core}/el/element.py (100%) rename packages/{reflex-components/src/reflex_components => reflex-components-core/src/reflex_components_core}/el/elements/__init__.py (100%) rename packages/{reflex-components/src/reflex_components => reflex-components-core/src/reflex_components_core}/el/elements/base.py (98%) rename packages/{reflex-components/src/reflex_components => reflex-components-core/src/reflex_components_core}/el/elements/forms.py (99%) rename packages/{reflex-components/src/reflex_components => reflex-components-core/src/reflex_components_core}/el/elements/inline.py (100%) rename packages/{reflex-components/src/reflex_components => reflex-components-core/src/reflex_components_core}/el/elements/media.py (99%) rename packages/{reflex-components/src/reflex_components => reflex-components-core/src/reflex_components_core}/el/elements/metadata.py (92%) rename packages/{reflex-components/src/reflex_components => reflex-components-core/src/reflex_components_core}/el/elements/other.py (100%) rename packages/{reflex-components/src/reflex_components => reflex-components-core/src/reflex_components_core}/el/elements/scripts.py (90%) rename packages/{reflex-components/src/reflex_components => reflex-components-core/src/reflex_components_core}/el/elements/sectioning.py (100%) rename packages/{reflex-components/src/reflex_components => reflex-components-core/src/reflex_components_core}/el/elements/tables.py (100%) rename packages/{reflex-components/src/reflex_components => reflex-components-core/src/reflex_components_core}/el/elements/typography.py (100%) create mode 100644 packages/reflex-components-dataeditor/README.md rename packages/{reflex-dataeditor => reflex-components-dataeditor}/pyproject.toml (52%) rename packages/{reflex-dataeditor/src/reflex_dataeditor => reflex-components-dataeditor/src/reflex_components_dataeditor}/__init__.py (100%) rename packages/{reflex-dataeditor/src/reflex_dataeditor => reflex-components-dataeditor/src/reflex_components_dataeditor}/dataeditor.py (99%) create mode 100644 packages/reflex-components-gridjs/README.md rename packages/{reflex-gridjs => reflex-components-gridjs}/pyproject.toml (52%) rename packages/{reflex-gridjs/src/reflex_gridjs => reflex-components-gridjs/src/reflex_components_gridjs}/__init__.py (100%) rename packages/{reflex-gridjs/src/reflex_gridjs => reflex-components-gridjs/src/reflex_components_gridjs}/datatable.py (100%) create mode 100644 packages/reflex-components-lucide/README.md rename packages/{reflex-lucide => reflex-components-lucide}/pyproject.toml (52%) rename packages/{reflex-lucide/src/reflex_lucide => reflex-components-lucide/src/reflex_components_lucide}/__init__.py (100%) rename packages/{reflex-lucide/src/reflex_lucide => reflex-components-lucide/src/reflex_components_lucide}/icon.py (100%) create mode 100644 packages/reflex-components-markdown/README.md rename packages/{reflex-markdown => reflex-components-markdown}/pyproject.toml (52%) rename packages/{reflex-markdown/src/reflex_markdown => reflex-components-markdown/src/reflex_components_markdown}/__init__.py (100%) rename packages/{reflex-markdown/src/reflex_markdown => reflex-components-markdown/src/reflex_components_markdown}/markdown.py (95%) create mode 100644 packages/reflex-components-moment/README.md rename packages/{reflex-moment => reflex-components-moment}/pyproject.toml (52%) rename packages/{reflex-moment/src/reflex_moment => reflex-components-moment/src/reflex_components_moment}/__init__.py (100%) rename packages/{reflex-moment/src/reflex_moment => reflex-components-moment/src/reflex_components_moment}/moment.py (100%) create mode 100644 packages/reflex-components-plotly/README.md rename packages/{reflex-plotly => reflex-components-plotly}/pyproject.toml (52%) rename packages/{reflex-plotly/src/reflex_plotly => reflex-components-plotly/src/reflex_components_plotly}/__init__.py (100%) rename packages/{reflex-plotly/src/reflex_plotly => reflex-components-plotly/src/reflex_components_plotly}/plotly.py (99%) create mode 100644 packages/reflex-components-radix/README.md rename packages/{reflex-radix => reflex-components-radix}/pyproject.toml (52%) rename packages/{reflex-radix/src/reflex_radix => reflex-components-radix/src/reflex_components_radix}/__init__.py (100%) rename packages/{reflex-radix/src/reflex_radix => reflex-components-radix/src/reflex_components_radix}/primitives/__init__.py (100%) rename packages/{reflex-radix/src/reflex_radix => reflex-components-radix/src/reflex_components_radix}/primitives/accordion.py (98%) rename packages/{reflex-radix/src/reflex_radix => reflex-components-radix/src/reflex_components_radix}/primitives/base.py (96%) rename packages/{reflex-radix/src/reflex_radix => reflex-components-radix/src/reflex_components_radix}/primitives/dialog.py (99%) rename packages/{reflex-radix/src/reflex_radix => reflex-components-radix/src/reflex_components_radix}/primitives/drawer.py (97%) rename packages/{reflex-radix/src/reflex_radix => reflex-components-radix/src/reflex_components_radix}/primitives/form.py (95%) rename packages/{reflex-radix/src/reflex_radix => reflex-components-radix/src/reflex_components_radix}/primitives/progress.py (92%) rename packages/{reflex-radix/src/reflex_radix => reflex-components-radix/src/reflex_components_radix}/primitives/slider.py (98%) rename packages/{reflex-radix/src/reflex_radix => reflex-components-radix/src/reflex_components_radix}/themes/__init__.py (100%) rename packages/{reflex-radix/src/reflex_radix => reflex-components-radix/src/reflex_components_radix}/themes/base.py (99%) rename packages/{reflex-radix/src/reflex_radix => reflex-components-radix/src/reflex_components_radix}/themes/color_mode.py (95%) rename packages/{reflex-radix/src/reflex_radix => reflex-components-radix/src/reflex_components_radix}/themes/components/__init__.py (100%) rename packages/{reflex-radix/src/reflex_radix => reflex-components-radix/src/reflex_components_radix}/themes/components/alert_dialog.py (93%) rename packages/{reflex-radix/src/reflex_radix => reflex-components-radix/src/reflex_components_radix}/themes/components/aspect_ratio.py (85%) rename packages/{reflex-radix/src/reflex_radix => reflex-components-radix/src/reflex_components_radix}/themes/components/avatar.py (90%) rename packages/{reflex-radix/src/reflex_radix => reflex-components-radix/src/reflex_components_radix}/themes/components/badge.py (85%) rename packages/{reflex-radix/src/reflex_radix => reflex-components-radix/src/reflex_components_radix}/themes/components/button.py (88%) rename packages/{reflex-radix/src/reflex_radix => reflex-components-radix/src/reflex_components_radix}/themes/components/callout.py (90%) rename packages/{reflex-radix/src/reflex_radix => reflex-components-radix/src/reflex_components_radix}/themes/components/card.py (80%) rename packages/{reflex-radix/src/reflex_radix => reflex-components-radix/src/reflex_components_radix}/themes/components/checkbox.py (95%) rename packages/{reflex-radix/src/reflex_radix => reflex-components-radix/src/reflex_components_radix}/themes/components/checkbox_cards.py (90%) rename packages/{reflex-radix/src/reflex_radix => reflex-components-radix/src/reflex_components_radix}/themes/components/checkbox_group.py (91%) rename packages/{reflex-radix/src/reflex_radix => reflex-components-radix/src/reflex_components_radix}/themes/components/context_menu.py (98%) rename packages/{reflex-radix/src/reflex_radix => reflex-components-radix/src/reflex_components_radix}/themes/components/data_list.py (92%) rename packages/{reflex-radix/src/reflex_radix => reflex-components-radix/src/reflex_components_radix}/themes/components/dialog.py (92%) rename packages/{reflex-radix/src/reflex_radix => reflex-components-radix/src/reflex_components_radix}/themes/components/dropdown_menu.py (99%) rename packages/{reflex-radix/src/reflex_radix => reflex-components-radix/src/reflex_components_radix}/themes/components/hover_card.py (94%) rename packages/{reflex-radix/src/reflex_radix => reflex-components-radix/src/reflex_components_radix}/themes/components/icon_button.py (92%) rename packages/{reflex-radix/src/reflex_radix => reflex-components-radix/src/reflex_components_radix}/themes/components/inset.py (86%) rename packages/{reflex-radix/src/reflex_radix => reflex-components-radix/src/reflex_components_radix}/themes/components/popover.py (95%) rename packages/{reflex-radix/src/reflex_radix => reflex-components-radix/src/reflex_components_radix}/themes/components/progress.py (94%) rename packages/{reflex-radix/src/reflex_radix => reflex-components-radix/src/reflex_components_radix}/themes/components/radio.py (84%) rename packages/{reflex-radix/src/reflex_radix => reflex-components-radix/src/reflex_components_radix}/themes/components/radio_cards.py (95%) rename packages/{reflex-radix/src/reflex_radix => reflex-components-radix/src/reflex_components_radix}/themes/components/radio_group.py (96%) rename packages/{reflex-radix/src/reflex_radix => reflex-components-radix/src/reflex_components_radix}/themes/components/scroll_area.py (93%) rename packages/{reflex-radix/src/reflex_radix => reflex-components-radix/src/reflex_components_radix}/themes/components/segmented_control.py (93%) rename packages/{reflex-radix/src/reflex_radix => reflex-components-radix/src/reflex_components_radix}/themes/components/select.py (98%) rename packages/{reflex-radix/src/reflex_radix => reflex-components-radix/src/reflex_components_radix}/themes/components/separator.py (86%) rename packages/{reflex-radix/src/reflex_radix => reflex-components-radix/src/reflex_components_radix}/themes/components/skeleton.py (85%) rename packages/{reflex-radix/src/reflex_radix => reflex-components-radix/src/reflex_components_radix}/themes/components/slider.py (95%) rename packages/{reflex-radix/src/reflex_radix => reflex-components-radix/src/reflex_components_radix}/themes/components/spinner.py (74%) rename packages/{reflex-radix/src/reflex_radix => reflex-components-radix/src/reflex_components_radix}/themes/components/switch.py (92%) rename packages/{reflex-radix/src/reflex_radix => reflex-components-radix/src/reflex_components_radix}/themes/components/table.py (95%) rename packages/{reflex-radix/src/reflex_radix => reflex-components-radix/src/reflex_components_radix}/themes/components/tabs.py (95%) rename packages/{reflex-radix/src/reflex_radix => reflex-components-radix/src/reflex_components_radix}/themes/components/text_area.py (94%) rename packages/{reflex-radix/src/reflex_radix => reflex-components-radix/src/reflex_components_radix}/themes/components/text_field.py (95%) rename packages/{reflex-radix/src/reflex_radix => reflex-components-radix/src/reflex_components_radix}/themes/components/tooltip.py (98%) rename packages/{reflex-radix/src/reflex_radix => reflex-components-radix/src/reflex_components_radix}/themes/layout/__init__.py (100%) rename packages/{reflex-radix/src/reflex_radix => reflex-components-radix/src/reflex_components_radix}/themes/layout/base.py (89%) rename packages/{reflex-radix/src/reflex_radix => reflex-components-radix/src/reflex_components_radix}/themes/layout/box.py (67%) rename packages/{reflex-radix/src/reflex_radix => reflex-components-radix/src/reflex_components_radix}/themes/layout/center.py (100%) rename packages/{reflex-radix/src/reflex_radix => reflex-components-radix/src/reflex_components_radix}/themes/layout/container.py (89%) rename packages/{reflex-radix/src/reflex_radix => reflex-components-radix/src/reflex_components_radix}/themes/layout/flex.py (91%) rename packages/{reflex-radix/src/reflex_radix => reflex-components-radix/src/reflex_components_radix}/themes/layout/grid.py (91%) rename packages/{reflex-radix/src/reflex_radix => reflex-components-radix/src/reflex_components_radix}/themes/layout/list.py (93%) rename packages/{reflex-radix/src/reflex_radix => reflex-components-radix/src/reflex_components_radix}/themes/layout/section.py (76%) rename packages/{reflex-radix/src/reflex_radix => reflex-components-radix/src/reflex_components_radix}/themes/layout/spacer.py (100%) rename packages/{reflex-radix/src/reflex_radix => reflex-components-radix/src/reflex_components_radix}/themes/layout/stack.py (92%) rename packages/{reflex-radix/src/reflex_radix => reflex-components-radix/src/reflex_components_radix}/themes/typography/__init__.py (100%) rename packages/{reflex-radix/src/reflex_radix => reflex-components-radix/src/reflex_components_radix}/themes/typography/base.py (100%) rename packages/{reflex-radix/src/reflex_radix => reflex-components-radix/src/reflex_components_radix}/themes/typography/blockquote.py (81%) rename packages/{reflex-radix/src/reflex_radix => reflex-components-radix/src/reflex_components_radix}/themes/typography/code.py (82%) rename packages/{reflex-radix/src/reflex_radix => reflex-components-radix/src/reflex_components_radix}/themes/typography/heading.py (85%) rename packages/{reflex-radix/src/reflex_radix => reflex-components-radix/src/reflex_components_radix}/themes/typography/link.py (89%) rename packages/{reflex-radix/src/reflex_radix => reflex-components-radix/src/reflex_components_radix}/themes/typography/text.py (91%) create mode 100644 packages/reflex-components-react-player/README.md rename packages/{reflex-react-player => reflex-components-react-player}/pyproject.toml (51%) rename packages/{reflex-react-player/src/reflex_react_player => reflex-components-react-player/src/reflex_components_react_player}/__init__.py (100%) rename packages/{reflex-react-player/src/reflex_react_player => reflex-components-react-player/src/reflex_components_react_player}/audio.py (61%) rename packages/{reflex-react-player/src/reflex_react_player => reflex-components-react-player/src/reflex_components_react_player}/react_player.py (99%) rename packages/{reflex-react-player/src/reflex_react_player => reflex-components-react-player/src/reflex_components_react_player}/video.py (61%) create mode 100644 packages/reflex-components-react-router/README.md rename packages/{reflex-react-router => reflex-components-react-router}/pyproject.toml (51%) rename packages/{reflex-react-router/src/reflex_react_router => reflex-components-react-router/src/reflex_components_react_router}/__init__.py (100%) rename packages/{reflex-react-router/src/reflex_react_router => reflex-components-react-router/src/reflex_components_react_router}/dom.py (97%) create mode 100644 packages/reflex-components-recharts/README.md rename packages/{reflex-recharts => reflex-components-recharts}/pyproject.toml (52%) rename packages/{reflex-recharts/src/reflex_recharts => reflex-components-recharts/src/reflex_components_recharts}/__init__.py (100%) rename packages/{reflex-recharts/src/reflex_recharts => reflex-components-recharts/src/reflex_components_recharts}/cartesian.py (100%) rename packages/{reflex-recharts/src/reflex_recharts => reflex-components-recharts/src/reflex_components_recharts}/charts.py (99%) rename packages/{reflex-recharts/src/reflex_recharts => reflex-components-recharts/src/reflex_components_recharts}/general.py (100%) rename packages/{reflex-recharts/src/reflex_recharts => reflex-components-recharts/src/reflex_components_recharts}/polar.py (100%) rename packages/{reflex-recharts/src/reflex_recharts => reflex-components-recharts/src/reflex_components_recharts}/recharts.py (100%) create mode 100644 packages/reflex-components-sonner/README.md rename packages/{reflex-sonner => reflex-components-sonner}/pyproject.toml (52%) rename packages/{reflex-sonner/src/reflex_sonner => reflex-components-sonner/src/reflex_components_sonner}/__init__.py (100%) rename packages/{reflex-sonner/src/reflex_sonner => reflex-components-sonner/src/reflex_components_sonner}/toast.py (99%) delete mode 100644 packages/reflex-components/README.md delete mode 100644 packages/reflex-dataeditor/README.md delete mode 100644 packages/reflex-gridjs/README.md delete mode 100644 packages/reflex-lucide/README.md delete mode 100644 packages/reflex-markdown/README.md delete mode 100644 packages/reflex-moment/README.md delete mode 100644 packages/reflex-plotly/README.md delete mode 100644 packages/reflex-radix/README.md delete mode 100644 packages/reflex-react-player/README.md delete mode 100644 packages/reflex-react-router/README.md delete mode 100644 packages/reflex-recharts/README.md delete mode 100644 packages/reflex-sonner/README.md diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 19658f509fe..a9c453eeed9 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -20,6 +20,8 @@ jobs: steps: - name: Checkout uses: actions/checkout@v6 + with: + fetch-depth: 0 - name: Install uv uses: astral-sh/setup-uv@v7 @@ -31,30 +33,20 @@ jobs: # Tag format: v1.2.3 for reflex, reflex-lucide-v0.1.0 for sub-packages if [[ "$TAG" =~ ^v([0-9].*)$ ]]; then echo "package=reflex" >> "$GITHUB_OUTPUT" - echo "version=${BASH_REMATCH[1]}" >> "$GITHUB_OUTPUT" echo "build_dir=." >> "$GITHUB_OUTPUT" elif [[ "$TAG" =~ ^(.+)-v([0-9].*)$ ]]; then PACKAGE="${BASH_REMATCH[1]}" - VERSION="${BASH_REMATCH[2]}" if [ ! -d "packages/$PACKAGE" ]; then echo "Error: packages/$PACKAGE does not exist" exit 1 fi echo "package=$PACKAGE" >> "$GITHUB_OUTPUT" - echo "version=$VERSION" >> "$GITHUB_OUTPUT" echo "build_dir=packages/$PACKAGE" >> "$GITHUB_OUTPUT" else echo "Error: Tag '$TAG' does not match expected format (v* or -v*)" exit 1 fi - - name: Set version - run: | - cd "${{ steps.parse.outputs.build_dir }}" - sed -i 's/^version = ".*"/version = "${{ steps.parse.outputs.version }}"/' pyproject.toml - echo "Building ${{ steps.parse.outputs.package }} v${{ steps.parse.outputs.version }}" - grep '^version' pyproject.toml - - name: Build run: uv build --directory "${{ steps.parse.outputs.build_dir }}" diff --git a/packages/reflex-code/README.md b/packages/reflex-code/README.md deleted file mode 100644 index 6d668189196..00000000000 --- a/packages/reflex-code/README.md +++ /dev/null @@ -1 +0,0 @@ -# Reflex code diff --git a/packages/reflex-components-code/README.md b/packages/reflex-components-code/README.md new file mode 100644 index 00000000000..78a2013b19c --- /dev/null +++ b/packages/reflex-components-code/README.md @@ -0,0 +1,3 @@ +# reflex-components-code + +Reflex code display components. diff --git a/packages/reflex-code/pyproject.toml b/packages/reflex-components-code/pyproject.toml similarity index 53% rename from packages/reflex-code/pyproject.toml rename to packages/reflex-components-code/pyproject.toml index 9f93a4d89c3..f8de699d651 100644 --- a/packages/reflex-code/pyproject.toml +++ b/packages/reflex-components-code/pyproject.toml @@ -1,6 +1,6 @@ [project] -name = "reflex-code" -version = "0.0.1" +name = "reflex-components-code" +dynamic = ["version"] description = "Reflex code display components." readme = "README.md" authors = [{ name = "Khaleel Al-Adhami", email = "khaleel@reflex.dev" }] @@ -8,6 +8,10 @@ maintainers = [{ name = "Khaleel Al-Adhami", email = "khaleel@reflex.dev" }] requires-python = ">=3.10" dependencies = [] +[tool.hatch.version] +source = "vcs" +raw-options = { root = "../..", tag_regex = "^reflex-components-code-v(?P.+)$" } + [build-system] -requires = ["uv_build>=0.10.11,<0.12.0"] -build-backend = "uv_build" +requires = ["hatchling", "hatch-vcs"] +build-backend = "hatchling.build" diff --git a/packages/reflex-code/src/reflex_code/__init__.py b/packages/reflex-components-code/src/reflex_components_code/__init__.py similarity index 100% rename from packages/reflex-code/src/reflex_code/__init__.py rename to packages/reflex-components-code/src/reflex_components_code/__init__.py diff --git a/packages/reflex-code/src/reflex_code/code.py b/packages/reflex-components-code/src/reflex_components_code/code.py similarity index 97% rename from packages/reflex-code/src/reflex_code/code.py rename to packages/reflex-components-code/src/reflex_components_code/code.py index a1ca1154f33..cbf589009b1 100644 --- a/packages/reflex-code/src/reflex_code/code.py +++ b/packages/reflex-components-code/src/reflex_components_code/code.py @@ -5,11 +5,11 @@ import dataclasses from typing import ClassVar, Literal -from reflex_components.core.cond import color_mode_cond -from reflex_lucide.icon import Icon -from reflex_markdown.markdown import MarkdownComponentMap -from reflex_radix.themes.components.button import Button -from reflex_radix.themes.layout.box import Box +from reflex_components_core.core.cond import color_mode_cond +from reflex_components_lucide.icon import Icon +from reflex_components_markdown.markdown import MarkdownComponentMap +from reflex_components_radix.themes.components.button import Button +from reflex_components_radix.themes.layout.box import Box from reflex.components.component import Component, ComponentNamespace, field from reflex.constants.colors import Color diff --git a/packages/reflex-code/src/reflex_code/shiki_code_block.py b/packages/reflex-components-code/src/reflex_components_code/shiki_code_block.py similarity index 98% rename from packages/reflex-code/src/reflex_code/shiki_code_block.py rename to packages/reflex-components-code/src/reflex_components_code/shiki_code_block.py index 32671b5605d..844eb7a1e53 100644 --- a/packages/reflex-code/src/reflex_code/shiki_code_block.py +++ b/packages/reflex-components-code/src/reflex_components_code/shiki_code_block.py @@ -8,12 +8,12 @@ from dataclasses import dataclass from typing import Any, Literal -from reflex_components.core.colors import color -from reflex_components.core.cond import color_mode_cond -from reflex_components.el.elements.forms import Button -from reflex_lucide.icon import Icon -from reflex_markdown.markdown import MarkdownComponentMap -from reflex_radix.themes.layout.box import Box +from reflex_components_core.core.colors import color +from reflex_components_core.core.cond import color_mode_cond +from reflex_components_core.el.elements.forms import Button +from reflex_components_lucide.icon import Icon +from reflex_components_markdown.markdown import MarkdownComponentMap +from reflex_components_radix.themes.layout.box import Box from reflex.components.component import Component, ComponentNamespace, field from reflex.components.props import NoExtrasAllowedProps diff --git a/packages/reflex-components-core/README.md b/packages/reflex-components-core/README.md new file mode 100644 index 00000000000..bedf7d6ed79 --- /dev/null +++ b/packages/reflex-components-core/README.md @@ -0,0 +1,3 @@ +# reflex-components-core + +UI components for Reflex. diff --git a/packages/reflex-components/pyproject.toml b/packages/reflex-components-core/pyproject.toml similarity index 52% rename from packages/reflex-components/pyproject.toml rename to packages/reflex-components-core/pyproject.toml index 60653b9bc96..8eaf58eccf3 100644 --- a/packages/reflex-components/pyproject.toml +++ b/packages/reflex-components-core/pyproject.toml @@ -1,6 +1,6 @@ [project] -name = "reflex-components" -version = "0.0.1" +name = "reflex-components-core" +dynamic = ["version"] description = "UI components for Reflex." readme = "README.md" authors = [{ name = "Khaleel Al-Adhami", email = "khaleel@reflex.dev" }] @@ -8,6 +8,10 @@ maintainers = [{ name = "Khaleel Al-Adhami", email = "khaleel@reflex.dev" }] requires-python = ">=3.10" dependencies = [] +[tool.hatch.version] +source = "vcs" +raw-options = { root = "../..", tag_regex = "^reflex-components-core-v(?P.+)$" } + [build-system] -requires = ["uv_build>=0.10.11,<0.12.0"] -build-backend = "uv_build" +requires = ["hatchling", "hatch-vcs"] +build-backend = "hatchling.build" diff --git a/packages/reflex-components/src/reflex_components/__init__.py b/packages/reflex-components-core/src/reflex_components_core/__init__.py similarity index 100% rename from packages/reflex-components/src/reflex_components/__init__.py rename to packages/reflex-components-core/src/reflex_components_core/__init__.py diff --git a/packages/reflex-components/src/reflex_components/base/__init__.py b/packages/reflex-components-core/src/reflex_components_core/base/__init__.py similarity index 100% rename from packages/reflex-components/src/reflex_components/base/__init__.py rename to packages/reflex-components-core/src/reflex_components_core/base/__init__.py diff --git a/packages/reflex-components/src/reflex_components/base/app_wrap.py b/packages/reflex-components-core/src/reflex_components_core/base/app_wrap.py similarity index 88% rename from packages/reflex-components/src/reflex_components/base/app_wrap.py rename to packages/reflex-components-core/src/reflex_components_core/base/app_wrap.py index c4e1a0b43bc..9b244ef23eb 100644 --- a/packages/reflex-components/src/reflex_components/base/app_wrap.py +++ b/packages/reflex-components-core/src/reflex_components_core/base/app_wrap.py @@ -2,7 +2,7 @@ from reflex.components.component import Component from reflex.vars.base import Var -from reflex_components.base.fragment import Fragment +from reflex_components_core.base.fragment import Fragment class AppWrap(Fragment): diff --git a/packages/reflex-components/src/reflex_components/base/bare.py b/packages/reflex-components-core/src/reflex_components_core/base/bare.py similarity index 100% rename from packages/reflex-components/src/reflex_components/base/bare.py rename to packages/reflex-components-core/src/reflex_components_core/base/bare.py diff --git a/packages/reflex-components/src/reflex_components/base/body.py b/packages/reflex-components-core/src/reflex_components_core/base/body.py similarity index 64% rename from packages/reflex-components/src/reflex_components/base/body.py rename to packages/reflex-components-core/src/reflex_components_core/base/body.py index 5ce7b0e8c0d..51fdc70d7e1 100644 --- a/packages/reflex-components/src/reflex_components/base/body.py +++ b/packages/reflex-components-core/src/reflex_components_core/base/body.py @@ -1,6 +1,6 @@ """Display the page body.""" -from reflex_components.el import elements +from reflex_components_core.el import elements class Body(elements.Body): diff --git a/packages/reflex-components/src/reflex_components/base/document.py b/packages/reflex-components-core/src/reflex_components_core/base/document.py similarity index 100% rename from packages/reflex-components/src/reflex_components/base/document.py rename to packages/reflex-components-core/src/reflex_components_core/base/document.py diff --git a/packages/reflex-components/src/reflex_components/base/error_boundary.py b/packages/reflex-components-core/src/reflex_components_core/base/error_boundary.py similarity index 98% rename from packages/reflex-components/src/reflex_components/base/error_boundary.py rename to packages/reflex-components-core/src/reflex_components_core/base/error_boundary.py index d257fa06eed..dcf3867aa2c 100644 --- a/packages/reflex-components/src/reflex_components/base/error_boundary.py +++ b/packages/reflex-components-core/src/reflex_components_core/base/error_boundary.py @@ -8,8 +8,8 @@ from reflex.vars.base import Var from reflex.vars.function import ArgsFunctionOperation from reflex.vars.object import ObjectVar -from reflex_components.datadisplay.logo import svg_logo -from reflex_components.el import a, button, div, h2, hr, p, pre, svg +from reflex_components_core.datadisplay.logo import svg_logo +from reflex_components_core.el import a, button, div, h2, hr, p, pre, svg def on_error_spec( diff --git a/packages/reflex-components/src/reflex_components/base/fragment.py b/packages/reflex-components-core/src/reflex_components_core/base/fragment.py similarity index 100% rename from packages/reflex-components/src/reflex_components/base/fragment.py rename to packages/reflex-components-core/src/reflex_components_core/base/fragment.py diff --git a/packages/reflex-components/src/reflex_components/base/link.py b/packages/reflex-components-core/src/reflex_components_core/base/link.py similarity index 94% rename from packages/reflex-components/src/reflex_components/base/link.py rename to packages/reflex-components-core/src/reflex_components_core/base/link.py index c0b77be9fb8..4df4deeebb6 100644 --- a/packages/reflex-components/src/reflex_components/base/link.py +++ b/packages/reflex-components-core/src/reflex_components_core/base/link.py @@ -2,7 +2,7 @@ from reflex.components.component import field from reflex.vars.base import Var -from reflex_components.el.elements.base import BaseHTML +from reflex_components_core.el.elements.base import BaseHTML class RawLink(BaseHTML): diff --git a/packages/reflex-components/src/reflex_components/base/meta.py b/packages/reflex-components-core/src/reflex_components_core/base/meta.py similarity index 85% rename from packages/reflex-components/src/reflex_components/base/meta.py rename to packages/reflex-components-core/src/reflex_components_core/base/meta.py index f01a01a1b63..f8fe76eeb25 100644 --- a/packages/reflex-components/src/reflex_components/base/meta.py +++ b/packages/reflex-components-core/src/reflex_components_core/base/meta.py @@ -4,9 +4,9 @@ from reflex.components.component import field from reflex.vars.base import Var -from reflex_components.base.bare import Bare -from reflex_components.el import elements -from reflex_components.el.elements.metadata import Meta as Meta # for compatibility +from reflex_components_core.base.bare import Bare +from reflex_components_core.el import elements +from reflex_components_core.el.elements.metadata import Meta as Meta # for compatibility class Title(elements.Title): diff --git a/packages/reflex-components/src/reflex_components/base/script.py b/packages/reflex-components-core/src/reflex_components_core/base/script.py similarity index 95% rename from packages/reflex-components/src/reflex_components/base/script.py rename to packages/reflex-components-core/src/reflex_components_core/base/script.py index 039fc0b339e..dcd121d023c 100644 --- a/packages/reflex-components/src/reflex_components/base/script.py +++ b/packages/reflex-components-core/src/reflex_components_core/base/script.py @@ -3,8 +3,8 @@ from __future__ import annotations from reflex.utils import console -from reflex_components import el as elements -from reflex_components.core.helmet import helmet +from reflex_components_core import el as elements +from reflex_components_core.core.helmet import helmet class Script(elements.Script): diff --git a/packages/reflex-components/src/reflex_components/base/strict_mode.py b/packages/reflex-components-core/src/reflex_components_core/base/strict_mode.py similarity index 100% rename from packages/reflex-components/src/reflex_components/base/strict_mode.py rename to packages/reflex-components-core/src/reflex_components_core/base/strict_mode.py diff --git a/packages/reflex-components/src/reflex_components/core/__init__.py b/packages/reflex-components-core/src/reflex_components_core/core/__init__.py similarity index 100% rename from packages/reflex-components/src/reflex_components/core/__init__.py rename to packages/reflex-components-core/src/reflex_components_core/core/__init__.py diff --git a/packages/reflex-components/src/reflex_components/core/auto_scroll.py b/packages/reflex-components-core/src/reflex_components_core/core/auto_scroll.py similarity index 98% rename from packages/reflex-components/src/reflex_components/core/auto_scroll.py rename to packages/reflex-components-core/src/reflex_components_core/core/auto_scroll.py index 8b948e7f2b6..0ef2fcdb41e 100644 --- a/packages/reflex-components/src/reflex_components/core/auto_scroll.py +++ b/packages/reflex-components-core/src/reflex_components_core/core/auto_scroll.py @@ -7,7 +7,7 @@ from reflex.constants.compiler import MemoizationDisposition, MemoizationMode from reflex.utils.imports import ImportDict from reflex.vars.base import Var, get_unique_variable_name -from reflex_components.el.elements.typography import Div +from reflex_components_core.el.elements.typography import Div class AutoScroll(Div): diff --git a/packages/reflex-components/src/reflex_components/core/banner.py b/packages/reflex-components-core/src/reflex_components_core/core/banner.py similarity index 97% rename from packages/reflex-components/src/reflex_components/core/banner.py rename to packages/reflex-components-core/src/reflex_components_core/core/banner.py index 433148d781c..2bfc6ea59a0 100644 --- a/packages/reflex-components/src/reflex_components/core/banner.py +++ b/packages/reflex-components-core/src/reflex_components_core/core/banner.py @@ -2,11 +2,11 @@ from __future__ import annotations -from reflex_lucide.icon import Icon -from reflex_radix.themes.components.dialog import DialogContent, DialogRoot, DialogTitle -from reflex_radix.themes.layout.flex import Flex -from reflex_radix.themes.typography.text import Text -from reflex_sonner.toast import ToastProps, toast_ref +from reflex_components_lucide.icon import Icon +from reflex_components_radix.themes.components.dialog import DialogContent, DialogRoot, DialogTitle +from reflex_components_radix.themes.layout.flex import Flex +from reflex_components_radix.themes.typography.text import Text +from reflex_components_sonner.toast import ToastProps, toast_ref from reflex import constants from reflex.components.component import Component @@ -19,9 +19,9 @@ from reflex.vars.function import FunctionStringVar from reflex.vars.number import BooleanVar from reflex.vars.sequence import LiteralArrayVar -from reflex_components.base.fragment import Fragment -from reflex_components.core.cond import cond -from reflex_components.el.elements.typography import Div +from reflex_components_core.base.fragment import Fragment +from reflex_components_core.core.cond import cond +from reflex_components_core.el.elements.typography import Div connect_error_var_data: VarData = VarData( imports=Imports.EVENTS, diff --git a/packages/reflex-components/src/reflex_components/core/breakpoints.py b/packages/reflex-components-core/src/reflex_components_core/core/breakpoints.py similarity index 100% rename from packages/reflex-components/src/reflex_components/core/breakpoints.py rename to packages/reflex-components-core/src/reflex_components_core/core/breakpoints.py diff --git a/packages/reflex-components/src/reflex_components/core/clipboard.py b/packages/reflex-components-core/src/reflex_components_core/core/clipboard.py similarity index 98% rename from packages/reflex-components/src/reflex_components/core/clipboard.py rename to packages/reflex-components-core/src/reflex_components_core/core/clipboard.py index a4eb48a3c89..e60fc8572ab 100644 --- a/packages/reflex-components/src/reflex_components/core/clipboard.py +++ b/packages/reflex-components-core/src/reflex_components_core/core/clipboard.py @@ -12,7 +12,7 @@ from reflex.utils.imports import ImportVar from reflex.vars import get_unique_variable_name from reflex.vars.base import Var, VarData -from reflex_components.base.fragment import Fragment +from reflex_components_core.base.fragment import Fragment class Clipboard(Fragment): diff --git a/packages/reflex-components/src/reflex_components/core/colors.py b/packages/reflex-components-core/src/reflex_components_core/core/colors.py similarity index 100% rename from packages/reflex-components/src/reflex_components/core/colors.py rename to packages/reflex-components-core/src/reflex_components_core/core/colors.py diff --git a/packages/reflex-components/src/reflex_components/core/cond.py b/packages/reflex-components-core/src/reflex_components_core/core/cond.py similarity index 98% rename from packages/reflex-components/src/reflex_components/core/cond.py rename to packages/reflex-components-core/src/reflex_components_core/core/cond.py index 007cf20eafe..8bd258cbd9c 100644 --- a/packages/reflex-components/src/reflex_components/core/cond.py +++ b/packages/reflex-components-core/src/reflex_components_core/core/cond.py @@ -13,7 +13,7 @@ from reflex.vars import VarData from reflex.vars.base import LiteralVar, Var from reflex.vars.number import ternary_operation -from reflex_components.base.fragment import Fragment +from reflex_components_core.base.fragment import Fragment _IS_TRUE_IMPORT: ImportDict = { f"$/{Dirs.STATE_PATH}": [ImportVar(tag="isTrue")], diff --git a/packages/reflex-components/src/reflex_components/core/debounce.py b/packages/reflex-components-core/src/reflex_components_core/core/debounce.py similarity index 100% rename from packages/reflex-components/src/reflex_components/core/debounce.py rename to packages/reflex-components-core/src/reflex_components_core/core/debounce.py diff --git a/packages/reflex-components/src/reflex_components/core/foreach.py b/packages/reflex-components-core/src/reflex_components_core/core/foreach.py similarity index 98% rename from packages/reflex-components/src/reflex_components/core/foreach.py rename to packages/reflex-components-core/src/reflex_components_core/core/foreach.py index 0743dce96b7..731a3471475 100644 --- a/packages/reflex-components/src/reflex_components/core/foreach.py +++ b/packages/reflex-components-core/src/reflex_components_core/core/foreach.py @@ -16,8 +16,8 @@ from reflex.utils import types from reflex.utils.exceptions import UntypedVarError from reflex.vars.base import LiteralVar, Var -from reflex_components.base.fragment import Fragment -from reflex_components.core.cond import cond +from reflex_components_core.base.fragment import Fragment +from reflex_components_core.core.cond import cond class ForeachVarError(TypeError): diff --git a/packages/reflex-components/src/reflex_components/core/helmet.py b/packages/reflex-components-core/src/reflex_components_core/core/helmet.py similarity index 100% rename from packages/reflex-components/src/reflex_components/core/helmet.py rename to packages/reflex-components-core/src/reflex_components_core/core/helmet.py diff --git a/packages/reflex-components/src/reflex_components/core/html.py b/packages/reflex-components-core/src/reflex_components_core/core/html.py similarity index 95% rename from packages/reflex-components/src/reflex_components/core/html.py rename to packages/reflex-components-core/src/reflex_components_core/core/html.py index 31790b1eed3..59455f25b1b 100644 --- a/packages/reflex-components/src/reflex_components/core/html.py +++ b/packages/reflex-components-core/src/reflex_components_core/core/html.py @@ -2,7 +2,7 @@ from reflex.components.component import field from reflex.vars.base import Var -from reflex_components.el.elements.typography import Div +from reflex_components_core.el.elements.typography import Div class Html(Div): diff --git a/packages/reflex-components/src/reflex_components/core/layout/__init__.py b/packages/reflex-components-core/src/reflex_components_core/core/layout/__init__.py similarity index 100% rename from packages/reflex-components/src/reflex_components/core/layout/__init__.py rename to packages/reflex-components-core/src/reflex_components_core/core/layout/__init__.py diff --git a/packages/reflex-components/src/reflex_components/core/match.py b/packages/reflex-components-core/src/reflex_components_core/core/match.py similarity index 99% rename from packages/reflex-components/src/reflex_components/core/match.py rename to packages/reflex-components-core/src/reflex_components_core/core/match.py index e92649d49e6..999a585006d 100644 --- a/packages/reflex-components/src/reflex_components/core/match.py +++ b/packages/reflex-components-core/src/reflex_components_core/core/match.py @@ -12,7 +12,7 @@ from reflex.utils.imports import ImportDict from reflex.vars import VarData from reflex.vars.base import LiteralVar, Var -from reflex_components.base import Fragment +from reflex_components_core.base import Fragment class Match(MemoizationLeaf): diff --git a/packages/reflex-components/src/reflex_components/core/responsive.py b/packages/reflex-components-core/src/reflex_components_core/core/responsive.py similarity index 96% rename from packages/reflex-components/src/reflex_components/core/responsive.py rename to packages/reflex-components-core/src/reflex_components_core/core/responsive.py index 34edc042493..f149ca24350 100644 --- a/packages/reflex-components/src/reflex_components/core/responsive.py +++ b/packages/reflex-components-core/src/reflex_components_core/core/responsive.py @@ -1,6 +1,6 @@ """Responsive components.""" -from reflex_radix.themes.layout.box import Box +from reflex_components_radix.themes.layout.box import Box # Add responsive styles shortcuts. diff --git a/packages/reflex-components/src/reflex_components/core/sticky.py b/packages/reflex-components-core/src/reflex_components_core/core/sticky.py similarity index 90% rename from packages/reflex-components/src/reflex_components/core/sticky.py rename to packages/reflex-components-core/src/reflex_components_core/core/sticky.py index cf9977c80bb..0758f75a6a1 100644 --- a/packages/reflex-components/src/reflex_components/core/sticky.py +++ b/packages/reflex-components-core/src/reflex_components_core/core/sticky.py @@ -1,14 +1,14 @@ """Components for displaying the Reflex sticky logo.""" -from reflex_radix.themes.typography.text import Text +from reflex_components_radix.themes.typography.text import Text from reflex.components.component import ComponentNamespace from reflex.style import Style -from reflex_components.core.colors import color -from reflex_components.core.cond import color_mode_cond -from reflex_components.core.responsive import desktop_only -from reflex_components.el.elements.inline import A -from reflex_components.el.elements.media import Path, Rect, Svg +from reflex_components_core.core.colors import color +from reflex_components_core.core.cond import color_mode_cond +from reflex_components_core.core.responsive import desktop_only +from reflex_components_core.el.elements.inline import A +from reflex_components_core.el.elements.media import Path, Rect, Svg class StickyLogo(Svg): diff --git a/packages/reflex-components/src/reflex_components/core/upload.py b/packages/reflex-components-core/src/reflex_components_core/core/upload.py similarity index 98% rename from packages/reflex-components/src/reflex_components/core/upload.py rename to packages/reflex-components-core/src/reflex_components_core/core/upload.py index d3c91e06474..2beb50dd9be 100644 --- a/packages/reflex-components/src/reflex_components/core/upload.py +++ b/packages/reflex-components-core/src/reflex_components_core/core/upload.py @@ -6,8 +6,8 @@ from pathlib import Path from typing import Any, ClassVar -from reflex_radix.themes.layout.box import Box -from reflex_sonner.toast import toast +from reflex_components_radix.themes.layout.box import Box +from reflex_components_sonner.toast import toast from reflex.app import UploadFile from reflex.components.component import ( @@ -40,9 +40,9 @@ from reflex.vars.function import FunctionVar from reflex.vars.object import ObjectVar from reflex.vars.sequence import ArrayVar, LiteralStringVar -from reflex_components.base.fragment import Fragment -from reflex_components.core.cond import cond -from reflex_components.el.elements.forms import Input +from reflex_components_core.base.fragment import Fragment +from reflex_components_core.core.cond import cond +from reflex_components_core.el.elements.forms import Input DEFAULT_UPLOAD_ID: str = "default" diff --git a/packages/reflex-components/src/reflex_components/core/window_events.py b/packages/reflex-components-core/src/reflex_components_core/core/window_events.py similarity index 98% rename from packages/reflex-components/src/reflex_components/core/window_events.py rename to packages/reflex-components-core/src/reflex_components_core/core/window_events.py index 31f322465a4..4379ff89cc7 100644 --- a/packages/reflex-components/src/reflex_components/core/window_events.py +++ b/packages/reflex-components-core/src/reflex_components_core/core/window_events.py @@ -10,7 +10,7 @@ from reflex.event import key_event, no_args_event_spec from reflex.vars.base import Var, VarData from reflex.vars.object import ObjectVar -from reflex_components.base.fragment import Fragment +from reflex_components_core.base.fragment import Fragment def _on_resize_spec() -> tuple[Var[int], Var[int]]: diff --git a/packages/reflex-components/src/reflex_components/datadisplay/__init__.py b/packages/reflex-components-core/src/reflex_components_core/datadisplay/__init__.py similarity index 100% rename from packages/reflex-components/src/reflex_components/datadisplay/__init__.py rename to packages/reflex-components-core/src/reflex_components_core/datadisplay/__init__.py diff --git a/packages/reflex-components/src/reflex_components/datadisplay/logo.py b/packages/reflex-components-core/src/reflex_components_core/datadisplay/logo.py similarity index 100% rename from packages/reflex-components/src/reflex_components/datadisplay/logo.py rename to packages/reflex-components-core/src/reflex_components_core/datadisplay/logo.py diff --git a/packages/reflex-components/src/reflex_components/el/__init__.py b/packages/reflex-components-core/src/reflex_components_core/el/__init__.py similarity index 92% rename from packages/reflex-components/src/reflex_components/el/__init__.py rename to packages/reflex-components-core/src/reflex_components_core/el/__init__.py index c3073f557ec..f0f414fe860 100644 --- a/packages/reflex-components/src/reflex_components/el/__init__.py +++ b/packages/reflex-components-core/src/reflex_components_core/el/__init__.py @@ -13,7 +13,7 @@ for k, attrs in elements._MAPPING.items() } _EXTRA_MAPPINGS: dict[str, str] = { - "a": "reflex_react_router.link", + "a": "reflex_components_react_router.link", } __getattr__, __dir__, __all__ = lazy_loader.attach( diff --git a/packages/reflex-components/src/reflex_components/el/element.py b/packages/reflex-components-core/src/reflex_components_core/el/element.py similarity index 100% rename from packages/reflex-components/src/reflex_components/el/element.py rename to packages/reflex-components-core/src/reflex_components_core/el/element.py diff --git a/packages/reflex-components/src/reflex_components/el/elements/__init__.py b/packages/reflex-components-core/src/reflex_components_core/el/elements/__init__.py similarity index 100% rename from packages/reflex-components/src/reflex_components/el/elements/__init__.py rename to packages/reflex-components-core/src/reflex_components_core/el/elements/__init__.py diff --git a/packages/reflex-components/src/reflex_components/el/elements/base.py b/packages/reflex-components-core/src/reflex_components_core/el/elements/base.py similarity index 98% rename from packages/reflex-components/src/reflex_components/el/elements/base.py rename to packages/reflex-components-core/src/reflex_components_core/el/elements/base.py index 6387fd5b987..873972ea800 100644 --- a/packages/reflex-components/src/reflex_components/el/elements/base.py +++ b/packages/reflex-components-core/src/reflex_components_core/el/elements/base.py @@ -4,7 +4,7 @@ from reflex.components.component import field from reflex.vars.base import Var -from reflex_components.el.element import Element +from reflex_components_core.el.element import Element AutoCapitalize = Literal["off", "none", "on", "sentences", "words", "characters"] ContentEditable = Literal["inherit", "plaintext-only"] | bool diff --git a/packages/reflex-components/src/reflex_components/el/elements/forms.py b/packages/reflex-components-core/src/reflex_components_core/el/elements/forms.py similarity index 99% rename from packages/reflex-components/src/reflex_components/el/elements/forms.py rename to packages/reflex-components-core/src/reflex_components_core/el/elements/forms.py index 131f1d634a9..24e331e484a 100644 --- a/packages/reflex-components/src/reflex_components/el/elements/forms.py +++ b/packages/reflex-components-core/src/reflex_components_core/el/elements/forms.py @@ -26,7 +26,7 @@ from reflex.vars import VarData from reflex.vars.base import LiteralVar, Var from reflex.vars.number import ternary_operation -from reflex_components.el.element import Element +from reflex_components_core.el.element import Element from .base import BaseHTML diff --git a/packages/reflex-components/src/reflex_components/el/elements/inline.py b/packages/reflex-components-core/src/reflex_components_core/el/elements/inline.py similarity index 100% rename from packages/reflex-components/src/reflex_components/el/elements/inline.py rename to packages/reflex-components-core/src/reflex_components_core/el/elements/inline.py diff --git a/packages/reflex-components/src/reflex_components/el/elements/media.py b/packages/reflex-components-core/src/reflex_components_core/el/elements/media.py similarity index 99% rename from packages/reflex-components/src/reflex_components/el/elements/media.py rename to packages/reflex-components-core/src/reflex_components_core/el/elements/media.py index 729800362fa..0e45b68e6e5 100644 --- a/packages/reflex-components/src/reflex_components/el/elements/media.py +++ b/packages/reflex-components-core/src/reflex_components_core/el/elements/media.py @@ -6,7 +6,7 @@ from reflex.components.component import field from reflex.constants.colors import Color from reflex.vars.base import Var -from reflex_components.el.elements.inline import ReferrerPolicy +from reflex_components_core.el.elements.inline import ReferrerPolicy from .base import BaseHTML diff --git a/packages/reflex-components/src/reflex_components/el/elements/metadata.py b/packages/reflex-components-core/src/reflex_components_core/el/elements/metadata.py similarity index 92% rename from packages/reflex-components/src/reflex_components/el/elements/metadata.py rename to packages/reflex-components-core/src/reflex_components_core/el/elements/metadata.py index 248dabcf54e..ebc6574fc06 100644 --- a/packages/reflex-components/src/reflex_components/el/elements/metadata.py +++ b/packages/reflex-components-core/src/reflex_components_core/el/elements/metadata.py @@ -2,9 +2,9 @@ from reflex.components.component import field from reflex.vars.base import Var -from reflex_components.el.element import Element -from reflex_components.el.elements.inline import ReferrerPolicy -from reflex_components.el.elements.media import CrossOrigin +from reflex_components_core.el.element import Element +from reflex_components_core.el.elements.inline import ReferrerPolicy +from reflex_components_core.el.elements.media import CrossOrigin from .base import BaseHTML diff --git a/packages/reflex-components/src/reflex_components/el/elements/other.py b/packages/reflex-components-core/src/reflex_components_core/el/elements/other.py similarity index 100% rename from packages/reflex-components/src/reflex_components/el/elements/other.py rename to packages/reflex-components-core/src/reflex_components_core/el/elements/other.py diff --git a/packages/reflex-components/src/reflex_components/el/elements/scripts.py b/packages/reflex-components-core/src/reflex_components_core/el/elements/scripts.py similarity index 90% rename from packages/reflex-components/src/reflex_components/el/elements/scripts.py rename to packages/reflex-components-core/src/reflex_components_core/el/elements/scripts.py index 45151631747..0c61f9cad09 100644 --- a/packages/reflex-components/src/reflex_components/el/elements/scripts.py +++ b/packages/reflex-components-core/src/reflex_components_core/el/elements/scripts.py @@ -2,8 +2,8 @@ from reflex.components.component import field from reflex.vars.base import Var -from reflex_components.el.elements.inline import ReferrerPolicy -from reflex_components.el.elements.media import CrossOrigin +from reflex_components_core.el.elements.inline import ReferrerPolicy +from reflex_components_core.el.elements.media import CrossOrigin from .base import BaseHTML diff --git a/packages/reflex-components/src/reflex_components/el/elements/sectioning.py b/packages/reflex-components-core/src/reflex_components_core/el/elements/sectioning.py similarity index 100% rename from packages/reflex-components/src/reflex_components/el/elements/sectioning.py rename to packages/reflex-components-core/src/reflex_components_core/el/elements/sectioning.py diff --git a/packages/reflex-components/src/reflex_components/el/elements/tables.py b/packages/reflex-components-core/src/reflex_components_core/el/elements/tables.py similarity index 100% rename from packages/reflex-components/src/reflex_components/el/elements/tables.py rename to packages/reflex-components-core/src/reflex_components_core/el/elements/tables.py diff --git a/packages/reflex-components/src/reflex_components/el/elements/typography.py b/packages/reflex-components-core/src/reflex_components_core/el/elements/typography.py similarity index 100% rename from packages/reflex-components/src/reflex_components/el/elements/typography.py rename to packages/reflex-components-core/src/reflex_components_core/el/elements/typography.py diff --git a/packages/reflex-components-dataeditor/README.md b/packages/reflex-components-dataeditor/README.md new file mode 100644 index 00000000000..6683e72a292 --- /dev/null +++ b/packages/reflex-components-dataeditor/README.md @@ -0,0 +1,3 @@ +# reflex-components-dataeditor + +Reflex dataeditor components. diff --git a/packages/reflex-dataeditor/pyproject.toml b/packages/reflex-components-dataeditor/pyproject.toml similarity index 52% rename from packages/reflex-dataeditor/pyproject.toml rename to packages/reflex-components-dataeditor/pyproject.toml index 778127d1e0a..d8f8e71c90d 100644 --- a/packages/reflex-dataeditor/pyproject.toml +++ b/packages/reflex-components-dataeditor/pyproject.toml @@ -1,6 +1,6 @@ [project] -name = "reflex-dataeditor" -version = "0.0.1" +name = "reflex-components-dataeditor" +dynamic = ["version"] description = "Reflex dataeditor components." readme = "README.md" authors = [{ name = "Khaleel Al-Adhami", email = "khaleel@reflex.dev" }] @@ -8,6 +8,10 @@ maintainers = [{ name = "Khaleel Al-Adhami", email = "khaleel@reflex.dev" }] requires-python = ">=3.10" dependencies = [] +[tool.hatch.version] +source = "vcs" +raw-options = { root = "../..", tag_regex = "^reflex-components-dataeditor-v(?P.+)$" } + [build-system] -requires = ["uv_build>=0.10.11,<0.12.0"] -build-backend = "uv_build" +requires = ["hatchling", "hatch-vcs"] +build-backend = "hatchling.build" diff --git a/packages/reflex-dataeditor/src/reflex_dataeditor/__init__.py b/packages/reflex-components-dataeditor/src/reflex_components_dataeditor/__init__.py similarity index 100% rename from packages/reflex-dataeditor/src/reflex_dataeditor/__init__.py rename to packages/reflex-components-dataeditor/src/reflex_components_dataeditor/__init__.py diff --git a/packages/reflex-dataeditor/src/reflex_dataeditor/dataeditor.py b/packages/reflex-components-dataeditor/src/reflex_components_dataeditor/dataeditor.py similarity index 99% rename from packages/reflex-dataeditor/src/reflex_dataeditor/dataeditor.py rename to packages/reflex-components-dataeditor/src/reflex_components_dataeditor/dataeditor.py index 7eca448d109..5b116e468d1 100644 --- a/packages/reflex-dataeditor/src/reflex_dataeditor/dataeditor.py +++ b/packages/reflex-components-dataeditor/src/reflex_components_dataeditor/dataeditor.py @@ -502,7 +502,7 @@ def create(cls, *children, **props) -> Component: Raises: ValueError: invalid input. """ - from reflex_components.el import Div + from reflex_components_core.el import Div columns = props.get("columns", []) data = props.get("data", []) @@ -564,7 +564,7 @@ def _get_app_wrap_components() -> dict[tuple[int, str], Component]: Returns: The app wrap components. """ - from reflex_components.el import Div + from reflex_components_core.el import Div class Portal(Div): def get_ref(self): diff --git a/packages/reflex-components-gridjs/README.md b/packages/reflex-components-gridjs/README.md new file mode 100644 index 00000000000..f476e663587 --- /dev/null +++ b/packages/reflex-components-gridjs/README.md @@ -0,0 +1,3 @@ +# reflex-components-gridjs + +Reflex gridjs components. diff --git a/packages/reflex-gridjs/pyproject.toml b/packages/reflex-components-gridjs/pyproject.toml similarity index 52% rename from packages/reflex-gridjs/pyproject.toml rename to packages/reflex-components-gridjs/pyproject.toml index fe17c9a82c7..f44dd96822b 100644 --- a/packages/reflex-gridjs/pyproject.toml +++ b/packages/reflex-components-gridjs/pyproject.toml @@ -1,6 +1,6 @@ [project] -name = "reflex-gridjs" -version = "0.0.1" +name = "reflex-components-gridjs" +dynamic = ["version"] description = "Reflex gridjs components." readme = "README.md" authors = [{ name = "Khaleel Al-Adhami", email = "khaleel@reflex.dev" }] @@ -8,6 +8,10 @@ maintainers = [{ name = "Khaleel Al-Adhami", email = "khaleel@reflex.dev" }] requires-python = ">=3.10" dependencies = [] +[tool.hatch.version] +source = "vcs" +raw-options = { root = "../..", tag_regex = "^reflex-components-gridjs-v(?P.+)$" } + [build-system] -requires = ["uv_build>=0.10.11,<0.12.0"] -build-backend = "uv_build" +requires = ["hatchling", "hatch-vcs"] +build-backend = "hatchling.build" diff --git a/packages/reflex-gridjs/src/reflex_gridjs/__init__.py b/packages/reflex-components-gridjs/src/reflex_components_gridjs/__init__.py similarity index 100% rename from packages/reflex-gridjs/src/reflex_gridjs/__init__.py rename to packages/reflex-components-gridjs/src/reflex_components_gridjs/__init__.py diff --git a/packages/reflex-gridjs/src/reflex_gridjs/datatable.py b/packages/reflex-components-gridjs/src/reflex_components_gridjs/datatable.py similarity index 100% rename from packages/reflex-gridjs/src/reflex_gridjs/datatable.py rename to packages/reflex-components-gridjs/src/reflex_components_gridjs/datatable.py diff --git a/packages/reflex-components-lucide/README.md b/packages/reflex-components-lucide/README.md new file mode 100644 index 00000000000..f889e94ec50 --- /dev/null +++ b/packages/reflex-components-lucide/README.md @@ -0,0 +1,3 @@ +# reflex-components-lucide + +Reflex lucide components. diff --git a/packages/reflex-lucide/pyproject.toml b/packages/reflex-components-lucide/pyproject.toml similarity index 52% rename from packages/reflex-lucide/pyproject.toml rename to packages/reflex-components-lucide/pyproject.toml index 99d34c569f6..1f2201311cd 100644 --- a/packages/reflex-lucide/pyproject.toml +++ b/packages/reflex-components-lucide/pyproject.toml @@ -1,6 +1,6 @@ [project] -name = "reflex-lucide" -version = "0.0.1" +name = "reflex-components-lucide" +dynamic = ["version"] description = "Reflex lucide components." readme = "README.md" authors = [{ name = "Khaleel Al-Adhami", email = "khaleel@reflex.dev" }] @@ -8,6 +8,10 @@ maintainers = [{ name = "Khaleel Al-Adhami", email = "khaleel@reflex.dev" }] requires-python = ">=3.10" dependencies = [] +[tool.hatch.version] +source = "vcs" +raw-options = { root = "../..", tag_regex = "^reflex-components-lucide-v(?P.+)$" } + [build-system] -requires = ["uv_build>=0.10.11,<0.12.0"] -build-backend = "uv_build" +requires = ["hatchling", "hatch-vcs"] +build-backend = "hatchling.build" diff --git a/packages/reflex-lucide/src/reflex_lucide/__init__.py b/packages/reflex-components-lucide/src/reflex_components_lucide/__init__.py similarity index 100% rename from packages/reflex-lucide/src/reflex_lucide/__init__.py rename to packages/reflex-components-lucide/src/reflex_components_lucide/__init__.py diff --git a/packages/reflex-lucide/src/reflex_lucide/icon.py b/packages/reflex-components-lucide/src/reflex_components_lucide/icon.py similarity index 100% rename from packages/reflex-lucide/src/reflex_lucide/icon.py rename to packages/reflex-components-lucide/src/reflex_components_lucide/icon.py diff --git a/packages/reflex-components-markdown/README.md b/packages/reflex-components-markdown/README.md new file mode 100644 index 00000000000..51648cfb68d --- /dev/null +++ b/packages/reflex-components-markdown/README.md @@ -0,0 +1,3 @@ +# reflex-components-markdown + +Reflex markdown components. diff --git a/packages/reflex-markdown/pyproject.toml b/packages/reflex-components-markdown/pyproject.toml similarity index 52% rename from packages/reflex-markdown/pyproject.toml rename to packages/reflex-components-markdown/pyproject.toml index cb5114d8784..d16a70fde1b 100644 --- a/packages/reflex-markdown/pyproject.toml +++ b/packages/reflex-components-markdown/pyproject.toml @@ -1,6 +1,6 @@ [project] -name = "reflex-markdown" -version = "0.0.1" +name = "reflex-components-markdown" +dynamic = ["version"] description = "Reflex markdown components." readme = "README.md" authors = [{ name = "Khaleel Al-Adhami", email = "khaleel@reflex.dev" }] @@ -8,6 +8,10 @@ maintainers = [{ name = "Khaleel Al-Adhami", email = "khaleel@reflex.dev" }] requires-python = ">=3.10" dependencies = [] +[tool.hatch.version] +source = "vcs" +raw-options = { root = "../..", tag_regex = "^reflex-components-markdown-v(?P.+)$" } + [build-system] -requires = ["uv_build>=0.10.11,<0.12.0"] -build-backend = "uv_build" +requires = ["hatchling", "hatch-vcs"] +build-backend = "hatchling.build" diff --git a/packages/reflex-markdown/src/reflex_markdown/__init__.py b/packages/reflex-components-markdown/src/reflex_components_markdown/__init__.py similarity index 100% rename from packages/reflex-markdown/src/reflex_markdown/__init__.py rename to packages/reflex-components-markdown/src/reflex_components_markdown/__init__.py diff --git a/packages/reflex-markdown/src/reflex_markdown/markdown.py b/packages/reflex-components-markdown/src/reflex_components_markdown/markdown.py similarity index 95% rename from packages/reflex-markdown/src/reflex_markdown/markdown.py rename to packages/reflex-components-markdown/src/reflex_components_markdown/markdown.py index 1eec0d644b9..ae9e303f81f 100644 --- a/packages/reflex-markdown/src/reflex_markdown/markdown.py +++ b/packages/reflex-components-markdown/src/reflex_components_markdown/markdown.py @@ -10,7 +10,7 @@ from types import SimpleNamespace from typing import Any -from reflex_components.el.elements.typography import Div +from reflex_components_core.el.elements.typography import Div from reflex.components.component import ( BaseComponent, @@ -89,79 +89,79 @@ def create( def _h1(value: object): - from reflex_radix.themes.typography.heading import Heading + from reflex_components_radix.themes.typography.heading import Heading return Heading.create(value, as_="h1", size="6", margin_y="0.5em") def _h2(value: object): - from reflex_radix.themes.typography.heading import Heading + from reflex_components_radix.themes.typography.heading import Heading return Heading.create(value, as_="h2", size="5", margin_y="0.5em") def _h3(value: object): - from reflex_radix.themes.typography.heading import Heading + from reflex_components_radix.themes.typography.heading import Heading return Heading.create(value, as_="h3", size="4", margin_y="0.5em") def _h4(value: object): - from reflex_radix.themes.typography.heading import Heading + from reflex_components_radix.themes.typography.heading import Heading return Heading.create(value, as_="h4", size="3", margin_y="0.5em") def _h5(value: object): - from reflex_radix.themes.typography.heading import Heading + from reflex_components_radix.themes.typography.heading import Heading return Heading.create(value, as_="h5", size="2", margin_y="0.5em") def _h6(value: object): - from reflex_radix.themes.typography.heading import Heading + from reflex_components_radix.themes.typography.heading import Heading return Heading.create(value, as_="h6", size="1", margin_y="0.5em") def _p(value: object): - from reflex_radix.themes.typography.text import Text + from reflex_components_radix.themes.typography.text import Text return Text.create(value, margin_y="1em") def _ul(value: object): - from reflex_radix.themes.layout.list import UnorderedList + from reflex_components_radix.themes.layout.list import UnorderedList return UnorderedList.create(value, margin_y="1em") def _ol(value: object): - from reflex_radix.themes.layout.list import OrderedList + from reflex_components_radix.themes.layout.list import OrderedList return OrderedList.create(value, margin_y="1em") def _li(value: object): - from reflex_radix.themes.layout.list import ListItem + from reflex_components_radix.themes.layout.list import ListItem return ListItem.create(value, margin_y="0.5em") def _a(value: object): - from reflex_radix.themes.typography.link import Link + from reflex_components_radix.themes.typography.link import Link return Link.create(value) def _code(value: object): - from reflex_radix.themes.typography.code import Code + from reflex_components_radix.themes.typography.code import Code return Code.create(value) def _codeblock(value: object, **props): - from reflex_code.code import CodeBlock + from reflex_components_code.code import CodeBlock return CodeBlock.create(value, margin_y="1em", wrap_long_lines=True, **props) diff --git a/packages/reflex-components-moment/README.md b/packages/reflex-components-moment/README.md new file mode 100644 index 00000000000..ca7092de983 --- /dev/null +++ b/packages/reflex-components-moment/README.md @@ -0,0 +1,3 @@ +# reflex-components-moment + +Reflex moment components. diff --git a/packages/reflex-moment/pyproject.toml b/packages/reflex-components-moment/pyproject.toml similarity index 52% rename from packages/reflex-moment/pyproject.toml rename to packages/reflex-components-moment/pyproject.toml index 1b9c5e726c2..c0ea1e8047d 100644 --- a/packages/reflex-moment/pyproject.toml +++ b/packages/reflex-components-moment/pyproject.toml @@ -1,6 +1,6 @@ [project] -name = "reflex-moment" -version = "0.0.1" +name = "reflex-components-moment" +dynamic = ["version"] description = "Reflex moment components." readme = "README.md" authors = [{ name = "Khaleel Al-Adhami", email = "khaleel@reflex.dev" }] @@ -8,6 +8,10 @@ maintainers = [{ name = "Khaleel Al-Adhami", email = "khaleel@reflex.dev" }] requires-python = ">=3.10" dependencies = [] +[tool.hatch.version] +source = "vcs" +raw-options = { root = "../..", tag_regex = "^reflex-components-moment-v(?P.+)$" } + [build-system] -requires = ["uv_build>=0.10.11,<0.12.0"] -build-backend = "uv_build" +requires = ["hatchling", "hatch-vcs"] +build-backend = "hatchling.build" diff --git a/packages/reflex-moment/src/reflex_moment/__init__.py b/packages/reflex-components-moment/src/reflex_components_moment/__init__.py similarity index 100% rename from packages/reflex-moment/src/reflex_moment/__init__.py rename to packages/reflex-components-moment/src/reflex_components_moment/__init__.py diff --git a/packages/reflex-moment/src/reflex_moment/moment.py b/packages/reflex-components-moment/src/reflex_components_moment/moment.py similarity index 100% rename from packages/reflex-moment/src/reflex_moment/moment.py rename to packages/reflex-components-moment/src/reflex_components_moment/moment.py diff --git a/packages/reflex-components-plotly/README.md b/packages/reflex-components-plotly/README.md new file mode 100644 index 00000000000..ebd0b1284a8 --- /dev/null +++ b/packages/reflex-components-plotly/README.md @@ -0,0 +1,3 @@ +# reflex-components-plotly + +Reflex plotly components. diff --git a/packages/reflex-plotly/pyproject.toml b/packages/reflex-components-plotly/pyproject.toml similarity index 52% rename from packages/reflex-plotly/pyproject.toml rename to packages/reflex-components-plotly/pyproject.toml index e62191ca964..f6dcf0048a2 100644 --- a/packages/reflex-plotly/pyproject.toml +++ b/packages/reflex-components-plotly/pyproject.toml @@ -1,6 +1,6 @@ [project] -name = "reflex-plotly" -version = "0.0.1" +name = "reflex-components-plotly" +dynamic = ["version"] description = "Reflex plotly components." readme = "README.md" authors = [{ name = "Khaleel Al-Adhami", email = "khaleel@reflex.dev" }] @@ -8,6 +8,10 @@ maintainers = [{ name = "Khaleel Al-Adhami", email = "khaleel@reflex.dev" }] requires-python = ">=3.10" dependencies = [] +[tool.hatch.version] +source = "vcs" +raw-options = { root = "../..", tag_regex = "^reflex-components-plotly-v(?P.+)$" } + [build-system] -requires = ["uv_build>=0.10.11,<0.12.0"] -build-backend = "uv_build" +requires = ["hatchling", "hatch-vcs"] +build-backend = "hatchling.build" diff --git a/packages/reflex-plotly/src/reflex_plotly/__init__.py b/packages/reflex-components-plotly/src/reflex_components_plotly/__init__.py similarity index 100% rename from packages/reflex-plotly/src/reflex_plotly/__init__.py rename to packages/reflex-components-plotly/src/reflex_components_plotly/__init__.py diff --git a/packages/reflex-plotly/src/reflex_plotly/plotly.py b/packages/reflex-components-plotly/src/reflex_components_plotly/plotly.py similarity index 99% rename from packages/reflex-plotly/src/reflex_plotly/plotly.py rename to packages/reflex-components-plotly/src/reflex_components_plotly/plotly.py index 8be79d3a693..a4bb2dcba78 100644 --- a/packages/reflex-plotly/src/reflex_plotly/plotly.py +++ b/packages/reflex-components-plotly/src/reflex_components_plotly/plotly.py @@ -4,7 +4,7 @@ from typing import TYPE_CHECKING, Any, TypedDict, TypeVar -from reflex_components.core.cond import color_mode_cond +from reflex_components_core.core.cond import color_mode_cond from reflex.components.component import Component, NoSSRComponent, field from reflex.event import EventHandler, no_args_event_spec diff --git a/packages/reflex-components-radix/README.md b/packages/reflex-components-radix/README.md new file mode 100644 index 00000000000..f5a060a25e3 --- /dev/null +++ b/packages/reflex-components-radix/README.md @@ -0,0 +1,3 @@ +# reflex-components-radix + +Reflex radix components. diff --git a/packages/reflex-radix/pyproject.toml b/packages/reflex-components-radix/pyproject.toml similarity index 52% rename from packages/reflex-radix/pyproject.toml rename to packages/reflex-components-radix/pyproject.toml index 90b515ff687..d2b16039938 100644 --- a/packages/reflex-radix/pyproject.toml +++ b/packages/reflex-components-radix/pyproject.toml @@ -1,6 +1,6 @@ [project] -name = "reflex-radix" -version = "0.0.1" +name = "reflex-components-radix" +dynamic = ["version"] description = "Reflex radix components." readme = "README.md" authors = [{ name = "Khaleel Al-Adhami", email = "khaleel@reflex.dev" }] @@ -8,6 +8,10 @@ maintainers = [{ name = "Khaleel Al-Adhami", email = "khaleel@reflex.dev" }] requires-python = ">=3.10" dependencies = [] +[tool.hatch.version] +source = "vcs" +raw-options = { root = "../..", tag_regex = "^reflex-components-radix-v(?P.+)$" } + [build-system] -requires = ["uv_build>=0.10.11,<0.12.0"] -build-backend = "uv_build" +requires = ["hatchling", "hatch-vcs"] +build-backend = "hatchling.build" diff --git a/packages/reflex-radix/src/reflex_radix/__init__.py b/packages/reflex-components-radix/src/reflex_components_radix/__init__.py similarity index 100% rename from packages/reflex-radix/src/reflex_radix/__init__.py rename to packages/reflex-components-radix/src/reflex_components_radix/__init__.py diff --git a/packages/reflex-radix/src/reflex_radix/primitives/__init__.py b/packages/reflex-components-radix/src/reflex_components_radix/primitives/__init__.py similarity index 100% rename from packages/reflex-radix/src/reflex_radix/primitives/__init__.py rename to packages/reflex-components-radix/src/reflex_components_radix/primitives/__init__.py diff --git a/packages/reflex-radix/src/reflex_radix/primitives/accordion.py b/packages/reflex-components-radix/src/reflex_components_radix/primitives/accordion.py similarity index 98% rename from packages/reflex-radix/src/reflex_radix/primitives/accordion.py rename to packages/reflex-components-radix/src/reflex_components_radix/primitives/accordion.py index cbf3d167b77..71fdc8a3f19 100644 --- a/packages/reflex-radix/src/reflex_radix/primitives/accordion.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/primitives/accordion.py @@ -5,9 +5,9 @@ from collections.abc import Sequence from typing import Any, ClassVar, Literal -from reflex_components.core.colors import color -from reflex_components.core.cond import cond -from reflex_lucide.icon import Icon +from reflex_components_core.core.colors import color +from reflex_components_core.core.cond import cond +from reflex_components_lucide.icon import Icon from reflex.components.component import Component, ComponentNamespace, field from reflex.constants.compiler import MemoizationMode @@ -15,8 +15,8 @@ from reflex.style import Style from reflex.vars import get_uuid_string_var from reflex.vars.base import LiteralVar, Var -from reflex_radix.primitives.base import RadixPrimitiveComponent -from reflex_radix.themes.base import LiteralAccentColor, LiteralRadius +from reflex_components_radix.primitives.base import RadixPrimitiveComponent +from reflex_components_radix.themes.base import LiteralAccentColor, LiteralRadius LiteralAccordionType = Literal["single", "multiple"] LiteralAccordionDir = Literal["ltr", "rtl"] diff --git a/packages/reflex-radix/src/reflex_radix/primitives/base.py b/packages/reflex-components-radix/src/reflex_components_radix/primitives/base.py similarity index 96% rename from packages/reflex-radix/src/reflex_radix/primitives/base.py rename to packages/reflex-components-radix/src/reflex_components_radix/primitives/base.py index 48fc2bb394a..6fe5112d884 100644 --- a/packages/reflex-radix/src/reflex_radix/primitives/base.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/primitives/base.py @@ -49,7 +49,7 @@ def create(cls, *children: Any, **props: Any) -> Component: Returns: The new RadixPrimitiveTriggerComponent instance. """ - from reflex_components.el.elements.typography import Div + from reflex_components_core.el.elements.typography import Div for child in children: if "on_click" in getattr(child, "event_triggers", {}): diff --git a/packages/reflex-radix/src/reflex_radix/primitives/dialog.py b/packages/reflex-components-radix/src/reflex_components_radix/primitives/dialog.py similarity index 99% rename from packages/reflex-radix/src/reflex_radix/primitives/dialog.py rename to packages/reflex-components-radix/src/reflex_components_radix/primitives/dialog.py index 7bc3023fe5e..987ef6f3a61 100644 --- a/packages/reflex-radix/src/reflex_radix/primitives/dialog.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/primitives/dialog.py @@ -2,7 +2,7 @@ from typing import Any, ClassVar -from reflex_components.el import elements +from reflex_components_core.el import elements from reflex.components.component import ComponentNamespace, field from reflex.constants.compiler import MemoizationMode diff --git a/packages/reflex-radix/src/reflex_radix/primitives/drawer.py b/packages/reflex-components-radix/src/reflex_components_radix/primitives/drawer.py similarity index 97% rename from packages/reflex-radix/src/reflex_radix/primitives/drawer.py rename to packages/reflex-components-radix/src/reflex_components_radix/primitives/drawer.py index 050c0f84840..a2ceb2b79de 100644 --- a/packages/reflex-radix/src/reflex_radix/primitives/drawer.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/primitives/drawer.py @@ -11,9 +11,9 @@ from reflex.constants.compiler import MemoizationMode from reflex.event import EventHandler, no_args_event_spec, passthrough_event_spec from reflex.vars.base import Var -from reflex_radix.primitives.base import RadixPrimitiveComponent -from reflex_radix.themes.base import Theme -from reflex_radix.themes.layout.flex import Flex +from reflex_components_radix.primitives.base import RadixPrimitiveComponent +from reflex_components_radix.themes.base import Theme +from reflex_components_radix.themes.layout.flex import Flex class DrawerComponent(RadixPrimitiveComponent): diff --git a/packages/reflex-radix/src/reflex_radix/primitives/form.py b/packages/reflex-components-radix/src/reflex_components_radix/primitives/form.py similarity index 95% rename from packages/reflex-radix/src/reflex_radix/primitives/form.py rename to packages/reflex-components-radix/src/reflex_components_radix/primitives/form.py index d81ac902189..73d5f4d78ed 100644 --- a/packages/reflex-radix/src/reflex_radix/primitives/form.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/primitives/form.py @@ -4,13 +4,13 @@ from typing import Any, Literal -from reflex_components.core.debounce import DebounceInput -from reflex_components.el.elements.forms import Form as HTMLForm +from reflex_components_core.core.debounce import DebounceInput +from reflex_components_core.el.elements.forms import Form as HTMLForm from reflex.components.component import ComponentNamespace, field from reflex.event import EventHandler, no_args_event_spec from reflex.vars.base import Var -from reflex_radix.themes.components.text_field import TextFieldRoot +from reflex_components_radix.themes.components.text_field import TextFieldRoot from .base import RadixPrimitiveComponentWithClassName diff --git a/packages/reflex-radix/src/reflex_radix/primitives/progress.py b/packages/reflex-components-radix/src/reflex_components_radix/primitives/progress.py similarity index 92% rename from packages/reflex-radix/src/reflex_radix/primitives/progress.py rename to packages/reflex-components-radix/src/reflex_components_radix/primitives/progress.py index 1f158ec89cc..8dea7cdf9f7 100644 --- a/packages/reflex-radix/src/reflex_radix/primitives/progress.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/primitives/progress.py @@ -4,13 +4,13 @@ from typing import Any -from reflex_components.core.colors import color +from reflex_components_core.core.colors import color from reflex.components.component import Component, ComponentNamespace, field from reflex.vars.base import Var -from reflex_radix.primitives.accordion import DEFAULT_ANIMATION_DURATION -from reflex_radix.primitives.base import RadixPrimitiveComponentWithClassName -from reflex_radix.themes.base import LiteralAccentColor, LiteralRadius +from reflex_components_radix.primitives.accordion import DEFAULT_ANIMATION_DURATION +from reflex_components_radix.primitives.base import RadixPrimitiveComponentWithClassName +from reflex_components_radix.themes.base import LiteralAccentColor, LiteralRadius class ProgressComponent(RadixPrimitiveComponentWithClassName): diff --git a/packages/reflex-radix/src/reflex_radix/primitives/slider.py b/packages/reflex-components-radix/src/reflex_components_radix/primitives/slider.py similarity index 98% rename from packages/reflex-radix/src/reflex_radix/primitives/slider.py rename to packages/reflex-components-radix/src/reflex_components_radix/primitives/slider.py index a14585de97c..3d132cd05b0 100644 --- a/packages/reflex-radix/src/reflex_radix/primitives/slider.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/primitives/slider.py @@ -8,7 +8,7 @@ from reflex.components.component import Component, ComponentNamespace, field from reflex.event import EventHandler, passthrough_event_spec from reflex.vars.base import Var -from reflex_radix.primitives.base import RadixPrimitiveComponentWithClassName +from reflex_components_radix.primitives.base import RadixPrimitiveComponentWithClassName LiteralSliderOrientation = Literal["horizontal", "vertical"] LiteralSliderDir = Literal["ltr", "rtl"] diff --git a/packages/reflex-radix/src/reflex_radix/themes/__init__.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/__init__.py similarity index 100% rename from packages/reflex-radix/src/reflex_radix/themes/__init__.py rename to packages/reflex-components-radix/src/reflex_components_radix/themes/__init__.py diff --git a/packages/reflex-radix/src/reflex_radix/themes/base.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/base.py similarity index 99% rename from packages/reflex-radix/src/reflex_radix/themes/base.py rename to packages/reflex-components-radix/src/reflex_components_radix/themes/base.py index cd814ecc394..aa0a39c9d1c 100644 --- a/packages/reflex-radix/src/reflex_radix/themes/base.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/base.py @@ -4,7 +4,7 @@ from typing import Any, ClassVar, Literal -from reflex_components.core.breakpoints import Responsive +from reflex_components_core.core.breakpoints import Responsive from reflex.components import Component from reflex.components.component import field diff --git a/packages/reflex-radix/src/reflex_radix/themes/color_mode.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/color_mode.py similarity index 95% rename from packages/reflex-radix/src/reflex_radix/themes/color_mode.py rename to packages/reflex-components-radix/src/reflex_components_radix/themes/color_mode.py index 4eabf63f066..4b5bccef52c 100644 --- a/packages/reflex-radix/src/reflex_radix/themes/color_mode.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/color_mode.py @@ -19,8 +19,8 @@ from typing import Any, Literal, get_args -from reflex_components.core.cond import Cond, color_mode_cond, cond -from reflex_lucide.icon import Icon +from reflex_components_core.core.cond import Cond, color_mode_cond, cond +from reflex_components_lucide.icon import Icon from reflex.components.component import BaseComponent, field from reflex.style import ( @@ -32,8 +32,8 @@ ) from reflex.vars.base import Var from reflex.vars.sequence import LiteralArrayVar -from reflex_radix.themes.components.dropdown_menu import dropdown_menu -from reflex_radix.themes.components.switch import Switch +from reflex_components_radix.themes.components.dropdown_menu import dropdown_menu +from reflex_components_radix.themes.components.switch import Switch from .components.icon_button import IconButton diff --git a/packages/reflex-radix/src/reflex_radix/themes/components/__init__.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/__init__.py similarity index 100% rename from packages/reflex-radix/src/reflex_radix/themes/components/__init__.py rename to packages/reflex-components-radix/src/reflex_components_radix/themes/components/__init__.py diff --git a/packages/reflex-radix/src/reflex_radix/themes/components/alert_dialog.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/alert_dialog.py similarity index 93% rename from packages/reflex-radix/src/reflex_radix/themes/components/alert_dialog.py rename to packages/reflex-components-radix/src/reflex_components_radix/themes/components/alert_dialog.py index 7db9ac831d2..c952288ff81 100644 --- a/packages/reflex-radix/src/reflex_radix/themes/components/alert_dialog.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/alert_dialog.py @@ -2,14 +2,14 @@ from typing import Literal -from reflex_components.core.breakpoints import Responsive -from reflex_components.el import elements +from reflex_components_core.core.breakpoints import Responsive +from reflex_components_core.el import elements from reflex.components.component import ComponentNamespace, field from reflex.constants.compiler import MemoizationMode from reflex.event import EventHandler, no_args_event_spec, passthrough_event_spec from reflex.vars.base import Var -from reflex_radix.themes.base import RadixThemesComponent, RadixThemesTriggerComponent +from reflex_components_radix.themes.base import RadixThemesComponent, RadixThemesTriggerComponent LiteralContentSize = Literal["1", "2", "3", "4"] diff --git a/packages/reflex-radix/src/reflex_radix/themes/components/aspect_ratio.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/aspect_ratio.py similarity index 85% rename from packages/reflex-radix/src/reflex_radix/themes/components/aspect_ratio.py rename to packages/reflex-components-radix/src/reflex_components_radix/themes/components/aspect_ratio.py index 1c1f56b3565..805fb0ed194 100644 --- a/packages/reflex-radix/src/reflex_radix/themes/components/aspect_ratio.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/aspect_ratio.py @@ -2,7 +2,7 @@ from reflex.components.component import field from reflex.vars.base import Var -from reflex_radix.themes.base import RadixThemesComponent +from reflex_components_radix.themes.base import RadixThemesComponent class AspectRatio(RadixThemesComponent): diff --git a/packages/reflex-radix/src/reflex_radix/themes/components/avatar.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/avatar.py similarity index 90% rename from packages/reflex-radix/src/reflex_radix/themes/components/avatar.py rename to packages/reflex-components-radix/src/reflex_components_radix/themes/components/avatar.py index e7403759d97..13fc8f27242 100644 --- a/packages/reflex-radix/src/reflex_radix/themes/components/avatar.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/avatar.py @@ -2,11 +2,11 @@ from typing import Literal -from reflex_components.core.breakpoints import Responsive +from reflex_components_core.core.breakpoints import Responsive from reflex.components.component import field from reflex.vars.base import Var -from reflex_radix.themes.base import ( +from reflex_components_radix.themes.base import ( LiteralAccentColor, LiteralRadius, RadixThemesComponent, diff --git a/packages/reflex-radix/src/reflex_radix/themes/components/badge.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/badge.py similarity index 85% rename from packages/reflex-radix/src/reflex_radix/themes/components/badge.py rename to packages/reflex-components-radix/src/reflex_components_radix/themes/components/badge.py index b415b0e1ce3..66dbd6bb9ad 100644 --- a/packages/reflex-radix/src/reflex_radix/themes/components/badge.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/badge.py @@ -2,12 +2,12 @@ from typing import Literal -from reflex_components.core.breakpoints import Responsive -from reflex_components.el import elements +from reflex_components_core.core.breakpoints import Responsive +from reflex_components_core.el import elements from reflex.components.component import field from reflex.vars.base import Var -from reflex_radix.themes.base import ( +from reflex_components_radix.themes.base import ( LiteralAccentColor, LiteralRadius, RadixThemesComponent, diff --git a/packages/reflex-radix/src/reflex_radix/themes/components/button.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/button.py similarity index 88% rename from packages/reflex-radix/src/reflex_radix/themes/components/button.py rename to packages/reflex-components-radix/src/reflex_components_radix/themes/components/button.py index 03acf2230f1..94032f3b480 100644 --- a/packages/reflex-radix/src/reflex_radix/themes/components/button.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/button.py @@ -2,12 +2,12 @@ from typing import Literal -from reflex_components.core.breakpoints import Responsive -from reflex_components.el import elements +from reflex_components_core.core.breakpoints import Responsive +from reflex_components_core.el import elements from reflex.components.component import field from reflex.vars.base import Var -from reflex_radix.themes.base import ( +from reflex_components_radix.themes.base import ( LiteralAccentColor, LiteralRadius, LiteralVariant, diff --git a/packages/reflex-radix/src/reflex_radix/themes/components/callout.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/callout.py similarity index 90% rename from packages/reflex-radix/src/reflex_radix/themes/components/callout.py rename to packages/reflex-components-radix/src/reflex_components_radix/themes/components/callout.py index 632decafed1..4b01e051400 100644 --- a/packages/reflex-radix/src/reflex_radix/themes/components/callout.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/callout.py @@ -2,14 +2,14 @@ from typing import Literal -from reflex_components.core.breakpoints import Responsive -from reflex_components.el import elements -from reflex_lucide.icon import Icon +from reflex_components_core.core.breakpoints import Responsive +from reflex_components_core.el import elements +from reflex_components_lucide.icon import Icon import reflex as rx from reflex.components.component import Component, ComponentNamespace, field from reflex.vars.base import Var -from reflex_radix.themes.base import LiteralAccentColor, RadixThemesComponent +from reflex_components_radix.themes.base import LiteralAccentColor, RadixThemesComponent CalloutVariant = Literal["soft", "surface", "outline"] diff --git a/packages/reflex-radix/src/reflex_radix/themes/components/card.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/card.py similarity index 80% rename from packages/reflex-radix/src/reflex_radix/themes/components/card.py rename to packages/reflex-components-radix/src/reflex_components_radix/themes/components/card.py index acafa575692..fa0655cc583 100644 --- a/packages/reflex-radix/src/reflex_radix/themes/components/card.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/card.py @@ -2,12 +2,12 @@ from typing import Literal -from reflex_components.core.breakpoints import Responsive -from reflex_components.el import elements +from reflex_components_core.core.breakpoints import Responsive +from reflex_components_core.el import elements from reflex.components.component import field from reflex.vars.base import Var -from reflex_radix.themes.base import RadixThemesComponent +from reflex_components_radix.themes.base import RadixThemesComponent class Card(elements.Div, RadixThemesComponent): diff --git a/packages/reflex-radix/src/reflex_radix/themes/components/checkbox.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/checkbox.py similarity index 95% rename from packages/reflex-radix/src/reflex_radix/themes/components/checkbox.py rename to packages/reflex-components-radix/src/reflex_components_radix/themes/components/checkbox.py index 8e7103152be..6da370ac347 100644 --- a/packages/reflex-radix/src/reflex_radix/themes/components/checkbox.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/checkbox.py @@ -2,18 +2,18 @@ from typing import Literal -from reflex_components.core.breakpoints import Responsive +from reflex_components_core.core.breakpoints import Responsive from reflex.components.component import Component, ComponentNamespace, field from reflex.event import EventHandler, passthrough_event_spec from reflex.vars.base import Var -from reflex_radix.themes.base import ( +from reflex_components_radix.themes.base import ( LiteralAccentColor, LiteralSpacing, RadixThemesComponent, ) -from reflex_radix.themes.layout.flex import Flex -from reflex_radix.themes.typography.text import Text +from reflex_components_radix.themes.layout.flex import Flex +from reflex_components_radix.themes.typography.text import Text LiteralCheckboxSize = Literal["1", "2", "3"] LiteralCheckboxVariant = Literal["classic", "surface", "soft"] diff --git a/packages/reflex-radix/src/reflex_radix/themes/components/checkbox_cards.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/checkbox_cards.py similarity index 90% rename from packages/reflex-radix/src/reflex_radix/themes/components/checkbox_cards.py rename to packages/reflex-components-radix/src/reflex_components_radix/themes/components/checkbox_cards.py index b5a5746eeb5..a7eccd91248 100644 --- a/packages/reflex-radix/src/reflex_radix/themes/components/checkbox_cards.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/checkbox_cards.py @@ -3,11 +3,11 @@ from types import SimpleNamespace from typing import Literal -from reflex_components.core.breakpoints import Responsive +from reflex_components_core.core.breakpoints import Responsive from reflex.components.component import field from reflex.vars.base import Var -from reflex_radix.themes.base import LiteralAccentColor, RadixThemesComponent +from reflex_components_radix.themes.base import LiteralAccentColor, RadixThemesComponent class CheckboxCardsRoot(RadixThemesComponent): diff --git a/packages/reflex-radix/src/reflex_radix/themes/components/checkbox_group.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/checkbox_group.py similarity index 91% rename from packages/reflex-radix/src/reflex_radix/themes/components/checkbox_group.py rename to packages/reflex-components-radix/src/reflex_components_radix/themes/components/checkbox_group.py index b8409c82281..388d6b48afd 100644 --- a/packages/reflex-radix/src/reflex_radix/themes/components/checkbox_group.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/checkbox_group.py @@ -4,11 +4,11 @@ from types import SimpleNamespace from typing import Literal -from reflex_components.core.breakpoints import Responsive +from reflex_components_core.core.breakpoints import Responsive from reflex.components.component import field from reflex.vars.base import Var -from reflex_radix.themes.base import LiteralAccentColor, RadixThemesComponent +from reflex_components_radix.themes.base import LiteralAccentColor, RadixThemesComponent class CheckboxGroupRoot(RadixThemesComponent): diff --git a/packages/reflex-radix/src/reflex_radix/themes/components/context_menu.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/context_menu.py similarity index 98% rename from packages/reflex-radix/src/reflex_radix/themes/components/context_menu.py rename to packages/reflex-components-radix/src/reflex_components_radix/themes/components/context_menu.py index 013fe700de8..b7ad94ff799 100644 --- a/packages/reflex-radix/src/reflex_radix/themes/components/context_menu.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/context_menu.py @@ -2,13 +2,13 @@ from typing import ClassVar, Literal -from reflex_components.core.breakpoints import Responsive +from reflex_components_core.core.breakpoints import Responsive from reflex.components.component import ComponentNamespace, field from reflex.constants.compiler import MemoizationMode from reflex.event import EventHandler, no_args_event_spec, passthrough_event_spec from reflex.vars.base import Var -from reflex_radix.themes.base import LiteralAccentColor, RadixThemesComponent +from reflex_components_radix.themes.base import LiteralAccentColor, RadixThemesComponent from .checkbox import Checkbox from .radio_group import HighLevelRadioGroup diff --git a/packages/reflex-radix/src/reflex_radix/themes/components/data_list.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/data_list.py similarity index 92% rename from packages/reflex-radix/src/reflex_radix/themes/components/data_list.py rename to packages/reflex-components-radix/src/reflex_components_radix/themes/components/data_list.py index f9a6655f843..a052bbcaaf1 100644 --- a/packages/reflex-radix/src/reflex_radix/themes/components/data_list.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/data_list.py @@ -3,11 +3,11 @@ from types import SimpleNamespace from typing import Literal -from reflex_components.core.breakpoints import Responsive +from reflex_components_core.core.breakpoints import Responsive from reflex.components.component import field from reflex.vars.base import Var -from reflex_radix.themes.base import LiteralAccentColor, RadixThemesComponent +from reflex_components_radix.themes.base import LiteralAccentColor, RadixThemesComponent class DataListRoot(RadixThemesComponent): diff --git a/packages/reflex-radix/src/reflex_radix/themes/components/dialog.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/dialog.py similarity index 92% rename from packages/reflex-radix/src/reflex_radix/themes/components/dialog.py rename to packages/reflex-components-radix/src/reflex_components_radix/themes/components/dialog.py index b5439c652b9..b0bc43827a3 100644 --- a/packages/reflex-radix/src/reflex_radix/themes/components/dialog.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/dialog.py @@ -2,14 +2,14 @@ from typing import Literal -from reflex_components.core.breakpoints import Responsive -from reflex_components.el import elements +from reflex_components_core.core.breakpoints import Responsive +from reflex_components_core.el import elements from reflex.components.component import ComponentNamespace, field from reflex.constants.compiler import MemoizationMode from reflex.event import EventHandler, no_args_event_spec, passthrough_event_spec from reflex.vars.base import Var -from reflex_radix.themes.base import RadixThemesComponent, RadixThemesTriggerComponent +from reflex_components_radix.themes.base import RadixThemesComponent, RadixThemesTriggerComponent class DialogRoot(RadixThemesComponent): diff --git a/packages/reflex-radix/src/reflex_radix/themes/components/dropdown_menu.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/dropdown_menu.py similarity index 99% rename from packages/reflex-radix/src/reflex_radix/themes/components/dropdown_menu.py rename to packages/reflex-components-radix/src/reflex_components_radix/themes/components/dropdown_menu.py index ff4c773f526..b3817993dfa 100644 --- a/packages/reflex-radix/src/reflex_radix/themes/components/dropdown_menu.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/dropdown_menu.py @@ -2,13 +2,13 @@ from typing import ClassVar, Literal -from reflex_components.core.breakpoints import Responsive +from reflex_components_core.core.breakpoints import Responsive from reflex.components.component import ComponentNamespace, field from reflex.constants.compiler import MemoizationMode from reflex.event import EventHandler, no_args_event_spec, passthrough_event_spec from reflex.vars.base import Var -from reflex_radix.themes.base import ( +from reflex_components_radix.themes.base import ( LiteralAccentColor, RadixThemesComponent, RadixThemesTriggerComponent, diff --git a/packages/reflex-radix/src/reflex_radix/themes/components/hover_card.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/hover_card.py similarity index 94% rename from packages/reflex-radix/src/reflex_radix/themes/components/hover_card.py rename to packages/reflex-components-radix/src/reflex_components_radix/themes/components/hover_card.py index 8e3971a5e29..0734d8dd891 100644 --- a/packages/reflex-radix/src/reflex_radix/themes/components/hover_card.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/hover_card.py @@ -2,14 +2,14 @@ from typing import Literal -from reflex_components.core.breakpoints import Responsive -from reflex_components.el import elements +from reflex_components_core.core.breakpoints import Responsive +from reflex_components_core.el import elements from reflex.components.component import ComponentNamespace, field from reflex.constants.compiler import MemoizationMode from reflex.event import EventHandler, passthrough_event_spec from reflex.vars.base import Var -from reflex_radix.themes.base import RadixThemesComponent, RadixThemesTriggerComponent +from reflex_components_radix.themes.base import RadixThemesComponent, RadixThemesTriggerComponent class HoverCardRoot(RadixThemesComponent): diff --git a/packages/reflex-radix/src/reflex_radix/themes/components/icon_button.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/icon_button.py similarity index 92% rename from packages/reflex-radix/src/reflex_radix/themes/components/icon_button.py rename to packages/reflex-components-radix/src/reflex_components_radix/themes/components/icon_button.py index 94df0b62ab7..82f313c8012 100644 --- a/packages/reflex-radix/src/reflex_radix/themes/components/icon_button.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/icon_button.py @@ -4,15 +4,15 @@ from typing import Literal -from reflex_components.core.breakpoints import Responsive -from reflex_components.core.match import Match -from reflex_components.el import elements -from reflex_lucide import Icon +from reflex_components_core.core.breakpoints import Responsive +from reflex_components_core.core.match import Match +from reflex_components_core.el import elements +from reflex_components_lucide import Icon from reflex.components.component import Component, field from reflex.style import Style from reflex.vars.base import Var -from reflex_radix.themes.base import ( +from reflex_components_radix.themes.base import ( LiteralAccentColor, LiteralRadius, LiteralVariant, diff --git a/packages/reflex-radix/src/reflex_radix/themes/components/inset.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/inset.py similarity index 86% rename from packages/reflex-radix/src/reflex_radix/themes/components/inset.py rename to packages/reflex-components-radix/src/reflex_components_radix/themes/components/inset.py index a1c5657c3d9..90abd565187 100644 --- a/packages/reflex-radix/src/reflex_radix/themes/components/inset.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/inset.py @@ -2,12 +2,12 @@ from typing import Literal -from reflex_components.core.breakpoints import Responsive -from reflex_components.el import elements +from reflex_components_core.core.breakpoints import Responsive +from reflex_components_core.el import elements from reflex.components.component import field from reflex.vars.base import Var -from reflex_radix.themes.base import RadixThemesComponent +from reflex_components_radix.themes.base import RadixThemesComponent LiteralButtonSize = Literal["1", "2", "3", "4"] diff --git a/packages/reflex-radix/src/reflex_radix/themes/components/popover.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/popover.py similarity index 95% rename from packages/reflex-radix/src/reflex_radix/themes/components/popover.py rename to packages/reflex-components-radix/src/reflex_components_radix/themes/components/popover.py index b7898496dc6..c7e337ff359 100644 --- a/packages/reflex-radix/src/reflex_radix/themes/components/popover.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/popover.py @@ -2,14 +2,14 @@ from typing import Literal -from reflex_components.core.breakpoints import Responsive -from reflex_components.el import elements +from reflex_components_core.core.breakpoints import Responsive +from reflex_components_core.el import elements from reflex.components.component import ComponentNamespace, field from reflex.constants.compiler import MemoizationMode from reflex.event import EventHandler, no_args_event_spec, passthrough_event_spec from reflex.vars.base import Var -from reflex_radix.themes.base import RadixThemesComponent, RadixThemesTriggerComponent +from reflex_components_radix.themes.base import RadixThemesComponent, RadixThemesTriggerComponent class PopoverRoot(RadixThemesComponent): diff --git a/packages/reflex-radix/src/reflex_radix/themes/components/progress.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/progress.py similarity index 94% rename from packages/reflex-radix/src/reflex_radix/themes/components/progress.py rename to packages/reflex-components-radix/src/reflex_components_radix/themes/components/progress.py index 06e1067feef..4993a85bea1 100644 --- a/packages/reflex-radix/src/reflex_radix/themes/components/progress.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/progress.py @@ -2,12 +2,12 @@ from typing import Literal -from reflex_components.core.breakpoints import Responsive +from reflex_components_core.core.breakpoints import Responsive from reflex.components.component import Component, field from reflex.style import Style from reflex.vars.base import Var -from reflex_radix.themes.base import LiteralAccentColor, RadixThemesComponent +from reflex_components_radix.themes.base import LiteralAccentColor, RadixThemesComponent class Progress(RadixThemesComponent): diff --git a/packages/reflex-radix/src/reflex_radix/themes/components/radio.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/radio.py similarity index 84% rename from packages/reflex-radix/src/reflex_radix/themes/components/radio.py rename to packages/reflex-components-radix/src/reflex_components_radix/themes/components/radio.py index a882a514ec9..5e92cc6041f 100644 --- a/packages/reflex-radix/src/reflex_radix/themes/components/radio.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/radio.py @@ -2,11 +2,11 @@ from typing import Literal -from reflex_components.core.breakpoints import Responsive +from reflex_components_core.core.breakpoints import Responsive from reflex.components.component import field from reflex.vars.base import Var -from reflex_radix.themes.base import LiteralAccentColor, RadixThemesComponent +from reflex_components_radix.themes.base import LiteralAccentColor, RadixThemesComponent class Radio(RadixThemesComponent): diff --git a/packages/reflex-radix/src/reflex_radix/themes/components/radio_cards.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/radio_cards.py similarity index 95% rename from packages/reflex-radix/src/reflex_radix/themes/components/radio_cards.py rename to packages/reflex-components-radix/src/reflex_components_radix/themes/components/radio_cards.py index f6fd2d31f8b..e1e9078d42b 100644 --- a/packages/reflex-radix/src/reflex_radix/themes/components/radio_cards.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/radio_cards.py @@ -3,12 +3,12 @@ from types import SimpleNamespace from typing import ClassVar, Literal -from reflex_components.core.breakpoints import Responsive +from reflex_components_core.core.breakpoints import Responsive from reflex.components.component import field from reflex.event import EventHandler, passthrough_event_spec from reflex.vars.base import Var -from reflex_radix.themes.base import LiteralAccentColor, RadixThemesComponent +from reflex_components_radix.themes.base import LiteralAccentColor, RadixThemesComponent class RadioCardsRoot(RadixThemesComponent): diff --git a/packages/reflex-radix/src/reflex_radix/themes/components/radio_group.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/radio_group.py similarity index 96% rename from packages/reflex-radix/src/reflex_radix/themes/components/radio_group.py rename to packages/reflex-components-radix/src/reflex_components_radix/themes/components/radio_group.py index 7408268c6a7..4137e813878 100644 --- a/packages/reflex-radix/src/reflex_radix/themes/components/radio_group.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/radio_group.py @@ -5,7 +5,7 @@ from collections.abc import Sequence from typing import Literal -from reflex_components.core.breakpoints import Responsive +from reflex_components_core.core.breakpoints import Responsive import reflex as rx from reflex.components.component import Component, ComponentNamespace, field @@ -13,13 +13,13 @@ from reflex.utils import types from reflex.vars.base import LiteralVar, Var from reflex.vars.sequence import StringVar -from reflex_radix.themes.base import ( +from reflex_components_radix.themes.base import ( LiteralAccentColor, LiteralSpacing, RadixThemesComponent, ) -from reflex_radix.themes.layout.flex import Flex -from reflex_radix.themes.typography.text import Text +from reflex_components_radix.themes.layout.flex import Flex +from reflex_components_radix.themes.typography.text import Text LiteralFlexDirection = Literal["row", "column", "row-reverse", "column-reverse"] diff --git a/packages/reflex-radix/src/reflex_radix/themes/components/scroll_area.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/scroll_area.py similarity index 93% rename from packages/reflex-radix/src/reflex_radix/themes/components/scroll_area.py rename to packages/reflex-components-radix/src/reflex_components_radix/themes/components/scroll_area.py index 300093e1bc1..afde09818bb 100644 --- a/packages/reflex-radix/src/reflex_radix/themes/components/scroll_area.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/scroll_area.py @@ -4,7 +4,7 @@ from reflex.components.component import field from reflex.vars.base import Var -from reflex_radix.themes.base import RadixThemesComponent +from reflex_components_radix.themes.base import RadixThemesComponent class ScrollArea(RadixThemesComponent): diff --git a/packages/reflex-radix/src/reflex_radix/themes/components/segmented_control.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/segmented_control.py similarity index 93% rename from packages/reflex-radix/src/reflex_radix/themes/components/segmented_control.py rename to packages/reflex-components-radix/src/reflex_components_radix/themes/components/segmented_control.py index 189b64719e5..0d32b1484dd 100644 --- a/packages/reflex-radix/src/reflex_radix/themes/components/segmented_control.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/segmented_control.py @@ -6,12 +6,12 @@ from types import SimpleNamespace from typing import ClassVar, Literal -from reflex_components.core.breakpoints import Responsive +from reflex_components_core.core.breakpoints import Responsive from reflex.components.component import field from reflex.event import EventHandler from reflex.vars.base import Var -from reflex_radix.themes.base import LiteralAccentColor, RadixThemesComponent +from reflex_components_radix.themes.base import LiteralAccentColor, RadixThemesComponent def on_value_change( diff --git a/packages/reflex-radix/src/reflex_radix/themes/components/select.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/select.py similarity index 98% rename from packages/reflex-radix/src/reflex_radix/themes/components/select.py rename to packages/reflex-components-radix/src/reflex_components_radix/themes/components/select.py index d2fed4b7833..69a1188520a 100644 --- a/packages/reflex-radix/src/reflex_radix/themes/components/select.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/select.py @@ -3,14 +3,14 @@ from collections.abc import Sequence from typing import ClassVar, Literal -from reflex_components.core.breakpoints import Responsive +from reflex_components_core.core.breakpoints import Responsive import reflex as rx from reflex.components.component import Component, ComponentNamespace, field from reflex.constants.compiler import MemoizationMode from reflex.event import no_args_event_spec, passthrough_event_spec from reflex.vars.base import Var -from reflex_radix.themes.base import ( +from reflex_components_radix.themes.base import ( LiteralAccentColor, LiteralRadius, RadixThemesComponent, diff --git a/packages/reflex-radix/src/reflex_radix/themes/components/separator.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/separator.py similarity index 86% rename from packages/reflex-radix/src/reflex_radix/themes/components/separator.py rename to packages/reflex-components-radix/src/reflex_components_radix/themes/components/separator.py index 76d784e91f5..3d9db2a21a1 100644 --- a/packages/reflex-radix/src/reflex_radix/themes/components/separator.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/separator.py @@ -2,11 +2,11 @@ from typing import Literal -from reflex_components.core.breakpoints import Responsive +from reflex_components_core.core.breakpoints import Responsive from reflex.components.component import field from reflex.vars.base import LiteralVar, Var -from reflex_radix.themes.base import LiteralAccentColor, RadixThemesComponent +from reflex_components_radix.themes.base import LiteralAccentColor, RadixThemesComponent LiteralSeparatorSize = Literal["1", "2", "3", "4"] diff --git a/packages/reflex-radix/src/reflex_radix/themes/components/skeleton.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/skeleton.py similarity index 85% rename from packages/reflex-radix/src/reflex_radix/themes/components/skeleton.py rename to packages/reflex-components-radix/src/reflex_components_radix/themes/components/skeleton.py index 6d6f7340bad..fea674943dc 100644 --- a/packages/reflex-radix/src/reflex_radix/themes/components/skeleton.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/skeleton.py @@ -1,11 +1,11 @@ """Skeleton theme from Radix components.""" -from reflex_components.core.breakpoints import Responsive +from reflex_components_core.core.breakpoints import Responsive from reflex.components.component import field from reflex.constants.compiler import MemoizationMode from reflex.vars.base import Var -from reflex_radix.themes.base import RadixLoadingProp, RadixThemesComponent +from reflex_components_radix.themes.base import RadixLoadingProp, RadixThemesComponent class Skeleton(RadixLoadingProp, RadixThemesComponent): diff --git a/packages/reflex-radix/src/reflex_radix/themes/components/slider.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/slider.py similarity index 95% rename from packages/reflex-radix/src/reflex_radix/themes/components/slider.py rename to packages/reflex-components-radix/src/reflex_components_radix/themes/components/slider.py index 7a8c9a42fa8..ff0e6499486 100644 --- a/packages/reflex-radix/src/reflex_radix/themes/components/slider.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/slider.py @@ -5,13 +5,13 @@ from collections.abc import Sequence from typing import Literal -from reflex_components.core.breakpoints import Responsive +from reflex_components_core.core.breakpoints import Responsive from reflex.components.component import Component, field from reflex.event import EventHandler, passthrough_event_spec from reflex.utils.types import typehint_issubclass from reflex.vars.base import Var -from reflex_radix.themes.base import LiteralAccentColor, RadixThemesComponent +from reflex_components_radix.themes.base import LiteralAccentColor, RadixThemesComponent on_value_event_spec = ( passthrough_event_spec(list[float]), diff --git a/packages/reflex-radix/src/reflex_radix/themes/components/spinner.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/spinner.py similarity index 74% rename from packages/reflex-radix/src/reflex_radix/themes/components/spinner.py rename to packages/reflex-components-radix/src/reflex_components_radix/themes/components/spinner.py index 24777c811cf..d9eccfb0fee 100644 --- a/packages/reflex-radix/src/reflex_radix/themes/components/spinner.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/spinner.py @@ -2,11 +2,11 @@ from typing import Literal -from reflex_components.core.breakpoints import Responsive +from reflex_components_core.core.breakpoints import Responsive from reflex.components.component import field from reflex.vars.base import Var -from reflex_radix.themes.base import RadixLoadingProp, RadixThemesComponent +from reflex_components_radix.themes.base import RadixLoadingProp, RadixThemesComponent LiteralSpinnerSize = Literal["1", "2", "3"] diff --git a/packages/reflex-radix/src/reflex_radix/themes/components/switch.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/switch.py similarity index 92% rename from packages/reflex-radix/src/reflex_radix/themes/components/switch.py rename to packages/reflex-components-radix/src/reflex_components_radix/themes/components/switch.py index 0d43ffbdc2d..eee57dac486 100644 --- a/packages/reflex-radix/src/reflex_radix/themes/components/switch.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/switch.py @@ -2,12 +2,12 @@ from typing import Literal -from reflex_components.core.breakpoints import Responsive +from reflex_components_core.core.breakpoints import Responsive from reflex.components.component import field from reflex.event import EventHandler, passthrough_event_spec from reflex.vars.base import Var -from reflex_radix.themes.base import LiteralAccentColor, RadixThemesComponent +from reflex_components_radix.themes.base import LiteralAccentColor, RadixThemesComponent LiteralSwitchSize = Literal["1", "2", "3"] diff --git a/packages/reflex-radix/src/reflex_radix/themes/components/table.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/table.py similarity index 95% rename from packages/reflex-radix/src/reflex_radix/themes/components/table.py rename to packages/reflex-components-radix/src/reflex_components_radix/themes/components/table.py index cdbe2902137..f352e4a8820 100644 --- a/packages/reflex-radix/src/reflex_radix/themes/components/table.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/table.py @@ -2,12 +2,12 @@ from typing import ClassVar, Literal -from reflex_components.core.breakpoints import Responsive -from reflex_components.el import elements +from reflex_components_core.core.breakpoints import Responsive +from reflex_components_core.el import elements from reflex.components.component import ComponentNamespace, field from reflex.vars.base import Var -from reflex_radix.themes.base import CommonPaddingProps, RadixThemesComponent +from reflex_components_radix.themes.base import CommonPaddingProps, RadixThemesComponent class TableRoot(elements.Table, RadixThemesComponent): diff --git a/packages/reflex-radix/src/reflex_radix/themes/components/tabs.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/tabs.py similarity index 95% rename from packages/reflex-radix/src/reflex_radix/themes/components/tabs.py rename to packages/reflex-components-radix/src/reflex_components_radix/themes/components/tabs.py index 26cd487025a..e6d2eb51feb 100644 --- a/packages/reflex-radix/src/reflex_radix/themes/components/tabs.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/tabs.py @@ -4,14 +4,14 @@ from typing import Any, ClassVar, Literal -from reflex_components.core.breakpoints import Responsive -from reflex_components.core.colors import color +from reflex_components_core.core.breakpoints import Responsive +from reflex_components_core.core.colors import color from reflex.components.component import Component, ComponentNamespace, field from reflex.constants.compiler import MemoizationMode from reflex.event import EventHandler, passthrough_event_spec from reflex.vars.base import Var -from reflex_radix.themes.base import LiteralAccentColor, RadixThemesComponent +from reflex_components_radix.themes.base import LiteralAccentColor, RadixThemesComponent vertical_orientation_css = "&[data-orientation='vertical']" diff --git a/packages/reflex-radix/src/reflex_radix/themes/components/text_area.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/text_area.py similarity index 94% rename from packages/reflex-radix/src/reflex_radix/themes/components/text_area.py rename to packages/reflex-components-radix/src/reflex_components_radix/themes/components/text_area.py index 5a27b3ae7ef..60b9f3e104f 100644 --- a/packages/reflex-radix/src/reflex_radix/themes/components/text_area.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/text_area.py @@ -2,13 +2,13 @@ from typing import Literal -from reflex_components.core.breakpoints import Responsive -from reflex_components.core.debounce import DebounceInput -from reflex_components.el import elements +from reflex_components_core.core.breakpoints import Responsive +from reflex_components_core.core.debounce import DebounceInput +from reflex_components_core.el import elements from reflex.components.component import Component, field from reflex.vars.base import Var -from reflex_radix.themes.base import ( +from reflex_components_radix.themes.base import ( LiteralAccentColor, LiteralRadius, RadixThemesComponent, diff --git a/packages/reflex-radix/src/reflex_radix/themes/components/text_field.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/text_field.py similarity index 95% rename from packages/reflex-radix/src/reflex_radix/themes/components/text_field.py rename to packages/reflex-components-radix/src/reflex_components_radix/themes/components/text_field.py index 4bdb3000f62..24ff5d7612a 100644 --- a/packages/reflex-radix/src/reflex_radix/themes/components/text_field.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/text_field.py @@ -4,16 +4,16 @@ from typing import Literal -from reflex_components.core.breakpoints import Responsive -from reflex_components.core.debounce import DebounceInput -from reflex_components.el import elements +from reflex_components_core.core.breakpoints import Responsive +from reflex_components_core.core.debounce import DebounceInput +from reflex_components_core.el import elements from reflex.components.component import Component, ComponentNamespace, field from reflex.event import EventHandler, input_event, key_event from reflex.utils.types import is_optional from reflex.vars.base import Var from reflex.vars.number import ternary_operation -from reflex_radix.themes.base import ( +from reflex_components_radix.themes.base import ( LiteralAccentColor, LiteralRadius, RadixThemesComponent, diff --git a/packages/reflex-radix/src/reflex_radix/themes/components/tooltip.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/tooltip.py similarity index 98% rename from packages/reflex-radix/src/reflex_radix/themes/components/tooltip.py rename to packages/reflex-components-radix/src/reflex_components_radix/themes/components/tooltip.py index 9cd6e23907e..90c9b2a8a8a 100644 --- a/packages/reflex-radix/src/reflex_radix/themes/components/tooltip.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/tooltip.py @@ -7,7 +7,7 @@ from reflex.event import EventHandler, no_args_event_spec, passthrough_event_spec from reflex.utils import format from reflex.vars.base import Var -from reflex_radix.themes.base import RadixThemesComponent +from reflex_components_radix.themes.base import RadixThemesComponent LiteralSideType = Literal[ "top", diff --git a/packages/reflex-radix/src/reflex_radix/themes/layout/__init__.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/layout/__init__.py similarity index 100% rename from packages/reflex-radix/src/reflex_radix/themes/layout/__init__.py rename to packages/reflex-components-radix/src/reflex_components_radix/themes/layout/__init__.py diff --git a/packages/reflex-radix/src/reflex_radix/themes/layout/base.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/layout/base.py similarity index 89% rename from packages/reflex-radix/src/reflex_radix/themes/layout/base.py rename to packages/reflex-components-radix/src/reflex_components_radix/themes/layout/base.py index 4ccc21a0dbc..f90ad69d63e 100644 --- a/packages/reflex-radix/src/reflex_radix/themes/layout/base.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/layout/base.py @@ -4,11 +4,11 @@ from typing import Literal -from reflex_components.core.breakpoints import Responsive +from reflex_components_core.core.breakpoints import Responsive from reflex.components.component import field from reflex.vars.base import Var -from reflex_radix.themes.base import ( +from reflex_components_radix.themes.base import ( CommonMarginProps, CommonPaddingProps, RadixThemesComponent, diff --git a/packages/reflex-radix/src/reflex_radix/themes/layout/box.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/layout/box.py similarity index 67% rename from packages/reflex-radix/src/reflex_radix/themes/layout/box.py rename to packages/reflex-components-radix/src/reflex_components_radix/themes/layout/box.py index 54452de74ad..16d8d0fbf33 100644 --- a/packages/reflex-radix/src/reflex_radix/themes/layout/box.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/layout/box.py @@ -2,9 +2,9 @@ from __future__ import annotations -from reflex_components.el import elements +from reflex_components_core.el import elements -from reflex_radix.themes.base import RadixThemesComponent +from reflex_components_radix.themes.base import RadixThemesComponent class Box(elements.Div, RadixThemesComponent): diff --git a/packages/reflex-radix/src/reflex_radix/themes/layout/center.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/layout/center.py similarity index 100% rename from packages/reflex-radix/src/reflex_radix/themes/layout/center.py rename to packages/reflex-components-radix/src/reflex_components_radix/themes/layout/center.py diff --git a/packages/reflex-radix/src/reflex_radix/themes/layout/container.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/layout/container.py similarity index 89% rename from packages/reflex-radix/src/reflex_radix/themes/layout/container.py rename to packages/reflex-components-radix/src/reflex_components_radix/themes/layout/container.py index fcfc7e0e255..9cdcc9080cf 100644 --- a/packages/reflex-radix/src/reflex_radix/themes/layout/container.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/layout/container.py @@ -4,13 +4,13 @@ from typing import Literal -from reflex_components.core.breakpoints import Responsive -from reflex_components.el import elements +from reflex_components_core.core.breakpoints import Responsive +from reflex_components_core.el import elements from reflex.components.component import field from reflex.style import STACK_CHILDREN_FULL_WIDTH from reflex.vars.base import LiteralVar, Var -from reflex_radix.themes.base import RadixThemesComponent +from reflex_components_radix.themes.base import RadixThemesComponent LiteralContainerSize = Literal["1", "2", "3", "4"] diff --git a/packages/reflex-radix/src/reflex_radix/themes/layout/flex.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/layout/flex.py similarity index 91% rename from packages/reflex-radix/src/reflex_radix/themes/layout/flex.py rename to packages/reflex-components-radix/src/reflex_components_radix/themes/layout/flex.py index 4ea2492e7f5..9b59dc4766a 100644 --- a/packages/reflex-radix/src/reflex_radix/themes/layout/flex.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/layout/flex.py @@ -4,12 +4,12 @@ from typing import ClassVar, Literal -from reflex_components.core.breakpoints import Responsive -from reflex_components.el import elements +from reflex_components_core.core.breakpoints import Responsive +from reflex_components_core.el import elements from reflex.components.component import field from reflex.vars.base import Var -from reflex_radix.themes.base import ( +from reflex_components_radix.themes.base import ( LiteralAlign, LiteralJustify, LiteralSpacing, diff --git a/packages/reflex-radix/src/reflex_radix/themes/layout/grid.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/layout/grid.py similarity index 91% rename from packages/reflex-radix/src/reflex_radix/themes/layout/grid.py rename to packages/reflex-components-radix/src/reflex_components_radix/themes/layout/grid.py index c2480f63e39..5e9298f8c5d 100644 --- a/packages/reflex-radix/src/reflex_radix/themes/layout/grid.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/layout/grid.py @@ -4,12 +4,12 @@ from typing import ClassVar, Literal -from reflex_components.core.breakpoints import Responsive -from reflex_components.el import elements +from reflex_components_core.core.breakpoints import Responsive +from reflex_components_core.el import elements from reflex.components.component import field from reflex.vars.base import Var -from reflex_radix.themes.base import ( +from reflex_components_radix.themes.base import ( LiteralAlign, LiteralJustify, LiteralSpacing, diff --git a/packages/reflex-radix/src/reflex_radix/themes/layout/list.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/layout/list.py similarity index 93% rename from packages/reflex-radix/src/reflex_radix/themes/layout/list.py rename to packages/reflex-components-radix/src/reflex_components_radix/themes/layout/list.py index a7fb7d6a622..a1ac0cec6f0 100644 --- a/packages/reflex-radix/src/reflex_radix/themes/layout/list.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/layout/list.py @@ -5,15 +5,15 @@ from collections.abc import Iterable from typing import Any, Literal -from reflex_components.core.foreach import Foreach -from reflex_components.el.elements.base import BaseHTML -from reflex_components.el.elements.typography import Li, Ol, Ul -from reflex_lucide.icon import Icon -from reflex_markdown.markdown import MarkdownComponentMap +from reflex_components_core.core.foreach import Foreach +from reflex_components_core.el.elements.base import BaseHTML +from reflex_components_core.el.elements.typography import Li, Ol, Ul +from reflex_components_lucide.icon import Icon +from reflex_components_markdown.markdown import MarkdownComponentMap from reflex.components.component import ComponentNamespace, field from reflex.vars.base import Var -from reflex_radix.themes.typography.text import Text +from reflex_components_radix.themes.typography.text import Text LiteralListStyleTypeUnordered = Literal[ "none", diff --git a/packages/reflex-radix/src/reflex_radix/themes/layout/section.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/layout/section.py similarity index 76% rename from packages/reflex-radix/src/reflex_radix/themes/layout/section.py rename to packages/reflex-components-radix/src/reflex_components_radix/themes/layout/section.py index 31507465f0f..b7ec569abbc 100644 --- a/packages/reflex-radix/src/reflex_radix/themes/layout/section.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/layout/section.py @@ -4,12 +4,12 @@ from typing import Literal -from reflex_components.core.breakpoints import Responsive -from reflex_components.el import elements +from reflex_components_core.core.breakpoints import Responsive +from reflex_components_core.el import elements from reflex.components.component import field from reflex.vars.base import LiteralVar, Var -from reflex_radix.themes.base import RadixThemesComponent +from reflex_components_radix.themes.base import RadixThemesComponent LiteralSectionSize = Literal["1", "2", "3"] diff --git a/packages/reflex-radix/src/reflex_radix/themes/layout/spacer.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/layout/spacer.py similarity index 100% rename from packages/reflex-radix/src/reflex_radix/themes/layout/spacer.py rename to packages/reflex-components-radix/src/reflex_components_radix/themes/layout/spacer.py diff --git a/packages/reflex-radix/src/reflex_radix/themes/layout/stack.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/layout/stack.py similarity index 92% rename from packages/reflex-radix/src/reflex_radix/themes/layout/stack.py rename to packages/reflex-components-radix/src/reflex_components_radix/themes/layout/stack.py index d3bfa7b66f1..c7463875fb8 100644 --- a/packages/reflex-radix/src/reflex_radix/themes/layout/stack.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/layout/stack.py @@ -2,11 +2,11 @@ from __future__ import annotations -from reflex_components.core.breakpoints import Responsive +from reflex_components_core.core.breakpoints import Responsive from reflex.components.component import Component, field from reflex.vars.base import Var -from reflex_radix.themes.base import LiteralAlign, LiteralSpacing +from reflex_components_radix.themes.base import LiteralAlign, LiteralSpacing from .flex import Flex, LiteralFlexDirection diff --git a/packages/reflex-radix/src/reflex_radix/themes/typography/__init__.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/typography/__init__.py similarity index 100% rename from packages/reflex-radix/src/reflex_radix/themes/typography/__init__.py rename to packages/reflex-components-radix/src/reflex_components_radix/themes/typography/__init__.py diff --git a/packages/reflex-radix/src/reflex_radix/themes/typography/base.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/typography/base.py similarity index 100% rename from packages/reflex-radix/src/reflex_radix/themes/typography/base.py rename to packages/reflex-components-radix/src/reflex_components_radix/themes/typography/base.py diff --git a/packages/reflex-radix/src/reflex_radix/themes/typography/blockquote.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/typography/blockquote.py similarity index 81% rename from packages/reflex-radix/src/reflex_radix/themes/typography/blockquote.py rename to packages/reflex-components-radix/src/reflex_components_radix/themes/typography/blockquote.py index b2291307179..689af36f89e 100644 --- a/packages/reflex-radix/src/reflex_radix/themes/typography/blockquote.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/typography/blockquote.py @@ -5,12 +5,12 @@ from __future__ import annotations -from reflex_components.core.breakpoints import Responsive -from reflex_components.el import elements +from reflex_components_core.core.breakpoints import Responsive +from reflex_components_core.el import elements from reflex.components.component import field from reflex.vars.base import Var -from reflex_radix.themes.base import LiteralAccentColor, RadixThemesComponent +from reflex_components_radix.themes.base import LiteralAccentColor, RadixThemesComponent from .base import LiteralTextSize, LiteralTextWeight diff --git a/packages/reflex-radix/src/reflex_radix/themes/typography/code.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/typography/code.py similarity index 82% rename from packages/reflex-radix/src/reflex_radix/themes/typography/code.py rename to packages/reflex-components-radix/src/reflex_components_radix/themes/typography/code.py index 75136d18170..a4c4c7248a4 100644 --- a/packages/reflex-radix/src/reflex_radix/themes/typography/code.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/typography/code.py @@ -5,13 +5,13 @@ from __future__ import annotations -from reflex_components.core.breakpoints import Responsive -from reflex_components.el import elements -from reflex_markdown.markdown import MarkdownComponentMap +from reflex_components_core.core.breakpoints import Responsive +from reflex_components_core.el import elements +from reflex_components_markdown.markdown import MarkdownComponentMap from reflex.components.component import field from reflex.vars.base import Var -from reflex_radix.themes.base import ( +from reflex_components_radix.themes.base import ( LiteralAccentColor, LiteralVariant, RadixThemesComponent, diff --git a/packages/reflex-radix/src/reflex_radix/themes/typography/heading.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/typography/heading.py similarity index 85% rename from packages/reflex-radix/src/reflex_radix/themes/typography/heading.py rename to packages/reflex-components-radix/src/reflex_components_radix/themes/typography/heading.py index 2b8a448cefb..e0799976ff9 100644 --- a/packages/reflex-radix/src/reflex_radix/themes/typography/heading.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/typography/heading.py @@ -5,13 +5,13 @@ from __future__ import annotations -from reflex_components.core.breakpoints import Responsive -from reflex_components.el import elements -from reflex_markdown.markdown import MarkdownComponentMap +from reflex_components_core.core.breakpoints import Responsive +from reflex_components_core.el import elements +from reflex_components_markdown.markdown import MarkdownComponentMap from reflex.components.component import field from reflex.vars.base import Var -from reflex_radix.themes.base import LiteralAccentColor, RadixThemesComponent +from reflex_components_radix.themes.base import LiteralAccentColor, RadixThemesComponent from .base import LiteralTextAlign, LiteralTextSize, LiteralTextTrim, LiteralTextWeight diff --git a/packages/reflex-radix/src/reflex_radix/themes/typography/link.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/typography/link.py similarity index 89% rename from packages/reflex-radix/src/reflex_radix/themes/typography/link.py rename to packages/reflex-components-radix/src/reflex_components_radix/themes/typography/link.py index 5684563252e..bf6fbe6dd7c 100644 --- a/packages/reflex-radix/src/reflex_radix/themes/typography/link.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/typography/link.py @@ -7,17 +7,17 @@ from typing import Literal -from reflex_components.core.breakpoints import Responsive -from reflex_components.core.colors import color -from reflex_components.core.cond import cond -from reflex_components.el.elements.inline import A -from reflex_markdown.markdown import MarkdownComponentMap -from reflex_react_router.dom import ReactRouterLink +from reflex_components_core.core.breakpoints import Responsive +from reflex_components_core.core.colors import color +from reflex_components_core.core.cond import cond +from reflex_components_core.el.elements.inline import A +from reflex_components_markdown.markdown import MarkdownComponentMap +from reflex_components_react_router.dom import ReactRouterLink from reflex.components.component import Component, MemoizationLeaf, field from reflex.utils.imports import ImportDict, ImportVar from reflex.vars.base import Var -from reflex_radix.themes.base import LiteralAccentColor, RadixThemesComponent +from reflex_components_radix.themes.base import LiteralAccentColor, RadixThemesComponent from .base import LiteralTextSize, LiteralTextTrim, LiteralTextWeight diff --git a/packages/reflex-radix/src/reflex_radix/themes/typography/text.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/typography/text.py similarity index 91% rename from packages/reflex-radix/src/reflex_radix/themes/typography/text.py rename to packages/reflex-components-radix/src/reflex_components_radix/themes/typography/text.py index e95932c8d22..7a79546bd34 100644 --- a/packages/reflex-radix/src/reflex_radix/themes/typography/text.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/typography/text.py @@ -7,13 +7,13 @@ from typing import Literal -from reflex_components.core.breakpoints import Responsive -from reflex_components.el import elements -from reflex_markdown.markdown import MarkdownComponentMap +from reflex_components_core.core.breakpoints import Responsive +from reflex_components_core.el import elements +from reflex_components_markdown.markdown import MarkdownComponentMap from reflex.components.component import ComponentNamespace, field from reflex.vars.base import Var -from reflex_radix.themes.base import LiteralAccentColor, RadixThemesComponent +from reflex_components_radix.themes.base import LiteralAccentColor, RadixThemesComponent from .base import LiteralTextAlign, LiteralTextSize, LiteralTextTrim, LiteralTextWeight diff --git a/packages/reflex-components-react-player/README.md b/packages/reflex-components-react-player/README.md new file mode 100644 index 00000000000..b1570fb70f5 --- /dev/null +++ b/packages/reflex-components-react-player/README.md @@ -0,0 +1,3 @@ +# reflex-components-react-player + +Reflex react-player components. diff --git a/packages/reflex-react-player/pyproject.toml b/packages/reflex-components-react-player/pyproject.toml similarity index 51% rename from packages/reflex-react-player/pyproject.toml rename to packages/reflex-components-react-player/pyproject.toml index 84931013428..44806159594 100644 --- a/packages/reflex-react-player/pyproject.toml +++ b/packages/reflex-components-react-player/pyproject.toml @@ -1,6 +1,6 @@ [project] -name = "reflex-react-player" -version = "0.0.1" +name = "reflex-components-react-player" +dynamic = ["version"] description = "Reflex react-player components." readme = "README.md" authors = [{ name = "Khaleel Al-Adhami", email = "khaleel@reflex.dev" }] @@ -8,6 +8,10 @@ maintainers = [{ name = "Khaleel Al-Adhami", email = "khaleel@reflex.dev" }] requires-python = ">=3.10" dependencies = [] +[tool.hatch.version] +source = "vcs" +raw-options = { root = "../..", tag_regex = "^reflex-components-react-player-v(?P.+)$" } + [build-system] -requires = ["uv_build>=0.10.11,<0.12.0"] -build-backend = "uv_build" +requires = ["hatchling", "hatch-vcs"] +build-backend = "hatchling.build" diff --git a/packages/reflex-react-player/src/reflex_react_player/__init__.py b/packages/reflex-components-react-player/src/reflex_components_react_player/__init__.py similarity index 100% rename from packages/reflex-react-player/src/reflex_react_player/__init__.py rename to packages/reflex-components-react-player/src/reflex_components_react_player/__init__.py diff --git a/packages/reflex-react-player/src/reflex_react_player/audio.py b/packages/reflex-components-react-player/src/reflex_components_react_player/audio.py similarity index 61% rename from packages/reflex-react-player/src/reflex_react_player/audio.py rename to packages/reflex-components-react-player/src/reflex_components_react_player/audio.py index ca0bf16c8e8..2580fa26944 100644 --- a/packages/reflex-react-player/src/reflex_react_player/audio.py +++ b/packages/reflex-components-react-player/src/reflex_components_react_player/audio.py @@ -1,6 +1,6 @@ """A audio component.""" -from reflex_react_player.react_player import ReactPlayer +from reflex_components_react_player.react_player import ReactPlayer class Audio(ReactPlayer): diff --git a/packages/reflex-react-player/src/reflex_react_player/react_player.py b/packages/reflex-components-react-player/src/reflex_components_react_player/react_player.py similarity index 99% rename from packages/reflex-react-player/src/reflex_react_player/react_player.py rename to packages/reflex-components-react-player/src/reflex_components_react_player/react_player.py index 2513c3a4756..af01d475c88 100644 --- a/packages/reflex-react-player/src/reflex_react_player/react_player.py +++ b/packages/reflex-components-react-player/src/reflex_components_react_player/react_player.py @@ -4,7 +4,7 @@ from typing import Any, TypedDict -from reflex_components.core.cond import cond +from reflex_components_core.core.cond import cond from reflex.components.component import Component, field from reflex.event import EventHandler, no_args_event_spec diff --git a/packages/reflex-react-player/src/reflex_react_player/video.py b/packages/reflex-components-react-player/src/reflex_components_react_player/video.py similarity index 61% rename from packages/reflex-react-player/src/reflex_react_player/video.py rename to packages/reflex-components-react-player/src/reflex_components_react_player/video.py index e63e3037946..df0dba45502 100644 --- a/packages/reflex-react-player/src/reflex_react_player/video.py +++ b/packages/reflex-components-react-player/src/reflex_components_react_player/video.py @@ -1,6 +1,6 @@ """A video component.""" -from reflex_react_player.react_player import ReactPlayer +from reflex_components_react_player.react_player import ReactPlayer class Video(ReactPlayer): diff --git a/packages/reflex-components-react-router/README.md b/packages/reflex-components-react-router/README.md new file mode 100644 index 00000000000..f4465ac2200 --- /dev/null +++ b/packages/reflex-components-react-router/README.md @@ -0,0 +1,3 @@ +# reflex-components-react-router + +Reflex react-router components. diff --git a/packages/reflex-react-router/pyproject.toml b/packages/reflex-components-react-router/pyproject.toml similarity index 51% rename from packages/reflex-react-router/pyproject.toml rename to packages/reflex-components-react-router/pyproject.toml index e02f826ac8d..ecd45acf554 100644 --- a/packages/reflex-react-router/pyproject.toml +++ b/packages/reflex-components-react-router/pyproject.toml @@ -1,6 +1,6 @@ [project] -name = "reflex-react-router" -version = "0.0.1" +name = "reflex-components-react-router" +dynamic = ["version"] description = "Reflex react-router components." readme = "README.md" authors = [{ name = "Khaleel Al-Adhami", email = "khaleel@reflex.dev" }] @@ -8,6 +8,10 @@ maintainers = [{ name = "Khaleel Al-Adhami", email = "khaleel@reflex.dev" }] requires-python = ">=3.10" dependencies = [] +[tool.hatch.version] +source = "vcs" +raw-options = { root = "../..", tag_regex = "^reflex-components-react-router-v(?P.+)$" } + [build-system] -requires = ["uv_build>=0.10.11,<0.12.0"] -build-backend = "uv_build" +requires = ["hatchling", "hatch-vcs"] +build-backend = "hatchling.build" diff --git a/packages/reflex-react-router/src/reflex_react_router/__init__.py b/packages/reflex-components-react-router/src/reflex_components_react_router/__init__.py similarity index 100% rename from packages/reflex-react-router/src/reflex_react_router/__init__.py rename to packages/reflex-components-react-router/src/reflex_components_react_router/__init__.py diff --git a/packages/reflex-react-router/src/reflex_react_router/dom.py b/packages/reflex-components-react-router/src/reflex_components_react_router/dom.py similarity index 97% rename from packages/reflex-react-router/src/reflex_react_router/dom.py rename to packages/reflex-components-react-router/src/reflex_components_react_router/dom.py index 683ebdcd348..9f744773ee8 100644 --- a/packages/reflex-react-router/src/reflex_react_router/dom.py +++ b/packages/reflex-components-react-router/src/reflex_components_react_router/dom.py @@ -4,7 +4,7 @@ from typing import ClassVar, Literal, TypedDict -from reflex_components.el.elements.inline import A +from reflex_components_core.el.elements.inline import A from reflex.components.component import field from reflex.vars.base import Var diff --git a/packages/reflex-components-recharts/README.md b/packages/reflex-components-recharts/README.md new file mode 100644 index 00000000000..f5e6de0e026 --- /dev/null +++ b/packages/reflex-components-recharts/README.md @@ -0,0 +1,3 @@ +# reflex-components-recharts + +Reflex recharts components. diff --git a/packages/reflex-recharts/pyproject.toml b/packages/reflex-components-recharts/pyproject.toml similarity index 52% rename from packages/reflex-recharts/pyproject.toml rename to packages/reflex-components-recharts/pyproject.toml index 348118467b6..e8390eae781 100644 --- a/packages/reflex-recharts/pyproject.toml +++ b/packages/reflex-components-recharts/pyproject.toml @@ -1,6 +1,6 @@ [project] -name = "reflex-recharts" -version = "0.0.1" +name = "reflex-components-recharts" +dynamic = ["version"] description = "Reflex recharts components." readme = "README.md" authors = [{ name = "Khaleel Al-Adhami", email = "khaleel@reflex.dev" }] @@ -8,6 +8,10 @@ maintainers = [{ name = "Khaleel Al-Adhami", email = "khaleel@reflex.dev" }] requires-python = ">=3.10" dependencies = [] +[tool.hatch.version] +source = "vcs" +raw-options = { root = "../..", tag_regex = "^reflex-components-recharts-v(?P.+)$" } + [build-system] -requires = ["uv_build>=0.10.11,<0.12.0"] -build-backend = "uv_build" +requires = ["hatchling", "hatch-vcs"] +build-backend = "hatchling.build" diff --git a/packages/reflex-recharts/src/reflex_recharts/__init__.py b/packages/reflex-components-recharts/src/reflex_components_recharts/__init__.py similarity index 100% rename from packages/reflex-recharts/src/reflex_recharts/__init__.py rename to packages/reflex-components-recharts/src/reflex_components_recharts/__init__.py diff --git a/packages/reflex-recharts/src/reflex_recharts/cartesian.py b/packages/reflex-components-recharts/src/reflex_components_recharts/cartesian.py similarity index 100% rename from packages/reflex-recharts/src/reflex_recharts/cartesian.py rename to packages/reflex-components-recharts/src/reflex_components_recharts/cartesian.py diff --git a/packages/reflex-recharts/src/reflex_recharts/charts.py b/packages/reflex-components-recharts/src/reflex_components_recharts/charts.py similarity index 99% rename from packages/reflex-recharts/src/reflex_recharts/charts.py rename to packages/reflex-components-recharts/src/reflex_components_recharts/charts.py index 26d53d38225..c9a9853c269 100644 --- a/packages/reflex-recharts/src/reflex_recharts/charts.py +++ b/packages/reflex-components-recharts/src/reflex_components_recharts/charts.py @@ -10,7 +10,7 @@ from reflex.constants.colors import Color from reflex.event import EventHandler, no_args_event_spec from reflex.vars.base import Var -from reflex_recharts.general import ResponsiveContainer +from reflex_components_recharts.general import ResponsiveContainer from .recharts import ( LiteralAnimationEasing, diff --git a/packages/reflex-recharts/src/reflex_recharts/general.py b/packages/reflex-components-recharts/src/reflex_components_recharts/general.py similarity index 100% rename from packages/reflex-recharts/src/reflex_recharts/general.py rename to packages/reflex-components-recharts/src/reflex_components_recharts/general.py diff --git a/packages/reflex-recharts/src/reflex_recharts/polar.py b/packages/reflex-components-recharts/src/reflex_components_recharts/polar.py similarity index 100% rename from packages/reflex-recharts/src/reflex_recharts/polar.py rename to packages/reflex-components-recharts/src/reflex_components_recharts/polar.py diff --git a/packages/reflex-recharts/src/reflex_recharts/recharts.py b/packages/reflex-components-recharts/src/reflex_components_recharts/recharts.py similarity index 100% rename from packages/reflex-recharts/src/reflex_recharts/recharts.py rename to packages/reflex-components-recharts/src/reflex_components_recharts/recharts.py diff --git a/packages/reflex-components-sonner/README.md b/packages/reflex-components-sonner/README.md new file mode 100644 index 00000000000..cfc4e39ac49 --- /dev/null +++ b/packages/reflex-components-sonner/README.md @@ -0,0 +1,3 @@ +# reflex-components-sonner + +Reflex sonner components. diff --git a/packages/reflex-sonner/pyproject.toml b/packages/reflex-components-sonner/pyproject.toml similarity index 52% rename from packages/reflex-sonner/pyproject.toml rename to packages/reflex-components-sonner/pyproject.toml index 60c983b5942..2b5845ef76c 100644 --- a/packages/reflex-sonner/pyproject.toml +++ b/packages/reflex-components-sonner/pyproject.toml @@ -1,6 +1,6 @@ [project] -name = "reflex-sonner" -version = "0.0.1" +name = "reflex-components-sonner" +dynamic = ["version"] description = "Reflex sonner components." readme = "README.md" authors = [{ name = "Khaleel Al-Adhami", email = "khaleel@reflex.dev" }] @@ -8,6 +8,10 @@ maintainers = [{ name = "Khaleel Al-Adhami", email = "khaleel@reflex.dev" }] requires-python = ">=3.10" dependencies = [] +[tool.hatch.version] +source = "vcs" +raw-options = { root = "../..", tag_regex = "^reflex-components-sonner-v(?P.+)$" } + [build-system] -requires = ["uv_build>=0.10.11,<0.12.0"] -build-backend = "uv_build" +requires = ["hatchling", "hatch-vcs"] +build-backend = "hatchling.build" diff --git a/packages/reflex-sonner/src/reflex_sonner/__init__.py b/packages/reflex-components-sonner/src/reflex_components_sonner/__init__.py similarity index 100% rename from packages/reflex-sonner/src/reflex_sonner/__init__.py rename to packages/reflex-components-sonner/src/reflex_components_sonner/__init__.py diff --git a/packages/reflex-sonner/src/reflex_sonner/toast.py b/packages/reflex-components-sonner/src/reflex_components_sonner/toast.py similarity index 99% rename from packages/reflex-sonner/src/reflex_sonner/toast.py rename to packages/reflex-components-sonner/src/reflex_components_sonner/toast.py index 5b87d741c2f..5514d1a8c80 100644 --- a/packages/reflex-sonner/src/reflex_sonner/toast.py +++ b/packages/reflex-components-sonner/src/reflex_components_sonner/toast.py @@ -5,7 +5,7 @@ import dataclasses from typing import Any, Literal -from reflex_lucide.icon import Icon +from reflex_components_lucide.icon import Icon from reflex.components.component import Component, ComponentNamespace, field from reflex.components.props import NoExtrasAllowedProps diff --git a/packages/reflex-components/README.md b/packages/reflex-components/README.md deleted file mode 100644 index e5e46a36522..00000000000 --- a/packages/reflex-components/README.md +++ /dev/null @@ -1 +0,0 @@ -# Reflex Components\n\nUI components for Reflex. diff --git a/packages/reflex-dataeditor/README.md b/packages/reflex-dataeditor/README.md deleted file mode 100644 index 3104034611b..00000000000 --- a/packages/reflex-dataeditor/README.md +++ /dev/null @@ -1 +0,0 @@ -# Reflex dataeditor diff --git a/packages/reflex-docgen/README.md b/packages/reflex-docgen/README.md index e69de29bb2d..40d2d955dcb 100644 --- a/packages/reflex-docgen/README.md +++ b/packages/reflex-docgen/README.md @@ -0,0 +1,3 @@ +# reflex-docgen + +Generate documentation for Reflex components. diff --git a/packages/reflex-docgen/pyproject.toml b/packages/reflex-docgen/pyproject.toml index 87c5ea4964c..73b9f61566b 100644 --- a/packages/reflex-docgen/pyproject.toml +++ b/packages/reflex-docgen/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "reflex-docgen" -version = "0.0.1" +dynamic = ["version"] description = "Generate documentation for Reflex components." readme = "README.md" authors = [{ name = "Khaleel Al-Adhami", email = "khaleel@reflex.dev" }] @@ -18,6 +18,10 @@ dependencies = [ [tool.uv.sources] reflex = { workspace = true } +[tool.hatch.version] +source = "vcs" +raw-options = { root = "../..", tag_regex = "^reflex-docgen-v(?P.+)$" } + [build-system] -requires = ["uv_build>=0.10.11,<0.11.0"] -build-backend = "uv_build" +requires = ["hatchling", "hatch-vcs"] +build-backend = "hatchling.build" diff --git a/packages/reflex-gridjs/README.md b/packages/reflex-gridjs/README.md deleted file mode 100644 index da4b78c28d6..00000000000 --- a/packages/reflex-gridjs/README.md +++ /dev/null @@ -1 +0,0 @@ -# Reflex gridjs diff --git a/packages/reflex-lucide/README.md b/packages/reflex-lucide/README.md deleted file mode 100644 index 3b55d0e3531..00000000000 --- a/packages/reflex-lucide/README.md +++ /dev/null @@ -1 +0,0 @@ -# Reflex lucide diff --git a/packages/reflex-markdown/README.md b/packages/reflex-markdown/README.md deleted file mode 100644 index 155a0e1c73c..00000000000 --- a/packages/reflex-markdown/README.md +++ /dev/null @@ -1 +0,0 @@ -# Reflex markdown diff --git a/packages/reflex-moment/README.md b/packages/reflex-moment/README.md deleted file mode 100644 index df964807846..00000000000 --- a/packages/reflex-moment/README.md +++ /dev/null @@ -1 +0,0 @@ -# Reflex moment diff --git a/packages/reflex-plotly/README.md b/packages/reflex-plotly/README.md deleted file mode 100644 index 758ebd0d3ab..00000000000 --- a/packages/reflex-plotly/README.md +++ /dev/null @@ -1 +0,0 @@ -# Reflex plotly diff --git a/packages/reflex-radix/README.md b/packages/reflex-radix/README.md deleted file mode 100644 index e49f890d64c..00000000000 --- a/packages/reflex-radix/README.md +++ /dev/null @@ -1 +0,0 @@ -# Reflex radix diff --git a/packages/reflex-react-player/README.md b/packages/reflex-react-player/README.md deleted file mode 100644 index b0e28d519ae..00000000000 --- a/packages/reflex-react-player/README.md +++ /dev/null @@ -1 +0,0 @@ -# Reflex react-player diff --git a/packages/reflex-react-router/README.md b/packages/reflex-react-router/README.md deleted file mode 100644 index 6aa783f98bd..00000000000 --- a/packages/reflex-react-router/README.md +++ /dev/null @@ -1 +0,0 @@ -# Reflex react-router diff --git a/packages/reflex-recharts/README.md b/packages/reflex-recharts/README.md deleted file mode 100644 index f706ed69105..00000000000 --- a/packages/reflex-recharts/README.md +++ /dev/null @@ -1 +0,0 @@ -# Reflex recharts diff --git a/packages/reflex-sonner/README.md b/packages/reflex-sonner/README.md deleted file mode 100644 index 086bf641a10..00000000000 --- a/packages/reflex-sonner/README.md +++ /dev/null @@ -1 +0,0 @@ -# Reflex sonner diff --git a/pyproject.toml b/pyproject.toml index e86373c14b3..1fc0dbe16c8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "reflex" -version = "0.9.0dev1" +dynamic = ["version"] description = "Web apps in pure Python." license.text = "Apache-2.0" authors = [ @@ -37,19 +37,19 @@ dependencies = [ "starlette >=0.47.0", "typing_extensions >=4.13.0", "wrapt >=1.17.0,<3.0", - "reflex-code", - "reflex-components", - "reflex-dataeditor", - "reflex-gridjs", - "reflex-lucide", - "reflex-markdown", - "reflex-moment", - "reflex-plotly", - "reflex-radix", - "reflex-react-player", - "reflex-react-router", - "reflex-recharts", - "reflex-sonner", + "reflex-components-code", + "reflex-components-core", + "reflex-components-dataeditor", + "reflex-components-gridjs", + "reflex-components-lucide", + "reflex-components-markdown", + "reflex-components-moment", + "reflex-components-plotly", + "reflex-components-radix", + "reflex-components-react-player", + "reflex-components-react-router", + "reflex-components-recharts", + "reflex-components-sonner", ] classifiers = [ @@ -115,9 +115,12 @@ dev = [ [build-system] -requires = ["hatchling"] +requires = ["hatchling", "hatch-vcs"] build-backend = "hatchling.build" +[tool.hatch.version] +source = "vcs" + [tool.hatch.build] include = ["reflex", "scripts/hatch_build.py"] targets.sdist.artifacts = ["*.pyi"] @@ -322,7 +325,7 @@ hooks = [ [tool.uv] required-version = ">=0.7.0" -sources = { reflex-code = { workspace = true }, reflex-components = { workspace = true }, reflex-dataeditor = { workspace = true }, reflex-docgen = { workspace = true }, reflex-gridjs = { workspace = true }, reflex-lucide = { workspace = true }, reflex-markdown = { workspace = true }, reflex-moment = { workspace = true }, reflex-plotly = { workspace = true }, reflex-radix = { workspace = true }, reflex-react-player = { workspace = true }, reflex-react-router = { workspace = true }, reflex-recharts = { workspace = true }, reflex-sonner = { workspace = true } } +sources = { reflex-components-code = { workspace = true }, reflex-components-core = { workspace = true }, reflex-components-dataeditor = { workspace = true }, reflex-components-gridjs = { workspace = true }, reflex-components-lucide = { workspace = true }, reflex-components-markdown = { workspace = true }, reflex-components-moment = { workspace = true }, reflex-components-plotly = { workspace = true }, reflex-components-radix = { workspace = true }, reflex-components-react-player = { workspace = true }, reflex-components-react-router = { workspace = true }, reflex-components-recharts = { workspace = true }, reflex-components-sonner = { workspace = true }, reflex-docgen = { workspace = true } } [tool.uv.workspace] members = ["packages/*"] diff --git a/reflex/app.py b/reflex/app.py index 07cff7e12f0..85415e49c0b 100644 --- a/reflex/app.py +++ b/reflex/app.py @@ -31,19 +31,19 @@ from types import SimpleNamespace from typing import TYPE_CHECKING, Any, BinaryIO, ParamSpec, get_args, get_type_hints -from reflex_components.base.app_wrap import AppWrap -from reflex_components.base.error_boundary import ErrorBoundary -from reflex_components.base.fragment import Fragment -from reflex_components.base.strict_mode import StrictMode -from reflex_components.core.banner import ( +from reflex_components_core.base.app_wrap import AppWrap +from reflex_components_core.base.error_boundary import ErrorBoundary +from reflex_components_core.base.fragment import Fragment +from reflex_components_core.base.strict_mode import StrictMode +from reflex_components_core.core.banner import ( backend_disabled, connection_pulser, connection_toaster, ) -from reflex_components.core.breakpoints import set_breakpoints -from reflex_components.core.sticky import sticky -from reflex_radix import themes -from reflex_sonner.toast import toast +from reflex_components_core.core.breakpoints import set_breakpoints +from reflex_components_core.core.sticky import sticky +from reflex_components_radix import themes +from reflex_components_sonner.toast import toast from rich.progress import MofNCompleteColumn, Progress, TimeElapsedColumn from socketio import ASGIApp as EngineIOApp from socketio import AsyncNamespace, AsyncServer @@ -156,7 +156,7 @@ def default_backend_exception_handler(exception: Exception) -> EventSpec: EventSpec: The window alert event. """ - from reflex_sonner.toast import toast + from reflex_components_sonner.toast import toast error = traceback.format_exc() @@ -696,7 +696,7 @@ def _add_default_endpoints(self): def _add_optional_endpoints(self): """Add optional api endpoints (_upload).""" - from reflex_components.core.upload import Upload, get_upload_dir + from reflex_components_core.core.upload import Upload, get_upload_dir if not self._api: return @@ -815,7 +815,7 @@ def add_page( if route == constants.Page404.SLUG: if component is None: - from reflex_components.el.elements import span + from reflex_components_core.el.elements import span component = span("404: Page not found") component = self._generate_component(component) diff --git a/reflex/compiler/compiler.py b/reflex/compiler/compiler.py index 7e88077bcc3..7737bf77ca1 100644 --- a/reflex/compiler/compiler.py +++ b/reflex/compiler/compiler.py @@ -8,7 +8,7 @@ from pathlib import Path from typing import TYPE_CHECKING, Any -from reflex_components.base.fragment import Fragment +from reflex_components_core.base.fragment import Fragment from reflex import constants from reflex.compiler import templates, utils diff --git a/reflex/compiler/utils.py b/reflex/compiler/utils.py index c515b7c527a..c5fc117566c 100644 --- a/reflex/compiler/utils.py +++ b/reflex/compiler/utils.py @@ -12,12 +12,12 @@ from typing import Any, TypedDict from urllib.parse import urlparse -from reflex_components.base import Description, Image, Scripts -from reflex_components.base.document import Links, ScrollRestoration -from reflex_components.base.document import Meta as ReactMeta -from reflex_components.el.elements.metadata import Head, Link, Meta, Title -from reflex_components.el.elements.other import Html -from reflex_components.el.elements.sectioning import Body +from reflex_components_core.base import Description, Image, Scripts +from reflex_components_core.base.document import Links, ScrollRestoration +from reflex_components_core.base.document import Meta as ReactMeta +from reflex_components_core.el.elements.metadata import Head, Link, Meta, Title +from reflex_components_core.el.elements.other import Html +from reflex_components_core.el.elements.sectioning import Body from reflex import constants from reflex.components.component import Component, ComponentStyle, CustomComponent diff --git a/reflex/components/__init__.py b/reflex/components/__init__.py index eaab63334f0..b44e3e4fdfd 100644 --- a/reflex/components/__init__.py +++ b/reflex/components/__init__.py @@ -19,29 +19,29 @@ # Mapping from subpackage name to the target top-level package. _SUBPACKAGE_TARGETS: dict[str, str] = { # reflex-components (base package) - "base": "reflex_components.base", - "core": "reflex_components.core", - "datadisplay": "reflex_components.datadisplay", - "el": "reflex_components.el", + "base": "reflex_components_core.base", + "core": "reflex_components_core.core", + "datadisplay": "reflex_components_core.datadisplay", + "el": "reflex_components_core.el", # Standalone packages - "gridjs": "reflex_gridjs", - "lucide": "reflex_lucide", - "markdown": "reflex_markdown", - "moment": "reflex_moment", - "plotly": "reflex_plotly", - "radix": "reflex_radix", - "react_player": "reflex_react_player", - "react_router": "reflex_react_router", - "recharts": "reflex_recharts", - "sonner": "reflex_sonner", + "gridjs": "reflex_components_gridjs", + "lucide": "reflex_components_lucide", + "markdown": "reflex_components_markdown", + "moment": "reflex_components_moment", + "plotly": "reflex_components_plotly", + "radix": "reflex_components_radix", + "react_player": "reflex_components_react_player", + "react_router": "reflex_components_react_router", + "recharts": "reflex_components_recharts", + "sonner": "reflex_components_sonner", } # Deeper overrides for subpackages that were split from datadisplay. # Checked before the general _SUBPACKAGE_TARGETS mapping. _DEEP_OVERRIDES: dict[str, str] = { - "datadisplay.code": "reflex_code.code", - "datadisplay.shiki_code_block": "reflex_code.shiki_code_block", - "datadisplay.dataeditor": "reflex_dataeditor.dataeditor", + "datadisplay.code": "reflex_components_code.code", + "datadisplay.shiki_code_block": "reflex_components_code.shiki_code_block", + "datadisplay.dataeditor": "reflex_components_dataeditor.dataeditor", } @@ -80,7 +80,7 @@ def find_spec( subpkg = parts[2] rest_parts = parts[3:] - # Check deep overrides first (e.g. datadisplay.code -> reflex_code.code). + # Check deep overrides first (e.g. datadisplay.code -> reflex_components_code.code). if rest_parts: deep_key = f"{subpkg}.{rest_parts[0]}" override = _DEEP_OVERRIDES.get(deep_key) diff --git a/reflex/components/component.py b/reflex/components/component.py index 116cf2f467a..673a5cb012a 100644 --- a/reflex/components/component.py +++ b/reflex/components/component.py @@ -17,7 +17,7 @@ from types import SimpleNamespace from typing import TYPE_CHECKING, Any, ClassVar, TypeVar, cast, get_args, get_origin -from reflex_components.core.breakpoints import Breakpoints +from reflex_components_core.core.breakpoints import Breakpoints from rich.markup import escape from typing_extensions import dataclass_transform @@ -1140,8 +1140,8 @@ def create(cls: type[T], *children, **props) -> T: The component. """ # Import here to avoid circular imports. - from reflex_components.base.bare import Bare - from reflex_components.base.fragment import Fragment + from reflex_components_core.base.bare import Bare + from reflex_components_core.base.fragment import Fragment # Filter out None props props = {key: value for key, value in props.items() if value is not None} @@ -1350,10 +1350,10 @@ def _validate_component_children(self, children: list[Component]): children: The children of the component. """ - from reflex_components.base.fragment import Fragment - from reflex_components.core.cond import Cond - from reflex_components.core.foreach import Foreach - from reflex_components.core.match import Match + from reflex_components_core.base.fragment import Fragment + from reflex_components_core.core.cond import Cond + from reflex_components_core.core.foreach import Foreach + from reflex_components_core.core.match import Match no_valid_parents_defined = all(child._valid_parents == [] for child in children) if ( @@ -2417,7 +2417,7 @@ def create(cls, component: Component) -> StatefulComponent | None: Returns: The stateful component or None if the component should not be memoized. """ - from reflex_components.core.foreach import Foreach + from reflex_components_core.core.foreach import Foreach if component._memoization_mode.disposition == MemoizationDisposition.NEVER: # Never memoize this component. @@ -2498,10 +2498,10 @@ def _child_var(child: Component) -> Var | Component: Returns: The Var from the child component or the child itself (for regular cases). """ - from reflex_components.base.bare import Bare - from reflex_components.core.cond import Cond - from reflex_components.core.foreach import Foreach - from reflex_components.core.match import Match + from reflex_components_core.base.bare import Bare + from reflex_components_core.core.cond import Cond + from reflex_components_core.core.foreach import Foreach + from reflex_components_core.core.match import Match if isinstance(child, Bare): return child.contents @@ -2851,7 +2851,7 @@ def empty_component() -> Component: Returns: An empty component. """ - from reflex_components.base.bare import Bare + from reflex_components_core.base.bare import Bare return Bare.create("") diff --git a/reflex/components/dynamic.py b/reflex/components/dynamic.py index 03c85430ace..c6d4ff9ad97 100644 --- a/reflex/components/dynamic.py +++ b/reflex/components/dynamic.py @@ -70,7 +70,7 @@ def make_component(component: Component) -> str: The generated code """ # Causes a circular import, so we import here. - from reflex_components.base.bare import Bare + from reflex_components_core.base.bare import Bare from reflex.compiler import compiler, templates, utils diff --git a/reflex/components/tags/iter_tag.py b/reflex/components/tags/iter_tag.py index 2f4db2ebd82..d268636e74f 100644 --- a/reflex/components/tags/iter_tag.py +++ b/reflex/components/tags/iter_tag.py @@ -79,9 +79,9 @@ def render_component(self) -> Component: ValueError: If the render function doesn't return a component. """ # Import here to avoid circular imports. - from reflex_components.base.fragment import Fragment - from reflex_components.core.cond import Cond - from reflex_components.core.foreach import Foreach + from reflex_components_core.base.fragment import Fragment + from reflex_components_core.core.cond import Cond + from reflex_components_core.core.foreach import Foreach from reflex.compiler.compiler import _into_component_once diff --git a/reflex/config.py b/reflex/config.py index 38a2b871455..f3f17aa2949 100644 --- a/reflex/config.py +++ b/reflex/config.py @@ -176,7 +176,7 @@ class BaseConfig: state_auto_setters: Whether to automatically create setters for state base vars. show_built_with_reflex: Whether to display the sticky "Built with Reflex" badge on all pages. is_reflex_cloud: Whether the app is running in the reflex cloud environment. - extra_overlay_function: Extra overlay function to run after the app is built. Formatted such that `from path_0.path_1... import path[-1]`, and calling it with no arguments would work. For example, "reflex_moment.moment". + extra_overlay_function: Extra overlay function to run after the app is built. Formatted such that `from path_0.path_1... import path[-1]`, and calling it with no arguments would work. For example, "reflex_components_moment.moment". plugins: List of plugins to use in the app. disable_plugins: List of plugin types to disable in the app. transport: The transport method for client-server communication. diff --git a/reflex/constants/colors.py b/reflex/constants/colors.py index bad9edc11a1..a318eca990f 100644 --- a/reflex/constants/colors.py +++ b/reflex/constants/colors.py @@ -68,7 +68,7 @@ def format_color( if isinstance(alpha, bool): return f"var(--{color}-{'a' if alpha else ''}{shade})" - from reflex_components.core import cond + from reflex_components_core.core import cond alpha_var = cond(alpha, "a", "") return f"var(--{color}-{alpha_var}{shade})" diff --git a/reflex/event.py b/reflex/event.py index 50f7afa65fc..75ccf80e9d1 100644 --- a/reflex/event.py +++ b/reflex/event.py @@ -880,7 +880,7 @@ def as_event_spec(self, handler: EventHandler) -> EventSpec: Raises: ValueError: If the on_upload_progress is not a valid event handler. """ - from reflex_components.core.upload import ( + from reflex_components_core.core.upload import ( DEFAULT_UPLOAD_ID, upload_files_context_var_data, ) @@ -1258,7 +1258,7 @@ def download( ValueError: If the URL provided is invalid, both URL and data are provided, or the data is not an expected type. """ - from reflex_components.core.cond import cond + from reflex_components_core.core.cond import cond if isinstance(url, str): if not url.startswith("/"): diff --git a/reflex/experimental/__init__.py b/reflex/experimental/__init__.py index 5ed1a413f23..09905906a7c 100644 --- a/reflex/experimental/__init__.py +++ b/reflex/experimental/__init__.py @@ -2,7 +2,7 @@ from types import SimpleNamespace -from reflex_code.shiki_code_block import code_block as code_block +from reflex_components_code.shiki_code_block import code_block as code_block from reflex.utils.console import warn from reflex.utils.misc import run_in_thread diff --git a/reflex/state.py b/reflex/state.py index 83840975581..affe7324376 100644 --- a/reflex/state.py +++ b/reflex/state.py @@ -2554,7 +2554,7 @@ def dynamic(func: Callable[[T], Component]): state_class: type[T] = values[0] def wrapper() -> Component: - from reflex_components.base.fragment import fragment + from reflex_components_core.base.fragment import fragment return fragment(state_class._evaluate(lambda state: func(state))) diff --git a/reflex/style.py b/reflex/style.py index e3964dc4740..d25b059f181 100644 --- a/reflex/style.py +++ b/reflex/style.py @@ -5,7 +5,7 @@ from collections.abc import Mapping from typing import Any, Literal -from reflex_components.core.breakpoints import Breakpoints, breakpoints_values +from reflex_components_core.core.breakpoints import Breakpoints, breakpoints_values from reflex import constants from reflex.event import EventChain, EventHandler, EventSpec, run_script diff --git a/reflex/utils/codespaces.py b/reflex/utils/codespaces.py index b339b1796de..c0decaf5b5c 100644 --- a/reflex/utils/codespaces.py +++ b/reflex/utils/codespaces.py @@ -4,9 +4,9 @@ import os -from reflex_components.base.script import Script -from reflex_components.core.banner import has_connection_errors -from reflex_components.core.cond import cond +from reflex_components_core.base.script import Script +from reflex_components_core.core.banner import has_connection_errors +from reflex_components_core.core.cond import cond from starlette.requests import Request from starlette.responses import HTMLResponse diff --git a/reflex/utils/misc.py b/reflex/utils/misc.py index d0a84e79338..67ae79a13ab 100644 --- a/reflex/utils/misc.py +++ b/reflex/utils/misc.py @@ -103,7 +103,7 @@ def preload_color_theme(): Returns: Script: A script component to add to App.head_components """ - from reflex_components.el.elements.scripts import Script + from reflex_components_core.el.elements.scripts import Script # Create direct inline script content (like next-themes dangerouslySetInnerHTML) script_content = """ diff --git a/reflex/utils/pyi_generator.py b/reflex/utils/pyi_generator.py index 858743050c7..355a5250cd6 100644 --- a/reflex/utils/pyi_generator.py +++ b/reflex/utils/pyi_generator.py @@ -83,7 +83,7 @@ # TODO: fix import ordering and unused imports with ruff later DEFAULT_IMPORTS = { "typing": sorted(DEFAULT_TYPING_IMPORTS), - "reflex_components.core.breakpoints": ["Breakpoints"], + "reflex_components_core.core.breakpoints": ["Breakpoints"], "reflex.event": [ "EventChain", "EventHandler", @@ -1243,25 +1243,25 @@ def _write_pyi_file(module_path: Path, source: str) -> str: # Mapping from component subpackage name to its target Python package. _COMPONENT_SUBPACKAGE_TARGETS: dict[str, str] = { # reflex-components (base package) - "base": "reflex_components.base", - "core": "reflex_components.core", - "datadisplay": "reflex_components.datadisplay", - "el": "reflex_components.el", - "gridjs": "reflex_gridjs", - "lucide": "reflex_lucide", - "moment": "reflex_moment", + "base": "reflex_components_core.base", + "core": "reflex_components_core.core", + "datadisplay": "reflex_components_core.datadisplay", + "el": "reflex_components_core.el", + "gridjs": "reflex_components_gridjs", + "lucide": "reflex_components_lucide", + "moment": "reflex_components_moment", # Deep overrides (datadisplay split) - "datadisplay.code": "reflex_code.code", - "datadisplay.shiki_code_block": "reflex_code.shiki_code_block", - "datadisplay.dataeditor": "reflex_dataeditor.dataeditor", + "datadisplay.code": "reflex_components_code.code", + "datadisplay.shiki_code_block": "reflex_components_code.shiki_code_block", + "datadisplay.dataeditor": "reflex_components_dataeditor.dataeditor", # Standalone packages - "markdown": "reflex_markdown", - "plotly": "reflex_plotly", - "radix": "reflex_radix", - "react_player": "reflex_react_player", - "react_router": "reflex_react_router", - "recharts": "reflex_recharts", - "sonner": "reflex_sonner", + "markdown": "reflex_components_markdown", + "plotly": "reflex_components_plotly", + "radix": "reflex_components_radix", + "react_player": "reflex_components_react_player", + "react_router": "reflex_components_react_router", + "recharts": "reflex_components_recharts", + "sonner": "reflex_components_sonner", } @@ -1272,13 +1272,13 @@ def _rewrite_component_import(module: str) -> str: module: The module path from ``_SUBMOD_ATTRS`` (e.g. ``"components.radix.themes.base"``). Returns: - An absolute import path (``"reflex_radix.themes.base"``) for moved + An absolute import path (``"reflex_components_radix.themes.base"``) for moved components, or a relative path (``".components.component"``) for everything else. """ if module == "components": # "components": ["el", "radix", ...] — these are re-exported submodules. # Can't map to a single package, but the pyi generator handles each attr individually. - return "reflex_components" + return "reflex_components_core" if module.startswith("components."): rest = module[len("components.") :] # Try progressively deeper matches (e.g. "datadisplay.code" before "datadisplay"). diff --git a/reflex/utils/types.py b/reflex/utils/types.py index dc4480d1ca1..0005f3fb7fd 100644 --- a/reflex/utils/types.py +++ b/reflex/utils/types.py @@ -34,7 +34,7 @@ from typing import get_origin as get_origin_og from typing import get_type_hints as get_type_hints_og -from reflex_components.core.breakpoints import Breakpoints +from reflex_components_core.core.breakpoints import Breakpoints from typing_extensions import Self as Self from typing_extensions import override as override diff --git a/reflex/vars/object.py b/reflex/vars/object.py index c044efd5546..53eb37ae700 100644 --- a/reflex/vars/object.py +++ b/reflex/vars/object.py @@ -251,7 +251,7 @@ def get(self, key: Var | Any, default: Var | Any | None = None) -> Var: Returns: The item from the object. """ - from reflex_components.core.cond import cond + from reflex_components_core.core.cond import cond if default is None: default = Var.create(None) diff --git a/scripts/make_pyi.py b/scripts/make_pyi.py index 318acc7347b..7976d24caff 100644 --- a/scripts/make_pyi.py +++ b/scripts/make_pyi.py @@ -16,19 +16,19 @@ "reflex/components", "reflex/experimental", "reflex/__init__.py", - "packages/reflex-code/src/reflex_code", - "packages/reflex-components/src/reflex_components", - "packages/reflex-dataeditor/src/reflex_dataeditor", - "packages/reflex-gridjs/src/reflex_gridjs", - "packages/reflex-lucide/src/reflex_lucide", - "packages/reflex-markdown/src/reflex_markdown", - "packages/reflex-moment/src/reflex_moment", - "packages/reflex-plotly/src/reflex_plotly", - "packages/reflex-radix/src/reflex_radix", - "packages/reflex-react-player/src/reflex_react_player", - "packages/reflex-react-router/src/reflex_react_router", - "packages/reflex-recharts/src/reflex_recharts", - "packages/reflex-sonner/src/reflex_sonner", + "packages/reflex-code/src/reflex_components_code", + "packages/reflex-components/src/reflex_components_core", + "packages/reflex-dataeditor/src/reflex_components_dataeditor", + "packages/reflex-gridjs/src/reflex_components_gridjs", + "packages/reflex-lucide/src/reflex_components_lucide", + "packages/reflex-markdown/src/reflex_components_markdown", + "packages/reflex-moment/src/reflex_components_moment", + "packages/reflex-plotly/src/reflex_components_plotly", + "packages/reflex-radix/src/reflex_components_radix", + "packages/reflex-react-player/src/reflex_components_react_player", + "packages/reflex-react-router/src/reflex_components_react_router", + "packages/reflex-recharts/src/reflex_components_recharts", + "packages/reflex-sonner/src/reflex_components_sonner", ] diff --git a/tests/integration/test_extra_overlay_function.py b/tests/integration/test_extra_overlay_function.py index f0b8511b39f..e766360f8ba 100644 --- a/tests/integration/test_extra_overlay_function.py +++ b/tests/integration/test_extra_overlay_function.py @@ -25,7 +25,7 @@ def index(): app = rx.App() rx.config.get_config().extra_overlay_function = ( - "reflex_radix.themes.components.button" + "reflex_components_radix.themes.components.button" ) app.add_page(index) diff --git a/tests/integration/test_icon.py b/tests/integration/test_icon.py index 2fff2426389..bcc59f026ef 100644 --- a/tests/integration/test_icon.py +++ b/tests/integration/test_icon.py @@ -3,14 +3,14 @@ from collections.abc import Generator import pytest -from reflex_lucide.icon import LUCIDE_ICON_LIST +from reflex_components_lucide.icon import LUCIDE_ICON_LIST from selenium.webdriver.common.by import By from reflex.testing import AppHarness, WebDriver def Icons(): - from reflex_lucide.icon import LUCIDE_ICON_LIST + from reflex_components_lucide.icon import LUCIDE_ICON_LIST import reflex as rx diff --git a/tests/units/compiler/test_compiler.py b/tests/units/compiler/test_compiler.py index 7baff787971..2cac70f49da 100644 --- a/tests/units/compiler/test_compiler.py +++ b/tests/units/compiler/test_compiler.py @@ -4,8 +4,8 @@ import pytest from pytest_mock import MockerFixture -from reflex_components.base import document -from reflex_components.el.elements.metadata import Link +from reflex_components_core.base import document +from reflex_components_core.el.elements.metadata import Link from reflex import constants from reflex.compiler import compiler, utils diff --git a/tests/units/components/base/test_bare.py b/tests/units/components/base/test_bare.py index ada6c9505cf..91e3c58fcf4 100644 --- a/tests/units/components/base/test_bare.py +++ b/tests/units/components/base/test_bare.py @@ -1,5 +1,5 @@ import pytest -from reflex_components.base.bare import Bare +from reflex_components_core.base.bare import Bare from reflex.vars.base import Var diff --git a/tests/units/components/base/test_link.py b/tests/units/components/base/test_link.py index 1a6c2a171af..9c78f4350f6 100644 --- a/tests/units/components/base/test_link.py +++ b/tests/units/components/base/test_link.py @@ -1,4 +1,4 @@ -from reflex_components.base.link import RawLink, ScriptTag +from reflex_components_core.base.link import RawLink, ScriptTag def test_raw_link(): diff --git a/tests/units/components/base/test_script.py b/tests/units/components/base/test_script.py index e41ba701367..0f25b8d862d 100644 --- a/tests/units/components/base/test_script.py +++ b/tests/units/components/base/test_script.py @@ -1,7 +1,7 @@ """Test that element script renders correctly.""" import pytest -from reflex_components.base.script import Script +from reflex_components_core.base.script import Script def test_script_inline(): diff --git a/tests/units/components/core/test_banner.py b/tests/units/components/core/test_banner.py index d2600bf6ef2..04987db5311 100644 --- a/tests/units/components/core/test_banner.py +++ b/tests/units/components/core/test_banner.py @@ -1,11 +1,11 @@ -from reflex_components.core.banner import ( +from reflex_components_core.core.banner import ( ConnectionBanner, ConnectionModal, ConnectionPulser, WebsocketTargetURL, ) -from reflex_radix.themes.base import RadixThemesComponent -from reflex_radix.themes.typography.text import Text +from reflex_components_radix.themes.base import RadixThemesComponent +from reflex_components_radix.themes.typography.text import Text def test_websocket_target_url(): diff --git a/tests/units/components/core/test_colors.py b/tests/units/components/core/test_colors.py index 08e07f24f09..5e3f621ec61 100644 --- a/tests/units/components/core/test_colors.py +++ b/tests/units/components/core/test_colors.py @@ -1,5 +1,5 @@ import pytest -from reflex_code.code import CodeBlock +from reflex_components_code.code import CodeBlock import reflex as rx from reflex.constants.colors import Color diff --git a/tests/units/components/core/test_cond.py b/tests/units/components/core/test_cond.py index 13858269894..9175ded0974 100644 --- a/tests/units/components/core/test_cond.py +++ b/tests/units/components/core/test_cond.py @@ -2,9 +2,9 @@ from typing import Any import pytest -from reflex_components.base.fragment import Fragment -from reflex_components.core.cond import Cond, cond -from reflex_radix.themes.typography.text import Text +from reflex_components_core.base.fragment import Fragment +from reflex_components_core.core.cond import Cond, cond +from reflex_components_radix.themes.typography.text import Text from reflex.constants.state import FIELD_MARKER from reflex.state import BaseState diff --git a/tests/units/components/core/test_debounce.py b/tests/units/components/core/test_debounce.py index f5b7247d4f0..44aacbaf122 100644 --- a/tests/units/components/core/test_debounce.py +++ b/tests/units/components/core/test_debounce.py @@ -1,7 +1,7 @@ """Test that DebounceInput collapses nested forms.""" import pytest -from reflex_components.core.debounce import DEFAULT_DEBOUNCE_TIMEOUT +from reflex_components_core.core.debounce import DEFAULT_DEBOUNCE_TIMEOUT import reflex as rx from reflex.state import BaseState diff --git a/tests/units/components/core/test_foreach.py b/tests/units/components/core/test_foreach.py index aead018d41a..ee64712cc0b 100644 --- a/tests/units/components/core/test_foreach.py +++ b/tests/units/components/core/test_foreach.py @@ -1,12 +1,12 @@ import pytest -from reflex_components.core.foreach import ( +from reflex_components_core.core.foreach import ( Foreach, ForeachRenderError, ForeachVarError, foreach, ) -from reflex_radix.themes.layout.box import box -from reflex_radix.themes.typography.text import text +from reflex_components_radix.themes.layout.box import box +from reflex_components_radix.themes.typography.text import text import reflex as rx from reflex import el diff --git a/tests/units/components/core/test_html.py b/tests/units/components/core/test_html.py index 27780481508..4241c9344ab 100644 --- a/tests/units/components/core/test_html.py +++ b/tests/units/components/core/test_html.py @@ -1,5 +1,5 @@ import pytest -from reflex_components.core.html import Html +from reflex_components_core.core.html import Html from reflex.state import State diff --git a/tests/units/components/core/test_match.py b/tests/units/components/core/test_match.py index 055c8717155..310f9d41cb2 100644 --- a/tests/units/components/core/test_match.py +++ b/tests/units/components/core/test_match.py @@ -1,7 +1,7 @@ import re import pytest -from reflex_components.core.match import Match +from reflex_components_core.core.match import Match import reflex as rx from reflex.components.component import Component @@ -141,7 +141,7 @@ def test_match_on_component_without_default(): """Test that matching cases with return values as components returns a Fragment as the default case if not provided. """ - from reflex_components.base.fragment import Fragment + from reflex_components_core.base.fragment import Fragment match_case_tuples = ( (1, rx.text("first value")), @@ -264,7 +264,7 @@ def test_match_case_tuple_elements(match_case): ), ( 'Match cases should have the same return types. Case 3 with return value `jsx(RadixThemesText,{as:"p"},"first value")` ' - "of type is not " + "of type is not " ), ), ], diff --git a/tests/units/components/core/test_responsive.py b/tests/units/components/core/test_responsive.py index 6d5392bfc18..515af37af9e 100644 --- a/tests/units/components/core/test_responsive.py +++ b/tests/units/components/core/test_responsive.py @@ -1,11 +1,11 @@ -from reflex_components.core.responsive import ( +from reflex_components_core.core.responsive import ( desktop_only, mobile_and_tablet, mobile_only, tablet_and_desktop, tablet_only, ) -from reflex_radix.themes.layout.box import Box +from reflex_components_radix.themes.layout.box import Box def test_mobile_only(): diff --git a/tests/units/components/core/test_upload.py b/tests/units/components/core/test_upload.py index cbf201b2f08..0e1fe3e2137 100644 --- a/tests/units/components/core/test_upload.py +++ b/tests/units/components/core/test_upload.py @@ -1,6 +1,6 @@ from typing import Any -from reflex_components.core.upload import ( +from reflex_components_core.core.upload import ( StyledUpload, Upload, UploadNamespace, diff --git a/tests/units/components/datadisplay/test_code.py b/tests/units/components/datadisplay/test_code.py index 79558c8d317..5b20c0584ea 100644 --- a/tests/units/components/datadisplay/test_code.py +++ b/tests/units/components/datadisplay/test_code.py @@ -1,5 +1,5 @@ import pytest -from reflex_code.code import CodeBlock, Theme +from reflex_components_code.code import CodeBlock, Theme @pytest.mark.parametrize( diff --git a/tests/units/components/datadisplay/test_dataeditor.py b/tests/units/components/datadisplay/test_dataeditor.py index 4359865264e..301149710ae 100644 --- a/tests/units/components/datadisplay/test_dataeditor.py +++ b/tests/units/components/datadisplay/test_dataeditor.py @@ -1,4 +1,4 @@ -from reflex_dataeditor.dataeditor import DataEditor +from reflex_components_dataeditor.dataeditor import DataEditor def test_dataeditor(): diff --git a/tests/units/components/datadisplay/test_datatable.py b/tests/units/components/datadisplay/test_datatable.py index 1e7ccb00eb5..de991618465 100644 --- a/tests/units/components/datadisplay/test_datatable.py +++ b/tests/units/components/datadisplay/test_datatable.py @@ -1,6 +1,6 @@ import pandas as pd import pytest -from reflex_gridjs.datatable import DataTable +from reflex_components_gridjs.datatable import DataTable import reflex as rx from reflex.constants.state import FIELD_MARKER diff --git a/tests/units/components/datadisplay/test_shiki_code.py b/tests/units/components/datadisplay/test_shiki_code.py index 4444a50f6b1..5b33be816f7 100644 --- a/tests/units/components/datadisplay/test_shiki_code.py +++ b/tests/units/components/datadisplay/test_shiki_code.py @@ -1,13 +1,13 @@ import pytest -from reflex_code.shiki_code_block import ( +from reflex_components_code.shiki_code_block import ( ShikiBaseTransformers, ShikiCodeBlock, ShikiHighLevelCodeBlock, ShikiJsTransformer, ) -from reflex_components.el.elements.forms import Button -from reflex_lucide.icon import Icon -from reflex_radix.themes.layout.box import Box +from reflex_components_core.el.elements.forms import Button +from reflex_components_lucide.icon import Icon +from reflex_components_radix.themes.layout.box import Box from reflex.style import Style from reflex.vars import Var diff --git a/tests/units/components/el/test_svg.py b/tests/units/components/el/test_svg.py index f409b80cce9..951b4a3afaa 100644 --- a/tests/units/components/el/test_svg.py +++ b/tests/units/components/el/test_svg.py @@ -1,4 +1,4 @@ -from reflex_components.el.elements.media import ( +from reflex_components_core.el.elements.media import ( Circle, Defs, Ellipse, diff --git a/tests/units/components/forms/test_form.py b/tests/units/components/forms/test_form.py index 999038674b9..ab80d2c9147 100644 --- a/tests/units/components/forms/test_form.py +++ b/tests/units/components/forms/test_form.py @@ -1,4 +1,4 @@ -from reflex_radix.primitives.form import Form +from reflex_components_radix.primitives.form import Form from reflex.event import EventChain, prevent_default from reflex.vars.base import Var diff --git a/tests/units/components/graphing/test_recharts.py b/tests/units/components/graphing/test_recharts.py index bc551d517f6..3e4268eb891 100644 --- a/tests/units/components/graphing/test_recharts.py +++ b/tests/units/components/graphing/test_recharts.py @@ -1,4 +1,4 @@ -from reflex_recharts.charts import ( +from reflex_components_recharts.charts import ( AreaChart, BarChart, LineChart, @@ -7,7 +7,7 @@ RadialBarChart, ScatterChart, ) -from reflex_recharts.general import ResponsiveContainer +from reflex_components_recharts.general import ResponsiveContainer def test_area_chart(): diff --git a/tests/units/components/lucide/test_icon.py b/tests/units/components/lucide/test_icon.py index b80f84cf453..26e70f7646d 100644 --- a/tests/units/components/lucide/test_icon.py +++ b/tests/units/components/lucide/test_icon.py @@ -1,5 +1,5 @@ import pytest -from reflex_lucide.icon import LUCIDE_ICON_LIST, LUCIDE_ICON_MAPPING_OVERRIDE, Icon +from reflex_components_lucide.icon import LUCIDE_ICON_LIST, LUCIDE_ICON_MAPPING_OVERRIDE, Icon from reflex.utils import format diff --git a/tests/units/components/markdown/test_markdown.py b/tests/units/components/markdown/test_markdown.py index 4666f2b9fc4..4f766c42aaa 100644 --- a/tests/units/components/markdown/test_markdown.py +++ b/tests/units/components/markdown/test_markdown.py @@ -1,9 +1,9 @@ import pytest -from reflex_code.code import CodeBlock -from reflex_code.shiki_code_block import ShikiHighLevelCodeBlock -from reflex_markdown.markdown import Markdown, MarkdownComponentMap -from reflex_radix.themes.layout.box import Box -from reflex_radix.themes.typography.heading import Heading +from reflex_components_code.code import CodeBlock +from reflex_components_code.shiki_code_block import ShikiHighLevelCodeBlock +from reflex_components_markdown.markdown import Markdown, MarkdownComponentMap +from reflex_components_radix.themes.layout.box import Box +from reflex_components_radix.themes.typography.heading import Heading from reflex.components.component import Component, memo from reflex.vars.base import Var diff --git a/tests/units/components/radix/test_icon_button.py b/tests/units/components/radix/test_icon_button.py index a6aa9937db5..7314293b970 100644 --- a/tests/units/components/radix/test_icon_button.py +++ b/tests/units/components/radix/test_icon_button.py @@ -1,6 +1,6 @@ import pytest -from reflex_lucide.icon import Icon -from reflex_radix.themes.components.icon_button import IconButton +from reflex_components_lucide.icon import Icon +from reflex_components_radix.themes.components.icon_button import IconButton from reflex.style import Style from reflex.vars.base import LiteralVar diff --git a/tests/units/components/radix/test_layout.py b/tests/units/components/radix/test_layout.py index 87803df9d7b..f84b27fda2f 100644 --- a/tests/units/components/radix/test_layout.py +++ b/tests/units/components/radix/test_layout.py @@ -1,4 +1,4 @@ -from reflex_radix.themes.layout.base import LayoutComponent +from reflex_components_radix.themes.layout.base import LayoutComponent def test_layout_component(): diff --git a/tests/units/components/recharts/test_cartesian.py b/tests/units/components/recharts/test_cartesian.py index 17191186d5c..d17cec511d3 100644 --- a/tests/units/components/recharts/test_cartesian.py +++ b/tests/units/components/recharts/test_cartesian.py @@ -1,4 +1,4 @@ -from reflex_recharts import Area, Bar, Brush, Line, Scatter, XAxis, YAxis, ZAxis +from reflex_components_recharts import Area, Bar, Brush, Line, Scatter, XAxis, YAxis, ZAxis def test_xaxis(): diff --git a/tests/units/components/recharts/test_polar.py b/tests/units/components/recharts/test_polar.py index e85c912df6f..27c3561a44c 100644 --- a/tests/units/components/recharts/test_polar.py +++ b/tests/units/components/recharts/test_polar.py @@ -1,4 +1,4 @@ -from reflex_recharts import ( +from reflex_components_recharts import ( Pie, PolarAngleAxis, PolarGrid, diff --git a/tests/units/components/test_component.py b/tests/units/components/test_component.py index 025237c082f..550c2dc95e4 100644 --- a/tests/units/components/test_component.py +++ b/tests/units/components/test_component.py @@ -2,9 +2,9 @@ from typing import Any, ClassVar import pytest -from reflex_components.base.bare import Bare -from reflex_components.base.fragment import Fragment -from reflex_radix.themes.layout.box import Box +from reflex_components_core.base.bare import Bare +from reflex_components_core.base.fragment import Fragment +from reflex_components_radix.themes.layout.box import Box import reflex as rx from reflex.base import Base @@ -861,7 +861,7 @@ def my_component(width: Var[int], color: Var[str]): color=color, ) - from reflex_radix.themes.typography.text import Text + from reflex_components_radix.themes.typography.text import Text ccomponent = my_component( rx.text("child"), width=LiteralVar.create(1), color=LiteralVar.create("red") diff --git a/tests/units/components/test_component_state.py b/tests/units/components/test_component_state.py index b27d86d05ae..333f9f5603d 100644 --- a/tests/units/components/test_component_state.py +++ b/tests/units/components/test_component_state.py @@ -1,7 +1,7 @@ """Ensure that Components returned by ComponentState.create have independent State classes.""" import pytest -from reflex_components.base.bare import Bare +from reflex_components_core.base.bare import Bare import reflex as rx from reflex.utils.exceptions import ReflexRuntimeError diff --git a/tests/units/components/typography/test_markdown.py b/tests/units/components/typography/test_markdown.py index 4e13e51291c..2ddc591252b 100644 --- a/tests/units/components/typography/test_markdown.py +++ b/tests/units/components/typography/test_markdown.py @@ -1,5 +1,5 @@ import pytest -from reflex_markdown.markdown import Markdown +from reflex_components_markdown.markdown import Markdown import reflex as rx diff --git a/tests/units/test_app.py b/tests/units/test_app.py index 7e479ff1ebd..e5a75395294 100644 --- a/tests/units/test_app.py +++ b/tests/units/test_app.py @@ -15,10 +15,10 @@ import pytest from pytest_mock import MockerFixture -from reflex_components.base.bare import Bare -from reflex_components.base.fragment import Fragment -from reflex_components.core.cond import Cond -from reflex_radix.themes.typography.text import Text +from reflex_components_core.base.bare import Bare +from reflex_components_core.base.fragment import Fragment +from reflex_components_core.core.cond import Cond +from reflex_components_radix.themes.typography.text import Text from starlette.applications import Starlette from starlette.datastructures import FormData, UploadFile from starlette.responses import StreamingResponse @@ -1842,7 +1842,7 @@ def test_call_app(): def test_app_with_optional_endpoints(): - from reflex_components.core.upload import Upload + from reflex_components_core.core.upload import Upload app = App() Upload.is_used = True diff --git a/tests/units/test_event.py b/tests/units/test_event.py index 34e111dd843..876da35e529 100644 --- a/tests/units/test_event.py +++ b/tests/units/test_event.py @@ -640,7 +640,7 @@ async def handle_old_background(self): def test_event_var_in_rx_cond(): """Test that EventVar and EventChainVar cannot be used in rx.cond().""" - from reflex_components.core.cond import cond as rx_cond + from reflex_components_core.core.cond import cond as rx_cond class S(BaseState): @event diff --git a/tests/units/test_var.py b/tests/units/test_var.py index 51a0195eae2..32778417a75 100644 --- a/tests/units/test_var.py +++ b/tests/units/test_var.py @@ -1896,7 +1896,7 @@ class StateWithVar(rx.State): field: int = 1 mocker.patch( - "reflex_components.base.bare.get_performance_mode", + "reflex_components_core.base.bare.get_performance_mode", return_value=PerformanceMode.RAISE, ) @@ -1906,7 +1906,7 @@ class StateWithVar(rx.State): ) mocker.patch( - "reflex_components.base.bare.get_performance_mode", + "reflex_components_core.base.bare.get_performance_mode", return_value=PerformanceMode.OFF, ) diff --git a/tests/units/utils/test_serializers.py b/tests/units/utils/test_serializers.py index 3370fa79c5a..c3a01bae508 100644 --- a/tests/units/utils/test_serializers.py +++ b/tests/units/utils/test_serializers.py @@ -6,7 +6,7 @@ from typing import Any import pytest -from reflex_components.core.colors import Color +from reflex_components_core.core.colors import Color from reflex.base import Base from reflex.utils import serializers From 62cbb9bd08f9444c8f9ed4ccc94c885b0ad28e08 Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Tue, 24 Mar 2026 18:30:57 -0700 Subject: [PATCH 018/113] rename --- .../reflex-components-code/pyproject.toml | 9 +- .../reflex-components-core/pyproject.toml | 9 +- .../src/reflex_components_core/base/meta.py | 4 +- .../src/reflex_components_core/core/banner.py | 6 +- .../pyproject.toml | 9 +- .../reflex-components-gridjs/pyproject.toml | 9 +- .../reflex-components-lucide/pyproject.toml | 9 +- .../reflex-components-markdown/pyproject.toml | 9 +- .../reflex-components-moment/pyproject.toml | 9 +- .../reflex-components-plotly/pyproject.toml | 9 +- .../reflex-components-radix/pyproject.toml | 9 +- .../themes/components/alert_dialog.py | 5 +- .../themes/components/dialog.py | 5 +- .../themes/components/hover_card.py | 5 +- .../themes/components/popover.py | 5 +- .../pyproject.toml | 9 +- .../pyproject.toml | 9 +- .../reflex-components-recharts/pyproject.toml | 9 +- .../reflex-components-sonner/pyproject.toml | 9 +- packages/reflex-docgen/pyproject.toml | 9 +- pyi_hashes.json | 240 +++++++++--------- pyproject.toml | 7 +- scripts/make_pyi.py | 26 +- tests/units/components/lucide/test_icon.py | 6 +- .../components/recharts/test_cartesian.py | 11 +- uv.lock | 185 +++++++------- 26 files changed, 346 insertions(+), 285 deletions(-) diff --git a/packages/reflex-components-code/pyproject.toml b/packages/reflex-components-code/pyproject.toml index f8de699d651..e7b95abc4a0 100644 --- a/packages/reflex-components-code/pyproject.toml +++ b/packages/reflex-components-code/pyproject.toml @@ -9,9 +9,12 @@ requires-python = ">=3.10" dependencies = [] [tool.hatch.version] -source = "vcs" -raw-options = { root = "../..", tag_regex = "^reflex-components-code-v(?P.+)$" } +source = "uv-dynamic-versioning" + +[tool.uv-dynamic-versioning] +pattern-prefix = "reflex-components-code-" +fallback-version = "0.0.0dev0" [build-system] -requires = ["hatchling", "hatch-vcs"] +requires = ["hatchling", "uv-dynamic-versioning"] build-backend = "hatchling.build" diff --git a/packages/reflex-components-core/pyproject.toml b/packages/reflex-components-core/pyproject.toml index 8eaf58eccf3..1f7f1a023e0 100644 --- a/packages/reflex-components-core/pyproject.toml +++ b/packages/reflex-components-core/pyproject.toml @@ -9,9 +9,12 @@ requires-python = ">=3.10" dependencies = [] [tool.hatch.version] -source = "vcs" -raw-options = { root = "../..", tag_regex = "^reflex-components-core-v(?P.+)$" } +source = "uv-dynamic-versioning" + +[tool.uv-dynamic-versioning] +pattern-prefix = "reflex-components-core-" +fallback-version = "0.0.0dev0" [build-system] -requires = ["hatchling", "hatch-vcs"] +requires = ["hatchling", "uv-dynamic-versioning"] build-backend = "hatchling.build" diff --git a/packages/reflex-components-core/src/reflex_components_core/base/meta.py b/packages/reflex-components-core/src/reflex_components_core/base/meta.py index f8fe76eeb25..947e68f4e53 100644 --- a/packages/reflex-components-core/src/reflex_components_core/base/meta.py +++ b/packages/reflex-components-core/src/reflex_components_core/base/meta.py @@ -6,7 +6,9 @@ from reflex.vars.base import Var from reflex_components_core.base.bare import Bare from reflex_components_core.el import elements -from reflex_components_core.el.elements.metadata import Meta as Meta # for compatibility +from reflex_components_core.el.elements.metadata import ( + Meta as Meta, +) # for compatibility class Title(elements.Title): diff --git a/packages/reflex-components-core/src/reflex_components_core/core/banner.py b/packages/reflex-components-core/src/reflex_components_core/core/banner.py index 2bfc6ea59a0..eb6f9cac585 100644 --- a/packages/reflex-components-core/src/reflex_components_core/core/banner.py +++ b/packages/reflex-components-core/src/reflex_components_core/core/banner.py @@ -3,7 +3,11 @@ from __future__ import annotations from reflex_components_lucide.icon import Icon -from reflex_components_radix.themes.components.dialog import DialogContent, DialogRoot, DialogTitle +from reflex_components_radix.themes.components.dialog import ( + DialogContent, + DialogRoot, + DialogTitle, +) from reflex_components_radix.themes.layout.flex import Flex from reflex_components_radix.themes.typography.text import Text from reflex_components_sonner.toast import ToastProps, toast_ref diff --git a/packages/reflex-components-dataeditor/pyproject.toml b/packages/reflex-components-dataeditor/pyproject.toml index d8f8e71c90d..025f3230e3c 100644 --- a/packages/reflex-components-dataeditor/pyproject.toml +++ b/packages/reflex-components-dataeditor/pyproject.toml @@ -9,9 +9,12 @@ requires-python = ">=3.10" dependencies = [] [tool.hatch.version] -source = "vcs" -raw-options = { root = "../..", tag_regex = "^reflex-components-dataeditor-v(?P.+)$" } +source = "uv-dynamic-versioning" + +[tool.uv-dynamic-versioning] +pattern-prefix = "reflex-components-dataeditor-" +fallback-version = "0.0.0dev0" [build-system] -requires = ["hatchling", "hatch-vcs"] +requires = ["hatchling", "uv-dynamic-versioning"] build-backend = "hatchling.build" diff --git a/packages/reflex-components-gridjs/pyproject.toml b/packages/reflex-components-gridjs/pyproject.toml index f44dd96822b..1429c3de472 100644 --- a/packages/reflex-components-gridjs/pyproject.toml +++ b/packages/reflex-components-gridjs/pyproject.toml @@ -9,9 +9,12 @@ requires-python = ">=3.10" dependencies = [] [tool.hatch.version] -source = "vcs" -raw-options = { root = "../..", tag_regex = "^reflex-components-gridjs-v(?P.+)$" } +source = "uv-dynamic-versioning" + +[tool.uv-dynamic-versioning] +pattern-prefix = "reflex-components-gridjs-" +fallback-version = "0.0.0dev0" [build-system] -requires = ["hatchling", "hatch-vcs"] +requires = ["hatchling", "uv-dynamic-versioning"] build-backend = "hatchling.build" diff --git a/packages/reflex-components-lucide/pyproject.toml b/packages/reflex-components-lucide/pyproject.toml index 1f2201311cd..23c722dd8aa 100644 --- a/packages/reflex-components-lucide/pyproject.toml +++ b/packages/reflex-components-lucide/pyproject.toml @@ -9,9 +9,12 @@ requires-python = ">=3.10" dependencies = [] [tool.hatch.version] -source = "vcs" -raw-options = { root = "../..", tag_regex = "^reflex-components-lucide-v(?P.+)$" } +source = "uv-dynamic-versioning" + +[tool.uv-dynamic-versioning] +pattern-prefix = "reflex-components-lucide-" +fallback-version = "0.0.0dev0" [build-system] -requires = ["hatchling", "hatch-vcs"] +requires = ["hatchling", "uv-dynamic-versioning"] build-backend = "hatchling.build" diff --git a/packages/reflex-components-markdown/pyproject.toml b/packages/reflex-components-markdown/pyproject.toml index d16a70fde1b..57743013bf5 100644 --- a/packages/reflex-components-markdown/pyproject.toml +++ b/packages/reflex-components-markdown/pyproject.toml @@ -9,9 +9,12 @@ requires-python = ">=3.10" dependencies = [] [tool.hatch.version] -source = "vcs" -raw-options = { root = "../..", tag_regex = "^reflex-components-markdown-v(?P.+)$" } +source = "uv-dynamic-versioning" + +[tool.uv-dynamic-versioning] +pattern-prefix = "reflex-components-markdown-" +fallback-version = "0.0.0dev0" [build-system] -requires = ["hatchling", "hatch-vcs"] +requires = ["hatchling", "uv-dynamic-versioning"] build-backend = "hatchling.build" diff --git a/packages/reflex-components-moment/pyproject.toml b/packages/reflex-components-moment/pyproject.toml index c0ea1e8047d..aac90ca206e 100644 --- a/packages/reflex-components-moment/pyproject.toml +++ b/packages/reflex-components-moment/pyproject.toml @@ -9,9 +9,12 @@ requires-python = ">=3.10" dependencies = [] [tool.hatch.version] -source = "vcs" -raw-options = { root = "../..", tag_regex = "^reflex-components-moment-v(?P.+)$" } +source = "uv-dynamic-versioning" + +[tool.uv-dynamic-versioning] +pattern-prefix = "reflex-components-moment-" +fallback-version = "0.0.0dev0" [build-system] -requires = ["hatchling", "hatch-vcs"] +requires = ["hatchling", "uv-dynamic-versioning"] build-backend = "hatchling.build" diff --git a/packages/reflex-components-plotly/pyproject.toml b/packages/reflex-components-plotly/pyproject.toml index f6dcf0048a2..6c9e1203c35 100644 --- a/packages/reflex-components-plotly/pyproject.toml +++ b/packages/reflex-components-plotly/pyproject.toml @@ -9,9 +9,12 @@ requires-python = ">=3.10" dependencies = [] [tool.hatch.version] -source = "vcs" -raw-options = { root = "../..", tag_regex = "^reflex-components-plotly-v(?P.+)$" } +source = "uv-dynamic-versioning" + +[tool.uv-dynamic-versioning] +pattern-prefix = "reflex-components-plotly-" +fallback-version = "0.0.0dev0" [build-system] -requires = ["hatchling", "hatch-vcs"] +requires = ["hatchling", "uv-dynamic-versioning"] build-backend = "hatchling.build" diff --git a/packages/reflex-components-radix/pyproject.toml b/packages/reflex-components-radix/pyproject.toml index d2b16039938..975ca929323 100644 --- a/packages/reflex-components-radix/pyproject.toml +++ b/packages/reflex-components-radix/pyproject.toml @@ -9,9 +9,12 @@ requires-python = ">=3.10" dependencies = [] [tool.hatch.version] -source = "vcs" -raw-options = { root = "../..", tag_regex = "^reflex-components-radix-v(?P.+)$" } +source = "uv-dynamic-versioning" + +[tool.uv-dynamic-versioning] +pattern-prefix = "reflex-components-radix-" +fallback-version = "0.0.0dev0" [build-system] -requires = ["hatchling", "hatch-vcs"] +requires = ["hatchling", "uv-dynamic-versioning"] build-backend = "hatchling.build" diff --git a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/alert_dialog.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/alert_dialog.py index c952288ff81..500924d51a8 100644 --- a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/alert_dialog.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/alert_dialog.py @@ -9,7 +9,10 @@ from reflex.constants.compiler import MemoizationMode from reflex.event import EventHandler, no_args_event_spec, passthrough_event_spec from reflex.vars.base import Var -from reflex_components_radix.themes.base import RadixThemesComponent, RadixThemesTriggerComponent +from reflex_components_radix.themes.base import ( + RadixThemesComponent, + RadixThemesTriggerComponent, +) LiteralContentSize = Literal["1", "2", "3", "4"] diff --git a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/dialog.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/dialog.py index b0bc43827a3..bc7ab03bed2 100644 --- a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/dialog.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/dialog.py @@ -9,7 +9,10 @@ from reflex.constants.compiler import MemoizationMode from reflex.event import EventHandler, no_args_event_spec, passthrough_event_spec from reflex.vars.base import Var -from reflex_components_radix.themes.base import RadixThemesComponent, RadixThemesTriggerComponent +from reflex_components_radix.themes.base import ( + RadixThemesComponent, + RadixThemesTriggerComponent, +) class DialogRoot(RadixThemesComponent): diff --git a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/hover_card.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/hover_card.py index 0734d8dd891..71c81063949 100644 --- a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/hover_card.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/hover_card.py @@ -9,7 +9,10 @@ from reflex.constants.compiler import MemoizationMode from reflex.event import EventHandler, passthrough_event_spec from reflex.vars.base import Var -from reflex_components_radix.themes.base import RadixThemesComponent, RadixThemesTriggerComponent +from reflex_components_radix.themes.base import ( + RadixThemesComponent, + RadixThemesTriggerComponent, +) class HoverCardRoot(RadixThemesComponent): diff --git a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/popover.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/popover.py index c7e337ff359..e61916d523b 100644 --- a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/popover.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/popover.py @@ -9,7 +9,10 @@ from reflex.constants.compiler import MemoizationMode from reflex.event import EventHandler, no_args_event_spec, passthrough_event_spec from reflex.vars.base import Var -from reflex_components_radix.themes.base import RadixThemesComponent, RadixThemesTriggerComponent +from reflex_components_radix.themes.base import ( + RadixThemesComponent, + RadixThemesTriggerComponent, +) class PopoverRoot(RadixThemesComponent): diff --git a/packages/reflex-components-react-player/pyproject.toml b/packages/reflex-components-react-player/pyproject.toml index 44806159594..f142c057526 100644 --- a/packages/reflex-components-react-player/pyproject.toml +++ b/packages/reflex-components-react-player/pyproject.toml @@ -9,9 +9,12 @@ requires-python = ">=3.10" dependencies = [] [tool.hatch.version] -source = "vcs" -raw-options = { root = "../..", tag_regex = "^reflex-components-react-player-v(?P.+)$" } +source = "uv-dynamic-versioning" + +[tool.uv-dynamic-versioning] +pattern-prefix = "reflex-components-react-player-" +fallback-version = "0.0.0dev0" [build-system] -requires = ["hatchling", "hatch-vcs"] +requires = ["hatchling", "uv-dynamic-versioning"] build-backend = "hatchling.build" diff --git a/packages/reflex-components-react-router/pyproject.toml b/packages/reflex-components-react-router/pyproject.toml index ecd45acf554..cdf35b0305e 100644 --- a/packages/reflex-components-react-router/pyproject.toml +++ b/packages/reflex-components-react-router/pyproject.toml @@ -9,9 +9,12 @@ requires-python = ">=3.10" dependencies = [] [tool.hatch.version] -source = "vcs" -raw-options = { root = "../..", tag_regex = "^reflex-components-react-router-v(?P.+)$" } +source = "uv-dynamic-versioning" + +[tool.uv-dynamic-versioning] +pattern-prefix = "reflex-components-react-router-" +fallback-version = "0.0.0dev0" [build-system] -requires = ["hatchling", "hatch-vcs"] +requires = ["hatchling", "uv-dynamic-versioning"] build-backend = "hatchling.build" diff --git a/packages/reflex-components-recharts/pyproject.toml b/packages/reflex-components-recharts/pyproject.toml index e8390eae781..8367651473e 100644 --- a/packages/reflex-components-recharts/pyproject.toml +++ b/packages/reflex-components-recharts/pyproject.toml @@ -9,9 +9,12 @@ requires-python = ">=3.10" dependencies = [] [tool.hatch.version] -source = "vcs" -raw-options = { root = "../..", tag_regex = "^reflex-components-recharts-v(?P.+)$" } +source = "uv-dynamic-versioning" + +[tool.uv-dynamic-versioning] +pattern-prefix = "reflex-components-recharts-" +fallback-version = "0.0.0dev0" [build-system] -requires = ["hatchling", "hatch-vcs"] +requires = ["hatchling", "uv-dynamic-versioning"] build-backend = "hatchling.build" diff --git a/packages/reflex-components-sonner/pyproject.toml b/packages/reflex-components-sonner/pyproject.toml index 2b5845ef76c..9cb844413d9 100644 --- a/packages/reflex-components-sonner/pyproject.toml +++ b/packages/reflex-components-sonner/pyproject.toml @@ -9,9 +9,12 @@ requires-python = ">=3.10" dependencies = [] [tool.hatch.version] -source = "vcs" -raw-options = { root = "../..", tag_regex = "^reflex-components-sonner-v(?P.+)$" } +source = "uv-dynamic-versioning" + +[tool.uv-dynamic-versioning] +pattern-prefix = "reflex-components-sonner-" +fallback-version = "0.0.0dev0" [build-system] -requires = ["hatchling", "hatch-vcs"] +requires = ["hatchling", "uv-dynamic-versioning"] build-backend = "hatchling.build" diff --git a/packages/reflex-docgen/pyproject.toml b/packages/reflex-docgen/pyproject.toml index 73b9f61566b..fcd2e920d7e 100644 --- a/packages/reflex-docgen/pyproject.toml +++ b/packages/reflex-docgen/pyproject.toml @@ -19,9 +19,12 @@ dependencies = [ reflex = { workspace = true } [tool.hatch.version] -source = "vcs" -raw-options = { root = "../..", tag_regex = "^reflex-docgen-v(?P.+)$" } +source = "uv-dynamic-versioning" + +[tool.uv-dynamic-versioning] +pattern-prefix = "reflex-docgen-" +fallback-version = "0.0.0dev0" [build-system] -requires = ["hatchling", "hatch-vcs"] +requires = ["hatchling", "uv-dynamic-versioning"] build-backend = "hatchling.build" diff --git a/pyi_hashes.json b/pyi_hashes.json index 21b6081c3d4..d3bf4838e6c 100644 --- a/pyi_hashes.json +++ b/pyi_hashes.json @@ -1,123 +1,123 @@ { - "packages/reflex-code/src/reflex_code/code.pyi": "deaf8aead1880d3c14b189e9817b5b75", - "packages/reflex-code/src/reflex_code/shiki_code_block.pyi": "87e08e6e649e4a43e879cc85dceb7ab0", - "packages/reflex-components/src/reflex_components/__init__.pyi": "44e8ecd8bc946d2a666d96eba51accdd", - "packages/reflex-components/src/reflex_components/base/__init__.pyi": "d2303a7a1a18f9390ffe0d885b20c9d5", - "packages/reflex-components/src/reflex_components/base/app_wrap.pyi": "b21c49560793be0a2dd87f65fcd02243", - "packages/reflex-components/src/reflex_components/base/body.pyi": "4b7b090ca81fa9913ba208a8a2dc3fd4", - "packages/reflex-components/src/reflex_components/base/document.pyi": "0ec240e365318f181aa9fdaa4e0ce7f0", - "packages/reflex-components/src/reflex_components/base/error_boundary.pyi": "81462e3ebf56153fbc1841dc47b0ebba", - "packages/reflex-components/src/reflex_components/base/fragment.pyi": "0e709d1366b59f68a4ba811544ef9650", - "packages/reflex-components/src/reflex_components/base/link.pyi": "29845b3aea542bc9882c17952486c480", - "packages/reflex-components/src/reflex_components/base/meta.pyi": "566bdf49f29297eb3067c248e3971bc2", - "packages/reflex-components/src/reflex_components/base/script.pyi": "7ccc6fba10a33722212e8a6d472d5ef9", - "packages/reflex-components/src/reflex_components/base/strict_mode.pyi": "b3bc948878137f371f0464163ab82a66", - "packages/reflex-components/src/reflex_components/core/__init__.pyi": "d47e3059a7531c2eadc0ec0c4283bd30", - "packages/reflex-components/src/reflex_components/core/auto_scroll.pyi": "4d670d66c3f51a512841e7602cae245e", - "packages/reflex-components/src/reflex_components/core/banner.pyi": "932f39b35449d97b37f868cdd552b00c", - "packages/reflex-components/src/reflex_components/core/clipboard.pyi": "ca379d3a1afaf4da438ac3eaf2cf8e3f", - "packages/reflex-components/src/reflex_components/core/debounce.pyi": "bce792c1b6fc05f2449b46a5e4580d97", - "packages/reflex-components/src/reflex_components/core/helmet.pyi": "9dda69cc8f6563a7062be75a5a9a1766", - "packages/reflex-components/src/reflex_components/core/html.pyi": "3a6e4412987660a94618141d5d9781f3", - "packages/reflex-components/src/reflex_components/core/sticky.pyi": "ced21fad06e510145f5c19500b15eb43", - "packages/reflex-components/src/reflex_components/core/upload.pyi": "bfdcf703ea1e8eb5b07e03dfe1756caa", - "packages/reflex-components/src/reflex_components/core/window_events.pyi": "ad784ba099a49b095f5f7e65511649cf", - "packages/reflex-components/src/reflex_components/datadisplay/__init__.pyi": "8844db1b47917daa949ed5ac2073c7b8", - "packages/reflex-components/src/reflex_components/el/__init__.pyi": "7702649d2a9304bc2d9345aa40c2c039", - "packages/reflex-components/src/reflex_components/el/element.pyi": "55604caea0edce331f7672a8f8e82f4a", - "packages/reflex-components/src/reflex_components/el/elements/__init__.pyi": "a9c6b03e1a2a73e6278a6ebfc3127429", - "packages/reflex-components/src/reflex_components/el/elements/base.pyi": "237c56feee3a1269c7bc7aa77421d57d", - "packages/reflex-components/src/reflex_components/el/elements/forms.pyi": "d8419d197afd40479e97486188527d7d", - "packages/reflex-components/src/reflex_components/el/elements/inline.pyi": "f96166755444d611daa933581c66e955", - "packages/reflex-components/src/reflex_components/el/elements/media.pyi": "98f02f19526fbb4e9f0935ca4fc2f6a7", - "packages/reflex-components/src/reflex_components/el/elements/metadata.pyi": "fed6c39500a30b88a5dd4fd9af0a604e", - "packages/reflex-components/src/reflex_components/el/elements/other.pyi": "2a03bd4e607ce7a90f65f0488eab8ca8", - "packages/reflex-components/src/reflex_components/el/elements/scripts.pyi": "2703d60187f624481247293870bb1a24", - "packages/reflex-components/src/reflex_components/el/elements/sectioning.pyi": "e44354124a23f88e31d5c158430a6341", - "packages/reflex-components/src/reflex_components/el/elements/tables.pyi": "e3b27ff3699fe612ac0ab81689452195", - "packages/reflex-components/src/reflex_components/el/elements/typography.pyi": "2987ced0f04c729751a09dbafdff558d", - "packages/reflex-dataeditor/src/reflex_dataeditor/dataeditor.pyi": "55921260dcc7be07ef80ac7a52d0c197", - "packages/reflex-gridjs/src/reflex_gridjs/datatable.pyi": "6d97e071b20bc42b96529e4fb639cfb8", - "packages/reflex-lucide/src/reflex_lucide/icon.pyi": "f9706da589d2fb75d46afdd44a020926", - "packages/reflex-markdown/src/reflex_markdown/markdown.pyi": "850554a9091ac20a12eb5eb483a1a251", - "packages/reflex-moment/src/reflex_moment/moment.pyi": "ffad10f828272ab33c74c26d8cb4a67e", - "packages/reflex-plotly/src/reflex_plotly/plotly.pyi": "44a46aaa75f1fe5f6e41d883b0802f6f", - "packages/reflex-radix/src/reflex_radix/__init__.pyi": "75cd2277d0f97081d32e19213906ad09", - "packages/reflex-radix/src/reflex_radix/primitives/__init__.pyi": "4c8a8df181c3f560d9a2608dac33dba7", - "packages/reflex-radix/src/reflex_radix/primitives/accordion.pyi": "6aa08325bdec956d5c2243e39639e123", - "packages/reflex-radix/src/reflex_radix/primitives/base.pyi": "95d45d707fd11b29cb9072c46c68dce8", - "packages/reflex-radix/src/reflex_radix/primitives/dialog.pyi": "355d4c52ca984eef4271a99e6036ae9e", - "packages/reflex-radix/src/reflex_radix/primitives/drawer.pyi": "20cab3cb30285d0b146def51f89cd189", - "packages/reflex-radix/src/reflex_radix/primitives/form.pyi": "e6d69600381a201dd5e2a2166cc2d18c", - "packages/reflex-radix/src/reflex_radix/primitives/progress.pyi": "8adcee24351c54ea8c5cbf9cf5a7c962", - "packages/reflex-radix/src/reflex_radix/primitives/slider.pyi": "7f3145da33f3584bed2382d5308d3d71", - "packages/reflex-radix/src/reflex_radix/themes/__init__.pyi": "70deb6963522fc835109fe8286b5830e", - "packages/reflex-radix/src/reflex_radix/themes/base.pyi": "447b2e074290552480b20e68d42f0af6", - "packages/reflex-radix/src/reflex_radix/themes/color_mode.pyi": "7c22d80a9517f67ed2fb5a268df97eec", - "packages/reflex-radix/src/reflex_radix/themes/components/__init__.pyi": "0b669b9a5800852098f4d8fdcd47e3df", - "packages/reflex-radix/src/reflex_radix/themes/components/alert_dialog.pyi": "b9746eeda7ed9d851e75fb257fbec758", - "packages/reflex-radix/src/reflex_radix/themes/components/aspect_ratio.pyi": "3a422f6fb832ce0d0a2a6cfb47020beb", - "packages/reflex-radix/src/reflex_radix/themes/components/avatar.pyi": "4b49b975cfe78b051a9693ad0a3f4a0f", - "packages/reflex-radix/src/reflex_radix/themes/components/badge.pyi": "d4e992419d705607063a479550f19ad3", - "packages/reflex-radix/src/reflex_radix/themes/components/button.pyi": "1f6959ca0e00e6a75af244c90198b4c1", - "packages/reflex-radix/src/reflex_radix/themes/components/callout.pyi": "905b5d6ad98ff1ac41ac4ff21a49aba6", - "packages/reflex-radix/src/reflex_radix/themes/components/card.pyi": "d4c9fe9d681f3d4e5116d2156119e762", - "packages/reflex-radix/src/reflex_radix/themes/components/checkbox.pyi": "6f6fb294939ada2c51863fb10ac8aece", - "packages/reflex-radix/src/reflex_radix/themes/components/checkbox_cards.pyi": "1ebd42045339b490bbacb42d7a91249b", - "packages/reflex-radix/src/reflex_radix/themes/components/checkbox_group.pyi": "39418af266dd65beb0f9ed4d8b931128", - "packages/reflex-radix/src/reflex_radix/themes/components/context_menu.pyi": "8e75c53758726158b0c03bacdd70a7eb", - "packages/reflex-radix/src/reflex_radix/themes/components/data_list.pyi": "ac4c62ec039dc270db06e9b893d9e5d3", - "packages/reflex-radix/src/reflex_radix/themes/components/dialog.pyi": "8a6229b1769347cb2cce40241ed3c18b", - "packages/reflex-radix/src/reflex_radix/themes/components/dropdown_menu.pyi": "df7987ca847489ae7e26c2e177266a52", - "packages/reflex-radix/src/reflex_radix/themes/components/hover_card.pyi": "38268d27b6b645fc8da68ce992f97608", - "packages/reflex-radix/src/reflex_radix/themes/components/icon_button.pyi": "086c9c0e0a2c366f31e86057ab4e360e", - "packages/reflex-radix/src/reflex_radix/themes/components/inset.pyi": "cf9aebf77708e6eb3d8fce5fc04693c1", - "packages/reflex-radix/src/reflex_radix/themes/components/popover.pyi": "2bf10a5e2d065d08332bb29a7e6f16d6", - "packages/reflex-radix/src/reflex_radix/themes/components/progress.pyi": "946f59142bb4145ddc57a76ae04f6470", - "packages/reflex-radix/src/reflex_radix/themes/components/radio.pyi": "e9c243f5c3b01805cfd7ad8189f14c5c", - "packages/reflex-radix/src/reflex_radix/themes/components/radio_cards.pyi": "af415db2dddb6fc4a2b99c326f9a4d0f", - "packages/reflex-radix/src/reflex_radix/themes/components/radio_group.pyi": "0761733c708e1d5f5bd58b6da4bfc853", - "packages/reflex-radix/src/reflex_radix/themes/components/scroll_area.pyi": "d7ef8d8e9b62e469bf0465019f3a49a5", - "packages/reflex-radix/src/reflex_radix/themes/components/segmented_control.pyi": "c280f75e5ac3877ba5b9722cdc562c53", - "packages/reflex-radix/src/reflex_radix/themes/components/select.pyi": "e7609d7df3f656a07652b8b872818e40", - "packages/reflex-radix/src/reflex_radix/themes/components/separator.pyi": "e2249dd4c3eafcd1b9f49c260db5eb3a", - "packages/reflex-radix/src/reflex_radix/themes/components/skeleton.pyi": "cae2b219cce7a1ec5ae05b9f7d1a1b76", - "packages/reflex-radix/src/reflex_radix/themes/components/slider.pyi": "87062150b2ffb63ecab59bf8b70e3769", - "packages/reflex-radix/src/reflex_radix/themes/components/spinner.pyi": "4611c927874be43ebd0069ee2b1c387e", - "packages/reflex-radix/src/reflex_radix/themes/components/switch.pyi": "15945496b266c663e8b35611883399b4", - "packages/reflex-radix/src/reflex_radix/themes/components/table.pyi": "253a0a1e9671aa9842d5bb40ccd122c8", - "packages/reflex-radix/src/reflex_radix/themes/components/tabs.pyi": "0ea3cb6aee40c2ad0632e605881872a9", - "packages/reflex-radix/src/reflex_radix/themes/components/text_area.pyi": "24df7e2528cadbdd010e6d84d8c811b2", - "packages/reflex-radix/src/reflex_radix/themes/components/text_field.pyi": "0154a98317b41feb9a541c1743730122", - "packages/reflex-radix/src/reflex_radix/themes/components/tooltip.pyi": "9fd2d97333c070482527106aaddb33df", - "packages/reflex-radix/src/reflex_radix/themes/layout/__init__.pyi": "2ee0097537b335fac09c78c8cdb18403", - "packages/reflex-radix/src/reflex_radix/themes/layout/base.pyi": "d6992c0dc01cdf1c0c08fe4ea08add2d", - "packages/reflex-radix/src/reflex_radix/themes/layout/box.pyi": "035bed4f0b70f4f058f71ade2df76b65", - "packages/reflex-radix/src/reflex_radix/themes/layout/center.pyi": "56f886020620ce0bf34a2147a75fca07", - "packages/reflex-radix/src/reflex_radix/themes/layout/container.pyi": "b50b26c4a9f105a5b691e749f19f380d", - "packages/reflex-radix/src/reflex_radix/themes/layout/flex.pyi": "88143f231c8f41ad461bfa233d41c59b", - "packages/reflex-radix/src/reflex_radix/themes/layout/grid.pyi": "addd8b6da6ab012bedbfc212567945ec", - "packages/reflex-radix/src/reflex_radix/themes/layout/list.pyi": "f33b5afc5acc2a8d3875b6fd724c23af", - "packages/reflex-radix/src/reflex_radix/themes/layout/section.pyi": "4ff65602b88ab3748ff1bcf75e3dd5a6", - "packages/reflex-radix/src/reflex_radix/themes/layout/spacer.pyi": "94154bbb47e32b7258ab4e355f0b152c", - "packages/reflex-radix/src/reflex_radix/themes/layout/stack.pyi": "814df3a8c4ab2dfca32ee5f5c1d7bd94", - "packages/reflex-radix/src/reflex_radix/themes/typography/__init__.pyi": "a124b5b991b9f703f0eec9266cbec3a4", - "packages/reflex-radix/src/reflex_radix/themes/typography/blockquote.pyi": "74fd7a55d5832b3ac17ee1c4b3522a2e", - "packages/reflex-radix/src/reflex_radix/themes/typography/code.pyi": "d36379b5eefe20a6c8536a7ee7c359a0", - "packages/reflex-radix/src/reflex_radix/themes/typography/heading.pyi": "575e1aa1b082cb99e16fbb76eeef5ff1", - "packages/reflex-radix/src/reflex_radix/themes/typography/link.pyi": "78270efdcb331f689013c041f1be6ebe", - "packages/reflex-radix/src/reflex_radix/themes/typography/text.pyi": "d764729c9e6eb6fd7f48642647ae0c79", - "packages/reflex-react-player/src/reflex_react_player/audio.pyi": "3a5e72ce19c44fa6435d7de39bf09ee9", - "packages/reflex-react-player/src/reflex_react_player/react_player.pyi": "e6b4676044a713f64998a73941858e88", - "packages/reflex-react-player/src/reflex_react_player/video.pyi": "4e88c0e52b81f02e354f689cde029d7c", - "packages/reflex-react-router/src/reflex_react_router/dom.pyi": "4f869685727b609a277536688172f68e", - "packages/reflex-recharts/src/reflex_recharts/__init__.pyi": "10d16ea120467dabf8e59aa1e80d56ad", - "packages/reflex-recharts/src/reflex_recharts/cartesian.pyi": "86fd21642953dde3ed3f98b6e0d25bc1", - "packages/reflex-recharts/src/reflex_recharts/charts.pyi": "130f5ba805b7963fc171b310d3755c7a", - "packages/reflex-recharts/src/reflex_recharts/general.pyi": "95e9d9fbc6ad1ada0fdf34f75bb09b68", - "packages/reflex-recharts/src/reflex_recharts/polar.pyi": "5e73f07de061c963118d329cb6f71f18", - "packages/reflex-recharts/src/reflex_recharts/recharts.pyi": "84b2c8e054a52d12ced9dfeb88e6221b", - "packages/reflex-sonner/src/reflex_sonner/toast.pyi": "5013a0c87953ea4896cb37b76ef988e9", - "reflex/__init__.pyi": "047892cecd4f922aedbc6c0ad7368503", + "packages/reflex-components-code/src/reflex_components_code/code.pyi": "c867c4384ade3ea5d97ba794966af140", + "packages/reflex-components-code/src/reflex_components_code/shiki_code_block.pyi": "8dcccd4551a7b6ff3280f826f97f2aca", + "packages/reflex-components-core/src/reflex_components_core/__init__.pyi": "6c3ceff429117483dd0035e7a21930f4", + "packages/reflex-components-core/src/reflex_components_core/base/__init__.pyi": "4beb5ba739680b7974c37241f3f6791c", + "packages/reflex-components-core/src/reflex_components_core/base/app_wrap.pyi": "f8cb9cee04ffa9006d74db1b3500aadf", + "packages/reflex-components-core/src/reflex_components_core/base/body.pyi": "3ad765bf23f5da9cee280b44a80dbcc5", + "packages/reflex-components-core/src/reflex_components_core/base/document.pyi": "ca67174aeb6419d320b6c54a1cdd1955", + "packages/reflex-components-core/src/reflex_components_core/base/error_boundary.pyi": "19dcd8cba0603b6371d24d768eaf84f4", + "packages/reflex-components-core/src/reflex_components_core/base/fragment.pyi": "75a5f1a9ac628f3f211cd7d72c605aa2", + "packages/reflex-components-core/src/reflex_components_core/base/link.pyi": "8893f124131be7c2f60f086fa4beb255", + "packages/reflex-components-core/src/reflex_components_core/base/meta.pyi": "1a1904b9118c22fdae1b5e70e5006aa7", + "packages/reflex-components-core/src/reflex_components_core/base/script.pyi": "58bae1b5f9d6fabd284752e0adf8d868", + "packages/reflex-components-core/src/reflex_components_core/base/strict_mode.pyi": "a037c6fcb007d884915e52a6ab0046af", + "packages/reflex-components-core/src/reflex_components_core/core/__init__.pyi": "17f79762cef09c69acd9df227cf3bb35", + "packages/reflex-components-core/src/reflex_components_core/core/auto_scroll.pyi": "59a02f780bd1568c26f7991c99bf2110", + "packages/reflex-components-core/src/reflex_components_core/core/banner.pyi": "eab4a1e0b86d546a324ec1814fb86de0", + "packages/reflex-components-core/src/reflex_components_core/core/clipboard.pyi": "3bf314f1e3a1faf9c060cd34f8fa7079", + "packages/reflex-components-core/src/reflex_components_core/core/debounce.pyi": "dab0e776a0fbe8c2525ed8950bdf0293", + "packages/reflex-components-core/src/reflex_components_core/core/helmet.pyi": "9fa0dea0b63a975dafe2b4c06c5a008c", + "packages/reflex-components-core/src/reflex_components_core/core/html.pyi": "f3d8fe298d93bc20aee9d6461f7d5b17", + "packages/reflex-components-core/src/reflex_components_core/core/sticky.pyi": "3bca0ab6a4b0f36e8adf6e8a144e403c", + "packages/reflex-components-core/src/reflex_components_core/core/upload.pyi": "d76f2abf2acc8b1b0a01f1e46a906794", + "packages/reflex-components-core/src/reflex_components_core/core/window_events.pyi": "22dbc116b98a9e70a36cb1ef846adc62", + "packages/reflex-components-core/src/reflex_components_core/datadisplay/__init__.pyi": "73e27e0bfd7e914c4baba7b166e75736", + "packages/reflex-components-core/src/reflex_components_core/el/__init__.pyi": "a1d4d65ddc73b5fbba3726f4bcf6d6f0", + "packages/reflex-components-core/src/reflex_components_core/el/element.pyi": "0cb57beaf2a3dd03be9d3b008bb0a916", + "packages/reflex-components-core/src/reflex_components_core/el/elements/__init__.pyi": "fbe4c6f4960bcd811311b1f73987cdb1", + "packages/reflex-components-core/src/reflex_components_core/el/elements/base.pyi": "7220e4f079af9f3473491751a4cacef1", + "packages/reflex-components-core/src/reflex_components_core/el/elements/forms.pyi": "da68f5ced0afa890ad79e646aecc094a", + "packages/reflex-components-core/src/reflex_components_core/el/elements/inline.pyi": "d0d132e32abe141ca2940637609875bd", + "packages/reflex-components-core/src/reflex_components_core/el/elements/media.pyi": "77703a560c2aa2f013ae58e28c6b0c88", + "packages/reflex-components-core/src/reflex_components_core/el/elements/metadata.pyi": "cc4c5bd0128af7b98dc44923c37739b6", + "packages/reflex-components-core/src/reflex_components_core/el/elements/other.pyi": "8f775db675bb055cd8c75c4ea00892f9", + "packages/reflex-components-core/src/reflex_components_core/el/elements/scripts.pyi": "6c3925340de81d4d26d1de74c5ed24e9", + "packages/reflex-components-core/src/reflex_components_core/el/elements/sectioning.pyi": "919119139837bd01475e90173602c381", + "packages/reflex-components-core/src/reflex_components_core/el/elements/tables.pyi": "8b7933cb9f4c11734f49ec41f2b80553", + "packages/reflex-components-core/src/reflex_components_core/el/elements/typography.pyi": "f5cfe2996ae3aaf7bd630c852c0e9068", + "packages/reflex-components-dataeditor/src/reflex_components_dataeditor/dataeditor.pyi": "0f8ec6d513cc649caf10981cf29bcd38", + "packages/reflex-components-gridjs/src/reflex_components_gridjs/datatable.pyi": "3dcdc432507f1a455b97f719160118ac", + "packages/reflex-components-lucide/src/reflex_components_lucide/icon.pyi": "21ac24351c0e0de123dec46a7e90209b", + "packages/reflex-components-markdown/src/reflex_components_markdown/markdown.pyi": "2968ff9eaf788b5a93fa5c4d6d5d818c", + "packages/reflex-components-moment/src/reflex_components_moment/moment.pyi": "dffd48bde7241e80093b2f8143cfc30e", + "packages/reflex-components-plotly/src/reflex_components_plotly/plotly.pyi": "84b5f3f01d5fcb60067be9105a4a12ba", + "packages/reflex-components-radix/src/reflex_components_radix/__init__.pyi": "899d63be42eb21c187519920518bf32e", + "packages/reflex-components-radix/src/reflex_components_radix/primitives/__init__.pyi": "11bc17db31334f11d7fcde315b29cae1", + "packages/reflex-components-radix/src/reflex_components_radix/primitives/accordion.pyi": "e3cbfe19cadd7e24e7a3556b898e9170", + "packages/reflex-components-radix/src/reflex_components_radix/primitives/base.pyi": "4ed7151c05f155a7140b608e3350b684", + "packages/reflex-components-radix/src/reflex_components_radix/primitives/dialog.pyi": "75539bdf369af2bab188335a0cad59f0", + "packages/reflex-components-radix/src/reflex_components_radix/primitives/drawer.pyi": "8eed776a75cc0583a5a27e12205fc22b", + "packages/reflex-components-radix/src/reflex_components_radix/primitives/form.pyi": "47f6c48ecd0733b6183af90a65797f45", + "packages/reflex-components-radix/src/reflex_components_radix/primitives/progress.pyi": "5181780f01dcb17a996d383a9fbda0e1", + "packages/reflex-components-radix/src/reflex_components_radix/primitives/slider.pyi": "dfeab92ef06442a0339eed625c8902f8", + "packages/reflex-components-radix/src/reflex_components_radix/themes/__init__.pyi": "a152450345e7da9124e954843224e936", + "packages/reflex-components-radix/src/reflex_components_radix/themes/base.pyi": "678774944ad675c1efad0dcd5ac26097", + "packages/reflex-components-radix/src/reflex_components_radix/themes/color_mode.pyi": "cdf329dd430dfb35bc0fc0c22903d2bf", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/__init__.pyi": "67d45aa96a97359ee556725641bb583f", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/alert_dialog.pyi": "4f332c3ca24a26d2349503689eb89c8c", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/aspect_ratio.pyi": "559909085f8c1cf37fad1c4ecd2502d7", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/avatar.pyi": "c8730ae9413528fc88caeb0a013e60ee", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/badge.pyi": "9227edf16541a8f0e413efb1d9cdca42", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/button.pyi": "6114e8e6330932100884e435544a3a52", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/callout.pyi": "d356e8555fa4a3141783827355f66524", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/card.pyi": "05e5b293b2d5388d619c5b4676609950", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/checkbox.pyi": "3d1439b90dbc2b1d4995e7ed91e040f3", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/checkbox_cards.pyi": "0f5736c128c8e6a7cf7a3c7eef1e7159", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/checkbox_group.pyi": "19042165e9ff53de6be5e8c18e61429f", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/context_menu.pyi": "0affc657216038379632808dcbbec1f5", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/data_list.pyi": "86fa8b592994d23e99d585d64056d0c1", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/dialog.pyi": "3cfc6ed392463054db7b41db012af862", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/dropdown_menu.pyi": "da1560563223edcb56cf8436ab1f5ef6", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/hover_card.pyi": "557e3cb9553c13fd3d2bae7c30371176", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/icon_button.pyi": "632ee951c76dc22bf63c904d2cbc74a7", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/inset.pyi": "a1e3374cda158ea64e180da6ce61d265", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/popover.pyi": "1442682c3b937f79ef84737ac5464245", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/progress.pyi": "2d064ffabbc2ed3a23e8f46d833b0277", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/radio.pyi": "a331ef9d0d869a997562d9f987e1814f", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/radio_cards.pyi": "4c236e3c3d1ed17ce062cd63a3eb613f", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/radio_group.pyi": "0355e29cf569e1405091bb29f525e86b", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/scroll_area.pyi": "5b7223cab1ac1ac42bdeeab6cd72bd8a", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/segmented_control.pyi": "d7451983f9591ff7b81f3bfbd72a2689", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/select.pyi": "791cb1c3c5c67f77bd581c6d0b99ab9b", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/separator.pyi": "00a858bbdc0d35f2220775eb06d2d733", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/skeleton.pyi": "fd31c794583ec744207312fa1a72c3d1", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/slider.pyi": "683128339dfe213d8a7401b75814953b", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/spinner.pyi": "713adb1481b8e3c525fd054d417239b0", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/switch.pyi": "7e038a571514d7ddedba423797498b4c", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/table.pyi": "1bdfc7f3f18894378a5629c3d16546fc", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/tabs.pyi": "079c79809feaea387c07716083184c94", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/text_area.pyi": "36d3ecbef7a6b28554f400fc41433522", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/text_field.pyi": "be36d108861eb1c5d486175637bd78d1", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/tooltip.pyi": "a68e09331535bbf6187d1efbd248baf1", + "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/__init__.pyi": "b7dbdecab770a7fc1435ad417d7f91c1", + "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/base.pyi": "a63c001cf714739c04a86556951e9bfc", + "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/box.pyi": "17f83beeb3c6c83b1571ea5d4887f76c", + "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/center.pyi": "123b2d713d9845f2715576d5cf9f2536", + "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/container.pyi": "fe41ccaa90e859f2f8a74b015fbde058", + "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/flex.pyi": "f615a989fcfbda790de3f1305c42605a", + "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/grid.pyi": "2a23d9b1d7f2098f3e05e6deeb7453fb", + "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/list.pyi": "07bd0842f489c9cd4f1c6e3800cc7afd", + "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/section.pyi": "edada2fc5d0139b61d035485ecaf5ebb", + "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/spacer.pyi": "a289f92e5da0ec4576e4c0bc1ee4986c", + "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/stack.pyi": "b857d2ed0549a217fa28e3cb86c52ffb", + "packages/reflex-components-radix/src/reflex_components_radix/themes/typography/__init__.pyi": "15b13877fc52b82182fc42acd8adb027", + "packages/reflex-components-radix/src/reflex_components_radix/themes/typography/blockquote.pyi": "833c57bded556a13db59726769183230", + "packages/reflex-components-radix/src/reflex_components_radix/themes/typography/code.pyi": "0ea298c8016e29b3343d708c43a66179", + "packages/reflex-components-radix/src/reflex_components_radix/themes/typography/heading.pyi": "e38ca88110e315303c18d615556d1542", + "packages/reflex-components-radix/src/reflex_components_radix/themes/typography/link.pyi": "edaf5f2f316bd35b6d32f7a369d5fad6", + "packages/reflex-components-radix/src/reflex_components_radix/themes/typography/text.pyi": "c6bf9ba91a7650d6742773be94bdde33", + "packages/reflex-components-react-player/src/reflex_components_react_player/audio.pyi": "383e6c81567915da364dd7c02705dd7c", + "packages/reflex-components-react-player/src/reflex_components_react_player/react_player.pyi": "20deae7c420096252c8958f1d9194b9e", + "packages/reflex-components-react-player/src/reflex_components_react_player/video.pyi": "58e2a086560862b2f9ecd1f18840e893", + "packages/reflex-components-react-router/src/reflex_components_react_router/dom.pyi": "41c140176951d4536af9d51aa22da96e", + "packages/reflex-components-recharts/src/reflex_components_recharts/__init__.pyi": "1f04ab9f482261f18010fbf728d7b946", + "packages/reflex-components-recharts/src/reflex_components_recharts/cartesian.pyi": "cb57586301f693b6667e29e916a34427", + "packages/reflex-components-recharts/src/reflex_components_recharts/charts.pyi": "e6e69455c92b91a8cd37cec7d83b3589", + "packages/reflex-components-recharts/src/reflex_components_recharts/general.pyi": "6bdf04181af6b94f06ebc07e32649a13", + "packages/reflex-components-recharts/src/reflex_components_recharts/polar.pyi": "7af2bc6774ef1c5240165a6afff12633", + "packages/reflex-components-recharts/src/reflex_components_recharts/recharts.pyi": "83a840eceb0515ce7d27d52644ff60a5", + "packages/reflex-components-sonner/src/reflex_components_sonner/toast.pyi": "296cb0d27bc1774f3a330ca9f79fe2bd", + "reflex/__init__.pyi": "22a843faeeedcfcda493b34727b6e957", "reflex/components/__init__.pyi": "d620d52015f908cda828d231c1064236" } diff --git a/pyproject.toml b/pyproject.toml index 1fc0dbe16c8..bd8849d903f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -115,11 +115,14 @@ dev = [ [build-system] -requires = ["hatchling", "hatch-vcs"] +requires = ["hatchling", "uv-dynamic-versioning"] build-backend = "hatchling.build" [tool.hatch.version] -source = "vcs" +source = "uv-dynamic-versioning" + +[tool.uv-dynamic-versioning] +fallback-version = "0.0.0dev0" [tool.hatch.build] include = ["reflex", "scripts/hatch_build.py"] diff --git a/scripts/make_pyi.py b/scripts/make_pyi.py index 7976d24caff..6ee5d0c125c 100644 --- a/scripts/make_pyi.py +++ b/scripts/make_pyi.py @@ -16,19 +16,19 @@ "reflex/components", "reflex/experimental", "reflex/__init__.py", - "packages/reflex-code/src/reflex_components_code", - "packages/reflex-components/src/reflex_components_core", - "packages/reflex-dataeditor/src/reflex_components_dataeditor", - "packages/reflex-gridjs/src/reflex_components_gridjs", - "packages/reflex-lucide/src/reflex_components_lucide", - "packages/reflex-markdown/src/reflex_components_markdown", - "packages/reflex-moment/src/reflex_components_moment", - "packages/reflex-plotly/src/reflex_components_plotly", - "packages/reflex-radix/src/reflex_components_radix", - "packages/reflex-react-player/src/reflex_components_react_player", - "packages/reflex-react-router/src/reflex_components_react_router", - "packages/reflex-recharts/src/reflex_components_recharts", - "packages/reflex-sonner/src/reflex_components_sonner", + "packages/reflex-components-code/src/reflex_components_code", + "packages/reflex-components-core/src/reflex_components_core", + "packages/reflex-components-dataeditor/src/reflex_components_dataeditor", + "packages/reflex-components-gridjs/src/reflex_components_gridjs", + "packages/reflex-components-lucide/src/reflex_components_lucide", + "packages/reflex-components-markdown/src/reflex_components_markdown", + "packages/reflex-components-moment/src/reflex_components_moment", + "packages/reflex-components-plotly/src/reflex_components_plotly", + "packages/reflex-components-radix/src/reflex_components_radix", + "packages/reflex-components-react-player/src/reflex_components_react_player", + "packages/reflex-components-react-router/src/reflex_components_react_router", + "packages/reflex-components-recharts/src/reflex_components_recharts", + "packages/reflex-components-sonner/src/reflex_components_sonner", ] diff --git a/tests/units/components/lucide/test_icon.py b/tests/units/components/lucide/test_icon.py index 26e70f7646d..71d5ab32a63 100644 --- a/tests/units/components/lucide/test_icon.py +++ b/tests/units/components/lucide/test_icon.py @@ -1,5 +1,9 @@ import pytest -from reflex_components_lucide.icon import LUCIDE_ICON_LIST, LUCIDE_ICON_MAPPING_OVERRIDE, Icon +from reflex_components_lucide.icon import ( + LUCIDE_ICON_LIST, + LUCIDE_ICON_MAPPING_OVERRIDE, + Icon, +) from reflex.utils import format diff --git a/tests/units/components/recharts/test_cartesian.py b/tests/units/components/recharts/test_cartesian.py index d17cec511d3..078455f4411 100644 --- a/tests/units/components/recharts/test_cartesian.py +++ b/tests/units/components/recharts/test_cartesian.py @@ -1,4 +1,13 @@ -from reflex_components_recharts import Area, Bar, Brush, Line, Scatter, XAxis, YAxis, ZAxis +from reflex_components_recharts import ( + Area, + Bar, + Brush, + Line, + Scatter, + XAxis, + YAxis, + ZAxis, +) def test_xaxis(): diff --git a/uv.lock b/uv.lock index 1b148febeaa..ce618c353e8 100644 --- a/uv.lock +++ b/uv.lock @@ -14,20 +14,20 @@ resolution-markers = [ [manifest] members = [ "reflex", - "reflex-code", - "reflex-components", - "reflex-dataeditor", + "reflex-components-code", + "reflex-components-core", + "reflex-components-dataeditor", + "reflex-components-gridjs", + "reflex-components-lucide", + "reflex-components-markdown", + "reflex-components-moment", + "reflex-components-plotly", + "reflex-components-radix", + "reflex-components-react-player", + "reflex-components-react-router", + "reflex-components-recharts", + "reflex-components-sonner", "reflex-docgen", - "reflex-gridjs", - "reflex-lucide", - "reflex-markdown", - "reflex-moment", - "reflex-plotly", - "reflex-radix", - "reflex-react-player", - "reflex-react-router", - "reflex-recharts", - "reflex-sonner", ] [[package]] @@ -2040,7 +2040,6 @@ wheels = [ [[package]] name = "reflex" -version = "0.9.0.dev1" source = { editable = "." } dependencies = [ { name = "alembic" }, @@ -2054,20 +2053,20 @@ dependencies = [ { name = "python-multipart" }, { name = "python-socketio" }, { name = "redis" }, - { name = "reflex-code" }, - { name = "reflex-components" }, - { name = "reflex-dataeditor" }, - { name = "reflex-gridjs" }, + { name = "reflex-components-code" }, + { name = "reflex-components-core" }, + { name = "reflex-components-dataeditor" }, + { name = "reflex-components-gridjs" }, + { name = "reflex-components-lucide" }, + { name = "reflex-components-markdown" }, + { name = "reflex-components-moment" }, + { name = "reflex-components-plotly" }, + { name = "reflex-components-radix" }, + { name = "reflex-components-react-player" }, + { name = "reflex-components-react-router" }, + { name = "reflex-components-recharts" }, + { name = "reflex-components-sonner" }, { name = "reflex-hosting-cli" }, - { name = "reflex-lucide" }, - { name = "reflex-markdown" }, - { name = "reflex-moment" }, - { name = "reflex-plotly" }, - { name = "reflex-radix" }, - { name = "reflex-react-player" }, - { name = "reflex-react-router" }, - { name = "reflex-recharts" }, - { name = "reflex-sonner" }, { name = "rich" }, { name = "sqlmodel" }, { name = "starlette" }, @@ -2134,20 +2133,20 @@ requires-dist = [ { name = "python-multipart", specifier = ">=0.0.20,<1.0" }, { name = "python-socketio", specifier = ">=5.12.0,<6.0" }, { name = "redis", specifier = ">=5.2.1,<8.0" }, - { name = "reflex-code", editable = "packages/reflex-code" }, - { name = "reflex-components", editable = "packages/reflex-components" }, - { name = "reflex-dataeditor", editable = "packages/reflex-dataeditor" }, - { name = "reflex-gridjs", editable = "packages/reflex-gridjs" }, + { name = "reflex-components-code", editable = "packages/reflex-components-code" }, + { name = "reflex-components-core", editable = "packages/reflex-components-core" }, + { name = "reflex-components-dataeditor", editable = "packages/reflex-components-dataeditor" }, + { name = "reflex-components-gridjs", editable = "packages/reflex-components-gridjs" }, + { name = "reflex-components-lucide", editable = "packages/reflex-components-lucide" }, + { name = "reflex-components-markdown", editable = "packages/reflex-components-markdown" }, + { name = "reflex-components-moment", editable = "packages/reflex-components-moment" }, + { name = "reflex-components-plotly", editable = "packages/reflex-components-plotly" }, + { name = "reflex-components-radix", editable = "packages/reflex-components-radix" }, + { name = "reflex-components-react-player", editable = "packages/reflex-components-react-player" }, + { name = "reflex-components-react-router", editable = "packages/reflex-components-react-router" }, + { name = "reflex-components-recharts", editable = "packages/reflex-components-recharts" }, + { name = "reflex-components-sonner", editable = "packages/reflex-components-sonner" }, { name = "reflex-hosting-cli", specifier = ">=0.1.61" }, - { name = "reflex-lucide", editable = "packages/reflex-lucide" }, - { name = "reflex-markdown", editable = "packages/reflex-markdown" }, - { name = "reflex-moment", editable = "packages/reflex-moment" }, - { name = "reflex-plotly", editable = "packages/reflex-plotly" }, - { name = "reflex-radix", editable = "packages/reflex-radix" }, - { name = "reflex-react-player", editable = "packages/reflex-react-player" }, - { name = "reflex-react-router", editable = "packages/reflex-react-router" }, - { name = "reflex-recharts", editable = "packages/reflex-recharts" }, - { name = "reflex-sonner", editable = "packages/reflex-sonner" }, { name = "rich", specifier = ">=13,<15" }, { name = "sqlmodel", specifier = ">=0.0.27,<0.1" }, { name = "sqlmodel", marker = "extra == 'db'", specifier = ">=0.0.24,<0.1" }, @@ -2193,23 +2192,59 @@ dev = [ ] [[package]] -name = "reflex-code" -version = "0.0.1" -source = { editable = "packages/reflex-code" } +name = "reflex-components-code" +source = { editable = "packages/reflex-components-code" } [[package]] -name = "reflex-components" -version = "0.0.1" -source = { editable = "packages/reflex-components" } +name = "reflex-components-core" +source = { editable = "packages/reflex-components-core" } [[package]] -name = "reflex-dataeditor" -version = "0.0.1" -source = { editable = "packages/reflex-dataeditor" } +name = "reflex-components-dataeditor" +source = { editable = "packages/reflex-components-dataeditor" } + +[[package]] +name = "reflex-components-gridjs" +source = { editable = "packages/reflex-components-gridjs" } + +[[package]] +name = "reflex-components-lucide" +source = { editable = "packages/reflex-components-lucide" } + +[[package]] +name = "reflex-components-markdown" +source = { editable = "packages/reflex-components-markdown" } + +[[package]] +name = "reflex-components-moment" +source = { editable = "packages/reflex-components-moment" } + +[[package]] +name = "reflex-components-plotly" +source = { editable = "packages/reflex-components-plotly" } + +[[package]] +name = "reflex-components-radix" +source = { editable = "packages/reflex-components-radix" } + +[[package]] +name = "reflex-components-react-player" +source = { editable = "packages/reflex-components-react-player" } + +[[package]] +name = "reflex-components-react-router" +source = { editable = "packages/reflex-components-react-router" } + +[[package]] +name = "reflex-components-recharts" +source = { editable = "packages/reflex-components-recharts" } + +[[package]] +name = "reflex-components-sonner" +source = { editable = "packages/reflex-components-sonner" } [[package]] name = "reflex-docgen" -version = "0.0.1" source = { editable = "packages/reflex-docgen" } dependencies = [ { name = "griffelib" }, @@ -2230,11 +2265,6 @@ requires-dist = [ { name = "typing-inspection", specifier = ">=0.4.2" }, ] -[[package]] -name = "reflex-gridjs" -version = "0.0.1" -source = { editable = "packages/reflex-gridjs" } - [[package]] name = "reflex-hosting-cli" version = "0.1.62" @@ -2251,51 +2281,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f7/67/415e1f7fe09e77027e9e0063f39053d74f1299e671e837c9a7745453676d/reflex_hosting_cli-0.1.62-py3-none-any.whl", hash = "sha256:73d517fa827b1d52dcb81ba9024671acfd4889015a436ed223d2eda3c07eab89", size = 45049, upload-time = "2026-03-10T01:12:15.033Z" }, ] -[[package]] -name = "reflex-lucide" -version = "0.0.1" -source = { editable = "packages/reflex-lucide" } - -[[package]] -name = "reflex-markdown" -version = "0.0.1" -source = { editable = "packages/reflex-markdown" } - -[[package]] -name = "reflex-moment" -version = "0.0.1" -source = { editable = "packages/reflex-moment" } - -[[package]] -name = "reflex-plotly" -version = "0.0.1" -source = { editable = "packages/reflex-plotly" } - -[[package]] -name = "reflex-radix" -version = "0.0.1" -source = { editable = "packages/reflex-radix" } - -[[package]] -name = "reflex-react-player" -version = "0.0.1" -source = { editable = "packages/reflex-react-player" } - -[[package]] -name = "reflex-react-router" -version = "0.0.1" -source = { editable = "packages/reflex-react-router" } - -[[package]] -name = "reflex-recharts" -version = "0.0.1" -source = { editable = "packages/reflex-recharts" } - -[[package]] -name = "reflex-sonner" -version = "0.0.1" -source = { editable = "packages/reflex-sonner" } - [[package]] name = "requests" version = "2.32.5" From 110e9c3b4b1dc53dc64c7907bc478af311d50ee7 Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Wed, 25 Mar 2026 11:00:51 -0700 Subject: [PATCH 019/113] kill process for sure --- scripts/integration.sh | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/scripts/integration.sh b/scripts/integration.sh index d01b2816588..5531c14aadf 100755 --- a/scripts/integration.sh +++ b/scripts/integration.sh @@ -21,7 +21,18 @@ reflex run --loglevel debug --env "$env_mode" "$@" & pid=$! # Within the context of this bash, $pid_in_bash is what we need to pass to "kill" on exit # This is true on all platforms. pid_in_bash=$pid -trap "kill -INT $pid_in_bash ||:" EXIT +cleanup() { + kill -INT $pid_in_bash ||: + # Wait for the process to exit so it releases file locks (important on Windows) + for i in $(seq 1 10); do + kill -0 $pid_in_bash 2>/dev/null || return 0 + sleep 1 + done + # Force kill if still alive + kill -9 $pid_in_bash 2>/dev/null ||: + sleep 1 +} +trap cleanup EXIT echo "Started server with PID $pid" From 524cb740ec2c8fbbb1cb47343e2a96adbfd76307 Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Wed, 25 Mar 2026 11:29:32 -0700 Subject: [PATCH 020/113] get back cleanup it to where it was --- scripts/integration.sh | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/scripts/integration.sh b/scripts/integration.sh index 5531c14aadf..d01b2816588 100755 --- a/scripts/integration.sh +++ b/scripts/integration.sh @@ -21,18 +21,7 @@ reflex run --loglevel debug --env "$env_mode" "$@" & pid=$! # Within the context of this bash, $pid_in_bash is what we need to pass to "kill" on exit # This is true on all platforms. pid_in_bash=$pid -cleanup() { - kill -INT $pid_in_bash ||: - # Wait for the process to exit so it releases file locks (important on Windows) - for i in $(seq 1 10); do - kill -0 $pid_in_bash 2>/dev/null || return 0 - sleep 1 - done - # Force kill if still alive - kill -9 $pid_in_bash 2>/dev/null ||: - sleep 1 -} -trap cleanup EXIT +trap "kill -INT $pid_in_bash ||:" EXIT echo "Started server with PID $pid" From 612b371c2d85f72d5edc1dee2bbb91f038ce4966 Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Wed, 25 Mar 2026 11:38:08 -0700 Subject: [PATCH 021/113] try taskkill on windows --- scripts/integration.sh | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/scripts/integration.sh b/scripts/integration.sh index d01b2816588..b20151b5566 100755 --- a/scripts/integration.sh +++ b/scripts/integration.sh @@ -21,7 +21,6 @@ reflex run --loglevel debug --env "$env_mode" "$@" & pid=$! # Within the context of this bash, $pid_in_bash is what we need to pass to "kill" on exit # This is true on all platforms. pid_in_bash=$pid -trap "kill -INT $pid_in_bash ||:" EXIT echo "Started server with PID $pid" @@ -29,9 +28,23 @@ echo "Started server with PID $pid" popd # In Windows, our Python script below needs to work with the WINPID +is_windows=false if [ -f /proc/$pid/winpid ]; then - pid=$(cat /proc/$pid/winpid) - echo "Windows detected, passing winpid $pid to port waiter" + winpid=$(cat /proc/$pid/winpid) + is_windows=true + echo "Windows detected, passing winpid $winpid to port waiter" + pid=$winpid fi +cleanup() { + if [ "$is_windows" = true ]; then + # Use taskkill to kill the entire process tree on Windows, + # so that reflex.exe and child processes release their file locks. + taskkill //F //T //PID $winpid 2>/dev/null ||: + else + kill -INT $pid_in_bash ||: + fi +} +trap cleanup EXIT + python scripts/wait_for_listening_port.py $check_ports --timeout=900 --server-pid "$pid" From b8d5376554fc84cd9028187b662969929ed3430d Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Wed, 25 Mar 2026 12:28:59 -0700 Subject: [PATCH 022/113] 3.14 --- .github/workflows/integration_tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/integration_tests.yml b/.github/workflows/integration_tests.yml index 464de2e4cb4..a9606c5c16d 100644 --- a/.github/workflows/integration_tests.yml +++ b/.github/workflows/integration_tests.yml @@ -43,7 +43,7 @@ jobs: matrix: # Show OS combos first in GUI os: [ubuntu-latest, windows-latest] - python-version: ["3.10", "3.11", "3.12", "3.13"] + python-version: ["3.14"] runs-on: ${{ matrix.os }} steps: From 70523ba0c72807dbbd2cb7d053aea5021da098ad Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Wed, 25 Mar 2026 17:06:52 -0700 Subject: [PATCH 023/113] get rid of circular deps and makde core not depend on radix --- .../reflex-components-code/pyproject.toml | 6 +- .../src/reflex_components_code/code.py | 2 +- .../shiki_code_block.py | 2 +- .../reflex-components-core/pyproject.toml | 5 +- .../src/reflex_components_core/core/banner.py | 29 +++---- .../core/markdown_component_map.py | 77 +++++++++++++++++++ .../reflex_components_core/core/responsive.py | 12 +-- .../src/reflex_components_core/core/sticky.py | 6 +- .../src/reflex_components_core/core/upload.py | 4 +- .../pyproject.toml | 4 +- .../reflex-components-markdown/pyproject.toml | 6 +- .../reflex_components_markdown/markdown.py | 67 +--------------- .../reflex-components-plotly/pyproject.toml | 4 +- .../reflex-components-radix/pyproject.toml | 6 +- .../themes/layout/list.py | 2 +- .../themes/typography/code.py | 2 +- .../themes/typography/heading.py | 2 +- .../themes/typography/link.py | 2 +- .../themes/typography/text.py | 2 +- .../pyproject.toml | 4 +- .../pyproject.toml | 4 +- .../reflex-components-sonner/pyproject.toml | 4 +- pyi_hashes.json | 22 +++--- .../components/markdown/test_markdown.py | 3 +- 24 files changed, 154 insertions(+), 123 deletions(-) create mode 100644 packages/reflex-components-core/src/reflex_components_core/core/markdown_component_map.py diff --git a/packages/reflex-components-code/pyproject.toml b/packages/reflex-components-code/pyproject.toml index e7b95abc4a0..6d97ca7f457 100644 --- a/packages/reflex-components-code/pyproject.toml +++ b/packages/reflex-components-code/pyproject.toml @@ -6,7 +6,11 @@ readme = "README.md" authors = [{ name = "Khaleel Al-Adhami", email = "khaleel@reflex.dev" }] maintainers = [{ name = "Khaleel Al-Adhami", email = "khaleel@reflex.dev" }] requires-python = ">=3.10" -dependencies = [] +dependencies = [ + "reflex-components-core", + "reflex-components-lucide", + "reflex-components-radix", +] [tool.hatch.version] source = "uv-dynamic-versioning" diff --git a/packages/reflex-components-code/src/reflex_components_code/code.py b/packages/reflex-components-code/src/reflex_components_code/code.py index cbf589009b1..529c2c43c0d 100644 --- a/packages/reflex-components-code/src/reflex_components_code/code.py +++ b/packages/reflex-components-code/src/reflex_components_code/code.py @@ -6,8 +6,8 @@ from typing import ClassVar, Literal from reflex_components_core.core.cond import color_mode_cond +from reflex_components_core.core.markdown_component_map import MarkdownComponentMap from reflex_components_lucide.icon import Icon -from reflex_components_markdown.markdown import MarkdownComponentMap from reflex_components_radix.themes.components.button import Button from reflex_components_radix.themes.layout.box import Box diff --git a/packages/reflex-components-code/src/reflex_components_code/shiki_code_block.py b/packages/reflex-components-code/src/reflex_components_code/shiki_code_block.py index 844eb7a1e53..6cc2aeed7ea 100644 --- a/packages/reflex-components-code/src/reflex_components_code/shiki_code_block.py +++ b/packages/reflex-components-code/src/reflex_components_code/shiki_code_block.py @@ -10,9 +10,9 @@ from reflex_components_core.core.colors import color from reflex_components_core.core.cond import color_mode_cond +from reflex_components_core.core.markdown_component_map import MarkdownComponentMap from reflex_components_core.el.elements.forms import Button from reflex_components_lucide.icon import Icon -from reflex_components_markdown.markdown import MarkdownComponentMap from reflex_components_radix.themes.layout.box import Box from reflex.components.component import Component, ComponentNamespace, field diff --git a/packages/reflex-components-core/pyproject.toml b/packages/reflex-components-core/pyproject.toml index 1f7f1a023e0..aa35fb1b2d9 100644 --- a/packages/reflex-components-core/pyproject.toml +++ b/packages/reflex-components-core/pyproject.toml @@ -6,7 +6,10 @@ readme = "README.md" authors = [{ name = "Khaleel Al-Adhami", email = "khaleel@reflex.dev" }] maintainers = [{ name = "Khaleel Al-Adhami", email = "khaleel@reflex.dev" }] requires-python = ">=3.10" -dependencies = [] +dependencies = [ + "reflex-components-lucide", + "reflex-components-sonner", +] [tool.hatch.version] source = "uv-dynamic-versioning" diff --git a/packages/reflex-components-core/src/reflex_components_core/core/banner.py b/packages/reflex-components-core/src/reflex_components_core/core/banner.py index eb6f9cac585..7af7981d69a 100644 --- a/packages/reflex-components-core/src/reflex_components_core/core/banner.py +++ b/packages/reflex-components-core/src/reflex_components_core/core/banner.py @@ -3,13 +3,6 @@ from __future__ import annotations from reflex_components_lucide.icon import Icon -from reflex_components_radix.themes.components.dialog import ( - DialogContent, - DialogRoot, - DialogTitle, -) -from reflex_components_radix.themes.layout.flex import Flex -from reflex_components_radix.themes.typography.text import Text from reflex_components_sonner.toast import ToastProps, toast_ref from reflex import constants @@ -25,6 +18,9 @@ from reflex.vars.sequence import LiteralArrayVar from reflex_components_core.base.fragment import Fragment from reflex_components_core.core.cond import cond +from reflex_components_core.el.elements.inline import Span +from reflex_components_core.el.elements.other import Dialog +from reflex_components_core.el.elements.sectioning import H2 from reflex_components_core.el.elements.typography import Div connect_error_var_data: VarData = VarData( @@ -211,13 +207,14 @@ def create(cls, comp: Component | None = None) -> Component: The connection banner component. """ if not comp: - comp = Flex.create( - Text.create( + comp = Div.create( + Span.create( *default_connection_error(), color="black", - size="4", + font_size="1.125rem", ), - justify="center", + display="flex", + justify_content="center", background_color="crimson", width="100vw", padding="5px", @@ -241,14 +238,12 @@ def create(cls, comp: Component | None = None) -> Component: The connection banner component. """ if not comp: - comp = Text.create(*default_connection_error()) + comp = Span.create(*default_connection_error()) return cond( has_too_many_connection_errors, - DialogRoot.create( - DialogContent.create( - DialogTitle.create("Connection Error"), - comp, - ), + Dialog.create( + H2.create("Connection Error"), + comp, open=has_too_many_connection_errors, z_index=9999, ), diff --git a/packages/reflex-components-core/src/reflex_components_core/core/markdown_component_map.py b/packages/reflex-components-core/src/reflex_components_core/core/markdown_component_map.py new file mode 100644 index 00000000000..97e805896e2 --- /dev/null +++ b/packages/reflex-components-core/src/reflex_components_core/core/markdown_component_map.py @@ -0,0 +1,77 @@ +"""Markdown component map mixin.""" + +from __future__ import annotations + +import dataclasses +from collections.abc import Sequence + +from reflex.vars.base import Var, VarData +from reflex.vars.function import ArgsFunctionOperation, DestructuredArg + +# Special vars used in the component map. +_CHILDREN = Var(_js_expr="children", _var_type=str) +_PROPS_SPREAD = Var(_js_expr="...props") + + +@dataclasses.dataclass() +class MarkdownComponentMap: + """Mixin class for handling custom component maps in Markdown components.""" + + _explicit_return: bool = dataclasses.field(default=False) + + @classmethod + 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 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: + """Create a function Var for the component map. + + Args: + 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. + """ + fn_args = fn_args or cls.get_fn_args() + fn_body = fn_body if fn_body is not None else cls.get_fn_body() + explicit_return = explicit_return or cls._explicit_return + + return ArgsFunctionOperation.create( + args_names=(DestructuredArg(fields=tuple(fn_args)),), + return_expr=fn_body, + explicit_return=explicit_return, + _var_data=var_data, + ) + + @classmethod + def get_fn_args(cls) -> Sequence[str]: + """Get the function arguments for the component map. + + Returns: + The function arguments as a list of strings. + """ + return ["node", _CHILDREN._js_expr, _PROPS_SPREAD._js_expr] + + @classmethod + def get_fn_body(cls) -> Var: + """Get the function body for the component map. + + Returns: + The function body as a string. + """ + return Var(_js_expr="undefined", _var_type=None) diff --git a/packages/reflex-components-core/src/reflex_components_core/core/responsive.py b/packages/reflex-components-core/src/reflex_components_core/core/responsive.py index f149ca24350..bb8e16e4bfb 100644 --- a/packages/reflex-components-core/src/reflex_components_core/core/responsive.py +++ b/packages/reflex-components-core/src/reflex_components_core/core/responsive.py @@ -1,6 +1,6 @@ """Responsive components.""" -from reflex_components_radix.themes.layout.box import Box +from reflex_components_core.el.elements.typography import Div # Add responsive styles shortcuts. @@ -14,7 +14,7 @@ def mobile_only(*children, **props): Returns: The component. """ - return Box.create(*children, **props, display=["block", "none", "none", "none"]) + return Div.create(*children, **props, display=["block", "none", "none", "none"]) def tablet_only(*children, **props): @@ -27,7 +27,7 @@ def tablet_only(*children, **props): Returns: The component. """ - return Box.create(*children, **props, display=["none", "block", "block", "none"]) + return Div.create(*children, **props, display=["none", "block", "block", "none"]) def desktop_only(*children, **props): @@ -40,7 +40,7 @@ def desktop_only(*children, **props): Returns: The component. """ - return Box.create(*children, **props, display=["none", "none", "none", "block"]) + return Div.create(*children, **props, display=["none", "none", "none", "block"]) def tablet_and_desktop(*children, **props): @@ -53,7 +53,7 @@ def tablet_and_desktop(*children, **props): Returns: The component. """ - return Box.create(*children, **props, display=["none", "block", "block", "block"]) + return Div.create(*children, **props, display=["none", "block", "block", "block"]) def mobile_and_tablet(*children, **props): @@ -66,4 +66,4 @@ def mobile_and_tablet(*children, **props): Returns: The component. """ - return Box.create(*children, **props, display=["block", "block", "block", "none"]) + return Div.create(*children, **props, display=["block", "block", "block", "none"]) diff --git a/packages/reflex-components-core/src/reflex_components_core/core/sticky.py b/packages/reflex-components-core/src/reflex_components_core/core/sticky.py index 0758f75a6a1..2de1282271b 100644 --- a/packages/reflex-components-core/src/reflex_components_core/core/sticky.py +++ b/packages/reflex-components-core/src/reflex_components_core/core/sticky.py @@ -1,13 +1,11 @@ """Components for displaying the Reflex sticky logo.""" -from reflex_components_radix.themes.typography.text import Text - from reflex.components.component import ComponentNamespace from reflex.style import Style from reflex_components_core.core.colors import color from reflex_components_core.core.cond import color_mode_cond from reflex_components_core.core.responsive import desktop_only -from reflex_components_core.el.elements.inline import A +from reflex_components_core.el.elements.inline import A, Span from reflex_components_core.el.elements.media import Path, Rect, Svg @@ -42,7 +40,7 @@ def add_style(self): }) -class StickyLabel(Text): +class StickyLabel(Span): """A label that displays the Reflex sticky.""" @classmethod diff --git a/packages/reflex-components-core/src/reflex_components_core/core/upload.py b/packages/reflex-components-core/src/reflex_components_core/core/upload.py index 2beb50dd9be..1f4c6e5dcda 100644 --- a/packages/reflex-components-core/src/reflex_components_core/core/upload.py +++ b/packages/reflex-components-core/src/reflex_components_core/core/upload.py @@ -6,7 +6,6 @@ from pathlib import Path from typing import Any, ClassVar -from reflex_components_radix.themes.layout.box import Box from reflex_components_sonner.toast import toast from reflex.app import UploadFile @@ -43,6 +42,7 @@ from reflex_components_core.base.fragment import Fragment from reflex_components_core.core.cond import cond from reflex_components_core.el.elements.forms import Input +from reflex_components_core.el.elements.typography import Div DEFAULT_UPLOAD_ID: str = "default" @@ -404,7 +404,7 @@ def create(cls, *children, **props) -> Component: ] # The dropzone to use. - zone = Box.create( + zone = Div.create( upload, *children, **{k: v for k, v in props.items() if k not in supported_props}, diff --git a/packages/reflex-components-dataeditor/pyproject.toml b/packages/reflex-components-dataeditor/pyproject.toml index 025f3230e3c..35c6253cf3e 100644 --- a/packages/reflex-components-dataeditor/pyproject.toml +++ b/packages/reflex-components-dataeditor/pyproject.toml @@ -6,7 +6,9 @@ readme = "README.md" authors = [{ name = "Khaleel Al-Adhami", email = "khaleel@reflex.dev" }] maintainers = [{ name = "Khaleel Al-Adhami", email = "khaleel@reflex.dev" }] requires-python = ">=3.10" -dependencies = [] +dependencies = [ + "reflex-components-core", +] [tool.hatch.version] source = "uv-dynamic-versioning" diff --git a/packages/reflex-components-markdown/pyproject.toml b/packages/reflex-components-markdown/pyproject.toml index 57743013bf5..f0b7ed2b69b 100644 --- a/packages/reflex-components-markdown/pyproject.toml +++ b/packages/reflex-components-markdown/pyproject.toml @@ -6,7 +6,11 @@ readme = "README.md" authors = [{ name = "Khaleel Al-Adhami", email = "khaleel@reflex.dev" }] maintainers = [{ name = "Khaleel Al-Adhami", email = "khaleel@reflex.dev" }] requires-python = ">=3.10" -dependencies = [] +dependencies = [ + "reflex-components-code", + "reflex-components-core", + "reflex-components-radix", +] [tool.hatch.version] source = "uv-dynamic-versioning" diff --git a/packages/reflex-components-markdown/src/reflex_components_markdown/markdown.py b/packages/reflex-components-markdown/src/reflex_components_markdown/markdown.py index ae9e303f81f..a6f4f181557 100644 --- a/packages/reflex-components-markdown/src/reflex_components_markdown/markdown.py +++ b/packages/reflex-components-markdown/src/reflex_components_markdown/markdown.py @@ -2,7 +2,6 @@ from __future__ import annotations -import dataclasses import textwrap from collections.abc import Callable, Sequence from functools import lru_cache @@ -10,6 +9,7 @@ from types import SimpleNamespace from typing import Any +from reflex_components_core.core.markdown_component_map import MarkdownComponentMap from reflex_components_core.el.elements.typography import Div from reflex.components.component import ( @@ -23,7 +23,6 @@ from reflex.utils import console from reflex.utils.imports import ImportDict, ImportTypes, ImportVar from reflex.vars.base import LiteralVar, Var, VarData -from reflex.vars.function import ArgsFunctionOperation, DestructuredArg from reflex.vars.number import ternary_operation from reflex.vars.sequence import LiteralArrayVar @@ -191,70 +190,6 @@ def get_base_component_map() -> dict[str, Callable]: } -@dataclasses.dataclass() -class MarkdownComponentMap: - """Mixin class for handling custom component maps in Markdown components.""" - - _explicit_return: bool = dataclasses.field(default=False) - - @classmethod - 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 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: - """Create a function Var for the component map. - - Args: - 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. - """ - fn_args = fn_args or cls.get_fn_args() - fn_body = fn_body if fn_body is not None else cls.get_fn_body() - explicit_return = explicit_return or cls._explicit_return - - return ArgsFunctionOperation.create( - args_names=(DestructuredArg(fields=tuple(fn_args)),), - return_expr=fn_body, - explicit_return=explicit_return, - _var_data=var_data, - ) - - @classmethod - def get_fn_args(cls) -> Sequence[str]: - """Get the function arguments for the component map. - - Returns: - The function arguments as a list of strings. - """ - return ["node", _CHILDREN._js_expr, _PROPS_SPREAD._js_expr] - - @classmethod - def get_fn_body(cls) -> Var: - """Get the function body for the component map. - - Returns: - The function body as a string. - """ - return Var(_js_expr="undefined", _var_type=None) - - class Markdown(Component): """A markdown component.""" diff --git a/packages/reflex-components-plotly/pyproject.toml b/packages/reflex-components-plotly/pyproject.toml index 6c9e1203c35..d841771dec3 100644 --- a/packages/reflex-components-plotly/pyproject.toml +++ b/packages/reflex-components-plotly/pyproject.toml @@ -6,7 +6,9 @@ readme = "README.md" authors = [{ name = "Khaleel Al-Adhami", email = "khaleel@reflex.dev" }] maintainers = [{ name = "Khaleel Al-Adhami", email = "khaleel@reflex.dev" }] requires-python = ">=3.10" -dependencies = [] +dependencies = [ + "reflex-components-core", +] [tool.hatch.version] source = "uv-dynamic-versioning" diff --git a/packages/reflex-components-radix/pyproject.toml b/packages/reflex-components-radix/pyproject.toml index 975ca929323..3b8cfe307dc 100644 --- a/packages/reflex-components-radix/pyproject.toml +++ b/packages/reflex-components-radix/pyproject.toml @@ -6,7 +6,11 @@ readme = "README.md" authors = [{ name = "Khaleel Al-Adhami", email = "khaleel@reflex.dev" }] maintainers = [{ name = "Khaleel Al-Adhami", email = "khaleel@reflex.dev" }] requires-python = ">=3.10" -dependencies = [] +dependencies = [ + "reflex-components-core", + "reflex-components-lucide", + "reflex-components-react-router", +] [tool.hatch.version] source = "uv-dynamic-versioning" diff --git a/packages/reflex-components-radix/src/reflex_components_radix/themes/layout/list.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/layout/list.py index a1ac0cec6f0..e09e474ac31 100644 --- a/packages/reflex-components-radix/src/reflex_components_radix/themes/layout/list.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/layout/list.py @@ -6,10 +6,10 @@ from typing import Any, Literal from reflex_components_core.core.foreach import Foreach +from reflex_components_core.core.markdown_component_map import MarkdownComponentMap from reflex_components_core.el.elements.base import BaseHTML from reflex_components_core.el.elements.typography import Li, Ol, Ul from reflex_components_lucide.icon import Icon -from reflex_components_markdown.markdown import MarkdownComponentMap from reflex.components.component import ComponentNamespace, field from reflex.vars.base import Var diff --git a/packages/reflex-components-radix/src/reflex_components_radix/themes/typography/code.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/typography/code.py index a4c4c7248a4..809d0160f20 100644 --- a/packages/reflex-components-radix/src/reflex_components_radix/themes/typography/code.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/typography/code.py @@ -6,8 +6,8 @@ from __future__ import annotations from reflex_components_core.core.breakpoints import Responsive +from reflex_components_core.core.markdown_component_map import MarkdownComponentMap from reflex_components_core.el import elements -from reflex_components_markdown.markdown import MarkdownComponentMap from reflex.components.component import field from reflex.vars.base import Var diff --git a/packages/reflex-components-radix/src/reflex_components_radix/themes/typography/heading.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/typography/heading.py index e0799976ff9..8ff3c17ae4d 100644 --- a/packages/reflex-components-radix/src/reflex_components_radix/themes/typography/heading.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/typography/heading.py @@ -6,8 +6,8 @@ from __future__ import annotations from reflex_components_core.core.breakpoints import Responsive +from reflex_components_core.core.markdown_component_map import MarkdownComponentMap from reflex_components_core.el import elements -from reflex_components_markdown.markdown import MarkdownComponentMap from reflex.components.component import field from reflex.vars.base import Var diff --git a/packages/reflex-components-radix/src/reflex_components_radix/themes/typography/link.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/typography/link.py index bf6fbe6dd7c..b83952da9b9 100644 --- a/packages/reflex-components-radix/src/reflex_components_radix/themes/typography/link.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/typography/link.py @@ -10,8 +10,8 @@ from reflex_components_core.core.breakpoints import Responsive from reflex_components_core.core.colors import color from reflex_components_core.core.cond import cond +from reflex_components_core.core.markdown_component_map import MarkdownComponentMap from reflex_components_core.el.elements.inline import A -from reflex_components_markdown.markdown import MarkdownComponentMap from reflex_components_react_router.dom import ReactRouterLink from reflex.components.component import Component, MemoizationLeaf, field diff --git a/packages/reflex-components-radix/src/reflex_components_radix/themes/typography/text.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/typography/text.py index 7a79546bd34..64ab2a62757 100644 --- a/packages/reflex-components-radix/src/reflex_components_radix/themes/typography/text.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/typography/text.py @@ -8,8 +8,8 @@ from typing import Literal from reflex_components_core.core.breakpoints import Responsive +from reflex_components_core.core.markdown_component_map import MarkdownComponentMap from reflex_components_core.el import elements -from reflex_components_markdown.markdown import MarkdownComponentMap from reflex.components.component import ComponentNamespace, field from reflex.vars.base import Var diff --git a/packages/reflex-components-react-player/pyproject.toml b/packages/reflex-components-react-player/pyproject.toml index f142c057526..7bffb88ad72 100644 --- a/packages/reflex-components-react-player/pyproject.toml +++ b/packages/reflex-components-react-player/pyproject.toml @@ -6,7 +6,9 @@ readme = "README.md" authors = [{ name = "Khaleel Al-Adhami", email = "khaleel@reflex.dev" }] maintainers = [{ name = "Khaleel Al-Adhami", email = "khaleel@reflex.dev" }] requires-python = ">=3.10" -dependencies = [] +dependencies = [ + "reflex-components-core", +] [tool.hatch.version] source = "uv-dynamic-versioning" diff --git a/packages/reflex-components-react-router/pyproject.toml b/packages/reflex-components-react-router/pyproject.toml index cdf35b0305e..13384885757 100644 --- a/packages/reflex-components-react-router/pyproject.toml +++ b/packages/reflex-components-react-router/pyproject.toml @@ -6,7 +6,9 @@ readme = "README.md" authors = [{ name = "Khaleel Al-Adhami", email = "khaleel@reflex.dev" }] maintainers = [{ name = "Khaleel Al-Adhami", email = "khaleel@reflex.dev" }] requires-python = ">=3.10" -dependencies = [] +dependencies = [ + "reflex-components-core", +] [tool.hatch.version] source = "uv-dynamic-versioning" diff --git a/packages/reflex-components-sonner/pyproject.toml b/packages/reflex-components-sonner/pyproject.toml index 9cb844413d9..43f736a9e47 100644 --- a/packages/reflex-components-sonner/pyproject.toml +++ b/packages/reflex-components-sonner/pyproject.toml @@ -6,7 +6,9 @@ readme = "README.md" authors = [{ name = "Khaleel Al-Adhami", email = "khaleel@reflex.dev" }] maintainers = [{ name = "Khaleel Al-Adhami", email = "khaleel@reflex.dev" }] requires-python = ">=3.10" -dependencies = [] +dependencies = [ + "reflex-components-lucide", +] [tool.hatch.version] source = "uv-dynamic-versioning" diff --git a/pyi_hashes.json b/pyi_hashes.json index d3bf4838e6c..0f54ec2f704 100644 --- a/pyi_hashes.json +++ b/pyi_hashes.json @@ -1,6 +1,6 @@ { - "packages/reflex-components-code/src/reflex_components_code/code.pyi": "c867c4384ade3ea5d97ba794966af140", - "packages/reflex-components-code/src/reflex_components_code/shiki_code_block.pyi": "8dcccd4551a7b6ff3280f826f97f2aca", + "packages/reflex-components-code/src/reflex_components_code/code.pyi": "a2718f93b53790394297b3b7d5f8515e", + "packages/reflex-components-code/src/reflex_components_code/shiki_code_block.pyi": "332ef077e91dfd8d4ffd3ae0c68d95d6", "packages/reflex-components-core/src/reflex_components_core/__init__.pyi": "6c3ceff429117483dd0035e7a21930f4", "packages/reflex-components-core/src/reflex_components_core/base/__init__.pyi": "4beb5ba739680b7974c37241f3f6791c", "packages/reflex-components-core/src/reflex_components_core/base/app_wrap.pyi": "f8cb9cee04ffa9006d74db1b3500aadf", @@ -14,13 +14,13 @@ "packages/reflex-components-core/src/reflex_components_core/base/strict_mode.pyi": "a037c6fcb007d884915e52a6ab0046af", "packages/reflex-components-core/src/reflex_components_core/core/__init__.pyi": "17f79762cef09c69acd9df227cf3bb35", "packages/reflex-components-core/src/reflex_components_core/core/auto_scroll.pyi": "59a02f780bd1568c26f7991c99bf2110", - "packages/reflex-components-core/src/reflex_components_core/core/banner.pyi": "eab4a1e0b86d546a324ec1814fb86de0", + "packages/reflex-components-core/src/reflex_components_core/core/banner.pyi": "26dfe9462144c32280876d4f65b40507", "packages/reflex-components-core/src/reflex_components_core/core/clipboard.pyi": "3bf314f1e3a1faf9c060cd34f8fa7079", "packages/reflex-components-core/src/reflex_components_core/core/debounce.pyi": "dab0e776a0fbe8c2525ed8950bdf0293", "packages/reflex-components-core/src/reflex_components_core/core/helmet.pyi": "9fa0dea0b63a975dafe2b4c06c5a008c", "packages/reflex-components-core/src/reflex_components_core/core/html.pyi": "f3d8fe298d93bc20aee9d6461f7d5b17", - "packages/reflex-components-core/src/reflex_components_core/core/sticky.pyi": "3bca0ab6a4b0f36e8adf6e8a144e403c", - "packages/reflex-components-core/src/reflex_components_core/core/upload.pyi": "d76f2abf2acc8b1b0a01f1e46a906794", + "packages/reflex-components-core/src/reflex_components_core/core/sticky.pyi": "472a83abd9c53f8e89755f095e9bb706", + "packages/reflex-components-core/src/reflex_components_core/core/upload.pyi": "d8b7d57438a0a4558906e5c75a627ce1", "packages/reflex-components-core/src/reflex_components_core/core/window_events.pyi": "22dbc116b98a9e70a36cb1ef846adc62", "packages/reflex-components-core/src/reflex_components_core/datadisplay/__init__.pyi": "73e27e0bfd7e914c4baba7b166e75736", "packages/reflex-components-core/src/reflex_components_core/el/__init__.pyi": "a1d4d65ddc73b5fbba3726f4bcf6d6f0", @@ -39,7 +39,7 @@ "packages/reflex-components-dataeditor/src/reflex_components_dataeditor/dataeditor.pyi": "0f8ec6d513cc649caf10981cf29bcd38", "packages/reflex-components-gridjs/src/reflex_components_gridjs/datatable.pyi": "3dcdc432507f1a455b97f719160118ac", "packages/reflex-components-lucide/src/reflex_components_lucide/icon.pyi": "21ac24351c0e0de123dec46a7e90209b", - "packages/reflex-components-markdown/src/reflex_components_markdown/markdown.pyi": "2968ff9eaf788b5a93fa5c4d6d5d818c", + "packages/reflex-components-markdown/src/reflex_components_markdown/markdown.pyi": "726d0c3d7fd6ef38110e77555fa69380", "packages/reflex-components-moment/src/reflex_components_moment/moment.pyi": "dffd48bde7241e80093b2f8143cfc30e", "packages/reflex-components-plotly/src/reflex_components_plotly/plotly.pyi": "84b5f3f01d5fcb60067be9105a4a12ba", "packages/reflex-components-radix/src/reflex_components_radix/__init__.pyi": "899d63be42eb21c187519920518bf32e", @@ -97,16 +97,16 @@ "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/container.pyi": "fe41ccaa90e859f2f8a74b015fbde058", "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/flex.pyi": "f615a989fcfbda790de3f1305c42605a", "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/grid.pyi": "2a23d9b1d7f2098f3e05e6deeb7453fb", - "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/list.pyi": "07bd0842f489c9cd4f1c6e3800cc7afd", + "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/list.pyi": "d6c7fc4dc8422d9ca3d020f4005e48de", "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/section.pyi": "edada2fc5d0139b61d035485ecaf5ebb", "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/spacer.pyi": "a289f92e5da0ec4576e4c0bc1ee4986c", "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/stack.pyi": "b857d2ed0549a217fa28e3cb86c52ffb", "packages/reflex-components-radix/src/reflex_components_radix/themes/typography/__init__.pyi": "15b13877fc52b82182fc42acd8adb027", "packages/reflex-components-radix/src/reflex_components_radix/themes/typography/blockquote.pyi": "833c57bded556a13db59726769183230", - "packages/reflex-components-radix/src/reflex_components_radix/themes/typography/code.pyi": "0ea298c8016e29b3343d708c43a66179", - "packages/reflex-components-radix/src/reflex_components_radix/themes/typography/heading.pyi": "e38ca88110e315303c18d615556d1542", - "packages/reflex-components-radix/src/reflex_components_radix/themes/typography/link.pyi": "edaf5f2f316bd35b6d32f7a369d5fad6", - "packages/reflex-components-radix/src/reflex_components_radix/themes/typography/text.pyi": "c6bf9ba91a7650d6742773be94bdde33", + "packages/reflex-components-radix/src/reflex_components_radix/themes/typography/code.pyi": "66e385994fcfc53183d2cba39e101174", + "packages/reflex-components-radix/src/reflex_components_radix/themes/typography/heading.pyi": "25c23877aba76ab41c5347e127a4e279", + "packages/reflex-components-radix/src/reflex_components_radix/themes/typography/link.pyi": "3ab992394caf0da003641a6b9dccbdde", + "packages/reflex-components-radix/src/reflex_components_radix/themes/typography/text.pyi": "0d4675582c25f95ff2317c3342e2b8c6", "packages/reflex-components-react-player/src/reflex_components_react_player/audio.pyi": "383e6c81567915da364dd7c02705dd7c", "packages/reflex-components-react-player/src/reflex_components_react_player/react_player.pyi": "20deae7c420096252c8958f1d9194b9e", "packages/reflex-components-react-player/src/reflex_components_react_player/video.pyi": "58e2a086560862b2f9ecd1f18840e893", diff --git a/tests/units/components/markdown/test_markdown.py b/tests/units/components/markdown/test_markdown.py index 4f766c42aaa..3ec94cd6f0e 100644 --- a/tests/units/components/markdown/test_markdown.py +++ b/tests/units/components/markdown/test_markdown.py @@ -1,7 +1,8 @@ import pytest from reflex_components_code.code import CodeBlock from reflex_components_code.shiki_code_block import ShikiHighLevelCodeBlock -from reflex_components_markdown.markdown import Markdown, MarkdownComponentMap +from reflex_components_core.core.markdown_component_map import MarkdownComponentMap +from reflex_components_markdown.markdown import Markdown from reflex_components_radix.themes.layout.box import Box from reflex_components_radix.themes.typography.heading import Heading From cc8b3e337f676d6b8e48605235becd03d0c71cd2 Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Wed, 25 Mar 2026 17:44:22 -0700 Subject: [PATCH 024/113] add build hook to all of those guys --- packages/hatch-reflex-pyi/pyproject.toml | 25 +++++++ .../src/hatch_reflex_pyi/__init__.py | 0 .../src/hatch_reflex_pyi/hooks.py | 11 +++ .../src/hatch_reflex_pyi/plugin.py | 75 +++++++++++++++++++ .../reflex-components-code/pyproject.toml | 9 ++- .../reflex-components-core/pyproject.toml | 9 ++- .../pyproject.toml | 9 ++- .../reflex-components-gridjs/pyproject.toml | 9 ++- .../reflex-components-lucide/pyproject.toml | 9 ++- .../reflex-components-markdown/pyproject.toml | 9 ++- .../reflex-components-moment/pyproject.toml | 9 ++- .../reflex-components-plotly/pyproject.toml | 9 ++- .../reflex-components-radix/pyproject.toml | 9 ++- .../pyproject.toml | 9 ++- .../pyproject.toml | 9 ++- .../reflex-components-recharts/pyproject.toml | 9 ++- .../reflex-components-sonner/pyproject.toml | 9 ++- 17 files changed, 215 insertions(+), 13 deletions(-) create mode 100644 packages/hatch-reflex-pyi/pyproject.toml create mode 100644 packages/hatch-reflex-pyi/src/hatch_reflex_pyi/__init__.py create mode 100644 packages/hatch-reflex-pyi/src/hatch_reflex_pyi/hooks.py create mode 100644 packages/hatch-reflex-pyi/src/hatch_reflex_pyi/plugin.py diff --git a/packages/hatch-reflex-pyi/pyproject.toml b/packages/hatch-reflex-pyi/pyproject.toml new file mode 100644 index 00000000000..a2754ad8a0c --- /dev/null +++ b/packages/hatch-reflex-pyi/pyproject.toml @@ -0,0 +1,25 @@ +[project] +name = "hatch-reflex-pyi" +dynamic = ["version"] +description = "Hatch build hook that generates .pyi stubs for Reflex component packages." +readme = "README.md" +authors = [{ name = "Khaleel Al-Adhami", email = "khaleel@reflex.dev" }] +maintainers = [{ name = "Khaleel Al-Adhami", email = "khaleel@reflex.dev" }] +requires-python = ">=3.10" +dependencies = [ + "hatchling", +] + +[project.entry-points.hatch] +reflex-pyi = "hatch_reflex_pyi.hooks" + +[tool.hatch.version] +source = "uv-dynamic-versioning" + +[tool.uv-dynamic-versioning] +pattern-prefix = "hatch-reflex-pyi-" +fallback-version = "0.0.0dev0" + +[build-system] +requires = ["hatchling", "uv-dynamic-versioning"] +build-backend = "hatchling.build" diff --git a/packages/hatch-reflex-pyi/src/hatch_reflex_pyi/__init__.py b/packages/hatch-reflex-pyi/src/hatch_reflex_pyi/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/packages/hatch-reflex-pyi/src/hatch_reflex_pyi/hooks.py b/packages/hatch-reflex-pyi/src/hatch_reflex_pyi/hooks.py new file mode 100644 index 00000000000..65009de40d0 --- /dev/null +++ b/packages/hatch-reflex-pyi/src/hatch_reflex_pyi/hooks.py @@ -0,0 +1,11 @@ +"""Hatch plugin registration for reflex-pyi build hook.""" + +from hatchling.plugin import hookimpl + +from hatch_reflex_pyi.plugin import ReflexPyiBuildHook + + +@hookimpl +def hatch_register_build_hook(): + """Register the reflex-pyi build hook.""" + return ReflexPyiBuildHook diff --git a/packages/hatch-reflex-pyi/src/hatch_reflex_pyi/plugin.py b/packages/hatch-reflex-pyi/src/hatch_reflex_pyi/plugin.py new file mode 100644 index 00000000000..4f3b1effde0 --- /dev/null +++ b/packages/hatch-reflex-pyi/src/hatch_reflex_pyi/plugin.py @@ -0,0 +1,75 @@ +"""Hatch build hook that generates .pyi stub files for Reflex component packages.""" + +from __future__ import annotations + +import pathlib +import subprocess +import sys +from typing import Any + +from hatchling.builders.hooks.plugin.interface import BuildHookInterface + + +class ReflexPyiBuildHook(BuildHookInterface): + """Build hook that generates .pyi stubs for component packages.""" + + PLUGIN_NAME = "reflex-pyi" + + def _src_dir(self) -> pathlib.Path | None: + """Find the source directory under src/. + + Returns: + The source directory path, or None if not found. + """ + src = pathlib.Path(self.root) / "src" + if not src.is_dir(): + return None + children = [ + d for d in src.iterdir() if d.is_dir() and not d.name.startswith(".") + ] + return children[0] if len(children) == 1 else None + + def _marker(self) -> pathlib.Path: + """Get the marker file path. + + Returns: + The marker file path. + """ + return ( + pathlib.Path(self.directory) + / f".{self.metadata.name}-{self.metadata.version}.pyi_generated" + ) + + def initialize(self, version: str, build_data: dict[str, Any]) -> None: + """Generate .pyi stubs before the build. + + Args: + version: The version being built. + build_data: Additional build data. + """ + if self._marker().exists(): + return + + src_dir = self._src_dir() + if src_dir is None: + return + + try: + from reflex.utils.pyi_generator import PyiGenerator # noqa: F401 + except ImportError: + # reflex is not installed — skip pyi generation. + # Pre-generated .pyi files in the sdist will be used. + return + + for file in src_dir.rglob("*.pyi"): + file.unlink(missing_ok=True) + + # Run from src/ so _path_to_module_name produces valid import names + # (e.g. "reflex_components_core.core.banner" instead of + # "packages.reflex-components-core.src.reflex_components_core.core.banner"). + subprocess.run( + [sys.executable, "-m", "reflex.utils.pyi_generator", src_dir.name], + cwd=src_dir.parent, + check=True, + ) + self._marker().touch() diff --git a/packages/reflex-components-code/pyproject.toml b/packages/reflex-components-code/pyproject.toml index 6d97ca7f457..9d5cef6f8e3 100644 --- a/packages/reflex-components-code/pyproject.toml +++ b/packages/reflex-components-code/pyproject.toml @@ -19,6 +19,13 @@ source = "uv-dynamic-versioning" pattern-prefix = "reflex-components-code-" fallback-version = "0.0.0dev0" +[tool.hatch.build] +targets.sdist.artifacts = ["*.pyi"] +targets.wheel.artifacts = ["*.pyi"] + +[tool.hatch.build.hooks.reflex-pyi] +dependencies = ["ruff"] + [build-system] -requires = ["hatchling", "uv-dynamic-versioning"] +requires = ["hatchling", "uv-dynamic-versioning", "hatch-reflex-pyi"] build-backend = "hatchling.build" diff --git a/packages/reflex-components-core/pyproject.toml b/packages/reflex-components-core/pyproject.toml index aa35fb1b2d9..1b8c8a188ea 100644 --- a/packages/reflex-components-core/pyproject.toml +++ b/packages/reflex-components-core/pyproject.toml @@ -18,6 +18,13 @@ source = "uv-dynamic-versioning" pattern-prefix = "reflex-components-core-" fallback-version = "0.0.0dev0" +[tool.hatch.build] +targets.sdist.artifacts = ["*.pyi"] +targets.wheel.artifacts = ["*.pyi"] + +[tool.hatch.build.hooks.reflex-pyi] +dependencies = ["ruff"] + [build-system] -requires = ["hatchling", "uv-dynamic-versioning"] +requires = ["hatchling", "uv-dynamic-versioning", "hatch-reflex-pyi"] build-backend = "hatchling.build" diff --git a/packages/reflex-components-dataeditor/pyproject.toml b/packages/reflex-components-dataeditor/pyproject.toml index 35c6253cf3e..157363d72b4 100644 --- a/packages/reflex-components-dataeditor/pyproject.toml +++ b/packages/reflex-components-dataeditor/pyproject.toml @@ -17,6 +17,13 @@ source = "uv-dynamic-versioning" pattern-prefix = "reflex-components-dataeditor-" fallback-version = "0.0.0dev0" +[tool.hatch.build] +targets.sdist.artifacts = ["*.pyi"] +targets.wheel.artifacts = ["*.pyi"] + +[tool.hatch.build.hooks.reflex-pyi] +dependencies = ["ruff"] + [build-system] -requires = ["hatchling", "uv-dynamic-versioning"] +requires = ["hatchling", "uv-dynamic-versioning", "hatch-reflex-pyi"] build-backend = "hatchling.build" diff --git a/packages/reflex-components-gridjs/pyproject.toml b/packages/reflex-components-gridjs/pyproject.toml index 1429c3de472..4be9625dd1e 100644 --- a/packages/reflex-components-gridjs/pyproject.toml +++ b/packages/reflex-components-gridjs/pyproject.toml @@ -15,6 +15,13 @@ source = "uv-dynamic-versioning" pattern-prefix = "reflex-components-gridjs-" fallback-version = "0.0.0dev0" +[tool.hatch.build] +targets.sdist.artifacts = ["*.pyi"] +targets.wheel.artifacts = ["*.pyi"] + +[tool.hatch.build.hooks.reflex-pyi] +dependencies = ["ruff"] + [build-system] -requires = ["hatchling", "uv-dynamic-versioning"] +requires = ["hatchling", "uv-dynamic-versioning", "hatch-reflex-pyi"] build-backend = "hatchling.build" diff --git a/packages/reflex-components-lucide/pyproject.toml b/packages/reflex-components-lucide/pyproject.toml index 23c722dd8aa..cb85be39b9e 100644 --- a/packages/reflex-components-lucide/pyproject.toml +++ b/packages/reflex-components-lucide/pyproject.toml @@ -15,6 +15,13 @@ source = "uv-dynamic-versioning" pattern-prefix = "reflex-components-lucide-" fallback-version = "0.0.0dev0" +[tool.hatch.build] +targets.sdist.artifacts = ["*.pyi"] +targets.wheel.artifacts = ["*.pyi"] + +[tool.hatch.build.hooks.reflex-pyi] +dependencies = ["ruff"] + [build-system] -requires = ["hatchling", "uv-dynamic-versioning"] +requires = ["hatchling", "uv-dynamic-versioning", "hatch-reflex-pyi"] build-backend = "hatchling.build" diff --git a/packages/reflex-components-markdown/pyproject.toml b/packages/reflex-components-markdown/pyproject.toml index f0b7ed2b69b..ca7abf47b61 100644 --- a/packages/reflex-components-markdown/pyproject.toml +++ b/packages/reflex-components-markdown/pyproject.toml @@ -19,6 +19,13 @@ source = "uv-dynamic-versioning" pattern-prefix = "reflex-components-markdown-" fallback-version = "0.0.0dev0" +[tool.hatch.build] +targets.sdist.artifacts = ["*.pyi"] +targets.wheel.artifacts = ["*.pyi"] + +[tool.hatch.build.hooks.reflex-pyi] +dependencies = ["ruff"] + [build-system] -requires = ["hatchling", "uv-dynamic-versioning"] +requires = ["hatchling", "uv-dynamic-versioning", "hatch-reflex-pyi"] build-backend = "hatchling.build" diff --git a/packages/reflex-components-moment/pyproject.toml b/packages/reflex-components-moment/pyproject.toml index aac90ca206e..08657dbb885 100644 --- a/packages/reflex-components-moment/pyproject.toml +++ b/packages/reflex-components-moment/pyproject.toml @@ -15,6 +15,13 @@ source = "uv-dynamic-versioning" pattern-prefix = "reflex-components-moment-" fallback-version = "0.0.0dev0" +[tool.hatch.build] +targets.sdist.artifacts = ["*.pyi"] +targets.wheel.artifacts = ["*.pyi"] + +[tool.hatch.build.hooks.reflex-pyi] +dependencies = ["ruff"] + [build-system] -requires = ["hatchling", "uv-dynamic-versioning"] +requires = ["hatchling", "uv-dynamic-versioning", "hatch-reflex-pyi"] build-backend = "hatchling.build" diff --git a/packages/reflex-components-plotly/pyproject.toml b/packages/reflex-components-plotly/pyproject.toml index d841771dec3..991e56e377b 100644 --- a/packages/reflex-components-plotly/pyproject.toml +++ b/packages/reflex-components-plotly/pyproject.toml @@ -17,6 +17,13 @@ source = "uv-dynamic-versioning" pattern-prefix = "reflex-components-plotly-" fallback-version = "0.0.0dev0" +[tool.hatch.build] +targets.sdist.artifacts = ["*.pyi"] +targets.wheel.artifacts = ["*.pyi"] + +[tool.hatch.build.hooks.reflex-pyi] +dependencies = ["ruff"] + [build-system] -requires = ["hatchling", "uv-dynamic-versioning"] +requires = ["hatchling", "uv-dynamic-versioning", "hatch-reflex-pyi"] build-backend = "hatchling.build" diff --git a/packages/reflex-components-radix/pyproject.toml b/packages/reflex-components-radix/pyproject.toml index 3b8cfe307dc..0eff373a9a5 100644 --- a/packages/reflex-components-radix/pyproject.toml +++ b/packages/reflex-components-radix/pyproject.toml @@ -19,6 +19,13 @@ source = "uv-dynamic-versioning" pattern-prefix = "reflex-components-radix-" fallback-version = "0.0.0dev0" +[tool.hatch.build] +targets.sdist.artifacts = ["*.pyi"] +targets.wheel.artifacts = ["*.pyi"] + +[tool.hatch.build.hooks.reflex-pyi] +dependencies = ["ruff"] + [build-system] -requires = ["hatchling", "uv-dynamic-versioning"] +requires = ["hatchling", "uv-dynamic-versioning", "hatch-reflex-pyi"] build-backend = "hatchling.build" diff --git a/packages/reflex-components-react-player/pyproject.toml b/packages/reflex-components-react-player/pyproject.toml index 7bffb88ad72..a356d5732d7 100644 --- a/packages/reflex-components-react-player/pyproject.toml +++ b/packages/reflex-components-react-player/pyproject.toml @@ -17,6 +17,13 @@ source = "uv-dynamic-versioning" pattern-prefix = "reflex-components-react-player-" fallback-version = "0.0.0dev0" +[tool.hatch.build] +targets.sdist.artifacts = ["*.pyi"] +targets.wheel.artifacts = ["*.pyi"] + +[tool.hatch.build.hooks.reflex-pyi] +dependencies = ["ruff"] + [build-system] -requires = ["hatchling", "uv-dynamic-versioning"] +requires = ["hatchling", "uv-dynamic-versioning", "hatch-reflex-pyi"] build-backend = "hatchling.build" diff --git a/packages/reflex-components-react-router/pyproject.toml b/packages/reflex-components-react-router/pyproject.toml index 13384885757..309472281b6 100644 --- a/packages/reflex-components-react-router/pyproject.toml +++ b/packages/reflex-components-react-router/pyproject.toml @@ -17,6 +17,13 @@ source = "uv-dynamic-versioning" pattern-prefix = "reflex-components-react-router-" fallback-version = "0.0.0dev0" +[tool.hatch.build] +targets.sdist.artifacts = ["*.pyi"] +targets.wheel.artifacts = ["*.pyi"] + +[tool.hatch.build.hooks.reflex-pyi] +dependencies = ["ruff"] + [build-system] -requires = ["hatchling", "uv-dynamic-versioning"] +requires = ["hatchling", "uv-dynamic-versioning", "hatch-reflex-pyi"] build-backend = "hatchling.build" diff --git a/packages/reflex-components-recharts/pyproject.toml b/packages/reflex-components-recharts/pyproject.toml index 8367651473e..098082c7597 100644 --- a/packages/reflex-components-recharts/pyproject.toml +++ b/packages/reflex-components-recharts/pyproject.toml @@ -15,6 +15,13 @@ source = "uv-dynamic-versioning" pattern-prefix = "reflex-components-recharts-" fallback-version = "0.0.0dev0" +[tool.hatch.build] +targets.sdist.artifacts = ["*.pyi"] +targets.wheel.artifacts = ["*.pyi"] + +[tool.hatch.build.hooks.reflex-pyi] +dependencies = ["ruff"] + [build-system] -requires = ["hatchling", "uv-dynamic-versioning"] +requires = ["hatchling", "uv-dynamic-versioning", "hatch-reflex-pyi"] build-backend = "hatchling.build" diff --git a/packages/reflex-components-sonner/pyproject.toml b/packages/reflex-components-sonner/pyproject.toml index 43f736a9e47..443afa0e5ec 100644 --- a/packages/reflex-components-sonner/pyproject.toml +++ b/packages/reflex-components-sonner/pyproject.toml @@ -17,6 +17,13 @@ source = "uv-dynamic-versioning" pattern-prefix = "reflex-components-sonner-" fallback-version = "0.0.0dev0" +[tool.hatch.build] +targets.sdist.artifacts = ["*.pyi"] +targets.wheel.artifacts = ["*.pyi"] + +[tool.hatch.build.hooks.reflex-pyi] +dependencies = ["ruff"] + [build-system] -requires = ["hatchling", "uv-dynamic-versioning"] +requires = ["hatchling", "uv-dynamic-versioning", "hatch-reflex-pyi"] build-backend = "hatchling.build" From 08105bb03e893a9bdddf1de7a0308aa23796bec8 Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Wed, 25 Mar 2026 17:47:04 -0700 Subject: [PATCH 025/113] precommit --- packages/hatch-reflex-pyi/src/hatch_reflex_pyi/__init__.py | 1 + packages/hatch-reflex-pyi/src/hatch_reflex_pyi/hooks.py | 6 +++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/hatch-reflex-pyi/src/hatch_reflex_pyi/__init__.py b/packages/hatch-reflex-pyi/src/hatch_reflex_pyi/__init__.py index e69de29bb2d..e32a5b34796 100644 --- a/packages/hatch-reflex-pyi/src/hatch_reflex_pyi/__init__.py +++ b/packages/hatch-reflex-pyi/src/hatch_reflex_pyi/__init__.py @@ -0,0 +1 @@ +"""Hatch build hook that generates .pyi stubs for Reflex component packages.""" diff --git a/packages/hatch-reflex-pyi/src/hatch_reflex_pyi/hooks.py b/packages/hatch-reflex-pyi/src/hatch_reflex_pyi/hooks.py index 65009de40d0..d244ef08210 100644 --- a/packages/hatch-reflex-pyi/src/hatch_reflex_pyi/hooks.py +++ b/packages/hatch-reflex-pyi/src/hatch_reflex_pyi/hooks.py @@ -7,5 +7,9 @@ @hookimpl def hatch_register_build_hook(): - """Register the reflex-pyi build hook.""" + """Register the reflex-pyi build hook. + + Returns: + ReflexPyiBuildHook: The build hook class to be registered." + """ return ReflexPyiBuildHook From 95fc672bb37df55befe81aae3f32e070cf4dcdd7 Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Wed, 25 Mar 2026 17:56:51 -0700 Subject: [PATCH 026/113] crazy how a readme can do that --- packages/hatch-reflex-pyi/README.md | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 packages/hatch-reflex-pyi/README.md diff --git a/packages/hatch-reflex-pyi/README.md b/packages/hatch-reflex-pyi/README.md new file mode 100644 index 00000000000..1083758b200 --- /dev/null +++ b/packages/hatch-reflex-pyi/README.md @@ -0,0 +1,3 @@ +# hatch-reflex-pyi + +Hatch build hook that generates .pyi stubs for Reflex component packages. From 634e750b6120ce6c598f89a2b1d05a19f45847ae Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Wed, 25 Mar 2026 18:00:13 -0700 Subject: [PATCH 027/113] add workspace package --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index bd8849d903f..03aa073f0c1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -328,7 +328,7 @@ hooks = [ [tool.uv] required-version = ">=0.7.0" -sources = { reflex-components-code = { workspace = true }, reflex-components-core = { workspace = true }, reflex-components-dataeditor = { workspace = true }, reflex-components-gridjs = { workspace = true }, reflex-components-lucide = { workspace = true }, reflex-components-markdown = { workspace = true }, reflex-components-moment = { workspace = true }, reflex-components-plotly = { workspace = true }, reflex-components-radix = { workspace = true }, reflex-components-react-player = { workspace = true }, reflex-components-react-router = { workspace = true }, reflex-components-recharts = { workspace = true }, reflex-components-sonner = { workspace = true }, reflex-docgen = { workspace = true } } +sources = { hatch-reflex-pyi = { workspace = true }, reflex-components-code = { workspace = true }, reflex-components-core = { workspace = true }, reflex-components-dataeditor = { workspace = true }, reflex-components-gridjs = { workspace = true }, reflex-components-lucide = { workspace = true }, reflex-components-markdown = { workspace = true }, reflex-components-moment = { workspace = true }, reflex-components-plotly = { workspace = true }, reflex-components-radix = { workspace = true }, reflex-components-react-player = { workspace = true }, reflex-components-react-router = { workspace = true }, reflex-components-recharts = { workspace = true }, reflex-components-sonner = { workspace = true }, reflex-docgen = { workspace = true } } [tool.uv.workspace] members = ["packages/*"] From 8a31e185f8f9b333f9dd006e7c1758ac4ed60644 Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Wed, 25 Mar 2026 18:04:43 -0700 Subject: [PATCH 028/113] fix unit test failures --- tests/units/components/core/test_banner.py | 3 --- tests/units/components/core/test_responsive.py | 12 ++++++------ 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/tests/units/components/core/test_banner.py b/tests/units/components/core/test_banner.py index 04987db5311..1df213f5936 100644 --- a/tests/units/components/core/test_banner.py +++ b/tests/units/components/core/test_banner.py @@ -4,7 +4,6 @@ ConnectionPulser, WebsocketTargetURL, ) -from reflex_components_radix.themes.base import RadixThemesComponent from reflex_components_radix.themes.typography.text import Text @@ -25,7 +24,6 @@ def test_connection_banner(): "react", "$/utils/context", "$/utils/state", - RadixThemesComponent.create().library or "", "$/env.json", )) @@ -41,7 +39,6 @@ def test_connection_modal(): "react", "$/utils/context", "$/utils/state", - RadixThemesComponent.create().library or "", "$/env.json", )) diff --git a/tests/units/components/core/test_responsive.py b/tests/units/components/core/test_responsive.py index 515af37af9e..b838bcf270b 100644 --- a/tests/units/components/core/test_responsive.py +++ b/tests/units/components/core/test_responsive.py @@ -5,34 +5,34 @@ tablet_and_desktop, tablet_only, ) -from reflex_components_radix.themes.layout.box import Box +from reflex_components_core.el.elements.typography import Div def test_mobile_only(): """Test the mobile_only responsive component.""" component = mobile_only("Content") - assert isinstance(component, Box) + assert isinstance(component, Div) def test_tablet_only(): """Test the tablet_only responsive component.""" component = tablet_only("Content") - assert isinstance(component, Box) + assert isinstance(component, Div) def test_desktop_only(): """Test the desktop_only responsive component.""" component = desktop_only("Content") - assert isinstance(component, Box) + assert isinstance(component, Div) def test_tablet_and_desktop(): """Test the tablet_and_desktop responsive component.""" component = tablet_and_desktop("Content") - assert isinstance(component, Box) + assert isinstance(component, Div) def test_mobile_and_tablet(): """Test the mobile_and_tablet responsive component.""" component = mobile_and_tablet("Content") - assert isinstance(component, Box) + assert isinstance(component, Div) From 14492b3ada19018e4848e25f0ca75d138e083cdb Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Wed, 25 Mar 2026 18:37:18 -0700 Subject: [PATCH 029/113] cleanup docs --- docs/advanced_onboarding/code_structure.md | 5 +-- docs/advanced_onboarding/configuration.md | 7 ++-- docs/advanced_onboarding/how-reflex-works.md | 15 ++++---- docs/api-reference/browser_javascript.md | 6 ++-- docs/api-reference/event_triggers.md | 3 +- docs/api-reference/plugins.md | 1 - docs/assets/overview.md | 5 +-- docs/assets/upload_and_download_files.md | 7 ++-- .../authentication/authentication_overview.md | 5 +-- docs/components/conditional_rendering.md | 10 +++--- docs/components/props.md | 10 +++--- docs/components/rendering_iterables.md | 7 ++-- docs/custom-components/command-reference.md | 5 +-- docs/custom-components/overview.md | 9 ++--- .../prerequisites-for-publishing.md | 7 ++-- docs/events/event_actions.md | 2 +- docs/events/events_overview.md | 3 +- docs/events/special_events.md | 3 +- docs/getting_started/basics.md | 32 ++++++++--------- docs/getting_started/chatapp_tutorial.md | 24 +++++-------- docs/getting_started/dashboard_tutorial.md | 25 +++++++------ docs/getting_started/installation.md | 1 - docs/getting_started/introduction.md | 26 +++++--------- docs/getting_started/open_source_templates.md | 1 - docs/getting_started/project-structure.md | 8 ++--- docs/library/data-display/avatar.md | 35 +++++++++---------- docs/library/data-display/callout.md | 1 - docs/library/forms/input.md | 3 +- docs/library/forms/select-ll.md | 3 +- docs/library/forms/switch.md | 1 - docs/library/forms/upload.md | 7 ++-- docs/library/graphing/charts/barchart.md | 3 +- docs/library/graphing/charts/composedchart.md | 3 +- docs/library/graphing/charts/linechart.md | 3 +- docs/library/graphing/charts/scatterchart.md | 3 +- docs/library/layout/aspect_ratio.md | 3 +- docs/library/layout/box.md | 3 +- docs/library/layout/card.md | 3 +- docs/library/layout/inset.md | 11 +++--- docs/library/media/audio.md | 3 +- docs/library/media/image.md | 10 +++--- docs/library/media/video.md | 3 +- docs/library/other/clipboard.md | 1 - docs/library/other/html_embed.md | 3 +- docs/library/other/memo.md | 1 - docs/library/overlay/dialog.md | 3 +- .../tables-and-data-grids/data_editor.md | 3 +- .../tables-and-data-grids/data_table.md | 3 +- docs/library/tables-and-data-grids/table.md | 9 +++-- docs/library/typography/link.md | 3 +- docs/pages/overview.md | 11 +++--- docs/recipes/auth/login_form.md | 9 +++-- docs/recipes/auth/signup_form.md | 9 +++-- docs/recipes/content/forms.md | 3 +- docs/recipes/content/grid.md | 6 ++-- docs/recipes/content/multi_column_row.md | 3 +- docs/recipes/content/top_banner.md | 3 +- docs/recipes/layout/footer.md | 7 ++-- docs/recipes/layout/navbar.md | 25 +++++++------ docs/recipes/layout/sidebar.md | 5 ++- docs/recipes/others/checkboxes.md | 3 +- docs/state_structure/component_state.md | 5 ++- docs/styling/custom-stylesheets.md | 3 +- docs/styling/overview.md | 7 ++-- docs/styling/tailwind.md | 3 +- docs/styling/theming.md | 8 ++--- docs/ui/overview.md | 4 +-- docs/vars/base_vars.md | 3 +- docs/vars/custom_vars.md | 3 +- docs/vars/var-operations.md | 4 +-- docs/wrapping-react/library-and-tags.md | 5 +-- docs/wrapping-react/more-wrapping-examples.md | 7 ++-- docs/wrapping-react/overview.md | 3 +- 73 files changed, 185 insertions(+), 305 deletions(-) diff --git a/docs/advanced_onboarding/code_structure.md b/docs/advanced_onboarding/code_structure.md index 70c53648aa2..0e1ab6c2146 100644 --- a/docs/advanced_onboarding/code_structure.md +++ b/docs/advanced_onboarding/code_structure.md @@ -1,6 +1,3 @@ -```python exec -from pcweb.pages.docs import custom_components -``` # Project Structure (Advanced) @@ -251,7 +248,7 @@ component. ### External Components -Reflex 0.4.3 introduced support for the [`reflex component` CLI commands]({custom_components.overview.path}), which makes it easy +Reflex 0.4.3 introduced support for the [`reflex component` CLI commands](/docs/custom-components/overview), which makes it easy to bundle up common functionality to publish on PyPI as a standalone Python package that can be installed and used in any Reflex app. diff --git a/docs/advanced_onboarding/configuration.md b/docs/advanced_onboarding/configuration.md index bce5179c727..09e54e94169 100644 --- a/docs/advanced_onboarding/configuration.md +++ b/docs/advanced_onboarding/configuration.md @@ -1,6 +1,3 @@ -```python exec -from pcweb.pages.docs import api_reference -``` # Configuration @@ -26,7 +23,7 @@ config = rx.Config( ) ``` -See the [config reference]({api_reference.config.path}) for all the parameters available. +See the [config reference](https://reflex.dev/docs/api-reference/config/) for all the parameters available. ## Environment Variables @@ -45,7 +42,7 @@ Finally, you can override the configuration file and environment variables by pa reflex run --frontend-port 3001 ``` -See the [CLI reference]({api_reference.cli.path}) for all the arguments available. +See the [CLI reference](/docs/api-reference/cli) for all the arguments available. ## Customizable App Data Directory diff --git a/docs/advanced_onboarding/how-reflex-works.md b/docs/advanced_onboarding/how-reflex-works.md index 47b42afab50..bb181e8d069 100644 --- a/docs/advanced_onboarding/how-reflex-works.md +++ b/docs/advanced_onboarding/how-reflex-works.md @@ -1,8 +1,5 @@ ```python exec from pcweb import constants -from pcweb.constants import REFLEX_ASSETS_CDN -from pcweb.pages.docs import wrapping_react, custom_components, styling, events -from pcweb.pages.docs.custom_components import custom_components as cc ``` # How Reflex Works @@ -60,7 +57,7 @@ from reflex_image_zoom import image_zoom ``` ```python eval -image_zoom(rx.image(src=f"{REFLEX_ASSETS_CDN}other/architecture.webp")) +image_zoom(rx.image(src="https://web.reflex-assets.dev/other/architecture.webp")) ``` ```python eval @@ -116,15 +113,15 @@ Many of our core components are based on [Radix](https://radix-ui.com/), a popul We chose React because it is a popular library with a huge ecosystem. Our goal isn't to recreate the web ecosystem, but to make it accessible to Python developers. -This also lets our users bring their own components if we don't have a component they need. Users can [wrap their own React components]({wrapping_react.overview.path}) and then [publish them]({custom_components.overview.path}) for others to use. Over time we will build out our [third party component ecosystem]({cc.path}) so that users can easily find and use components that others have built. +This also lets our users bring their own components if we don't have a component they need. Users can [wrap their own React components](/docs/wrapping-react/overview) and then [publish them](/docs/custom-components/overview) for others to use. Over time we will build out our [third party component ecosystem](/docs/custom-components/overview) so that users can easily find and use components that others have built. ### Styling We wanted to make sure Reflex apps look good out of the box, while still giving developers full control over the appearance of their app. -We have a core [theming system]({styling.theming.path}) that lets you set high level styling options such as dark mode and accent color throughout your app to give it a unified look and feel. +We have a core [theming system](/docs/styling/theming) that lets you set high level styling options such as dark mode and accent color throughout your app to give it a unified look and feel. -Beyond this, Reflex components can be styled using the full power of CSS. We leverage the [Emotion](https://emotion.sh/docs/introduction) library to allow "CSS-in-Python" styling, so you can pass any CSS prop as a keyword argument to a component. This includes [responsive props]({styling.responsive.path}) by passing a list of values. +Beyond this, Reflex components can be styled using the full power of CSS. We leverage the [Emotion](https://emotion.sh/docs/introduction) library to allow "CSS-in-Python" styling, so you can pass any CSS prop as a keyword argument to a component. This includes [responsive props](/docs/styling/responsive) by passing a list of values. ## Backend @@ -199,7 +196,7 @@ On the frontend, we maintain an event queue of all pending events. When an event is triggered, it is added to the queue. We have a `processing` flag to make sure only one event is processed at a time. This ensures that the state is always consistent and there aren't any race conditions with two event handlers modifying the state at the same time. ```md alert info -# There are exceptions to this, such as [background events]({events.background_events.path}) which allow you to run events in the background without blocking the UI. +# There are exceptions to this, such as [background events](/docs/events/background_events) which allow you to run events in the background without blocking the UI. ``` Once the event is ready to be processed, it is sent to the backend through a WebSocket connection. @@ -227,7 +224,7 @@ In our example, the `set_profile` event handler is run on the user's state. This ### State Updates -Every time an event handler returns (or [yields]({events.yield_events.path})), we save the state in the state manager and send the **state updates** to the frontend to update the UI. +Every time an event handler returns (or [yields](/docs/events/yield_events)), we save the state in the state manager and send the **state updates** to the frontend to update the UI. To maintain performance as your state grows, internally Reflex keeps track of vars that were updated during the event handler (**dirty vars**). When the event handler is done processing, we find all the dirty vars and create a state update to send to the frontend. diff --git a/docs/api-reference/browser_javascript.md b/docs/api-reference/browser_javascript.md index 7321653d108..892ffd2c064 100644 --- a/docs/api-reference/browser_javascript.md +++ b/docs/api-reference/browser_javascript.md @@ -2,8 +2,6 @@ import asyncio from typing import Any import reflex as rx -from pcweb.pages.docs import wrapping_react -from pcweb.pages.docs import library ``` # Browser Javascript @@ -25,7 +23,7 @@ Prefer to use the Python API whenever possible and file an issue if you need add There are four ways to execute custom Javascript code into your Reflex app: -- `rx.script` - Injects the script via `next/script` for efficient loading of inline and external Javascript code. Described further in the [component library]({library.other.script.path}). +- `rx.script` - Injects the script via `next/script` for efficient loading of inline and external Javascript code. Described further in the [component library](/docs/library/other/script). - These components can be directly included in the body of a page, or they may be passed to `rx.App(head_components=[rx.script(...)])` to be included in the `` tag of all pages. @@ -36,7 +34,7 @@ These previous two methods can work in tandem to load external scripts and then call functions defined within them in response to user events. The following two methods are geared towards wrapping components and are -described with examples in the [Wrapping React]({wrapping_react.overview.path}) +described with examples in the [Wrapping React](/docs/wrapping-react/overview) section. - `_get_hooks` and `_get_custom_code` in an `rx.Component` subclass diff --git a/docs/api-reference/event_triggers.md b/docs/api-reference/event_triggers.md index 4a9620d99f5..d7438636c27 100644 --- a/docs/api-reference/event_triggers.md +++ b/docs/api-reference/event_triggers.md @@ -4,7 +4,6 @@ from datetime import datetime import reflex as rx from pcweb.templates.docpage import docdemo, h1_comp, text_comp, docpage -from pcweb.pages.docs import events SYNTHETIC_EVENTS = [ { @@ -378,7 +377,7 @@ def protected_page(): return rx.text('Protected content') ``` -For more details on page load events, see the [page load events documentation]({events.page_load_events.path}). +For more details on page load events, see the [page load events documentation](/docs/events/page_load_events). # Event Reference diff --git a/docs/api-reference/plugins.md b/docs/api-reference/plugins.md index b05263ef359..ff0643a5e2f 100644 --- a/docs/api-reference/plugins.md +++ b/docs/api-reference/plugins.md @@ -1,6 +1,5 @@ ```python exec import reflex as rx -from pcweb.pages.docs import advanced_onboarding ``` # Plugins diff --git a/docs/assets/overview.md b/docs/assets/overview.md index 51ba7e1599d..d864bbf892d 100644 --- a/docs/assets/overview.md +++ b/docs/assets/overview.md @@ -1,6 +1,5 @@ ```python exec import reflex as rx -from pcweb.constants import REFLEX_ASSETS_CDN ``` # Assets @@ -32,7 +31,7 @@ assets Then you can display it using a `rx.image` component: ```python demo -rx.image(src=f"{REFLEX_ASSETS_CDN}other/Reflex.svg", width="5em") +rx.image(src="https://web.reflex-assets.dev/other/Reflex.svg", width="5em") ``` ```md alert @@ -58,7 +57,6 @@ Shared assets are placed next to your Python file and are linked to the app's ex ```python box # my_component.py import reflex as rx -from pcweb.constants import REFLEX_ASSETS_CDN # my_script.js is located in the same directory as this Python file def my_component(): @@ -73,7 +71,6 @@ You can also specify a subfolder for shared assets: ```python box # my_component.py import reflex as rx -from pcweb.constants import REFLEX_ASSETS_CDN # image.png is located in a subfolder next to this Python file def my_component_with_image(): diff --git a/docs/assets/upload_and_download_files.md b/docs/assets/upload_and_download_files.md index 646c48ff45f..50b30df60fb 100644 --- a/docs/assets/upload_and_download_files.md +++ b/docs/assets/upload_and_download_files.md @@ -1,8 +1,5 @@ ```python exec import reflex as rx -from pcweb.constants import REFLEX_ASSETS_CDN -from pcweb.pages.docs import library -from pcweb.pages.docs import api_reference from pcweb.styles.styles import get_code_style from pcweb.styles.colors import c_color ``` @@ -131,7 +128,7 @@ def download_random_data_button(): The `data` arg accepts `str` or `bytes` data, a `data:` URI, `PIL.Image`, or any state Var. If the Var is not already a string, it will be converted to a string using `JSON.stringify`. This allows complex state structures to be offered as JSON downloads. -Reference page for `rx.download` [here]({api_reference.special_events.path}#rx.download). +Reference page for `rx.download` [here](/docs/api-reference/special_events#rx.download). ## Upload @@ -149,4 +146,4 @@ def index(): ) ``` -For detailed information, see the reference page of the component [here]({library.forms.upload.path}). +For detailed information, see the reference page of the component [here](/docs/library/forms/upload). diff --git a/docs/authentication/authentication_overview.md b/docs/authentication/authentication_overview.md index 72dc4de4ecd..68d97ce78b5 100644 --- a/docs/authentication/authentication_overview.md +++ b/docs/authentication/authentication_overview.md @@ -1,6 +1,3 @@ -```python exec -from pcweb.pages.docs import vars -``` # Authentication Overview @@ -19,7 +16,7 @@ If you're using the AI Builder, you can also use the built-in [Authentication In ## Guidance for Implementing Authentication -- Store sensitive user tokens and information in [backend-only vars]({vars.base_vars.path}#backend-only-vars). +- Store sensitive user tokens and information in [backend-only vars](/docs/vars/base_vars#backend-only-vars). - Validate user session and permissions for each event handler that performs an authenticated action and all computed vars or loader events that access private data. - All content that is statically rendered in the frontend (for example, data hardcoded or loaded at compile time in the UI) will be publicly available, even if the page redirects to a login or uses `rx.cond` to hide content. - Only data that originates from state can be truly private and protected. diff --git a/docs/components/conditional_rendering.md b/docs/components/conditional_rendering.md index 9def2313da3..3ce2101c05d 100644 --- a/docs/components/conditional_rendering.md +++ b/docs/components/conditional_rendering.md @@ -1,20 +1,18 @@ ```python exec import reflex as rx -from pcweb.pages.docs import library -from pcweb.pages import docs ``` # Conditional Rendering -Recall from the [basics]({docs.getting_started.basics.path}) that we cannot use Python `if/else` statements when referencing state vars in Reflex. Instead, use the `rx.cond` component to conditionally render components or set props based on the value of a state var. +Recall from the [basics](/docs/getting_started/basics) that we cannot use Python `if/else` statements when referencing state vars in Reflex. Instead, use the `rx.cond` component to conditionally render components or set props based on the value of a state var. ```md video https://youtube.com/embed/ITOZkzjtjUA?start=6040&end=6463 # Video: Conditional Rendering ``` ```md alert -# Check out the API reference for [cond docs]({library.dynamic_rendering.cond.path}). +# Check out the API reference for [cond docs](/docs/library/dynamic-rendering/cond). ``` ```python eval @@ -70,11 +68,11 @@ def cond_prop(): ## Var Operations -You can use [var operations]({docs.vars.var_operations.path}) with the `cond` component for more complex conditions. See the full [cond reference]({library.dynamic_rendering.cond.path}) for more details. +You can use [var operations](/docs/vars/var-operations) with the `cond` component for more complex conditions. See the full [cond reference](/docs/library/dynamic-rendering/cond) for more details. ## Multiple Conditional Statements -The [`rx.match`]({library.dynamic_rendering.match.path}) component in Reflex provides a powerful alternative to`rx.cond` for handling multiple conditional statements and structural pattern matching. This component allows you to handle multiple conditions and their associated components in a cleaner and more readable way compared to nested `rx.cond` structures. +The [`rx.match`](/docs/library/dynamic-rendering/match) component in Reflex provides a powerful alternative to`rx.cond` for handling multiple conditional statements and structural pattern matching. This component allows you to handle multiple conditions and their associated components in a cleaner and more readable way compared to nested `rx.cond` structures. ```python demo exec from typing import List diff --git a/docs/components/props.md b/docs/components/props.md index 9c53a92cfe9..03779db0339 100644 --- a/docs/components/props.md +++ b/docs/components/props.md @@ -1,7 +1,5 @@ ```python exec import reflex as rx -from pcweb.pages.docs.library import library -from pcweb.pages import docs ``` # Props @@ -21,7 +19,7 @@ rx.image( ) ``` -Check the docs for the component you are using to see what props are available and how they affect the component (see the `rx.image` [reference]({docs.library.media.image.path}#api-reference) page for example). +Check the docs for the component you are using to see what props are available and how they affect the component (see the `rx.image` [reference](/docs/library/media/image#api-reference) page for example). ## Common Props @@ -53,15 +51,15 @@ rx.button( ) ``` -See the [styling docs]({docs.styling.overview.path}) to learn more about customizing the appearance of your app. +See the [styling docs](/docs/styling/overview) to learn more about customizing the appearance of your app. ## Binding Props to State ```md alert warning -# Optional: Learn all about [State]({docs.state.overview.path}) first. +# Optional: Learn all about [State](/docs/state/overview) first. ``` -Reflex apps define [State]({docs.state.overview.path}) classes that hold variables that can change over time. +Reflex apps define [State](/docs/state/overview) classes that hold variables that can change over time. State may be modified in response to things like user input like clicking a button, or in response to events like loading a page. diff --git a/docs/components/rendering_iterables.md b/docs/components/rendering_iterables.md index 0c434cb8a54..bdcd195536b 100644 --- a/docs/components/rendering_iterables.md +++ b/docs/components/rendering_iterables.md @@ -1,14 +1,13 @@ ```python exec import reflex as rx -from pcweb.pages import docs ``` # Rendering Iterables -Recall again from the [basics]({docs.getting_started.basics.path}) that we cannot use Python `for` loops when referencing state vars in Reflex. Instead, use the `rx.foreach` component to render components from a collection of data. +Recall again from the [basics](/docs/getting_started/basics) that we cannot use Python `for` loops when referencing state vars in Reflex. Instead, use the `rx.foreach` component to render components from a collection of data. -For dynamic content that should automatically scroll to show the newest items, consider using the [auto scroll]({docs.library.dynamic_rendering.auto_scroll.path}) component together with `rx.foreach`. +For dynamic content that should automatically scroll to show the newest items, consider using the [auto scroll](/docs/library/dynamic-rendering/auto_scroll) component together with `rx.foreach`. ```python demo exec class IterState(rx.State): @@ -226,7 +225,7 @@ def projects_example() -> rx.Component: return rx.box(rx.foreach(NestedStateFE.projects, project_item)) ``` -If you want an example where not all of the values in the dict are the same type then check out the example on [var operations using foreach]({docs.vars.var_operations.path}). +If you want an example where not all of the values in the dict are the same type then check out the example on [var operations using foreach](/docs/vars/var-operations). Here is a further example of how to use `foreach` with a nested data structure. diff --git a/docs/custom-components/command-reference.md b/docs/custom-components/command-reference.md index 56b95b74676..989312feb64 100644 --- a/docs/custom-components/command-reference.md +++ b/docs/custom-components/command-reference.md @@ -1,6 +1,3 @@ -```python exec -from pcweb.pages import docs -``` # Command Reference @@ -85,7 +82,7 @@ The `custom_components` folder is where the actual implementation is. Do not wor `reflex_google_auth` is the top folder for importable code. The `reflex_google_auth/__init__.py` imports everything from the `reflex_google_auth/google_auth.py`. For the user of the package, the import looks like `from reflex_google_auth import ABC, XYZ`. -`reflex_google_auth/google_auth.py` is prefilled with code example and instructions from the [wrapping react guide]({docs.wrapping_react.overview.path}). +`reflex_google_auth/google_auth.py` is prefilled with code example and instructions from the [wrapping react guide](/docs/wrapping-react/overview). ### Demo App Folder diff --git a/docs/custom-components/overview.md b/docs/custom-components/overview.md index 3970c73d97f..0c303d2c4b8 100644 --- a/docs/custom-components/overview.md +++ b/docs/custom-components/overview.md @@ -2,26 +2,23 @@ ```python exec import reflex as rx -from pcweb.constants import REFLEX_ASSETS_CDN -from pcweb.pages.docs import custom_components -from pcweb.pages.docs.custom_components import custom_components as custom_components_gallery ``` Reflex users create many components of their own: ready to use high level components, or nicely wrapped React components. With **Custom Components**, the community can easily share these components now. Release **0.4.3** introduces a series of `reflex component` commands that help developers wrap react components, test, and publish them as python packages. As shown in the image below, there are already a few custom components published on PyPI, such as `reflex-spline`, `reflex-webcam`. -Check out the custom components gallery [here]({custom_components_gallery.path}). +Check out the custom components gallery [here](/docs/custom-components/overview). ```python eval rx.center( - rx.image(src=f"{REFLEX_ASSETS_CDN}custom_components/pypi_reflex_custom_components.webp", width="400px", border_radius="15px", border="1px solid"), + rx.image(src="https://web.reflex-assets.dev/custom_components/pypi_reflex_custom_components.webp", width="400px", border_radius="15px", border="1px solid"), ) ``` ## Prerequisites for Publishing -In order to publish a Python package, an account is required with a python package index, for example, PyPI. The documentation to create accounts and generate API tokens can be found on their websites. For a quick reference, check out our [Prerequisites for Publishing]({custom_components.prerequisites_for_publishing.path}) page. +In order to publish a Python package, an account is required with a python package index, for example, PyPI. The documentation to create accounts and generate API tokens can be found on their websites. For a quick reference, check out our [Prerequisites for Publishing](/docs/custom-components/prerequisites-for-publishing) page. ## Steps to Publishing diff --git a/docs/custom-components/prerequisites-for-publishing.md b/docs/custom-components/prerequisites-for-publishing.md index f836e4dad12..44768525ae0 100644 --- a/docs/custom-components/prerequisites-for-publishing.md +++ b/docs/custom-components/prerequisites-for-publishing.md @@ -2,7 +2,6 @@ ```python exec import reflex as rx -from pcweb.constants import REFLEX_ASSETS_CDN from pcweb.styles.colors import c_color image_style = { "width": "400px", @@ -19,7 +18,7 @@ It is straightforward to create accounts and API tokens with PyPI. There is offi ```python eval rx.center( - rx.image(src=f"{REFLEX_ASSETS_CDN}custom_components/pypi_register.webp", style=image_style, margin_bottom="16px", loading="lazy"), + rx.image(src="https://web.reflex-assets.dev/custom_components/pypi_register.webp", style=image_style, margin_bottom="16px", loading="lazy"), ) ``` @@ -27,7 +26,7 @@ A user can use username and password to authenticate with PyPI when publishing. ```python eval rx.center( - rx.image(src=f"{REFLEX_ASSETS_CDN}custom_components/pypi_account_settings.webp", style=image_style, margin_bottom="16px", loading="lazy"), + rx.image(src="https://web.reflex-assets.dev/custom_components/pypi_account_settings.webp", style=image_style, margin_bottom="16px", loading="lazy"), ) ``` @@ -35,6 +34,6 @@ Scroll down to the API tokens section and click on the "Add API token" button. F ```python eval rx.center( - rx.image(src=f"{REFLEX_ASSETS_CDN}custom_components/pypi_api_tokens.webp", style=image_style, width="700px", loading="lazy"), + rx.image(src="https://web.reflex-assets.dev/custom_components/pypi_api_tokens.webp", style=image_style, width="700px", loading="lazy"), ) ``` diff --git a/docs/events/event_actions.md b/docs/events/event_actions.md index 1d431eddc8b..6bd514799b8 100644 --- a/docs/events/event_actions.md +++ b/docs/events/event_actions.md @@ -140,7 +140,7 @@ def throttle_example(): return ( scroll_box(), rx.text( - f"Last Scroll Event: ", + "Last Scroll Event: ", rx.moment(ThrottleState.last_scroll, format="HH:mm:ss.SSS"), ), ) diff --git a/docs/events/events_overview.md b/docs/events/events_overview.md index fdea0c283f6..909beb53963 100644 --- a/docs/events/events_overview.md +++ b/docs/events/events_overview.md @@ -1,7 +1,6 @@ ```python exec import reflex as rx -from pcweb.pages.docs.library import library ``` # Events Overview @@ -11,7 +10,7 @@ Events are composed of two parts: Event Triggers and Event Handlers. - **Events Handlers** are how the State of a Reflex application is updated. They are triggered by user interactions with the UI, such as clicking a button or hovering over an element. Events can also be triggered by the page loading or by other events. - **Event triggers** are component props that create an event to be sent to an event handler. - Each component supports a set of events triggers. They are described in each [component's documentation]({library.path}) in the event trigger section. + Each component supports a set of events triggers. They are described in each [component's documentation](/docs/library) in the event trigger section. ## Example diff --git a/docs/events/special_events.md b/docs/events/special_events.md index 0b6ae665e70..2c8b6a9b091 100644 --- a/docs/events/special_events.md +++ b/docs/events/special_events.md @@ -1,12 +1,11 @@ ```python exec import reflex as rx -from pcweb.pages.docs import api_reference ``` # Special Events -Reflex also has built-in special events can be found in the [reference]({api_reference.special_events.path}). +Reflex also has built-in special events can be found in the [reference](/docs/api-reference/special_events). For example, an event handler can trigger an alert on the browser. diff --git a/docs/getting_started/basics.md b/docs/getting_started/basics.md index b05d88ce87e..017dcf4320e 100644 --- a/docs/getting_started/basics.md +++ b/docs/getting_started/basics.md @@ -1,8 +1,4 @@ ```python exec -from pcweb.pages.docs import components, getting_started -from pcweb.pages.docs.library import library -from pcweb.pages.docs.custom_components import custom_components -from pcweb.pages import docs import reflex as rx ``` @@ -22,7 +18,7 @@ This page gives an introduction to the most common concepts that you will use to - Create pages and navigate between them ``` -[Install]({docs.getting_started.installation.path}) `reflex` using pip. +[Install](/docs/getting_started/installation) `reflex` using pip. ```bash pip install reflex @@ -36,7 +32,7 @@ import reflex as rx ## Creating and nesting components -[Components]({docs.ui.overview.path}) are the building blocks for your app's user interface (UI). They are the visual elements that make up your app, like buttons, text, and images. Reflex has a wide selection of [built-in components]({library.path}) to get you started quickly. +[Components](/docs/ui/overview) are the building blocks for your app's user interface (UI). They are the visual elements that make up your app, like buttons, text, and images. Reflex has a wide selection of [built-in components](/docs/library) to get you started quickly. Components are created using functions that return a component object. @@ -58,7 +54,7 @@ def my_page(): ) ``` -You can also use any base HTML element through the [`rx.el`]({docs.library.other.html.path}) namespace. This allows you to use standard HTML elements directly in your Reflex app when you need more control or when a specific component isn't available in the Reflex component library. +You can also use any base HTML element through the [`rx.el`](/docs/library/other/html) namespace. This allows you to use standard HTML elements directly in your Reflex app when you need more control or when a specific component isn't available in the Reflex component library. ```python demo exec def my_div(): @@ -67,11 +63,11 @@ def my_div(): ) ``` -If you need a component not provided by Reflex, you can check the [3rd party ecosystem]({custom_components.path}) or [wrap your own React component]({docs.wrapping_react.library_and_tags.path}). +If you need a component not provided by Reflex, you can check the [3rd party ecosystem](/docs/custom-components) or [wrap your own React component](/docs/wrapping-react/library-and-tags). ## Customizing and styling components -Components can be customized using [props]({docs.components.props.path}), which are passed in as keyword arguments to the component function. +Components can be customized using [props](/docs/components/props), which are passed in as keyword arguments to the component function. Each component has props that are specific to that component. Check the docs for the component you are using to see what props are available. @@ -91,7 +87,7 @@ def round_button(): Use the `snake_case` version of the CSS property name as the prop name. ``` -See the [styling guide]({docs.styling.overview.path}) for more information on how to style components +See the [styling guide](/docs/styling/overview) for more information on how to style components In summary, components are made up of children and props. @@ -109,9 +105,9 @@ In summary, components are made up of children and props. ## Displaying data that changes over time -Apps need to store and display data that changes over time. Reflex handles this through [State]({docs.state.overview.path}), which is a Python class that stores variables that can change when the app is running, as well as the functions that can change those variables. +Apps need to store and display data that changes over time. Reflex handles this through [State](/docs/state/overview), which is a Python class that stores variables that can change when the app is running, as well as the functions that can change those variables. -To define a state class, subclass `rx.State` and define fields that store the state of your app. The state variables ([vars]({docs.vars.base_vars.path})) should have a type annotation, and can be initialized with a default value. +To define a state class, subclass `rx.State` and define fields that store the state of your app. The state variables ([vars](/docs/vars/base_vars)) should have a type annotation, and can be initialized with a default value. ```python class MyState(rx.State): @@ -142,7 +138,7 @@ Vars can be referenced in multiple components, and will automatically update whe ## Responding to events and updating the screen -So far, we've defined state vars but we haven't shown how to change them. All state changes are handled through functions in the state class, called [event handlers]({docs.events.events_overview.path}). +So far, we've defined state vars but we haven't shown how to change them. All state changes are handled through functions in the state class, called [event handlers](/docs/events/events_overview). ```md alert Event handlers are the ONLY way to change state in Reflex. @@ -315,7 +311,7 @@ In the next sections, we will show how to handle these cases. ## Conditional rendering -As mentioned above, you cannot use Python `if/else` statements with state vars in components. Instead, use the [`rx.cond`]({docs.components.conditional_rendering.path}) function to conditionally render components. +As mentioned above, you cannot use Python `if/else` statements with state vars in components. Instead, use the [`rx.cond`](/docs/components/conditional_rendering) function to conditionally render components. ```python demo exec class LoginState(rx.State): @@ -338,7 +334,7 @@ def show_login(): ## Rendering lists -To iterate over a var that is a list, use the [`rx.foreach`]({docs.components.rendering_iterables.path}) function to render a list of components. +To iterate over a var that is a list, use the [`rx.foreach`](/docs/components/rendering_iterables) function to render a list of components. Pass the list var and a function that returns a component as arguments to `rx.foreach`. @@ -361,7 +357,7 @@ The function that renders each item takes in a `Var`, since this will get compil ## Var Operations -You can't use arbitrary Python operations on state vars in components, but Reflex has [var operations]({docs.vars.var_operations.path}) that you can use to manipulate state vars. +You can't use arbitrary Python operations on state vars in components, but Reflex has [var operations](/docs/vars/var-operations) that you can use to manipulate state vars. For example, to check if a var is even, you can use the `%` and `==` var operations. @@ -402,5 +398,5 @@ app.add_page(index, route="/") Now that you have a basic understanding of how Reflex works, the next step is to start coding your own apps. Try one of the following tutorials: -- [Dashboard Tutorial]({getting_started.dashboard_tutorial.path}) -- [Chatapp Tutorial]({getting_started.chatapp_tutorial.path}) +- [Dashboard Tutorial](/docs/getting_started/dashboard_tutorial) +- [Chatapp Tutorial](/docs/getting_started/chatapp_tutorial) diff --git a/docs/getting_started/chatapp_tutorial.md b/docs/getting_started/chatapp_tutorial.md index ebb8ca4d22c..dc494e169f3 100644 --- a/docs/getting_started/chatapp_tutorial.md +++ b/docs/getting_started/chatapp_tutorial.md @@ -6,12 +6,6 @@ import openai from pcweb.constants import CHAT_APP_URL from pcweb import constants -from pcweb.pages.docs import components -from pcweb.pages.docs import styling -from pcweb.pages.docs import library -from pcweb.pages.docs import events -from pcweb.pages.docs import state -from pcweb.pages.docs import hosting from docs.getting_started import chat_tutorial_style as style from docs.getting_started.chat_tutorial_utils import ChatappState @@ -96,7 +90,7 @@ Now that we have our project set up, in the next section we will start building ## Basic Frontend -Let's start with defining the frontend for our chat app. In Reflex, the frontend can be broken down into independent, reusable components. See the [components docs]({components.props.path}) for more information. +Let's start with defining the frontend for our chat app. In Reflex, the frontend can be broken down into independent, reusable components. See the [components docs](/docs/components/props) for more information. ### Display A Question And Answer @@ -144,7 +138,7 @@ app.add_page(index) Components can be nested inside each other to create complex layouts. Here we create a parent container that contains two boxes for the question and answer. -We also add some basic styling to the components. Components take in keyword arguments, called [props]({components.props.path}), that modify the appearance and functionality of the component. We use the `text_align` prop to align the text to the left and right. +We also add some basic styling to the components. Components take in keyword arguments, called [props](/docs/components/props), that modify the appearance and functionality of the component. We use the `text_align` prop to align the text to the left and right. ### Reusing Components @@ -206,7 +200,7 @@ def index() -> rx.Component: ### Chat Input -Now we want a way for the user to input a question. For this, we will use the [input]({library.forms.input.path}) component to have the user add text and a [button]({library.forms.button.path}) component to submit the question. +Now we want a way for the user to input a question. For this, we will use the [input](/docs/library/forms/input) component to have the user add text and a [button](/docs/library/forms/button) component to submit the question. ```python exec def action_bar() -> rx.Component: @@ -239,7 +233,7 @@ def index() -> rx.Component: ### Styling -Let's add some styling to the app. More information on styling can be found in the [styling docs]({styling.overview.path}). To keep our code clean, we will move the styling to a separate file `chatapp/style.py`. +Let's add some styling to the app. More information on styling can be found in the [styling docs](/docs/styling/overview). To keep our code clean, we will move the styling to a separate file `chatapp/style.py`. ```python # style.py @@ -356,7 +350,7 @@ The app is looking good, but it's not very useful yet! In the next section, we w ## State -Now let’s make the chat app interactive by adding state. The state is where we define all the variables that can change in the app and all the functions that can modify them. You can learn more about state in the [state docs]({state.overview.path}). +Now let’s make the chat app interactive by adding state. The state is where we define all the variables that can change in the app and all the functions that can modify them. You can learn more about state in the [state docs](/docs/state/overview). ### Defining State @@ -445,9 +439,9 @@ def action_bar() -> rx.Component: ) ``` -Normal Python `for` loops don't work for iterating over state vars because these values can change and aren't known at compile time. Instead, we use the [foreach]({library.dynamic_rendering.foreach.path}) component to iterate over the chat history. +Normal Python `for` loops don't work for iterating over state vars because these values can change and aren't known at compile time. Instead, we use the [foreach](/docs/library/dynamic-rendering/foreach) component to iterate over the chat history. -We also bind the input's `on_change` event to the `set_question` event handler, which will update the `question` state var while the user types in the input. We bind the button's `on_click` event to the `answer` event handler, which will process the question and add the answer to the chat history. The `set_question` event handler is a built-in implicitly defined event handler. Every base var has one. Learn more in the [events docs]({events.setters.path}) under the Setters section. +We also bind the input's `on_change` event to the `set_question` event handler, which will update the `question` state var while the user types in the input. We bind the button's `on_click` event to the `answer` event handler, which will process the question and add the answer to the chat history. The `set_question` event handler is a built-in implicitly defined event handler. Every base var has one. Learn more in the [events docs](/docs/events/setters) under the Setters section. ### Clearing the Input @@ -498,7 +492,7 @@ def answer(self): ### Streaming Text -Normally state updates are sent to the frontend when an event handler returns. However, we want to stream the text from the chatbot as it is generated. We can do this by yielding from the event handler. See the [yield events docs]({events.yield_events.path}) for more info. +Normally state updates are sent to the frontend when an event handler returns. However, we want to stream the text from the chatbot as it is generated. We can do this by yielding from the event handler. See the [yield events docs](/docs/events/yield_events) for more info. ```python exec def action_bar3() -> rx.Component: @@ -788,4 +782,4 @@ Congratulations! You have built your first chatbot. From here, you can read thro ### One More Thing -With our hosting service, you can deploy this app with a single command within minutes. Check out our [Hosting Quick Start]({hosting.deploy_quick_start.path}). +With our hosting service, you can deploy this app with a single command within minutes. Check out our [Hosting Quick Start](https://reflex.dev/docs/hosting/deploy-quick-start/). diff --git a/docs/getting_started/dashboard_tutorial.md b/docs/getting_started/dashboard_tutorial.md index 77ac09fd1b5..4c7c0952681 100644 --- a/docs/getting_started/dashboard_tutorial.md +++ b/docs/getting_started/dashboard_tutorial.md @@ -1,11 +1,10 @@ ```python exec import reflex as rx -from pcweb.pages import docs ``` # Tutorial: Data Dashboard -During this tutorial you will build a small data dashboard, where you can input data and it will be rendered in table and a graph. This tutorial does not assume any existing Reflex knowledge, but we do recommend checking out the quick [Basics Guide]({docs.getting_started.basics.path}) first. +During this tutorial you will build a small data dashboard, where you can input data and it will be rendered in table and a graph. This tutorial does not assume any existing Reflex knowledge, but we do recommend checking out the quick [Basics Guide](/docs/getting_started/basics) first. The techniques you’ll learn in the tutorial are fundamental to building any Reflex app, and fully understanding it will give you a deep understanding of Reflex. @@ -340,7 +339,7 @@ Don't worry if you don't understand the code above, in this tutorial we are goin ## Setup for the tutorial -Check out the [installation docs]({docs.getting_started.installation.path}) to get Reflex set up on your machine. Follow these to create a folder called `dashboard_tutorial`, which you will `cd` into and `pip install reflex`. +Check out the [installation docs](/docs/getting_started/installation) to get Reflex set up on your machine. Follow these to create a folder called `dashboard_tutorial`, which you will `cd` into and `pip install reflex`. We will choose template `0` when we run `reflex init` to get the blank template. Finally run `reflex run` to start the app and confirm everything is set up correctly. @@ -350,9 +349,9 @@ Now that you’re set up, let’s get an overview of Reflex! ### Inspecting the starter code -Within our `dashboard_tutorial` folder we just `cd`'d into, there is a `rxconfig.py` file that contains the configuration for our Reflex app. (Check out the [config docs]({docs.advanced_onboarding.configuration.path}) for more information) +Within our `dashboard_tutorial` folder we just `cd`'d into, there is a `rxconfig.py` file that contains the configuration for our Reflex app. (Check out the [config docs](/docs/advanced_onboarding/configuration) for more information) -There is also an `assets` folder where static files such as images and stylesheets can be placed to be referenced within your app. ([asset docs]({docs.assets.overview.path}) for more information) +There is also an `assets` folder where static files such as images and stylesheets can be placed to be referenced within your app. ([asset docs](/docs/assets/overview) for more information) Most importantly there is a folder also called `dashboard_tutorial` which contains all the code for your app. Inside of this folder there is a file named `dashboard_tutorial.py`. To begin this tutorial we will delete all the code in this file so that we can start from scratch and explain every step as we go. @@ -509,7 +508,7 @@ Up until this point all the data we are showing in the app is static. This is no This is where `State` comes in. `State` is a Python class that stores variables that can change when the app is running, as well as the functions that can change those variables. -To define a state class, subclass `rx.State` and define fields that store the state of your app. The state variables (vars) should have a type annotation, and can be initialized with a default value. Check out the [basics]({docs.getting_started.basics.path}) section for a simple example of how state works. +To define a state class, subclass `rx.State` and define fields that store the state of your app. The state variables (vars) should have a type annotation, and can be initialized with a default value. Check out the [basics](/docs/getting_started/basics) section for a simple example of how state works. In the example below we define a `State` class called `State` that has a variable called `users` that is a list of lists of strings. Each list in the `users` list represents a user and contains their name, email and gender. @@ -521,12 +520,12 @@ class State(rx.State): ] ``` -To iterate over a state var that is a list, we use the [`rx.foreach`]({docs.components.rendering_iterables.path}) function to render a list of components. The `rx.foreach` component takes an `iterable` (list, tuple or dict) and a `function` that renders each item in the `iterable`. +To iterate over a state var that is a list, we use the [`rx.foreach`](/docs/components/rendering_iterables) function to render a list of components. The `rx.foreach` component takes an `iterable` (list, tuple or dict) and a `function` that renders each item in the `iterable`. ```md alert info # Why can we not just splat this in a `for` loop -You might be wondering why a `foreach` is even needed to render this state variable and why we cannot just splat a `for` loop. Check out this [documentation](<{docs.getting_started.basics.path}#compile-time-vs.-runtime-(important)>) to learn why. +You might be wondering why a `foreach` is even needed to render this state variable and why we cannot just splat a `for` loop. Check out this [documentation]() to learn why. ``` Here the render function is `show_user` which takes in a single user and returns a `table.row` component that displays the users name, email and gender. @@ -709,7 +708,7 @@ Next let's add a form to the app so we can add new users to the table. ## Using a Form to Add Data -We build a form using `rx.form`, which takes several components such as `rx.input` and `rx.select`, which represent the form fields that allow you to add information to submit with the form. Check out the [form]({docs.library.forms.form.path}) docs for more information on form components. +We build a form using `rx.form`, which takes several components such as `rx.input` and `rx.select`, which represent the form fields that allow you to add information to submit with the form. Check out the [form](/docs/library/forms/form) docs for more information on form components. The `rx.input` component takes in several props. The `placeholder` prop is the text that is displayed in the input field when it is empty. The `name` prop is the name of the input field, which gets passed through in the dictionary when the form is submitted. The `required` prop is a boolean that determines if the input field is required. @@ -732,7 +731,7 @@ rx.form( ) ``` -This form is all very compact as you can see from the example, so we need to add some styling to make it look better. We can do this by adding a `vstack` component around the form fields. The `vstack` component stacks the form fields vertically. Check out the [layout]({docs.styling.layout.path}) docs for more information on how to layout your app. +This form is all very compact as you can see from the example, so we need to add some styling to make it look better. We can do this by adding a `vstack` component around the form fields. The `vstack` component stacks the form fields vertically. Check out the [layout](/docs/styling/layout) docs for more information on how to layout your app. ```python demo rx.form( @@ -755,7 +754,7 @@ rx.form( Now you have probably realised that we have all the form fields, but we have no way to submit the form. We can add a submit button to the form by adding a `rx.button` component to the `vstack` component. The `rx.button` component takes in the text that is displayed on the button and the `type` prop which is the type of button. The `type` prop is set to `submit` so that the form is submitted when the button is clicked. -In addition to this we need a way to update the `users` state variable when the form is submitted. All state changes are handled through functions in the state class, called [event handlers]({docs.events.events_overview.path}). +In addition to this we need a way to update the `users` state variable when the form is submitted. All state changes are handled through functions in the state class, called [event handlers](/docs/events/events_overview). Components have special props called event triggers, such as `on_submit`, that can be used to make components interactive. Event triggers connect components to event handlers, which update the state. Different event triggers expect the event handler that you hook them up to, to take in different arguments (and some do not take in any arguments). @@ -1588,9 +1587,9 @@ The most important one is `theme` which allows you to customize the look and fee The `radius` prop sets the global radius value for the app that is inherited by all components that have a `radius` prop. It can be overwritten locally for a specific component by manually setting the `radius` prop. -The `accent_color` prop sets the accent color of the app. Check out other options for the accent color [here]({docs.library.other.theme.path}). +The `accent_color` prop sets the accent color of the app. Check out other options for the accent color [here](/docs/library/other/theme). -To see other props that can be set at the app level check out this [documentation]({docs.styling.theming.path}) +To see other props that can be set at the app level check out this [documentation](/docs/styling/theming) ```python app = rx.App( diff --git a/docs/getting_started/installation.md b/docs/getting_started/installation.md index edb63660d60..4a14c4f10cc 100644 --- a/docs/getting_started/installation.md +++ b/docs/getting_started/installation.md @@ -1,7 +1,6 @@ ```python exec from pcweb import constants import reflex as rx -from pcweb.pages.gallery import gallery app_name = "my_app_name" default_url = "http://localhost:3000" ``` diff --git a/docs/getting_started/introduction.md b/docs/getting_started/introduction.md index 3e14c84dee3..889225bf79f 100644 --- a/docs/getting_started/introduction.md +++ b/docs/getting_started/introduction.md @@ -1,17 +1,9 @@ ```python exec import reflex as rx from pcweb import constants, styles -from pcweb.pages.docs import getting_started -from pcweb.pages.docs import wrapping_react -from pcweb.pages.docs.library import library -from pcweb.pages.docs import pages -from pcweb.pages.docs import vars from pcweb.styles.colors import c_color -from pcweb.pages.docs import styling from pcweb.styles.fonts import base -from pcweb.pages.docs import hosting from pcweb.flexdown import markdown_with_shiki -from pcweb.pages.docs import advanced_onboarding ``` @@ -107,7 +99,7 @@ def tabs(): ), rx.tabs.content( markdown_with_shiki( - f"""Each page is a Python function that returns a Reflex component. You can define multiple pages and navigate between them, see the [Routing]({pages.overview.path}) section for more information. + """Each page is a Python function that returns a Reflex component. You can define multiple pages and navigate between them, see the [Routing](/docs/pages/overview) section for more information. - Start with a single page and scale to 100s of pages. """, @@ -242,7 +234,7 @@ class State(rx.State): count: int = 0 ``` -The state defines all the variables (called **[vars]({vars.base_vars.path})**) in an app that can change, as well as the functions (called **[event_handlers](#event-handlers)**) that change them. +The state defines all the variables (called **[vars](/docs/vars/base_vars)**) in an app that can change, as well as the functions (called **[event_handlers](#event-handlers)**) that change them. Here our state has a single var, `count`, which holds the current value of the counter. We initialize it to `0`. @@ -288,10 +280,10 @@ def index(): This function defines the app's user interface. -We use different components such as `rx.hstack`, `rx.button`, and `rx.heading` to build the frontend. Components can be nested to create complex layouts, and can be styled using the full power of CSS or [Tailwind CSS]({styling.tailwind.path}). +We use different components such as `rx.hstack`, `rx.button`, and `rx.heading` to build the frontend. Components can be nested to create complex layouts, and can be styled using the full power of CSS or [Tailwind CSS](/docs/styling/tailwind). -Reflex comes with [50+ built-in components]({library.path}) to help you get started. -We are actively adding more components. Also, it's easy to [wrap your own React components]({wrapping_react.overview.path}). +Reflex comes with [50+ built-in components](/docs/library) to help you get started. +We are actively adding more components. Also, it's easy to [wrap your own React components](/docs/wrapping-react/overview). ```python rx.heading(State.count, font_size="2em"), @@ -341,12 +333,12 @@ By continuing with our documentation, you will learn how to build awesome apps w For a glimpse of the possibilities, check out these resources: -- For a more real-world example, check out either the [dashboard tutorial]({getting_started.dashboard_tutorial.path}) or the [chatapp tutorial]({getting_started.chatapp_tutorial.path}). -- Check out our open-source [templates]({getting_started.open_source_templates.path})! +- For a more real-world example, check out either the [dashboard tutorial](/docs/getting_started/dashboard_tutorial) or the [chatapp tutorial](/docs/getting_started/chatapp_tutorial). +- Check out our open-source [templates](/docs/getting_started/open_source_templates)! - We have an AI Builder that can generate full Reflex apps or help with your existing app! Check it out at [Reflex Build]({constants.REFLEX_BUILD_URL})! -- Deploy your app with a single command using [Reflex Cloud]({hosting.deploy_quick_start.path})! +- Deploy your app with a single command using [Reflex Cloud](https://reflex.dev/docs/hosting/deploy-quick-start/)! -If you want to learn more about how Reflex works, check out the [How Reflex Works]({advanced_onboarding.how_reflex_works.path}) section. +If you want to learn more about how Reflex works, check out the [How Reflex Works](/docs/advanced_onboarding/how-reflex-works) section. ## Join our Community diff --git a/docs/getting_started/open_source_templates.md b/docs/getting_started/open_source_templates.md index aa28a5e23e7..f9f02790445 100644 --- a/docs/getting_started/open_source_templates.md +++ b/docs/getting_started/open_source_templates.md @@ -3,7 +3,6 @@ Check out what the community is building with Reflex. See 2000+ more public projects on [Github](https://github.com/reflex-dev/reflex/network/dependents). Want to get your app featured? Submit it [here](https://github.com/reflex-dev/templates). Copy the template command and use it during `reflex init` ```python exec - import reflex as rx from pcweb.components.code_card import gallery_app_card diff --git a/docs/getting_started/project-structure.md b/docs/getting_started/project-structure.md index b47e278f568..6065441280c 100644 --- a/docs/getting_started/project-structure.md +++ b/docs/getting_started/project-structure.md @@ -1,9 +1,5 @@ # Project Structure -```python exec -from pcweb.pages.docs import advanced_onboarding -from pcweb.constants import REFLEX_ASSETS_CDN -``` ## Directory Structure @@ -46,7 +42,7 @@ The `assets` directory is where you can store any static assets you want to be p For example, if you save an image to `assets/image.png` you can display it from your app like this: ```python -rx.image(src=f"{REFLEX_ASSETS_CDN}other/image.png") +rx.image(src="https://web.reflex-assets.dev/other/image.png") ``` j @@ -70,4 +66,4 @@ config = rx.Config( ) ``` -We will discuss project structure and configuration in more detail in the [advanced project structure]({advanced_onboarding.code_structure.path}) documentation. +We will discuss project structure and configuration in more detail in the [advanced project structure](/docs/advanced_onboarding/code_structure) documentation. diff --git a/docs/library/data-display/avatar.md b/docs/library/data-display/avatar.md index 725ac8f8328..5d8250fca85 100644 --- a/docs/library/data-display/avatar.md +++ b/docs/library/data-display/avatar.md @@ -2,14 +2,13 @@ components: - rx.avatar Avatar: | - lambda **props: rx.hstack(rx.avatar(src=f"{REFLEX_ASSETS_CDN}other/logo.jpg", **props), rx.avatar(fallback="RX", **props), spacing="3") + lambda **props: rx.hstack(rx.avatar(src="https://web.reflex-assets.dev/other/logo.jpg", **props), rx.avatar(fallback="RX", **props), spacing="3") --- # Avatar ```python exec import reflex as rx -from pcweb.constants import REFLEX_ASSETS_CDN from pcweb.templates.docpage import style_grid ``` @@ -20,7 +19,7 @@ The Avatar component is used to represent a user, and display their profile pict To create an avatar component with an image, pass the image URL as the `src` prop. ```python demo -rx.avatar(src=f"{REFLEX_ASSETS_CDN}other/logo.jpg") +rx.avatar(src="https://web.reflex-assets.dev/other/logo.jpg") ``` To display a text such as initials, set the `fallback` prop without passing the `src` prop. @@ -41,15 +40,15 @@ The `size` prop controls the size and spacing of the avatar. The acceptable size ```python demo rx.flex( - rx.avatar(src=f"{REFLEX_ASSETS_CDN}other/logo.jpg", fallback="RX", size="1"), - rx.avatar(src=f"{REFLEX_ASSETS_CDN}other/logo.jpg", fallback="RX", size="2"), - rx.avatar(src=f"{REFLEX_ASSETS_CDN}other/logo.jpg", fallback="RX", size="3"), - rx.avatar(src=f"{REFLEX_ASSETS_CDN}other/logo.jpg", fallback="RX"), - rx.avatar(src=f"{REFLEX_ASSETS_CDN}other/logo.jpg", fallback="RX", size="4"), - rx.avatar(src=f"{REFLEX_ASSETS_CDN}other/logo.jpg", fallback="RX", size="5"), - rx.avatar(src=f"{REFLEX_ASSETS_CDN}other/logo.jpg", fallback="RX", size="6"), - rx.avatar(src=f"{REFLEX_ASSETS_CDN}other/logo.jpg", fallback="RX", size="7"), - rx.avatar(src=f"{REFLEX_ASSETS_CDN}other/logo.jpg", fallback="RX", size="8"), + rx.avatar(src="https://web.reflex-assets.dev/other/logo.jpg", fallback="RX", size="1"), + rx.avatar(src="https://web.reflex-assets.dev/other/logo.jpg", fallback="RX", size="2"), + rx.avatar(src="https://web.reflex-assets.dev/other/logo.jpg", fallback="RX", size="3"), + rx.avatar(src="https://web.reflex-assets.dev/other/logo.jpg", fallback="RX"), + rx.avatar(src="https://web.reflex-assets.dev/other/logo.jpg", fallback="RX", size="4"), + rx.avatar(src="https://web.reflex-assets.dev/other/logo.jpg", fallback="RX", size="5"), + rx.avatar(src="https://web.reflex-assets.dev/other/logo.jpg", fallback="RX", size="6"), + rx.avatar(src="https://web.reflex-assets.dev/other/logo.jpg", fallback="RX", size="7"), + rx.avatar(src="https://web.reflex-assets.dev/other/logo.jpg", fallback="RX", size="8"), spacing="1", ) ``` @@ -103,15 +102,15 @@ The `radius` prop sets specific radius value, ignoring the global theme. It can ```python demo rx.grid( - rx.avatar(src=f"{REFLEX_ASSETS_CDN}other/logo.jpg", fallback="RX", radius="none"), + rx.avatar(src="https://web.reflex-assets.dev/other/logo.jpg", fallback="RX", radius="none"), rx.avatar(fallback="RX", radius="none"), - rx.avatar(src=f"{REFLEX_ASSETS_CDN}other/logo.jpg", fallback="RX", radius="small"), + rx.avatar(src="https://web.reflex-assets.dev/other/logo.jpg", fallback="RX", radius="small"), rx.avatar(fallback="RX", radius="small"), - rx.avatar(src=f"{REFLEX_ASSETS_CDN}other/logo.jpg", fallback="RX", radius="medium"), + rx.avatar(src="https://web.reflex-assets.dev/other/logo.jpg", fallback="RX", radius="medium"), rx.avatar(fallback="RX", radius="medium"), - rx.avatar(src=f"{REFLEX_ASSETS_CDN}other/logo.jpg", fallback="RX", radius="large"), + rx.avatar(src="https://web.reflex-assets.dev/other/logo.jpg", fallback="RX", radius="large"), rx.avatar(fallback="RX", radius="large"), - rx.avatar(src=f"{REFLEX_ASSETS_CDN}other/logo.jpg", fallback="RX", radius="full"), + rx.avatar(src="https://web.reflex-assets.dev/other/logo.jpg", fallback="RX", radius="full"), rx.avatar(fallback="RX", radius="full"), rows="2", spacing="2", @@ -137,7 +136,7 @@ As part of a user profile page, the Avatar component is used to display the user ```python demo rx.flex( - rx.avatar(src=f"{REFLEX_ASSETS_CDN}other/logo.jpg", fallback="RU", size="9"), + rx.avatar(src="https://web.reflex-assets.dev/other/logo.jpg", fallback="RU", size="9"), rx.text("Reflex User", weight="bold", size="4"), rx.text("@reflexuser", color_scheme="gray"), rx.button("Edit Profile", color_scheme="indigo", variant="solid"), diff --git a/docs/library/data-display/callout.md b/docs/library/data-display/callout.md index ebd286004e4..ab16f930b48 100644 --- a/docs/library/data-display/callout.md +++ b/docs/library/data-display/callout.md @@ -18,7 +18,6 @@ CalloutRoot: | ```python exec import reflex as rx -from pcweb.pages import docs ``` # Callout diff --git a/docs/library/forms/input.md b/docs/library/forms/input.md index bd41b0a6688..cca93566f4a 100644 --- a/docs/library/forms/input.md +++ b/docs/library/forms/input.md @@ -18,7 +18,6 @@ TextFieldSlot: | ```python exec import reflex as rx -from pcweb.pages.docs import library ``` # Input @@ -150,7 +149,7 @@ def form_input1(): ) ``` -To learn more about how to use forms in the [Form]({library.forms.form.path}) docs. +To learn more about how to use forms in the [Form](/docs/library/forms/form) docs. ## Setting a value without using a State var diff --git a/docs/library/forms/select-ll.md b/docs/library/forms/select-ll.md index 50b7ad26ef3..551bd8130bc 100644 --- a/docs/library/forms/select-ll.md +++ b/docs/library/forms/select-ll.md @@ -13,7 +13,6 @@ components: ```python exec import random import reflex as rx -from pcweb.constants import REFLEX_ASSETS_CDN import reflex.components.radix.primitives as rdxp from pcweb.templates.docpage import style_grid ``` @@ -248,7 +247,7 @@ def form_select(): ```python demo rx.card( rx.flex( - rx.image(src=f"{REFLEX_ASSETS_CDN}other/reflex_banner.png", width="100%", height="auto"), + rx.image(src="https://web.reflex-assets.dev/other/reflex_banner.png", width="100%", height="auto"), rx.flex( rx.heading("Reflex Swag", size="4", margin_bottom="4px"), rx.heading("$99", size="6", margin_bottom="4px"), diff --git a/docs/library/forms/switch.md b/docs/library/forms/switch.md index 8efc3e687ca..cf241606065 100644 --- a/docs/library/forms/switch.md +++ b/docs/library/forms/switch.md @@ -9,7 +9,6 @@ Switch: | ```python exec import reflex as rx from pcweb.templates.docpage import style_grid -from pcweb.pages.docs import vars ``` # Switch diff --git a/docs/library/forms/upload.md b/docs/library/forms/upload.md index 767122e5e4c..73b4bde769d 100644 --- a/docs/library/forms/upload.md +++ b/docs/library/forms/upload.md @@ -9,7 +9,6 @@ Upload: | ```python exec import reflex as rx -from pcweb.constants import REFLEX_ASSETS_CDN ``` # File Upload @@ -22,7 +21,6 @@ You can let users upload files and keep track of them in your app’s state. The ```python import reflex as rx -from pcweb.constants import REFLEX_ASSETS_CDN class State(rx.State): uploaded_files: list[str] = [] @@ -82,7 +80,6 @@ Here is the standard pattern for handling file uploads: ```python import reflex as rx -from pcweb.constants import REFLEX_ASSETS_CDN def create_unique_filename(file_name: str): import random @@ -146,7 +143,7 @@ def upload_component(): Below is an example of how to allow multiple file uploads (in this case images). ```python demo box -rx.image(src=f"{REFLEX_ASSETS_CDN}other/upload.gif") +rx.image(src="https://web.reflex-assets.dev/other/upload.gif") ``` ```python @@ -209,7 +206,7 @@ def index(): Below is an example of how to allow only a single file upload and render (in this case a video). ```python demo box -rx.el.video(src=f"{REFLEX_ASSETS_CDN}other/upload_single_video.webm", auto_play=True, controls=True, loop=True) +rx.el.video(src="https://web.reflex-assets.dev/other/upload_single_video.webm", auto_play=True, controls=True, loop=True) ``` ```python diff --git a/docs/library/graphing/charts/barchart.md b/docs/library/graphing/charts/barchart.md index dd5de4efa3f..9359661b7ae 100644 --- a/docs/library/graphing/charts/barchart.md +++ b/docs/library/graphing/charts/barchart.md @@ -9,7 +9,6 @@ components: ```python exec import reflex as rx import random -from pcweb.pages.docs import library ``` A bar chart presents categorical data with rectangular bars with heights or lengths proportional to the values that they represent. @@ -385,4 +384,4 @@ def bar_vertical(): ) ``` -To learn how to use the `sync_id`, `stack_id`,`x_axis_id` and `y_axis_id` props check out the of the area chart [documentation]({library.graphing.charts.areachart.path}), where these props are all described with examples. +To learn how to use the `sync_id`, `stack_id`,`x_axis_id` and `y_axis_id` props check out the of the area chart [documentation](/docs/library/graphing/charts/areachart), where these props are all described with examples. diff --git a/docs/library/graphing/charts/composedchart.md b/docs/library/graphing/charts/composedchart.md index a2475f2becc..c2c97210b3c 100644 --- a/docs/library/graphing/charts/composedchart.md +++ b/docs/library/graphing/charts/composedchart.md @@ -5,7 +5,6 @@ components: ```python exec import reflex as rx -from pcweb.pages.docs import library ``` # Composed Chart @@ -13,7 +12,7 @@ from pcweb.pages.docs import library A `composed_chart` is a higher-level component chart that is composed of multiple charts, where other charts are the children of the `composed_chart`. The charts are placed on top of each other in the order they are provided in the `composed_chart` function. ```md alert info -# To learn more about individual charts, checkout: **[area_chart]({library.graphing.charts.areachart.path})**, **[line_chart]({library.graphing.charts.linechart.path})**, or **[bar_chart]({library.graphing.charts.barchart.path})**. +# To learn more about individual charts, checkout: **[area_chart](/docs/library/graphing/charts/areachart)**, **[line_chart](/docs/library/graphing/charts/linechart)**, or **[bar_chart](/docs/library/graphing/charts/barchart)**. ``` ```python demo graphing diff --git a/docs/library/graphing/charts/linechart.md b/docs/library/graphing/charts/linechart.md index 4df06cd466d..a2dddfd7db3 100644 --- a/docs/library/graphing/charts/linechart.md +++ b/docs/library/graphing/charts/linechart.md @@ -9,7 +9,6 @@ components: ```python exec import random from typing import Any -from pcweb.pages.docs import library import reflex as rx ``` @@ -306,4 +305,4 @@ def line_dynamic(): ) ``` -To learn how to use the `sync_id`, `x_axis_id` and `y_axis_id` props check out the of the area chart [documentation]({library.graphing.charts.areachart.path}), where these props are all described with examples. +To learn how to use the `sync_id`, `x_axis_id` and `y_axis_id` props check out the of the area chart [documentation](/docs/library/graphing/charts/areachart), where these props are all described with examples. diff --git a/docs/library/graphing/charts/scatterchart.md b/docs/library/graphing/charts/scatterchart.md index 03afdd264b8..1f877eaa95e 100644 --- a/docs/library/graphing/charts/scatterchart.md +++ b/docs/library/graphing/charts/scatterchart.md @@ -9,7 +9,6 @@ components: ```python exec import reflex as rx from pcweb.templates.docpage import docgraphing -from pcweb.pages.docs import library ``` A scatter chart always has two value axes to show one set of numerical data along a horizontal (value) axis and another set of numerical values along a vertical (value) axis. The chart displays points at the intersection of an x and y numerical value, combining these values into single data points. @@ -158,7 +157,7 @@ def scatter_double(): ) ``` -To learn how to use the `x_axis_id` and `y_axis_id` props, check out the Multiple Axis section of the area chart [documentation]({library.graphing.charts.areachart.path}). +To learn how to use the `x_axis_id` and `y_axis_id` props, check out the Multiple Axis section of the area chart [documentation](/docs/library/graphing/charts/areachart). ## Dynamic Data diff --git a/docs/library/layout/aspect_ratio.md b/docs/library/layout/aspect_ratio.md index cc5713af5aa..0b9326e9612 100644 --- a/docs/library/layout/aspect_ratio.md +++ b/docs/library/layout/aspect_ratio.md @@ -5,7 +5,6 @@ components: ```python exec import reflex as rx -from pcweb.constants import REFLEX_ASSETS_CDN ``` # Aspect Ratio @@ -72,7 +71,7 @@ rx.flex( *[ rx.box( rx.aspect_ratio( - rx.image(src=f"{REFLEX_ASSETS_CDN}other/logo.jpg", width="100%", height="100%"), + rx.image(src="https://web.reflex-assets.dev/other/logo.jpg", width="100%", height="100%"), ratio=ratio, ), width="20%", diff --git a/docs/library/layout/box.md b/docs/library/layout/box.md index 188e791208a..c170f397066 100644 --- a/docs/library/layout/box.md +++ b/docs/library/layout/box.md @@ -5,7 +5,6 @@ components: ```python exec import reflex as rx -from pcweb.constants import REFLEX_ASSETS_CDN ``` # Box @@ -39,7 +38,7 @@ rx.flex( rx.box(background="linear-gradient(45deg, var(--tomato-9), var(--plum-9))", width="20%", height="100%"), rx.box(background="linear-gradient(red, yellow, blue, orange)", width="20%", height="100%"), rx.box(background="radial-gradient(at 0% 30%, red 10px, yellow 30%, #1e90ff 50%)", width="20%", height="100%"), - rx.box(background=f"center/cover url('{REFLEX_ASSETS_CDN}other/reflex_banner.png')", width="20%", height="100%"), + rx.box(background="center/cover url('https://web.reflex-assets.dev/other/reflex_banner.png')", width="20%", height="100%"), spacing="2", width="100%", height="10vh", diff --git a/docs/library/layout/card.md b/docs/library/layout/card.md index 78d70efaad2..844a542be22 100644 --- a/docs/library/layout/card.md +++ b/docs/library/layout/card.md @@ -8,7 +8,6 @@ Card: | ```python exec import reflex as rx -from pcweb.constants import REFLEX_ASSETS_CDN ``` # Card @@ -44,7 +43,7 @@ commonly used to make a Card clickable. rx.card( rx.link( rx.flex( - rx.avatar(src=f"{REFLEX_ASSETS_CDN}other/reflex_banner.png"), + rx.avatar(src="https://web.reflex-assets.dev/other/reflex_banner.png"), rx.box( rx.heading("Quick Start"), rx.text("Get started with Reflex in 5 minutes."), diff --git a/docs/library/layout/inset.md b/docs/library/layout/inset.md index d70aa9fea74..f7f8951ada0 100644 --- a/docs/library/layout/inset.md +++ b/docs/library/layout/inset.md @@ -5,7 +5,7 @@ components: Inset: | lambda **props: rx.card( rx.inset( - rx.image(src=f"{REFLEX_ASSETS_CDN}other/reflex_banner.png", height="auto"), + rx.image(src="https://web.reflex-assets.dev/other/reflex_banner.png", height="auto"), **props, ), width="500px", @@ -14,7 +14,6 @@ Inset: | ```python exec import reflex as rx -from pcweb.constants import REFLEX_ASSETS_CDN ``` # Inset @@ -28,7 +27,7 @@ Nesting an Inset component inside a Card will render the content from edge to ed ```python demo rx.card( rx.inset( - rx.image(src=f"{REFLEX_ASSETS_CDN}other/reflex_banner.png", width="100%", height="auto"), + rx.image(src="https://web.reflex-assets.dev/other/reflex_banner.png", width="100%", height="auto"), side="top", pb="current", ), @@ -47,7 +46,7 @@ content would have had if it went to the edge of the parent component. rx.card( rx.text("The inset below uses a bottom side."), rx.inset( - rx.image(src=f"{REFLEX_ASSETS_CDN}other/reflex_banner.png", width="100%", height="auto"), + rx.image(src="https://web.reflex-assets.dev/other/reflex_banner.png", width="100%", height="auto"), side="bottom", pt="current", ), @@ -60,7 +59,7 @@ rx.card( rx.flex( rx.text("This inset uses a right side, which requires a flex with direction row."), rx.inset( - rx.box(background=f"center/cover url('{REFLEX_ASSETS_CDN}other/reflex_banner.png')", height="100%"), + rx.box(background="center/cover url('https://web.reflex-assets.dev/other/reflex_banner.png')", height="100%"), width="100%", side="right", pl="current", @@ -76,7 +75,7 @@ rx.card( rx.card( rx.flex( rx.inset( - rx.box(background=f"center/cover url('{REFLEX_ASSETS_CDN}other/reflex_banner.png')", height="100%"), + rx.box(background="center/cover url('https://web.reflex-assets.dev/other/reflex_banner.png')", height="100%"), width="100%", side="left", pr="current", diff --git a/docs/library/media/audio.md b/docs/library/media/audio.md index d3f8e8ebeaf..cd7b02f12f3 100644 --- a/docs/library/media/audio.md +++ b/docs/library/media/audio.md @@ -7,7 +7,6 @@ components: ```python exec import reflex as rx -from pcweb.pages.docs import library ``` The audio component can display an audio given an src path as an argument. This could either be a local path from the assets folder or an external link. @@ -25,5 +24,5 @@ If we had a local file in the `assets` folder named `test.mp3` we could set `src ```md alert info # How to let your user upload an audio file -To let a user upload an audio file to your app check out the [upload docs]({library.forms.upload.path}). +To let a user upload an audio file to your app check out the [upload docs](/docs/library/forms/upload). ``` diff --git a/docs/library/media/image.md b/docs/library/media/image.md index 5640f1c230a..598f365e2db 100644 --- a/docs/library/media/image.md +++ b/docs/library/media/image.md @@ -5,8 +5,6 @@ components: ```python exec import reflex as rx -from pcweb.constants import REFLEX_ASSETS_CDN -from pcweb.pages.docs import library ``` # Image @@ -15,14 +13,14 @@ The Image component can display an image given a `src` path as an argument. This could either be a local path from the assets folder or an external link. ```python demo -rx.image(src=f"{REFLEX_ASSETS_CDN}other/logo.jpg", width="100px", height="auto") +rx.image(src="https://web.reflex-assets.dev/other/logo.jpg", width="100px", height="auto") ``` Image composes a box and can be styled similarly. ```python demo rx.image( - src=f"{REFLEX_ASSETS_CDN}other/logo.jpg", + src="https://web.reflex-assets.dev/other/logo.jpg", width="100px", height="auto", border_radius="15px 50px", @@ -42,7 +40,7 @@ import requests class ImageState(rx.State): - url: str = f"https://picsum.photos/id/1/200/300" + url: str = "https://picsum.photos/id/1/200/300" image: Image.Image = Image.open(requests.get(url, stream=True).raw) @@ -61,5 +59,5 @@ A cv2 image must be converted to a PIL image to be passed directly to `rx.image` ```md alert info # How to let your user upload an image -To let a user upload an image to your app check out the [upload docs]({library.forms.upload.path}). +To let a user upload an image to your app check out the [upload docs](/docs/library/forms/upload). ``` diff --git a/docs/library/media/video.md b/docs/library/media/video.md index 668e00ba1d9..a98ad200a82 100644 --- a/docs/library/media/video.md +++ b/docs/library/media/video.md @@ -7,7 +7,6 @@ components: ```python exec import reflex as rx -from pcweb.pages.docs import library ``` The video component can display a video given an src path as an argument. This could either be a local path from the assets folder or an external link. @@ -25,5 +24,5 @@ If we had a local file in the `assets` folder named `test.mp4` we could set `url ```md alert info # How to let your user upload a video -To let a user upload a video to your app check out the [upload docs]({library.forms.upload.path}). +To let a user upload a video to your app check out the [upload docs](/docs/library/forms/upload). ``` diff --git a/docs/library/other/clipboard.md b/docs/library/other/clipboard.md index 061dc5d33a0..5fe2076154c 100644 --- a/docs/library/other/clipboard.md +++ b/docs/library/other/clipboard.md @@ -5,7 +5,6 @@ components: ```python exec import reflex as rx -from pcweb.pages.docs import styling ``` # Clipboard diff --git a/docs/library/other/html_embed.md b/docs/library/other/html_embed.md index f3ff0916823..2e54677fc31 100644 --- a/docs/library/other/html_embed.md +++ b/docs/library/other/html_embed.md @@ -5,7 +5,6 @@ components: ```python exec import reflex as rx -from pcweb.pages.docs import styling ``` # HTML Embed @@ -29,7 +28,7 @@ rx.vstack( # Missing Styles? Reflex uses Radix-UI and tailwind for styling, both of which reset default styles for headings. -If you are using the html component and want pretty default styles, consider setting `class_name='prose'`, adding `@tailwindcss/typography` package to `frontend_packages` and enabling it via `tailwind` config in `rxconfig.py`. See the [Tailwind docs]({styling.overview.path}) for an example of adding this plugin. +If you are using the html component and want pretty default styles, consider setting `class_name='prose'`, adding `@tailwindcss/typography` package to `frontend_packages` and enabling it via `tailwind` config in `rxconfig.py`. See the [Tailwind docs](/docs/styling/overview) for an example of adding this plugin. ``` In this example, we render an image. diff --git a/docs/library/other/memo.md b/docs/library/other/memo.md index 83ee4306bfd..2e75238c3ca 100644 --- a/docs/library/other/memo.md +++ b/docs/library/other/memo.md @@ -1,6 +1,5 @@ ```python exec import reflex as rx -from pcweb.pages.docs import styling ``` # Memo diff --git a/docs/library/overlay/dialog.md b/docs/library/overlay/dialog.md index 604dc5c43c9..a2df0150ba4 100644 --- a/docs/library/overlay/dialog.md +++ b/docs/library/overlay/dialog.md @@ -43,7 +43,6 @@ DialogContent: | ```python exec import reflex as rx -from pcweb.pages.docs import library ``` # Dialog @@ -193,7 +192,7 @@ def dialog_example(): ) ``` -Check out the [menu docs]({library.overlay.dropdown_menu.path}) for an example of opening a dialog from within a dropdown menu. +Check out the [menu docs](/docs/library/overlay/dropdown_menu) for an example of opening a dialog from within a dropdown menu. ## Form Submission to a Database from a Dialog diff --git a/docs/library/tables-and-data-grids/data_editor.md b/docs/library/tables-and-data-grids/data_editor.md index c64eac81b1b..37448be9a91 100644 --- a/docs/library/tables-and-data-grids/data_editor.md +++ b/docs/library/tables-and-data-grids/data_editor.md @@ -9,7 +9,6 @@ A datagrid editor based on [Glide Data Grid](https://grid.glideapps.com/) ```python exec import reflex as rx -from pcweb.pages.docs import library from typing import Any columns: list[dict[str, str]] = [ @@ -37,7 +36,7 @@ data: list[list[Any]] = [ ``` -This component is introduced as an alternative to the [datatable]({library.tables_and_data_grids.data_table.path}) to support editing the displayed data. +This component is introduced as an alternative to the [datatable](/docs/library/tables-and-data-grids/data_table) to support editing the displayed data. ## Columns diff --git a/docs/library/tables-and-data-grids/data_table.md b/docs/library/tables-and-data-grids/data_table.md index d8ef4c3f94c..be56fc98e1e 100644 --- a/docs/library/tables-and-data-grids/data_table.md +++ b/docs/library/tables-and-data-grids/data_table.md @@ -5,7 +5,6 @@ components: ```python exec import reflex as rx -from pcweb.pages.docs import library ``` # Data Table @@ -17,7 +16,7 @@ In this example we will read data from a csv file, convert it to a pandas datafr We will also add a search, pagination, sorting to the data_table to make it more accessible. -If you want to [add, edit or remove data]({library.tables_and_data_grids.table.path}) in your app or deal with anything but static data then the [`rx.table`]({library.tables_and_data_grids.table.path}) might be a better fit for your use case. +If you want to [add, edit or remove data](/docs/library/tables-and-data-grids/table) in your app or deal with anything but static data then the [`rx.table`](/docs/library/tables-and-data-grids/table) might be a better fit for your use case. ```python demo box rx.data_table( diff --git a/docs/library/tables-and-data-grids/table.md b/docs/library/tables-and-data-grids/table.md index 140ae333674..4616609f0f5 100644 --- a/docs/library/tables-and-data-grids/table.md +++ b/docs/library/tables-and-data-grids/table.md @@ -139,14 +139,13 @@ TableRowHeaderCell: | ```python exec import reflex as rx from pcweb.models import Customer -from pcweb.pages.docs import vars, events, database, library, components ``` # Table A semantic table for presenting tabular data. -If you just want to [represent static data]({library.tables_and_data_grids.data_table.path}) then the [`rx.data_table`]({library.tables_and_data_grids.data_table.path}) might be a better fit for your use case as it comes with in-built pagination, search and sorting. +If you just want to [represent static data](/docs/library/tables-and-data-grids/data_table) then the [`rx.data_table`](/docs/library/tables-and-data-grids/data_table) might be a better fit for your use case as it comes with in-built pagination, search and sorting. ## Basic Example @@ -187,7 +186,7 @@ rx.table.root( Many times there is a need for the data we represent in our table to be dynamic. Dynamic data must be in `State`. Later we will show an example of how to access data from a database and how to load data from a source file. -In this example there is a `people` data structure in `State` that is [iterated through using `rx.foreach`]({components.rendering_iterables.path}). +In this example there is a `people` data structure in `State` that is [iterated through using `rx.foreach`](/docs/components/rendering_iterables). ```python demo exec class TableForEachState(rx.State): @@ -439,9 +438,9 @@ The code below shows how to load data from a database and place it in an `rx.tab ## Loading data into table -A `Customer` [model]({database.tables.path}) is defined that inherits from `rx.Model`. +A `Customer` [model](/docs/database/tables) is defined that inherits from `rx.Model`. -The `load_entries` event handler executes a [query]({database.queries.path}) that is used to request information from a database table. This `load_entries` event handler is called on the `on_mount` event trigger of the `rx.table.root`. +The `load_entries` event handler executes a [query](/docs/database/queries) that is used to request information from a database table. This `load_entries` event handler is called on the `on_mount` event trigger of the `rx.table.root`. If you want to load the data when the page in the app loads you can set `on_load` in `app.add_page()` to equal this event handler, like `app.add_page(page_name, on_load=State.load_entries)`. diff --git a/docs/library/typography/link.md b/docs/library/typography/link.md index 8f4b0a73d90..fd70d985ef8 100644 --- a/docs/library/typography/link.md +++ b/docs/library/typography/link.md @@ -5,7 +5,6 @@ components: ```python exec import reflex as rx -from pcweb.pages.docs import api_reference ``` # Link @@ -43,7 +42,7 @@ rx.link("Example", href="/docs/library/typography/link#example") ```md alert info # Redirecting the user using State -It is also possible to redirect the user to a new path within the application, using `rx.redirect()`. Check out the docs [here]({api_reference.special_events.path}). +It is also possible to redirect the user to a new path within the application, using `rx.redirect()`. Check out the docs [here](/docs/api-reference/special_events). ``` # Style diff --git a/docs/pages/overview.md b/docs/pages/overview.md index ac19bdd74d2..05e173587fe 100644 --- a/docs/pages/overview.md +++ b/docs/pages/overview.md @@ -1,8 +1,6 @@ ```python exec import reflex as rx from pcweb import constants, styles -from pcweb.pages import docs -from pcweb.pages.docs import api_reference, library ``` # Pages @@ -70,7 +68,7 @@ You can directly import the module or import another module that imports the dec ### Links -[Links]({library.typography.link.path}) are accessible elements used primarily for navigation. Use the `href` prop to specify the location for the link to navigate to. +[Links](/docs/library/typography/link) are accessible elements used primarily for navigation. Use the `href` prop to specify the location for the link to navigate to. ```python demo rx.link("Reflex Home Page.", href="https://reflex.dev/") @@ -88,7 +86,7 @@ To open the link in a new tab, set the `is_external` prop to `True`. rx.link("Open in new tab", href="https://reflex.dev/", is_external=True) ``` -Check out the [link docs]({library.typography.link.path}) to learn more. +Check out the [link docs](/docs/library/typography/link) to learn more. ```md video https://youtube.com/embed/ITOZkzjtjUA?start=4083&end=4423 # Video: Link-based Navigation @@ -156,7 +154,6 @@ This component will be available at `/nested/page`. ## Page Metadata ```python exec - import reflex as rx meta_data = ( @@ -201,7 +198,7 @@ You can add page metadata such as: ## Getting the Current Page -You can access the current page from the `router` attribute in any state. See the [router docs]({docs.utility_methods.router_attributes.path}) for all available attributes. +You can access the current page from the `router` attribute in any state. See the [router docs](/docs/utility_methods/router_attributes) for all available attributes. ```python class State(rx.State): @@ -212,7 +209,7 @@ class State(rx.State): ``` The `router.page.path` attribute allows you to obtain the path of the current page from the router data, -for [dynamic pages]({docs.pages.dynamic_routing.path}) this will contain the slug rather than the actual value used to load the page. +for [dynamic pages](/docs/pages/dynamic_routing) this will contain the slug rather than the actual value used to load the page. To get the actual URL displayed in the browser, use `router.page.raw_path`. This will contain all query parameters and dynamic path segments. diff --git a/docs/recipes/auth/login_form.md b/docs/recipes/auth/login_form.md index 9189800e4ad..ef410f43081 100644 --- a/docs/recipes/auth/login_form.md +++ b/docs/recipes/auth/login_form.md @@ -1,6 +1,5 @@ ```python exec import reflex as rx -from pcweb.constants import REFLEX_ASSETS_CDN ``` # Login Form @@ -14,7 +13,7 @@ def login_default() -> rx.Component: return rx.card( rx.vstack( rx.center( - rx.image(src=f"{REFLEX_ASSETS_CDN}other/logo.jpg", width="2.5em", height="auto", border_radius="25%"), + rx.image(src="https://web.reflex-assets.dev/other/logo.jpg", width="2.5em", height="auto", border_radius="25%"), rx.heading("Sign in to your account", size="6", as_="h2", text_align="center", width="100%"), direction="column", spacing="5", @@ -62,7 +61,7 @@ def login_default_icons() -> rx.Component: return rx.card( rx.vstack( rx.center( - rx.image(src=f"{REFLEX_ASSETS_CDN}other/logo.jpg", width="2.5em", height="auto", border_radius="25%"), + rx.image(src="https://web.reflex-assets.dev/other/logo.jpg", width="2.5em", height="auto", border_radius="25%"), rx.heading("Sign in to your account", size="6", as_="h2", text_align="center", width="100%"), direction="column", spacing="5", @@ -110,7 +109,7 @@ def login_single_thirdparty() -> rx.Component: return rx.card( rx.vstack( rx.flex( - rx.image(src=f"{REFLEX_ASSETS_CDN}other/logo.jpg", width="2.5em", height="auto", border_radius="25%"), + rx.image(src="https://web.reflex-assets.dev/other/logo.jpg", width="2.5em", height="auto", border_radius="25%"), rx.heading("Sign in to your account", size="6", as_="h2", text_align="left", width="100%"), rx.hstack( rx.text("New here?", size="3", text_align="left"), @@ -173,7 +172,7 @@ def login_multiple_thirdparty() -> rx.Component: return rx.card( rx.vstack( rx.flex( - rx.image(src=f"{REFLEX_ASSETS_CDN}other/logo.jpg", width="2.5em", height="auto", border_radius="25%"), + rx.image(src="https://web.reflex-assets.dev/other/logo.jpg", width="2.5em", height="auto", border_radius="25%"), rx.heading("Sign in to your account", size="6", as_="h2", width="100%"), rx.hstack( rx.text("New here?", size="3", text_align="left"), diff --git a/docs/recipes/auth/signup_form.md b/docs/recipes/auth/signup_form.md index 90f8fb3c0f0..24ba4719988 100644 --- a/docs/recipes/auth/signup_form.md +++ b/docs/recipes/auth/signup_form.md @@ -1,6 +1,5 @@ ```python exec import reflex as rx -from pcweb.constants import REFLEX_ASSETS_CDN ``` # Sign up Form @@ -14,7 +13,7 @@ def signup_default() -> rx.Component: return rx.card( rx.vstack( rx.center( - rx.image(src=f"{REFLEX_ASSETS_CDN}other/logo.jpg", width="2.5em", height="auto", border_radius="25%"), + rx.image(src="https://web.reflex-assets.dev/other/logo.jpg", width="2.5em", height="auto", border_radius="25%"), rx.heading("Create an account", size="6", as_="h2", text_align="center", width="100%"), direction="column", spacing="5", @@ -66,7 +65,7 @@ def signup_default_icons() -> rx.Component: return rx.card( rx.vstack( rx.center( - rx.image(src=f"{REFLEX_ASSETS_CDN}other/logo.jpg", width="2.5em", height="auto", border_radius="25%"), + rx.image(src="https://web.reflex-assets.dev/other/logo.jpg", width="2.5em", height="auto", border_radius="25%"), rx.heading("Create an account", size="6", as_="h2", text_align="center", width="100%"), direction="column", spacing="5", @@ -119,7 +118,7 @@ def signup_single_thirdparty() -> rx.Component: return rx.card( rx.vstack( rx.flex( - rx.image(src=f"{REFLEX_ASSETS_CDN}other/logo.jpg", width="2.5em", height="auto", border_radius="25%"), + rx.image(src="https://web.reflex-assets.dev/other/logo.jpg", width="2.5em", height="auto", border_radius="25%"), rx.heading("Create an account", size="6", as_="h2", text_align="left", width="100%"), rx.hstack( rx.text("Already registered?", size="3", text_align="left"), @@ -186,7 +185,7 @@ def signup_multiple_thirdparty() -> rx.Component: return rx.card( rx.vstack( rx.flex( - rx.image(src=f"{REFLEX_ASSETS_CDN}other/logo.jpg", width="2.5em", height="auto", border_radius="25%"), + rx.image(src="https://web.reflex-assets.dev/other/logo.jpg", width="2.5em", height="auto", border_radius="25%"), rx.heading("Create an account", size="6", as_="h2", width="100%"), rx.hstack( rx.text("Already registered?", size="3", text_align="left"), diff --git a/docs/recipes/content/forms.md b/docs/recipes/content/forms.md index 0b4a076af0f..e1c1b9292df 100644 --- a/docs/recipes/content/forms.md +++ b/docs/recipes/content/forms.md @@ -1,13 +1,12 @@ ```python exec import reflex as rx -from pcweb.pages.docs import library ``` ## Forms Forms are a common way to gather information from users. Below are some examples. -For more details, see the [form docs page]({library.forms.form.path}). +For more details, see the [form docs page](/docs/library/forms/form). ## Event creation diff --git a/docs/recipes/content/grid.md b/docs/recipes/content/grid.md index 5c629c5f65a..5495098d79f 100644 --- a/docs/recipes/content/grid.md +++ b/docs/recipes/content/grid.md @@ -1,14 +1,12 @@ ```python exec import reflex as rx -from pcweb.constants import REFLEX_ASSETS_CDN -from pcweb.pages.docs import styling ``` # Grid A simple responsive grid layout. We specify the number of columns to the `grid_template_columns` property as a list. The grid will automatically adjust the number of columns based on the screen size. -For details, see the [responsive docs page]({styling.responsive.path}). +For details, see the [responsive docs page](/docs/styling/responsive). ## Cards @@ -33,7 +31,7 @@ rx.grid( lambda i: rx.card( rx.inset( rx.image( - src=f"{REFLEX_ASSETS_CDN}other/reflex_banner.png", + src="https://web.reflex-assets.dev/other/reflex_banner.png", width="100%", height="auto", ), diff --git a/docs/recipes/content/multi_column_row.md b/docs/recipes/content/multi_column_row.md index 891da8cc606..97db67cf5cc 100644 --- a/docs/recipes/content/multi_column_row.md +++ b/docs/recipes/content/multi_column_row.md @@ -1,13 +1,12 @@ ```python exec import reflex as rx -from pcweb.pages.docs import styling ``` # Multi-column and row layout A simple responsive multi-column and row layout. We specify the number of columns/rows to the `flex_direction` property as a list. The layout will automatically adjust the number of columns/rows based on the screen size. -For details, see the [responsive docs page]({styling.responsive.path}). +For details, see the [responsive docs page](/docs/styling/responsive). ## Column diff --git a/docs/recipes/content/top_banner.md b/docs/recipes/content/top_banner.md index b4e2c0b2855..66f5889f023 100644 --- a/docs/recipes/content/top_banner.md +++ b/docs/recipes/content/top_banner.md @@ -1,6 +1,5 @@ ```python exec import reflex as rx -from pcweb.constants import REFLEX_ASSETS_CDN ``` # Top Banner @@ -89,7 +88,7 @@ class TopBannerSignup(rx.ComponentState): ~cls.hide, rx.flex( rx.image( - src=f"{REFLEX_ASSETS_CDN}other/logo.jpg", + src="https://web.reflex-assets.dev/other/logo.jpg", width="2em", height="auto", border_radius="25%", diff --git a/docs/recipes/layout/footer.md b/docs/recipes/layout/footer.md index d13e12bae91..6e4872f820b 100644 --- a/docs/recipes/layout/footer.md +++ b/docs/recipes/layout/footer.md @@ -1,6 +1,5 @@ ```python exec import reflex as rx -from pcweb.constants import REFLEX_ASSETS_CDN ``` # Footer Bar @@ -59,7 +58,7 @@ def footer() -> rx.Component: rx.flex( rx.vstack( rx.hstack( - rx.image(src=f"{REFLEX_ASSETS_CDN}other/logo.jpg", width="2.25em", height="auto", border_radius="25%"), + rx.image(src="https://web.reflex-assets.dev/other/logo.jpg", width="2.25em", height="auto", border_radius="25%"), rx.heading("Reflex", size="7", weight="bold"), align_items="center" ), @@ -168,7 +167,7 @@ def footer_newsletter() -> rx.Component: rx.divider(), rx.flex( rx.hstack( - rx.image(src=f"{REFLEX_ASSETS_CDN}other/logo.jpg", width="2em", height="auto", border_radius="25%"), + rx.image(src="https://web.reflex-assets.dev/other/logo.jpg", width="2em", height="auto", border_radius="25%"), rx.text("© 2024 Reflex, Inc", size="3", white_space="nowrap", weight="medium"), spacing="2", align="center", @@ -261,7 +260,7 @@ def footer_three_columns() -> rx.Component: rx.divider(), rx.flex( rx.hstack( - rx.image(src=f"{REFLEX_ASSETS_CDN}other/logo.jpg", width="2em", height="auto", border_radius="25%"), + rx.image(src="https://web.reflex-assets.dev/other/logo.jpg", width="2em", height="auto", border_radius="25%"), rx.text("© 2024 Reflex, Inc", size="3", white_space="nowrap", weight="medium"), spacing="2", align="center", diff --git a/docs/recipes/layout/navbar.md b/docs/recipes/layout/navbar.md index 5ca3acde41a..3feed7f68c4 100644 --- a/docs/recipes/layout/navbar.md +++ b/docs/recipes/layout/navbar.md @@ -1,6 +1,5 @@ ```python exec import reflex as rx -from pcweb.constants import REFLEX_ASSETS_CDN ``` # Navigation Bar @@ -26,7 +25,7 @@ def navbar() -> rx.Component: rx.desktop_only( rx.hstack( rx.hstack( - rx.image(src=f"{REFLEX_ASSETS_CDN}other/logo.jpg", width="2.25em", height="auto", border_radius="25%"), + rx.image(src="https://web.reflex-assets.dev/other/logo.jpg", width="2.25em", height="auto", border_radius="25%"), rx.heading("Reflex", size="7", weight="bold"), align_items="center"), rx.hstack( navbar_link("Home", "/#"), @@ -43,7 +42,7 @@ def navbar() -> rx.Component: rx.mobile_and_tablet( rx.hstack( rx.hstack( - rx.image(src=f"{REFLEX_ASSETS_CDN}other/logo.jpg", width="2em", + rx.image(src="https://web.reflex-assets.dev/other/logo.jpg", width="2em", height="auto", border_radius="25%"), rx.heading("Reflex", size="6", weight="bold"), align_items="center"), rx.menu.root( @@ -80,7 +79,7 @@ def navbar_dropdown() -> rx.Component: rx.desktop_only( rx.hstack( rx.hstack( - rx.image(src=f"{REFLEX_ASSETS_CDN}other/logo.jpg", width="2.25em", height="auto", border_radius="25%"), + rx.image(src="https://web.reflex-assets.dev/other/logo.jpg", width="2.25em", height="auto", border_radius="25%"), rx.heading("Reflex", size="7", weight="bold"), align_items="center"), rx.hstack( navbar_link("Home", "/#"), @@ -107,7 +106,7 @@ def navbar_dropdown() -> rx.Component: rx.mobile_and_tablet( rx.hstack( rx.hstack( - rx.image(src=f"{REFLEX_ASSETS_CDN}other/logo.jpg", width="2em", height="auto", border_radius="25%"), + rx.image(src="https://web.reflex-assets.dev/other/logo.jpg", width="2em", height="auto", border_radius="25%"), rx.heading("Reflex", size="6", weight="bold"), align_items="center"), rx.menu.root( rx.menu.trigger(rx.icon("menu", size=30)), @@ -148,7 +147,7 @@ def navbar_searchbar() -> rx.Component: rx.desktop_only( rx.hstack( rx.hstack( - rx.image(src=f"{REFLEX_ASSETS_CDN}other/logo.jpg", width="2.25em", height="auto", border_radius="25%"), + rx.image(src="https://web.reflex-assets.dev/other/logo.jpg", width="2.25em", height="auto", border_radius="25%"), rx.heading("Reflex", size="7", weight="bold"), align_items="center"), rx.input( rx.input.slot(rx.icon("search")), @@ -163,7 +162,7 @@ def navbar_searchbar() -> rx.Component: rx.mobile_and_tablet( rx.hstack( rx.hstack( - rx.image(src=f"{REFLEX_ASSETS_CDN}other/logo.jpg", width="2em", height="auto", border_radius="25%"), + rx.image(src="https://web.reflex-assets.dev/other/logo.jpg", width="2em", height="auto", border_radius="25%"), rx.heading("Reflex", size="6", weight="bold"), align_items="center"), rx.input( rx.input.slot(rx.icon("search")), @@ -198,7 +197,7 @@ def navbar_icons() -> rx.Component: rx.desktop_only( rx.hstack( rx.hstack( - rx.image(src=f"{REFLEX_ASSETS_CDN}other/logo.jpg", width="2.25em", height="auto", border_radius="25%"), + rx.image(src="https://web.reflex-assets.dev/other/logo.jpg", width="2.25em", height="auto", border_radius="25%"), rx.heading("Reflex", size="7", weight="bold"), align_items="center"), rx.hstack( navbar_icons_item("Home", "home", "/#"), @@ -214,7 +213,7 @@ def navbar_icons() -> rx.Component: rx.mobile_and_tablet( rx.hstack( rx.hstack( - rx.image(src=f"{REFLEX_ASSETS_CDN}other/logo.jpg", width="2em", height="auto", border_radius="25%"), + rx.image(src="https://web.reflex-assets.dev/other/logo.jpg", width="2em", height="auto", border_radius="25%"), rx.heading("Reflex", size="6", weight="bold"), align_items="center"), rx.menu.root( rx.menu.trigger(rx.icon("menu", size=30)), @@ -250,7 +249,7 @@ def navbar_buttons() -> rx.Component: rx.desktop_only( rx.hstack( rx.hstack( - rx.image(src=f"{REFLEX_ASSETS_CDN}other/logo.jpg", width="2.25em", height="auto", border_radius="25%"), + rx.image(src="https://web.reflex-assets.dev/other/logo.jpg", width="2.25em", height="auto", border_radius="25%"), rx.heading("Reflex", size="7", weight="bold"), align_items="center"), rx.hstack( navbar_link("Home", "/#"), @@ -272,7 +271,7 @@ def navbar_buttons() -> rx.Component: rx.mobile_and_tablet( rx.hstack( rx.hstack( - rx.image(src=f"{REFLEX_ASSETS_CDN}other/logo.jpg", width="2em", height="auto", border_radius="25%"), + rx.image(src="https://web.reflex-assets.dev/other/logo.jpg", width="2em", height="auto", border_radius="25%"), rx.heading("Reflex", size="6", weight="bold"), align_items="center"), rx.menu.root( rx.menu.trigger(rx.icon("menu", size=30)), @@ -311,7 +310,7 @@ def navbar_user() -> rx.Component: rx.desktop_only( rx.hstack( rx.hstack( - rx.image(src=f"{REFLEX_ASSETS_CDN}other/logo.jpg", width="2.25em", height="auto", border_radius="25%"), + rx.image(src="https://web.reflex-assets.dev/other/logo.jpg", width="2.25em", height="auto", border_radius="25%"), rx.heading("Reflex", size="7", weight="bold"), align_items="center"), rx.hstack( navbar_link("Home", "/#"), @@ -338,7 +337,7 @@ def navbar_user() -> rx.Component: rx.mobile_and_tablet( rx.hstack( rx.hstack( - rx.image(src=f"{REFLEX_ASSETS_CDN}other/logo.jpg", width="2em", height="auto", border_radius="25%"), + rx.image(src="https://web.reflex-assets.dev/other/logo.jpg", width="2em", height="auto", border_radius="25%"), rx.heading("Reflex", size="6", weight="bold"), align_items="center"), rx.menu.root( rx.menu.trigger(rx.icon_button( diff --git a/docs/recipes/layout/sidebar.md b/docs/recipes/layout/sidebar.md index e30f90d566c..8183ab1e276 100644 --- a/docs/recipes/layout/sidebar.md +++ b/docs/recipes/layout/sidebar.md @@ -1,6 +1,5 @@ ```python exec import reflex as rx -from pcweb.constants import REFLEX_ASSETS_CDN def sidebar_item(text: str, icon: str, href: str) -> rx.Component: return rx.link( @@ -81,7 +80,7 @@ def sidebar() -> rx.Component: rx.desktop_only( rx.vstack( rx.hstack( - rx.image(src=f"{REFLEX_ASSETS_CDN}other/logo.jpg", width="2.25em", + rx.image(src="https://web.reflex-assets.dev/other/logo.jpg", width="2.25em", height="auto", border_radius="25%"), rx.heading("Reflex", size="7", weight="bold"), align="center", @@ -176,7 +175,7 @@ def sidebar_bottom_profile() -> rx.Component: rx.desktop_only( rx.vstack( rx.hstack( - rx.image(src=f"{REFLEX_ASSETS_CDN}other/logo.jpg", width="2.25em", + rx.image(src="https://web.reflex-assets.dev/other/logo.jpg", width="2.25em", height="auto", border_radius="25%"), rx.heading("Reflex", size="7", weight="bold"), align="center", diff --git a/docs/recipes/others/checkboxes.md b/docs/recipes/others/checkboxes.md index 308211f95a5..8b55f81c111 100644 --- a/docs/recipes/others/checkboxes.md +++ b/docs/recipes/others/checkboxes.md @@ -1,6 +1,5 @@ ```python exec import reflex as rx -from pcweb.constants import REFLEX_ASSETS_CDN ``` # Smart Checkboxes Group @@ -10,7 +9,7 @@ A smart checkboxes group where you can track all checked boxes, as well as place ## Recipe ```python eval -rx.center(rx.image(src=f"{REFLEX_ASSETS_CDN}templates/smart_checkboxes.webp")) +rx.center(rx.image(src="https://web.reflex-assets.dev/templates/smart_checkboxes.webp")) ``` This recipe use a `dict[str, bool]` for the checkboxes state tracking. diff --git a/docs/state_structure/component_state.md b/docs/state_structure/component_state.md index 174f90d52b8..12a097b4207 100644 --- a/docs/state_structure/component_state.md +++ b/docs/state_structure/component_state.md @@ -1,6 +1,5 @@ ```python exec import reflex as rx -from pcweb.pages.docs import events, ui, vars ``` # Component State @@ -9,8 +8,8 @@ _New in version 0.4.6_. Defining a subclass of `rx.ComponentState` creates a special type of state that is tied to an instance of a component, rather than existing globally in the app. A Component State combines -[UI code]({ui.overview.path}) with state [Vars]({vars.base_vars.path}) and -[Event Handlers]({events.events_overview.path}), +[UI code](/docs/ui/overview) with state [Vars](/docs/vars/base_vars) and +[Event Handlers](/docs/events/events_overview), and is useful for creating reusable components which operate independently of each other. ```md alert warning diff --git a/docs/styling/custom-stylesheets.md b/docs/styling/custom-stylesheets.md index 01213c6d27d..de8aecce63d 100644 --- a/docs/styling/custom-stylesheets.md +++ b/docs/styling/custom-stylesheets.md @@ -1,6 +1,5 @@ ```python exec import reflex as rx -from pcweb.pages.docs import assets ``` # Custom Stylesheets @@ -17,7 +16,7 @@ app = rx.App( ## Local Stylesheets -You can also add local stylesheets. Just put the stylesheet under [`assets/`]({assets.upload_and_download_files.path}) and pass the path to the stylesheet to `rx.App`: +You can also add local stylesheets. Just put the stylesheet under [`assets/`](/docs/assets/upload_and_download_files) and pass the path to the stylesheet to `rx.App`: ```python app = rx.App( diff --git a/docs/styling/overview.md b/docs/styling/overview.md index b247d3ef11a..960ad3d169f 100644 --- a/docs/styling/overview.md +++ b/docs/styling/overview.md @@ -1,6 +1,5 @@ ```python exec import reflex as rx -from pcweb.pages.docs import styling, library ``` # Styling @@ -74,7 +73,7 @@ Using style dictionaries like this, you can easily create a consistent theme for ```md alert warning # Watch out for underscores in class names and IDs -Reflex automatically converts `snake_case` identifiers into `camelCase` format when applying styles. To ensure consistency, it is recommended to use the dash character or camelCase identifiers in your own class names and IDs. To style third-party libraries relying on underscore class names, an external stylesheet should be used. See [custom stylesheets]({styling.custom_stylesheets.path}) for how to reference external CSS files. +Reflex automatically converts `snake_case` identifiers into `camelCase` format when applying styles. To ensure consistency, it is recommended to use the dash character or camelCase identifiers in your own class names and IDs. To style third-party libraries relying on underscore class names, an external stylesheet should be used. See [custom stylesheets](/docs/styling/custom-stylesheets) for how to reference external CSS files. ``` ## Inline Styles @@ -158,7 +157,7 @@ The style dictionaries are applied in the order they are passed in. This means t ## Theming -As of Reflex 'v0.4.0', you can now theme your Reflex web apps. To learn more checkout the [Theme docs]({styling.theming.path}). +As of Reflex 'v0.4.0', you can now theme your Reflex web apps. To learn more checkout the [Theme docs](/docs/styling/theming). The `Theme` component is used to change the theme of the application. The `Theme` can be set directly in your rx.App. @@ -170,7 +169,7 @@ app = rx.App( ) ``` -Additionally you can modify the theme of your app through using the `Theme Panel` component which can be found in the [Theme Panel docs]({library.other.theme.path}). +Additionally you can modify the theme of your app through using the `Theme Panel` component which can be found in the [Theme Panel docs](/docs/library/other/theme). ## Special Styles diff --git a/docs/styling/tailwind.md b/docs/styling/tailwind.md index fcbcfa227e7..0839b840ebc 100644 --- a/docs/styling/tailwind.md +++ b/docs/styling/tailwind.md @@ -1,6 +1,5 @@ ```python exec import reflex as rx -from pcweb.pages.docs import library ``` @@ -256,4 +255,4 @@ def tailwind_demo(): Reflex core components are built on Radix Themes, which means they come with pre-defined styling. When you apply Tailwind classes to these components, you may encounter styling conflicts or unexpected behavior as the Tailwind styles compete with the built-in Radix styles. -For the best experience when using Tailwind CSS in your Reflex application, we recommend using the lower-level `rx.el` components. These components don't have pre-applied styles, giving you complete control over styling with Tailwind classes without any conflicts. Check the list of HTML components [here]({library.other.html.path}). +For the best experience when using Tailwind CSS in your Reflex application, we recommend using the lower-level `rx.el` components. These components don't have pre-applied styles, giving you complete control over styling with Tailwind classes without any conflicts. Check the list of HTML components [here](/docs/library/other/html). diff --git a/docs/styling/theming.md b/docs/styling/theming.md index 78493c5de7c..1d0ec110b98 100644 --- a/docs/styling/theming.md +++ b/docs/styling/theming.md @@ -1,7 +1,5 @@ ```python exec import reflex as rx -from pcweb.constants import REFLEX_ASSETS_CDN -from pcweb.pages.docs import library from pcweb.styles.styles import get_code_style_rdx, cell_style ``` @@ -75,7 +73,7 @@ rx.table.root( ``` -Additionally you can modify the theme of your app through using the `Theme Panel` component which can be found in the [Theme Panel docs]({library.other.theme.path}). +Additionally you can modify the theme of your app through using the `Theme Panel` component which can be found in the [Theme Panel docs](/docs/library/other/theme). ## Colors @@ -188,8 +186,8 @@ To render a different component depending on whether the app is in `light` mode ```python demo rx.color_mode_cond( - light=rx.image(src=f"{REFLEX_ASSETS_CDN}logos/light/reflex.svg", alt="Reflex Logo light", height="4em"), - dark=rx.image(src=f"{REFLEX_ASSETS_CDN}logos/dark/reflex.svg", alt="Reflex Logo dark", height="4em"), + light=rx.image(src="https://web.reflex-assets.dev/logos/light/reflex.svg", alt="Reflex Logo light", height="4em"), + dark=rx.image(src="https://web.reflex-assets.dev/logos/dark/reflex.svg", alt="Reflex Logo dark", height="4em"), ) ``` diff --git a/docs/ui/overview.md b/docs/ui/overview.md index f6b34f49037..54f929a46e2 100644 --- a/docs/ui/overview.md +++ b/docs/ui/overview.md @@ -1,6 +1,4 @@ ```python exec -from pcweb.pages.docs import components -from pcweb.pages.docs.library import library import reflex as rx ``` @@ -53,7 +51,7 @@ Some props are specific to a component. For example, the `header` and `content` Styling props like `color` are shared across many components. ```md alert info -# You can find all the props for a component by checking its documentation page in the [component library]({library.path}). +# You can find all the props for a component by checking its documentation page in the [component library](/docs/library). ``` ## Pages diff --git a/docs/vars/base_vars.md b/docs/vars/base_vars.md index b75bb8e161c..c8367f2df4b 100644 --- a/docs/vars/base_vars.md +++ b/docs/vars/base_vars.md @@ -4,7 +4,6 @@ import time import reflex as rx -from pcweb.pages.docs import vars ``` # Base Vars @@ -45,7 +44,7 @@ In this example `ticker` and `price` are base vars in the app, which can be modi ```md alert warning # Vars must be JSON serializable. -Vars are used to communicate between the frontend and backend. They must be primitive Python types, Plotly figures, Pandas dataframes, or [a custom defined type]({vars.custom_vars.path}). +Vars are used to communicate between the frontend and backend. They must be primitive Python types, Plotly figures, Pandas dataframes, or [a custom defined type](/docs/vars/custom_vars). ``` ## Accessing state variables on different pages diff --git a/docs/vars/custom_vars.md b/docs/vars/custom_vars.md index 4de84b059b1..ef7603b9c30 100644 --- a/docs/vars/custom_vars.md +++ b/docs/vars/custom_vars.md @@ -3,12 +3,11 @@ import reflex as rx import dataclasses from typing import TypedDict -from pcweb.pages.docs import vars ``` # Custom Vars -As mentioned in the [vars page]({vars.base_vars.path}), Reflex vars must be JSON serializable. +As mentioned in the [vars page](/docs/vars/base_vars), Reflex vars must be JSON serializable. This means we can support any Python primitive types, as well as lists, dicts, and tuples. However, you can also create more complex var types using dataclasses (recommended), TypedDict, or Pydantic models. diff --git a/docs/vars/var-operations.md b/docs/vars/var-operations.md index 7a56a73e798..6d57163da07 100644 --- a/docs/vars/var-operations.md +++ b/docs/vars/var-operations.md @@ -118,8 +118,8 @@ def var_operation_example(): rx.heading(f"The number: {OperState.number}", size="3"), rx.hstack( rx.text("Negated:", rx.badge(-OperState.number, variant="soft", color_scheme="green")), - rx.text(f"Absolute:", rx.badge(abs(OperState.number), variant="soft", color_scheme="blue")), - rx.text(f"Numbers seen:", rx.badge(OperState.numbers_seen.length(), variant="soft", color_scheme="red")), + rx.text("Absolute:", rx.badge(abs(OperState.number), variant="soft", color_scheme="blue")), + rx.text("Numbers seen:", rx.badge(OperState.numbers_seen.length(), variant="soft", color_scheme="red")), ), rx.button("Update", on_click=OperState.update), ) diff --git a/docs/wrapping-react/library-and-tags.md b/docs/wrapping-react/library-and-tags.md index 75e2d45935c..140937e1d14 100644 --- a/docs/wrapping-react/library-and-tags.md +++ b/docs/wrapping-react/library-and-tags.md @@ -2,9 +2,6 @@ title: Library and Tags --- -```python exec -from pcweb.pages.docs import api_reference -``` # Find The Component @@ -19,7 +16,7 @@ In both cases, the process of wrapping the component is the same except for the To start wrapping your React component, the first step is to create a new component in your Reflex app. This is done by creating a new class that inherits from `rx.Component` or `rx.NoSSRComponent`. -See the [API Reference]({api_reference.component.path}) for more details on the `rx.Component` class. +See the [API Reference](https://reflex.dev/docs/api-reference/component/) for more details on the `rx.Component` class. This is when we will define the most important attributes of the component: diff --git a/docs/wrapping-react/more-wrapping-examples.md b/docs/wrapping-react/more-wrapping-examples.md index 8fc11d855d8..e1dff462482 100644 --- a/docs/wrapping-react/more-wrapping-examples.md +++ b/docs/wrapping-react/more-wrapping-examples.md @@ -105,13 +105,10 @@ app.add_page(index) ## React Leaflet -```python exec -from pcweb.pages import docs -``` In this example we are wrapping the React Leaflet library from the NPM package [react-leaflet](https://www.npmjs.com/package/react-leaflet). -On line `1` we import the `dynamic` function from Next.js and on line `21` we set `ssr: false`. Lines `4` and `6` use the `dynamic` function to import the `MapContainer` and `TileLayer` components from the `react-leaflet` library. This is used to dynamically import the `MapContainer` and `TileLayer` components from the `react-leaflet` library. This is done in Reflex by using the `NoSSRComponent` class when defining the component. There is more information of when this is needed on the `Dynamic Imports` section of this [page]({docs.wrapping_react.library_and_tags.path}). +On line `1` we import the `dynamic` function from Next.js and on line `21` we set `ssr: false`. Lines `4` and `6` use the `dynamic` function to import the `MapContainer` and `TileLayer` components from the `react-leaflet` library. This is used to dynamically import the `MapContainer` and `TileLayer` components from the `react-leaflet` library. This is done in Reflex by using the `NoSSRComponent` class when defining the component. There is more information of when this is needed on the `Dynamic Imports` section of this [page](/docs/wrapping-react/library-and-tags). It mentions in the documentation that it is necessary to include the Leaflet CSS file, which is added on line `2` in the React code below. This can be done in Reflex by using the `add_imports` method in the `MapContainer` component. We can add a relative path from within the React library or a full URL to the CSS file. @@ -216,7 +213,7 @@ app.add_page(index) In this example we are wrapping the React renderer for creating PDF files on the browser and server from the NPM package [@react-pdf/renderer](https://www.npmjs.com/package/@react-pdf/renderer). -This example is similar to the previous examples, and again Dynamic Imports are required for this library. This is done in Reflex by using the `NoSSRComponent` class when defining the component. There is more information on why this is needed on the `Dynamic Imports` section of this [page]({docs.wrapping_react.library_and_tags.path}). +This example is similar to the previous examples, and again Dynamic Imports are required for this library. This is done in Reflex by using the `NoSSRComponent` class when defining the component. There is more information on why this is needed on the `Dynamic Imports` section of this [page](/docs/wrapping-react/library-and-tags). The main difference with this example is that the `style` prop, used on lines `20`, `21` and `24` in React code, is a reserved name in Reflex so can not be wrapped. A different name must be used when wrapping this prop and then this name must be changed back to the original with the `rename_props` method. In this example we name the prop `theme` in our Reflex code and then change it back to `style` with the `rename_props` method in both the `Page` and `View` components. diff --git a/docs/wrapping-react/overview.md b/docs/wrapping-react/overview.md index bb7f4d3a93d..1a97ed1af80 100644 --- a/docs/wrapping-react/overview.md +++ b/docs/wrapping-react/overview.md @@ -2,7 +2,6 @@ import reflex as rx from typing import Any from pcweb.components.spline import spline -from pcweb.pages.docs import custom_components from pcweb import constants ``` @@ -12,7 +11,7 @@ One of Reflex's most powerful features is the ability to wrap React components a If you want a specific component for your app but Reflex doesn't provide it, there's a good chance it's available as a React component. Search for it on [npm]({constants.NPMJS_URL}), and if it's there, you can use it in your Reflex app. You can also create your own local React components and wrap them in Reflex. -Once you wrap your component, you [publish it]({custom_components.overview.path}) to the Reflex library so that others can use it. +Once you wrap your component, you [publish it](/docs/custom-components/overview) to the Reflex library so that others can use it. ## Simple Example From a056e3715d25b32b31deae98e3a9ca7621c19d0f Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Wed, 25 Mar 2026 19:31:41 -0700 Subject: [PATCH 030/113] remove almost all pcweb imports --- docs/advanced_onboarding/how-reflex-works.md | 4 -- docs/api-reference/event_triggers.md | 2 +- docs/api-reference/utils.md | 1 - docs/assets/upload_and_download_files.md | 10 ++--- .../prerequisites-for-publishing.md | 3 +- docs/events/background_events.md | 1 - docs/getting_started/chatapp_tutorial.md | 7 +--- docs/getting_started/installation.md | 9 ++-- docs/getting_started/introduction.md | 14 +++---- docs/getting_started/open_source_templates.md | 2 - docs/library/data-display/avatar.md | 4 -- docs/library/data-display/badge.md | 4 -- docs/library/data-display/moment.md | 32 +++++--------- docs/library/data-display/scroll_area.md | 1 - docs/library/forms/radio_group.md | 1 - docs/library/forms/select-ll.md | 1 - docs/library/forms/select.md | 1 - docs/library/forms/slider.md | 1 - docs/library/forms/switch.md | 1 - docs/library/graphing/charts/scatterchart.md | 1 - docs/library/layout/fragment.md | 3 +- docs/library/tables-and-data-grids/table.md | 6 ++- docs/pages/dynamic_routing.md | 1 - docs/pages/overview.md | 1 - docs/state/overview.md | 13 +++++- docs/state_structure/mixins.md | 1 - docs/state_structure/shared_state.md | 1 - docs/styling/common-props.md | 8 ++-- docs/styling/responsive.md | 14 +++---- docs/styling/theming.md | 42 +++++++++---------- docs/utility_methods/router_attributes.md | 4 +- docs/vars/var-operations.md | 1 - docs/wrapping-react/overview.md | 6 +-- 33 files changed, 81 insertions(+), 120 deletions(-) diff --git a/docs/advanced_onboarding/how-reflex-works.md b/docs/advanced_onboarding/how-reflex-works.md index bb181e8d069..f401f9d896f 100644 --- a/docs/advanced_onboarding/how-reflex-works.md +++ b/docs/advanced_onboarding/how-reflex-works.md @@ -1,7 +1,3 @@ -```python exec -from pcweb import constants -``` - # How Reflex Works We'll use the following basic app that displays Github profile images as an example to explain the different parts of the architecture. diff --git a/docs/api-reference/event_triggers.md b/docs/api-reference/event_triggers.md index d7438636c27..3e75905c3f6 100644 --- a/docs/api-reference/event_triggers.md +++ b/docs/api-reference/event_triggers.md @@ -3,7 +3,7 @@ from datetime import datetime import reflex as rx -from pcweb.templates.docpage import docdemo, h1_comp, text_comp, docpage +from pcweb.templates.docpage import docdemo, h1_comp, text_comp SYNTHETIC_EVENTS = [ { diff --git a/docs/api-reference/utils.md b/docs/api-reference/utils.md index 6a42a54b8c9..c0424ab6f36 100644 --- a/docs/api-reference/utils.md +++ b/docs/api-reference/utils.md @@ -1,6 +1,5 @@ ```python exec import reflex as rx -from pcweb import constants, styles ``` # Utility Functions diff --git a/docs/assets/upload_and_download_files.md b/docs/assets/upload_and_download_files.md index 50b30df60fb..685309f8ab7 100644 --- a/docs/assets/upload_and_download_files.md +++ b/docs/assets/upload_and_download_files.md @@ -1,7 +1,5 @@ ```python exec import reflex as rx -from pcweb.styles.styles import get_code_style -from pcweb.styles.colors import c_color ``` # Files @@ -33,12 +31,12 @@ rx.table.root( rx.table.row( rx.table.cell(rx.text("Location", font_weight="bold")), rx.table.cell(rx.hstack( - rx.code("assets/", style=get_code_style("violet")), + rx.code("assets/", style={"color": rx.color("violet", 11), "border_radius": "0.25rem", "border": f"1px solid {rx.color('violet', 5)}", "background": rx.color("violet", 3)}), rx.text(" folder or next to Python files (shared assets)"), spacing="2", )), rx.table.cell(rx.hstack( - rx.code("uploaded_files/", style=get_code_style("violet")), + rx.code("uploaded_files/", style={"color": rx.color("violet", 11), "border_radius": "0.25rem", "border": f"1px solid {rx.color('violet', 5)}", "background": rx.color("violet", 3)}), rx.text(" directory (configurable)"), spacing="2", )), @@ -46,11 +44,11 @@ rx.table.root( rx.table.row( rx.table.cell(rx.text("Access Method", font_weight="bold")), rx.table.cell(rx.hstack( - rx.code("rx.asset()", style=get_code_style("violet")), + rx.code("rx.asset()", style={"color": rx.color("violet", 11), "border_radius": "0.25rem", "border": f"1px solid {rx.color('violet', 5)}", "background": rx.color("violet", 3)}), rx.text(" or direct path reference"), spacing="2", )), - rx.table.cell(rx.code("rx.get_upload_url()", style=get_code_style("violet"))), + rx.table.cell(rx.code("rx.get_upload_url()", style={"color": rx.color("violet", 11), "border_radius": "0.25rem", "border": f"1px solid {rx.color('violet', 5)}", "background": rx.color("violet", 3)})), ), rx.table.row( rx.table.cell(rx.text("When to Use", font_weight="bold")), diff --git a/docs/custom-components/prerequisites-for-publishing.md b/docs/custom-components/prerequisites-for-publishing.md index 44768525ae0..7eaea88e115 100644 --- a/docs/custom-components/prerequisites-for-publishing.md +++ b/docs/custom-components/prerequisites-for-publishing.md @@ -2,11 +2,10 @@ ```python exec import reflex as rx -from pcweb.styles.colors import c_color image_style = { "width": "400px", "border_radius": "12px", - "border": f"1px solid {c_color('slate', 5)}", + "border": "1px solid var(--c-slate-5)", } ``` diff --git a/docs/events/background_events.md b/docs/events/background_events.md index 04d381cb7d0..94efaf1f573 100644 --- a/docs/events/background_events.md +++ b/docs/events/background_events.md @@ -1,6 +1,5 @@ ```python exec import reflex as rx -from pcweb import constants, styles ``` # Background Tasks diff --git a/docs/getting_started/chatapp_tutorial.md b/docs/getting_started/chatapp_tutorial.md index dc494e169f3..6639456e064 100644 --- a/docs/getting_started/chatapp_tutorial.md +++ b/docs/getting_started/chatapp_tutorial.md @@ -4,9 +4,6 @@ import os import reflex as rx import openai -from pcweb.constants import CHAT_APP_URL -from pcweb import constants - from docs.getting_started import chat_tutorial_style as style from docs.getting_started.chat_tutorial_utils import ChatappState @@ -20,7 +17,7 @@ if "OPENAI_API_KEY" not in os.environ: This tutorial will walk you through building an AI chat app with Reflex. This app is fairly complex, but don't worry - we'll break it down into small steps. -You can find the full source code for this app [here]({CHAT_APP_URL}). +You can find the full source code for this app [here](https://github.com/reflex-dev/reflex-chat). ### What You'll Learn @@ -44,7 +41,7 @@ We will start by creating a new project and setting up our development environme ~ $ cd chatapp ``` -Next, we will create a virtual environment for our project. This is optional, but recommended. In this example, we will use [venv]({constants.VENV_URL}) to create our virtual environment. +Next, we will create a virtual environment for our project. This is optional, but recommended. In this example, we will use [venv](https://docs.python.org/3/library/venv.html) to create our virtual environment. ```bash chatapp $ python3 -m venv venv diff --git a/docs/getting_started/installation.md b/docs/getting_started/installation.md index 4a14c4f10cc..be51070f335 100644 --- a/docs/getting_started/installation.md +++ b/docs/getting_started/installation.md @@ -1,5 +1,4 @@ ```python exec -from pcweb import constants import reflex as rx app_name = "my_app_name" default_url = "http://localhost:3000" @@ -17,7 +16,7 @@ Reflex requires Python 3.10+. We **highly recommend** creating a virtual environment for your project. -[uv]({constants.UV_URL}) is the recommended modern option. [venv]({constants.VENV_URL}), [conda]({constants.CONDA_URL}) and [poetry]({constants.POETRY_URL}) are some alternatives. +[uv](https://docs.astral.sh/uv/) is the recommended modern option. [venv](https://docs.python.org/3/library/venv.html), [conda](https://conda.io/) and [poetry](https://python-poetry.org/) are some alternatives. # Install Reflex on your system @@ -27,7 +26,7 @@ We **highly recommend** creating a virtual environment for your project. ## Install on macOS/Linux -We will go with [uv]({constants.UV_URL}) here. +We will go with [uv](https://docs.astral.sh/uv/) here. ### Prerequisites @@ -83,7 +82,7 @@ For Windows users, we recommend using [Windows Subsystem for Linux (WSL)](https: For the rest of this section we will work with native Windows (non-WSL). -We will go with [uv]({constants.UV_URL}) here. +We will go with [uv](https://docs.astral.sh/uv/) here. ### Prerequisites @@ -134,7 +133,7 @@ Bun requires runtime components of Visual C++ libraries to run on Windows. This --- -Running `uv run reflex init` will return the option to start with a blank Reflex app, premade templates built by the Reflex team, or to try our [AI builder]({constants.REFLEX_BUILD_URL}). +Running `uv run reflex init` will return the option to start with a blank Reflex app, premade templates built by the Reflex team, or to try our [AI builder](https://build.reflex.dev/). ```bash Initializing the web directory. diff --git a/docs/getting_started/introduction.md b/docs/getting_started/introduction.md index 889225bf79f..7ac5d59e717 100644 --- a/docs/getting_started/introduction.md +++ b/docs/getting_started/introduction.md @@ -1,9 +1,5 @@ ```python exec import reflex as rx -from pcweb import constants, styles -from pcweb.styles.colors import c_color -from pcweb.styles.fonts import base -from pcweb.flexdown import markdown_with_shiki ``` @@ -78,7 +74,7 @@ def tabs(): ), ), rx.tabs.content( - markdown_with_shiki( + rx.markdown( """The frontend is built declaratively using Reflex components. Components are compiled down to JS and served to the users browser, therefore: - Only use Reflex components, vars, and var operations when building your UI. Any other logic should be put in your `State` (backend). @@ -90,7 +86,7 @@ def tabs(): class_name="pt-4" ), rx.tabs.content( - markdown_with_shiki( + rx.markdown( """Write your backend in the `State` class. Here you can define functions and variables that can be referenced in the frontend. This code runs directly on the server and is not compiled, so there are no special caveats. Here you can use any Python external library and call any method/function. """, ), @@ -98,7 +94,7 @@ def tabs(): class_name="pt-4" ), rx.tabs.content( - markdown_with_shiki( + rx.markdown( """Each page is a Python function that returns a Reflex component. You can define multiple pages and navigate between them, see the [Routing](/docs/pages/overview) section for more information. - Start with a single page and scale to 100s of pages. @@ -335,11 +331,11 @@ For a glimpse of the possibilities, check out these resources: - For a more real-world example, check out either the [dashboard tutorial](/docs/getting_started/dashboard_tutorial) or the [chatapp tutorial](/docs/getting_started/chatapp_tutorial). - Check out our open-source [templates](/docs/getting_started/open_source_templates)! -- We have an AI Builder that can generate full Reflex apps or help with your existing app! Check it out at [Reflex Build]({constants.REFLEX_BUILD_URL})! +- We have an AI Builder that can generate full Reflex apps or help with your existing app! Check it out at [Reflex Build](https://build.reflex.dev/)! - Deploy your app with a single command using [Reflex Cloud](https://reflex.dev/docs/hosting/deploy-quick-start/)! If you want to learn more about how Reflex works, check out the [How Reflex Works](/docs/advanced_onboarding/how-reflex-works) section. ## Join our Community -If you have questions about anything related to Reflex, you're always welcome to ask our community on [GitHub Discussions]({constants.GITHUB_DISCUSSIONS_URL}), [Discord]({constants.DISCORD_URL}), [Forum]({constants.FORUM_URL}), and [X]({constants.TWITTER_URL}). +If you have questions about anything related to Reflex, you're always welcome to ask our community on [GitHub Discussions](https://github.com/orgs/reflex-dev/discussions), [Discord](https://discord.gg/T5WSbC2YtQ), [Forum](https://forum.reflex.dev), and [X](https://twitter.com/getreflex). diff --git a/docs/getting_started/open_source_templates.md b/docs/getting_started/open_source_templates.md index f9f02790445..267a31bba1d 100644 --- a/docs/getting_started/open_source_templates.md +++ b/docs/getting_started/open_source_templates.md @@ -6,9 +6,7 @@ Check out what the community is building with Reflex. See 2000+ more public proj import reflex as rx from pcweb.components.code_card import gallery_app_card -from pcweb.components.webpage.comps import h1_title from pcweb.pages.gallery.sidebar import TemplatesState, pagination, sidebar -from pcweb.templates.webpage import webpage @rx.memo diff --git a/docs/library/data-display/avatar.md b/docs/library/data-display/avatar.md index 5d8250fca85..6956f5dbf5b 100644 --- a/docs/library/data-display/avatar.md +++ b/docs/library/data-display/avatar.md @@ -9,7 +9,6 @@ Avatar: | ```python exec import reflex as rx -from pcweb.templates.docpage import style_grid ``` The Avatar component is used to represent a user, and display their profile pictures or fallback texts such as initials. @@ -30,9 +29,6 @@ rx.avatar(fallback="RX") ## Styling -```python eval -style_grid(component_used=rx.avatar, component_used_str="rx.avatar", variants=["solid", "soft"], fallback="RX") -``` ### Size diff --git a/docs/library/data-display/badge.md b/docs/library/data-display/badge.md index 24b8113ed39..3c4a340fe67 100644 --- a/docs/library/data-display/badge.md +++ b/docs/library/data-display/badge.md @@ -10,7 +10,6 @@ Badge: | ```python exec import reflex as rx -from pcweb.templates.docpage import style_grid ``` Badges are used to highlight an item's status for quick recognition. @@ -25,9 +24,6 @@ rx.badge("New") ## Styling -```python eval -style_grid(component_used=rx.badge, component_used_str="rx.badge", variants=["solid", "soft", "surface", "outline"], components_passed="England!",) -``` ### Size diff --git a/docs/library/data-display/moment.md b/docs/library/data-display/moment.md index a6eeb294620..c6ab9c3e6f9 100644 --- a/docs/library/data-display/moment.md +++ b/docs/library/data-display/moment.md @@ -12,7 +12,6 @@ To make it easy, Reflex is wrapping [react-moment](https://www.npmjs.com/package ```python exec import reflex as rx from reflex.utils.serializers import serialize_datetime -from pcweb.templates.docpage import docdemo, docdemobox, doccode, docgraphing ``` ## Examples @@ -77,45 +76,34 @@ rx.moment(MomentState.date_now, format="HH:mm:ss") With the props `add` and `subtract`, you can pass an `rx.MomentDelta` object to modify the displayed date without affecting the stored date in your state. -```python exec -add_example = """rx.vstack( +#### Add + +```python demo +rx.vstack( rx.moment(MomentState.date_now, add=rx.MomentDelta(years=2), format="YYYY-MM-DD - HH:mm:ss"), rx.moment(MomentState.date_now, add=rx.MomentDelta(quarters=2), format="YYYY-MM-DD - HH:mm:ss"), rx.moment(MomentState.date_now, add=rx.MomentDelta(months=2), format="YYYY-MM-DD - HH:mm:ss"), - rx.moment(MomentState.date_now, add=rx.MomentDelta(months=2), format="YYYY-MM-DD - HH:mm:ss"), - rx.moment(MomentState.date_now, add=rx.MomentDelta(months=2), format="YYYY-MM-DD - HH:mm:ss"), rx.moment(MomentState.date_now, add=rx.MomentDelta(weeks=2), format="YYYY-MM-DD - HH:mm:ss"), rx.moment(MomentState.date_now, add=rx.MomentDelta(days=2), format="YYYY-MM-DD - HH:mm:ss"), rx.moment(MomentState.date_now, add=rx.MomentDelta(hours=2), format="YYYY-MM-DD - HH:mm:ss"), rx.moment(MomentState.date_now, add=rx.MomentDelta(minutes=2), format="YYYY-MM-DD - HH:mm:ss"), rx.moment(MomentState.date_now, add=rx.MomentDelta(seconds=2), format="YYYY-MM-DD - HH:mm:ss"), ) -""" -subtract_example = """rx.vstack( +``` + +#### Subtract + +```python demo +rx.vstack( rx.moment(MomentState.date_now, subtract=rx.MomentDelta(years=2), format="YYYY-MM-DD - HH:mm:ss"), rx.moment(MomentState.date_now, subtract=rx.MomentDelta(quarters=2), format="YYYY-MM-DD - HH:mm:ss"), rx.moment(MomentState.date_now, subtract=rx.MomentDelta(months=2), format="YYYY-MM-DD - HH:mm:ss"), - rx.moment(MomentState.date_now, subtract=rx.MomentDelta(months=2), format="YYYY-MM-DD - HH:mm:ss"), - rx.moment(MomentState.date_now, subtract=rx.MomentDelta(months=2), format="YYYY-MM-DD - HH:mm:ss"), rx.moment(MomentState.date_now, subtract=rx.MomentDelta(weeks=2), format="YYYY-MM-DD - HH:mm:ss"), rx.moment(MomentState.date_now, subtract=rx.MomentDelta(days=2), format="YYYY-MM-DD - HH:mm:ss"), rx.moment(MomentState.date_now, subtract=rx.MomentDelta(hours=2), format="YYYY-MM-DD - HH:mm:ss"), rx.moment(MomentState.date_now, subtract=rx.MomentDelta(minutes=2), format="YYYY-MM-DD - HH:mm:ss"), rx.moment(MomentState.date_now, subtract=rx.MomentDelta(seconds=2), format="YYYY-MM-DD - HH:mm:ss"), ) -""" -``` - -```python eval -rx.tabs( - rx.tabs.list( - rx.tabs.trigger("Add", value="add"), - rx.tabs.trigger("Subtract", value="subtract") - ), - rx.tabs.content(docdemo(add_example, comp=eval(add_example)), value="add"), - rx.tabs.content(docdemo(subtract_example, comp=eval(subtract_example)), value="subtract"), - default_value="add", -) ``` ### Timezones diff --git a/docs/library/data-display/scroll_area.md b/docs/library/data-display/scroll_area.md index 5c42be41b9b..e2f0829d001 100644 --- a/docs/library/data-display/scroll_area.md +++ b/docs/library/data-display/scroll_area.md @@ -25,7 +25,6 @@ ScrollArea: | ```python exec import random import reflex as rx -from pcweb.templates.docpage import style_grid ``` # Scroll Area diff --git a/docs/library/forms/radio_group.md b/docs/library/forms/radio_group.md index e0c62187d9a..593f2d4da4e 100644 --- a/docs/library/forms/radio_group.md +++ b/docs/library/forms/radio_group.md @@ -29,7 +29,6 @@ RadioGroupItem: | ```python exec import reflex as rx -from pcweb.templates.docpage import style_grid ``` # Radio Group diff --git a/docs/library/forms/select-ll.md b/docs/library/forms/select-ll.md index 551bd8130bc..8d279842b72 100644 --- a/docs/library/forms/select-ll.md +++ b/docs/library/forms/select-ll.md @@ -14,7 +14,6 @@ components: import random import reflex as rx import reflex.components.radix.primitives as rdxp -from pcweb.templates.docpage import style_grid ``` # Select diff --git a/docs/library/forms/select.md b/docs/library/forms/select.md index b37e7104880..a54318e40c0 100644 --- a/docs/library/forms/select.md +++ b/docs/library/forms/select.md @@ -70,7 +70,6 @@ SelectItem: | ```python exec import random import reflex as rx -from pcweb.templates.docpage import style_grid ``` # Select diff --git a/docs/library/forms/slider.md b/docs/library/forms/slider.md index cc537b224b6..6c35d49ebd3 100644 --- a/docs/library/forms/slider.md +++ b/docs/library/forms/slider.md @@ -8,7 +8,6 @@ Slider: | ```python exec import reflex as rx -from pcweb.templates.docpage import style_grid ``` # Slider diff --git a/docs/library/forms/switch.md b/docs/library/forms/switch.md index cf241606065..f97180de61b 100644 --- a/docs/library/forms/switch.md +++ b/docs/library/forms/switch.md @@ -8,7 +8,6 @@ Switch: | ```python exec import reflex as rx -from pcweb.templates.docpage import style_grid ``` # Switch diff --git a/docs/library/graphing/charts/scatterchart.md b/docs/library/graphing/charts/scatterchart.md index 1f877eaa95e..c75918ccd33 100644 --- a/docs/library/graphing/charts/scatterchart.md +++ b/docs/library/graphing/charts/scatterchart.md @@ -8,7 +8,6 @@ components: ```python exec import reflex as rx -from pcweb.templates.docpage import docgraphing ``` A scatter chart always has two value axes to show one set of numerical data along a horizontal (value) axis and another set of numerical values along a vertical (value) axis. The chart displays points at the intersection of an x and y numerical value, combining these values into single data points. diff --git a/docs/library/layout/fragment.md b/docs/library/layout/fragment.md index 99f3ff2fc19..731e9eb02a4 100644 --- a/docs/library/layout/fragment.md +++ b/docs/library/layout/fragment.md @@ -7,12 +7,11 @@ components: ```python exec import reflex as rx -from pcweb import constants ``` A Fragment is a Component that allow you to group multiple Components without a wrapper node. -Refer to the React docs at [React/Fragment]({constants.FRAGMENT_COMPONENT_INFO_URL}) for more information on its use-case. +Refer to the React docs at [React/Fragment](https://react.dev/reference/react/Fragment) for more information on its use-case. ```python demo rx.fragment( diff --git a/docs/library/tables-and-data-grids/table.md b/docs/library/tables-and-data-grids/table.md index 4616609f0f5..61a90ab4f5d 100644 --- a/docs/library/tables-and-data-grids/table.md +++ b/docs/library/tables-and-data-grids/table.md @@ -138,7 +138,11 @@ TableRowHeaderCell: | ```python exec import reflex as rx -from pcweb.models import Customer +class Customer(rx.Model, table=True): + name: str + email: str + phone: str + address: str ``` # Table diff --git a/docs/pages/dynamic_routing.md b/docs/pages/dynamic_routing.md index 72e555b62cc..14a4be85bec 100644 --- a/docs/pages/dynamic_routing.md +++ b/docs/pages/dynamic_routing.md @@ -1,6 +1,5 @@ ```python exec import reflex as rx -from pcweb import constants, styles ``` # Dynamic Routes diff --git a/docs/pages/overview.md b/docs/pages/overview.md index 05e173587fe..9d1e67d3463 100644 --- a/docs/pages/overview.md +++ b/docs/pages/overview.md @@ -1,6 +1,5 @@ ```python exec import reflex as rx -from pcweb import constants, styles ``` # Pages diff --git a/docs/state/overview.md b/docs/state/overview.md index a9397a3da19..7e32f9c8960 100644 --- a/docs/state/overview.md +++ b/docs/state/overview.md @@ -1,6 +1,17 @@ ```python exec import reflex as rx -from pcweb.templates.docpage import definition +def definition(title, *children): + return rx.vstack( + rx.heading(title, font_size="1em", font_weight="bold", color=rx.color("mauve", 12)), + *children, + color=rx.color("mauve", 10), + padding="1em", + border=f"1px solid {rx.color('mauve', 4)}", + background_color=rx.color("mauve", 2), + border_radius="8px", + _hover={"border": f"1px solid {rx.color('mauve', 5)}", "background_color": rx.color("mauve", 3)}, + align_items="start", + ) ``` # State diff --git a/docs/state_structure/mixins.md b/docs/state_structure/mixins.md index 566f50de8f1..34b90624590 100644 --- a/docs/state_structure/mixins.md +++ b/docs/state_structure/mixins.md @@ -1,6 +1,5 @@ ```python exec import reflex as rx -from pcweb.templates.docpage import definition ``` # State Mixins diff --git a/docs/state_structure/shared_state.md b/docs/state_structure/shared_state.md index 4666b16f4d1..4d8d067b8e7 100644 --- a/docs/state_structure/shared_state.md +++ b/docs/state_structure/shared_state.md @@ -1,6 +1,5 @@ ```python exec import reflex as rx -from pcweb.templates.docpage import definition ``` # Shared State diff --git a/docs/styling/common-props.md b/docs/styling/common-props.md index 75491d265c9..24d940f1718 100644 --- a/docs/styling/common-props.md +++ b/docs/styling/common-props.md @@ -2,8 +2,8 @@ ```python exec import reflex as rx -from pcweb.styles.styles import get_code_style, cell_style -from pcweb.styles.colors import c_color +cell_style = {"font_family": "Instrument Sans", "font_style": "normal", "font_weight": "500", "font_size": "14px", "line_height": "1.5", "letter_spacing": "-0.0125em", "color": "var(--c-slate-11)"} +c_color = lambda color, shade: f"var(--c-{color}-{shade})" props = { "align": { @@ -178,7 +178,7 @@ def show_props(key, props_dict): rx.table.cell( rx.link( rx.hstack( - rx.code(key, style=get_code_style("violet")), + rx.code(key, style={"color": rx.color("violet", 11), "border_radius": "0.25rem", "border": f"1px solid {rx.color('violet', 5)}", "background": rx.color("violet", 3)}), rx.icon("square_arrow_out_up_right", color=c_color("slate", 9), size=15, flex_shrink="0"), align="center" ), @@ -187,7 +187,7 @@ def show_props(key, props_dict): ), justify="start",), rx.table.cell(prop_details["description"], justify="start", style=cell_style), - rx.table.cell(rx.hstack(*[rx.code(value, style=get_code_style("violet")) for value in prop_details["values"]], flex_wrap="wrap"), justify="start",), + rx.table.cell(rx.hstack(*[rx.code(value, style={"color": rx.color("violet", 11), "border_radius": "0.25rem", "border": f"1px solid {rx.color('violet', 5)}", "background": rx.color("violet", 3)}) for value in prop_details["values"]], flex_wrap="wrap"), justify="start",), justify="center", align="center", diff --git a/docs/styling/responsive.md b/docs/styling/responsive.md index ea7e5d2177c..feb6cd1d97b 100644 --- a/docs/styling/responsive.md +++ b/docs/styling/responsive.md @@ -1,6 +1,6 @@ ```python exec import reflex as rx -from pcweb.styles.styles import get_code_style, cell_style +cell_style = {"font_family": "Instrument Sans", "font_style": "normal", "font_weight": "500", "font_size": "14px", "line_height": "1.5", "letter_spacing": "-0.0125em", "color": "var(--c-slate-11)"} ``` # Responsive @@ -77,27 +77,27 @@ rx.table.root( ), rx.table.body( rx.table.row( - rx.table.cell(rx.code("initial", style=get_code_style("violet"))), + rx.table.cell(rx.code("initial", style={"color": rx.color("violet", 11), "border_radius": "0.25rem", "border": f"1px solid {rx.color('violet', 5)}", "background": rx.color("violet", 3)})), rx.table.cell("0px", style=cell_style), ), rx.table.row( - rx.table.cell(rx.code("xs", style=get_code_style("violet"))), + rx.table.cell(rx.code("xs", style={"color": rx.color("violet", 11), "border_radius": "0.25rem", "border": f"1px solid {rx.color('violet', 5)}", "background": rx.color("violet", 3)})), rx.table.cell("30em", style=cell_style), ), rx.table.row( - rx.table.cell(rx.code("sm", style=get_code_style("violet"))), + rx.table.cell(rx.code("sm", style={"color": rx.color("violet", 11), "border_radius": "0.25rem", "border": f"1px solid {rx.color('violet', 5)}", "background": rx.color("violet", 3)})), rx.table.cell("48em", style=cell_style), ), rx.table.row( - rx.table.cell(rx.code("md", style=get_code_style("violet"))), + rx.table.cell(rx.code("md", style={"color": rx.color("violet", 11), "border_radius": "0.25rem", "border": f"1px solid {rx.color('violet', 5)}", "background": rx.color("violet", 3)})), rx.table.cell("62em", style=cell_style), ), rx.table.row( - rx.table.cell(rx.code("lg", style=get_code_style("violet"))), + rx.table.cell(rx.code("lg", style={"color": rx.color("violet", 11), "border_radius": "0.25rem", "border": f"1px solid {rx.color('violet', 5)}", "background": rx.color("violet", 3)})), rx.table.cell("80em", style=cell_style), ), rx.table.row( - rx.table.cell(rx.code("xl", style=get_code_style("violet"))), + rx.table.cell(rx.code("xl", style={"color": rx.color("violet", 11), "border_radius": "0.25rem", "border": f"1px solid {rx.color('violet', 5)}", "background": rx.color("violet", 3)})), rx.table.cell("96em", style=cell_style), ), ), diff --git a/docs/styling/theming.md b/docs/styling/theming.md index 1d0ec110b98..41fdf36fd38 100644 --- a/docs/styling/theming.md +++ b/docs/styling/theming.md @@ -1,6 +1,6 @@ ```python exec import reflex as rx -from pcweb.styles.styles import get_code_style_rdx, cell_style +cell_style = {"font_family": "Instrument Sans", "font_style": "normal", "font_weight": "500", "font_size": "14px", "line_height": "1.5", "letter_spacing": "-0.0125em", "color": "var(--c-slate-11)"} ``` # Theming @@ -32,38 +32,38 @@ rx.table.root( ), rx.table.body( rx.table.row( - rx.table.row_header_cell(rx.code("has_background", style=get_code_style_rdx("violet"), class_name="code-style")), - rx.table.cell(rx.code("Bool", style=get_code_style_rdx("gray"), class_name="code-style")), + rx.table.row_header_cell(rx.code("has_background", style={"color": rx.color("violet", 11), "border_radius": "0.25rem", "border": f"1px solid {rx.color('violet', 5)}", "background": rx.color("violet", 3)}, class_name="code-style")), + rx.table.cell(rx.code("Bool", style={"color": rx.color("gray", 11), "border_radius": "0.25rem", "border": f"1px solid {rx.color('gray', 5)}", "background": rx.color("gray", 3)}, class_name="code-style")), rx.table.cell("Whether to apply the themes background color to the theme node. Defaults to True.", style=cell_style), ), rx.table.row( - rx.table.row_header_cell(rx.code("appearance", style=get_code_style_rdx("violet"), class_name="code-style")), - rx.table.cell(rx.code('"inherit" | "light" | "dark"', style=get_code_style_rdx("gray"), class_name="code-style")), + rx.table.row_header_cell(rx.code("appearance", style={"color": rx.color("violet", 11), "border_radius": "0.25rem", "border": f"1px solid {rx.color('violet', 5)}", "background": rx.color("violet", 3)}, class_name="code-style")), + rx.table.cell(rx.code('"inherit" | "light" | "dark"', style={"color": rx.color("gray", 11), "border_radius": "0.25rem", "border": f"1px solid {rx.color('gray', 5)}", "background": rx.color("gray", 3)}, class_name="code-style")), rx.table.cell("The appearance of the theme. Can be 'light' or 'dark'. Defaults to 'light'.", style=cell_style), ), rx.table.row( - rx.table.row_header_cell(rx.code("accent_color", style=get_code_style_rdx("violet"), class_name="code-style")), - rx.table.cell(rx.code("Str", style=get_code_style_rdx("gray"), class_name="code-style")), + rx.table.row_header_cell(rx.code("accent_color", style={"color": rx.color("violet", 11), "border_radius": "0.25rem", "border": f"1px solid {rx.color('violet', 5)}", "background": rx.color("violet", 3)}, class_name="code-style")), + rx.table.cell(rx.code("Str", style={"color": rx.color("gray", 11), "border_radius": "0.25rem", "border": f"1px solid {rx.color('gray', 5)}", "background": rx.color("gray", 3)}, class_name="code-style")), rx.table.cell("The primary color used for default buttons, typography, backgrounds, etc.", style=cell_style), ), rx.table.row( - rx.table.row_header_cell(rx.code("gray_color", style=get_code_style_rdx("violet"), class_name="code-style")), - rx.table.cell(rx.code("Str", style=get_code_style_rdx("gray"), class_name="code-style")), + rx.table.row_header_cell(rx.code("gray_color", style={"color": rx.color("violet", 11), "border_radius": "0.25rem", "border": f"1px solid {rx.color('violet', 5)}", "background": rx.color("violet", 3)}, class_name="code-style")), + rx.table.cell(rx.code("Str", style={"color": rx.color("gray", 11), "border_radius": "0.25rem", "border": f"1px solid {rx.color('gray', 5)}", "background": rx.color("gray", 3)}, class_name="code-style")), rx.table.cell("The secondary color used for default buttons, typography, backgrounds, etc.", style=cell_style), ), rx.table.row( - rx.table.row_header_cell(rx.code("panel_background", style=get_code_style_rdx("violet"), class_name="code-style")), - rx.table.cell(rx.code('"solid" | "translucent"', style=get_code_style_rdx("gray"), class_name="code-style")), + rx.table.row_header_cell(rx.code("panel_background", style={"color": rx.color("violet", 11), "border_radius": "0.25rem", "border": f"1px solid {rx.color('violet', 5)}", "background": rx.color("violet", 3)}, class_name="code-style")), + rx.table.cell(rx.code('"solid" | "translucent"', style={"color": rx.color("gray", 11), "border_radius": "0.25rem", "border": f"1px solid {rx.color('gray', 5)}", "background": rx.color("gray", 3)}, class_name="code-style")), rx.table.cell('Whether panel backgrounds are translucent: "solid" | "translucent" (default).', style=cell_style), ), rx.table.row( - rx.table.row_header_cell(rx.code("radius", style=get_code_style_rdx("violet"), class_name="code-style")), - rx.table.cell(rx.code('"none" | "small" | "medium" | "large" | "full"', style=get_code_style_rdx("gray"))), + rx.table.row_header_cell(rx.code("radius", style={"color": rx.color("violet", 11), "border_radius": "0.25rem", "border": f"1px solid {rx.color('violet', 5)}", "background": rx.color("violet", 3)}, class_name="code-style")), + rx.table.cell(rx.code('"none" | "small" | "medium" | "large" | "full"', style={"color": rx.color("gray", 11), "border_radius": "0.25rem", "border": f"1px solid {rx.color('gray', 5)}", "background": rx.color("gray", 3)})), rx.table.cell("The radius of the theme. Can be 'small', 'medium', or 'large'. Defaults to 'medium'.", style=cell_style), ), rx.table.row( - rx.table.row_header_cell(rx.code("scaling", style=get_code_style_rdx("violet"), class_name="code-style")), - rx.table.cell(rx.code('"90%" | "95%" | "100%" | "105%" | "110%"', style=get_code_style_rdx("gray"), class_name="code-style")), + rx.table.row_header_cell(rx.code("scaling", style={"color": rx.color("violet", 11), "border_radius": "0.25rem", "border": f"1px solid {rx.color('violet', 5)}", "background": rx.color("violet", 3)}, class_name="code-style")), + rx.table.cell(rx.code('"90%" | "95%" | "100%" | "105%" | "110%"', style={"color": rx.color("gray", 11), "border_radius": "0.25rem", "border": f"1px solid {rx.color('gray', 5)}", "background": rx.color("gray", 3)}, class_name="code-style")), rx.table.cell("Scale of all theme items.", style=cell_style), ), ), @@ -126,18 +126,18 @@ rx.table.root( ), rx.table.body( rx.table.row( - rx.table.row_header_cell(rx.code("color", style=get_code_style_rdx("violet"), class_name="code-style")), - rx.table.cell(rx.code("Str", style=get_code_style_rdx("gray"), class_name="code-style")), + rx.table.row_header_cell(rx.code("color", style={"color": rx.color("violet", 11), "border_radius": "0.25rem", "border": f"1px solid {rx.color('violet', 5)}", "background": rx.color("violet", 3)}, class_name="code-style")), + rx.table.cell(rx.code("Str", style={"color": rx.color("gray", 11), "border_radius": "0.25rem", "border": f"1px solid {rx.color('gray', 5)}", "background": rx.color("gray", 3)}, class_name="code-style")), rx.table.cell("The color to use. Can be any valid accent color or 'accent' to reference the current theme color.", style=cell_style), ), rx.table.row( - rx.table.row_header_cell(rx.code("shade", style=get_code_style_rdx("violet"), class_name="code-style")), - rx.table.cell(rx.link(rx.code('1 - 12', style=get_code_style_rdx("gray"), class_name="code-style"), href="https://www.radix-ui.com/colors")), + rx.table.row_header_cell(rx.code("shade", style={"color": rx.color("violet", 11), "border_radius": "0.25rem", "border": f"1px solid {rx.color('violet', 5)}", "background": rx.color("violet", 3)}, class_name="code-style")), + rx.table.cell(rx.link(rx.code('1 - 12', style={"color": rx.color("gray", 11), "border_radius": "0.25rem", "border": f"1px solid {rx.color('gray', 5)}", "background": rx.color("gray", 3)}, class_name="code-style"), href="https://www.radix-ui.com/colors")), rx.table.cell("The shade of the color to use. Defaults to 7.", style=cell_style), ), rx.table.row( - rx.table.row_header_cell(rx.code("alpha", style=get_code_style_rdx("violet"), class_name="code-style")), - rx.table.cell(rx.code("Bool", style=get_code_style_rdx("gray"), class_name="code-style")), + rx.table.row_header_cell(rx.code("alpha", style={"color": rx.color("violet", 11), "border_radius": "0.25rem", "border": f"1px solid {rx.color('violet', 5)}", "background": rx.color("violet", 3)}, class_name="code-style")), + rx.table.cell(rx.code("Bool", style={"color": rx.color("gray", 11), "border_radius": "0.25rem", "border": f"1px solid {rx.color('gray', 5)}", "background": rx.color("gray", 3)}, class_name="code-style")), rx.table.cell("Whether to use the alpha value of the color. Defaults to False.", style=cell_style), ) ), diff --git a/docs/utility_methods/router_attributes.md b/docs/utility_methods/router_attributes.md index fc00dbdb059..edf0acfc55c 100644 --- a/docs/utility_methods/router_attributes.md +++ b/docs/utility_methods/router_attributes.md @@ -1,6 +1,6 @@ ```python exec box import reflex as rx -from pcweb.styles.styles import get_code_style, cell_style +cell_style = {"font_family": "Instrument Sans", "font_style": "normal", "font_weight": "500", "font_size": "14px", "line_height": "1.5", "letter_spacing": "-0.0125em", "color": "var(--c-slate-11)"} class RouterState(rx.State): pass @@ -86,7 +86,7 @@ rx.table.root( *[ rx.table.row( rx.table.cell(item["name"], style=cell_style), - rx.table.cell(rx.code(item["value"], style=get_code_style("violet"))), + rx.table.cell(rx.code(item["value"], style={"color": rx.color("violet", 11), "border_radius": "0.25rem", "border": f"1px solid {rx.color('violet', 5)}", "background": rx.color("violet", 3)})), ) for item in router_data ] diff --git a/docs/vars/var-operations.md b/docs/vars/var-operations.md index 6d57163da07..28606fa4e53 100644 --- a/docs/vars/var-operations.md +++ b/docs/vars/var-operations.md @@ -6,7 +6,6 @@ import numpy as np import reflex as rx -from pcweb.templates.docpage import docpage ``` # Var Operations diff --git a/docs/wrapping-react/overview.md b/docs/wrapping-react/overview.md index 1a97ed1af80..7e05df852bc 100644 --- a/docs/wrapping-react/overview.md +++ b/docs/wrapping-react/overview.md @@ -1,15 +1,13 @@ ```python exec import reflex as rx from typing import Any -from pcweb.components.spline import spline -from pcweb import constants ``` # Wrapping React One of Reflex's most powerful features is the ability to wrap React components and take advantage of the vast ecosystem of React libraries. -If you want a specific component for your app but Reflex doesn't provide it, there's a good chance it's available as a React component. Search for it on [npm]({constants.NPMJS_URL}), and if it's there, you can use it in your Reflex app. You can also create your own local React components and wrap them in Reflex. +If you want a specific component for your app but Reflex doesn't provide it, there's a good chance it's available as a React component. Search for it on [npm](https://www.npmjs.com/), and if it's there, you can use it in your Reflex app. You can also create your own local React components and wrap them in Reflex. Once you wrap your component, you [publish it](/docs/custom-components/overview) to the Reflex library so that others can use it. @@ -17,7 +15,7 @@ Once you wrap your component, you [publish it](/docs/custom-components/overview) Simple components that don't have any interaction can be wrapped with just a few lines of code. -Below we show how to wrap the [Spline]({constants.SPLINE_URL}) library can be used to create 3D scenes and animations. +Below we show how to wrap the [Spline](https://github.com/splinetool/react-spline) library can be used to create 3D scenes and animations. ```python demo exec import reflex as rx From 2cd1e8dc9865b16610af0bb106feb19437f2dfc3 Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Thu, 26 Mar 2026 11:27:47 -0700 Subject: [PATCH 031/113] this is getting out of hand to be in one line --- pyproject.toml | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 03aa073f0c1..54419cb0d02 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -328,7 +328,23 @@ hooks = [ [tool.uv] required-version = ">=0.7.0" -sources = { hatch-reflex-pyi = { workspace = true }, reflex-components-code = { workspace = true }, reflex-components-core = { workspace = true }, reflex-components-dataeditor = { workspace = true }, reflex-components-gridjs = { workspace = true }, reflex-components-lucide = { workspace = true }, reflex-components-markdown = { workspace = true }, reflex-components-moment = { workspace = true }, reflex-components-plotly = { workspace = true }, reflex-components-radix = { workspace = true }, reflex-components-react-player = { workspace = true }, reflex-components-react-router = { workspace = true }, reflex-components-recharts = { workspace = true }, reflex-components-sonner = { workspace = true }, reflex-docgen = { workspace = true } } + +[tool.uv.sources] +hatch-reflex-pyi.workspace = true +reflex-components-code.workspace = true +reflex-components-core.workspace = true +reflex-components-dataeditor.workspace = true +reflex-components-gridjs.workspace = true +reflex-components-lucide.workspace = true +reflex-components-markdown.workspace = true +reflex-components-moment.workspace = true +reflex-components-plotly.workspace = true +reflex-components-radix.workspace = true +reflex-components-react-player.workspace = true +reflex-components-react-router.workspace = true +reflex-components-recharts.workspace = true +reflex-components-sonner.workspace = true +reflex-docgen.workspace = true [tool.uv.workspace] members = ["packages/*"] From cb3464c5a19adb7897578fc56d4cfe51112e92df Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Thu, 26 Mar 2026 11:29:26 -0700 Subject: [PATCH 032/113] merge with main --- pyi_hashes.json | 4 ++-- tests/units/components/core/test_upload.py | 3 +-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/pyi_hashes.json b/pyi_hashes.json index 0f54ec2f704..f083347d2e2 100644 --- a/pyi_hashes.json +++ b/pyi_hashes.json @@ -20,7 +20,7 @@ "packages/reflex-components-core/src/reflex_components_core/core/helmet.pyi": "9fa0dea0b63a975dafe2b4c06c5a008c", "packages/reflex-components-core/src/reflex_components_core/core/html.pyi": "f3d8fe298d93bc20aee9d6461f7d5b17", "packages/reflex-components-core/src/reflex_components_core/core/sticky.pyi": "472a83abd9c53f8e89755f095e9bb706", - "packages/reflex-components-core/src/reflex_components_core/core/upload.pyi": "d8b7d57438a0a4558906e5c75a627ce1", + "packages/reflex-components-core/src/reflex_components_core/core/upload.pyi": "ee6fa123b4726b17b581f5887984a65e", "packages/reflex-components-core/src/reflex_components_core/core/window_events.pyi": "22dbc116b98a9e70a36cb1ef846adc62", "packages/reflex-components-core/src/reflex_components_core/datadisplay/__init__.pyi": "73e27e0bfd7e914c4baba7b166e75736", "packages/reflex-components-core/src/reflex_components_core/el/__init__.pyi": "a1d4d65ddc73b5fbba3726f4bcf6d6f0", @@ -118,6 +118,6 @@ "packages/reflex-components-recharts/src/reflex_components_recharts/polar.pyi": "7af2bc6774ef1c5240165a6afff12633", "packages/reflex-components-recharts/src/reflex_components_recharts/recharts.pyi": "83a840eceb0515ce7d27d52644ff60a5", "packages/reflex-components-sonner/src/reflex_components_sonner/toast.pyi": "296cb0d27bc1774f3a330ca9f79fe2bd", - "reflex/__init__.pyi": "22a843faeeedcfcda493b34727b6e957", + "reflex/__init__.pyi": "6868bbf6636ec6d1ac6a5696509b05b1", "reflex/components/__init__.pyi": "d620d52015f908cda828d231c1064236" } diff --git a/tests/units/components/core/test_upload.py b/tests/units/components/core/test_upload.py index d72bef38324..82a03b03c24 100644 --- a/tests/units/components/core/test_upload.py +++ b/tests/units/components/core/test_upload.py @@ -1,8 +1,6 @@ from typing import Any, cast import pytest - -import reflex as rx from reflex_components_core.core.upload import ( StyledUpload, Upload, @@ -12,6 +10,7 @@ get_upload_url, ) +import reflex as rx from reflex import event from reflex.event import EventChain, EventHandler, EventSpec from reflex.state import State From b03c474b33cd7fc06b29451ca1ecf6611e304790 Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Thu, 26 Mar 2026 11:38:29 -0700 Subject: [PATCH 033/113] regenerate pyi hashes --- pyi_hashes.json | 5 +++-- reflex/experimental/memo.py | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/pyi_hashes.json b/pyi_hashes.json index f083347d2e2..e609ff5dbd8 100644 --- a/pyi_hashes.json +++ b/pyi_hashes.json @@ -118,6 +118,7 @@ "packages/reflex-components-recharts/src/reflex_components_recharts/polar.pyi": "7af2bc6774ef1c5240165a6afff12633", "packages/reflex-components-recharts/src/reflex_components_recharts/recharts.pyi": "83a840eceb0515ce7d27d52644ff60a5", "packages/reflex-components-sonner/src/reflex_components_sonner/toast.pyi": "296cb0d27bc1774f3a330ca9f79fe2bd", - "reflex/__init__.pyi": "6868bbf6636ec6d1ac6a5696509b05b1", - "reflex/components/__init__.pyi": "d620d52015f908cda828d231c1064236" + "reflex/__init__.pyi": "66cea31b171fff0049e6f54b03f93469", + "reflex/components/__init__.pyi": "d620d52015f908cda828d231c1064236", + "reflex/experimental/memo.pyi": "72b2c75d02f165dde5a3229210d5545d" } diff --git a/reflex/experimental/memo.py b/reflex/experimental/memo.py index ee1912d1757..97c7ecb1bad 100644 --- a/reflex/experimental/memo.py +++ b/reflex/experimental/memo.py @@ -8,9 +8,10 @@ from functools import cache, update_wrapper from typing import Any, get_args, get_origin, get_type_hints +from reflex_components_core.base.bare import Bare +from reflex_components_core.base.fragment import Fragment + from reflex import constants -from reflex.components.base.bare import Bare -from reflex.components.base.fragment import Fragment from reflex.components.component import Component from reflex.components.dynamic import bundled_libraries from reflex.constants.compiler import SpecialAttributes From aa189418fcef85ca3a2ccab4158c0226c7d97355 Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Thu, 26 Mar 2026 12:35:18 -0700 Subject: [PATCH 034/113] add base --- reflex/base.py | 73 -------------------------------------------------- 1 file changed, 73 deletions(-) delete mode 100644 reflex/base.py diff --git a/reflex/base.py b/reflex/base.py deleted file mode 100644 index a7bd70b3c99..00000000000 --- a/reflex/base.py +++ /dev/null @@ -1,73 +0,0 @@ -"""Define the base Reflex class.""" - -from importlib.util import find_spec - -if find_spec("pydantic") and find_spec("pydantic.v1"): - from pydantic.v1 import BaseModel - - from reflex.utils.compat import ModelMetaclassLazyAnnotations - - class Base(BaseModel, metaclass=ModelMetaclassLazyAnnotations): - """The base class subclassed by all Reflex classes. - - This class wraps Pydantic and provides common methods such as - serialization and setting fields. - - Any data structure that needs to be transferred between the - frontend and backend should subclass this class. - """ - - class Config: - """Pydantic config.""" - - arbitrary_types_allowed = True - use_enum_values = True - extra = "allow" - - def __init_subclass__(cls): - """Warn that rx.Base is deprecated.""" - from reflex.utils import console - - console.deprecate( - feature_name="rx.Base", - reason=f"{cls!r} is subclassing rx.Base. You can subclass from `pydantic.BaseModel` directly instead or use dataclasses if possible.", - deprecation_version="0.8.15", - removal_version="0.9.0", - ) - super().__init_subclass__() - - def json(self) -> str: - """Convert the object to a json string. - - Returns: - The object as a json string. - """ - from reflex.utils.serializers import serialize - - return self.__config__.json_dumps( - self.dict(), - default=serialize, - ) - -else: - - class PydanticNotFoundFallback: - """Fallback base class for environments without Pydantic.""" - - def __init__(self, *args, **kwargs): - """Initialize the base class. - - Args: - *args: Positional arguments. - **kwargs: Keyword arguments. - - Raises: - ImportError: As Pydantic is not installed. - """ - msg = ( - "Pydantic is not installed. Please install it to use rx.Base." - "You can install it with `pip install pydantic`." - ) - raise ImportError(msg) - - Base = PydanticNotFoundFallback # pyright: ignore[reportAssignmentType] From 38c967ccf547a98c4276ef02d07bb9db22360316 Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Thu, 26 Mar 2026 12:35:18 -0700 Subject: [PATCH 035/113] remove base --- pyi_hashes.json | 2 +- pyproject.toml | 4 +- reflex/__init__.py | 1 - reflex/istate/proxy.py | 16 +--- reflex/state.py | 7 +- reflex/utils/compat.py | 26 ------ reflex/utils/pyi_generator.py | 54 +++++++---- reflex/utils/serializers.py | 41 +-------- reflex/utils/types.py | 8 +- reflex/vars/base.py | 25 ----- reflex/vars/object.py | 3 +- reflex/vars/sequence.py | 8 +- tests/integration/test_var_operations.py | 4 +- tests/units/components/core/test_foreach.py | 4 +- tests/units/components/test_component.py | 4 +- .../units/docgen/test_class_and_component.py | 8 +- tests/units/states/mutation.py | 23 ++--- tests/units/test_attribute_access_type.py | 28 +++--- tests/units/test_base.py | 77 ---------------- tests/units/test_state.py | 52 ++--------- tests/units/test_var.py | 8 +- tests/units/utils/test_serializers.py | 2 +- tests/units/vars/test_object.py | 5 +- uv.lock | 91 ++++++++++++++++++- 24 files changed, 195 insertions(+), 306 deletions(-) delete mode 100644 tests/units/test_base.py diff --git a/pyi_hashes.json b/pyi_hashes.json index e609ff5dbd8..9d34f3ba46e 100644 --- a/pyi_hashes.json +++ b/pyi_hashes.json @@ -118,7 +118,7 @@ "packages/reflex-components-recharts/src/reflex_components_recharts/polar.pyi": "7af2bc6774ef1c5240165a6afff12633", "packages/reflex-components-recharts/src/reflex_components_recharts/recharts.pyi": "83a840eceb0515ce7d27d52644ff60a5", "packages/reflex-components-sonner/src/reflex_components_sonner/toast.pyi": "296cb0d27bc1774f3a330ca9f79fe2bd", - "reflex/__init__.pyi": "66cea31b171fff0049e6f54b03f93469", + "reflex/__init__.pyi": "77b2efa084aa8473dce836f78fc4f61a", "reflex/components/__init__.pyi": "d620d52015f908cda828d231c1064236", "reflex/experimental/memo.pyi": "72b2c75d02f165dde5a3229210d5545d" } diff --git a/pyproject.toml b/pyproject.toml index 54419cb0d02..e666a6a9003 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -27,7 +27,7 @@ dependencies = [ "packaging >=24.2,<27", "platformdirs >=4.3.7,<5.0", "psutil >=7.0.0,<8.0; sys_platform == 'win32'", - "pydantic >=1.10.21,<3.0", + "pydantic >=2.12.0,<3.0", "python-multipart >=0.0.20,<1.0", "python-socketio >=5.12.0,<6.0", "redis >=5.2.1,<8.0", @@ -66,7 +66,7 @@ classifiers = [ [project.optional-dependencies] db = [ "alembic >=1.15.2,<2.0", - "pydantic >=1.10.21,<3.0", + "pydantic >=2.12.0,<3.0", "sqlmodel >=0.0.24,<0.1", ] diff --git a/reflex/__init__.py b/reflex/__init__.py index a43df91ce70..a90aa7af250 100644 --- a/reflex/__init__.py +++ b/reflex/__init__.py @@ -266,7 +266,6 @@ "admin": ["AdminDash"], "app": ["App", "UploadFile"], "assets": ["asset"], - "base": ["Base"], "components.component": [ "Component", "NoSSRComponent", diff --git a/reflex/istate/proxy.py b/reflex/istate/proxy.py index 7f9cb78394b..87ed0a14fd7 100644 --- a/reflex/istate/proxy.py +++ b/reflex/istate/proxy.py @@ -17,7 +17,6 @@ import wrapt from typing_extensions import Self -from reflex.base import Base from reflex.event import Event from reflex.utils import prerequisites from reflex.utils.exceptions import ImmutableStateError @@ -354,9 +353,7 @@ def mark_dirty(self): if find_spec("pydantic"): import pydantic - NEVER_WRAP_BASE_ATTRS = set(Base.__dict__) - {"set"} | set( - pydantic.BaseModel.__dict__ - ) + NEVER_WRAP_BASE_ATTRS = set(pydantic.BaseModel.__dict__) else: NEVER_WRAP_BASE_ATTRS = {} @@ -364,7 +361,6 @@ def mark_dirty(self): list, dict, set, - Base, ) if find_spec("sqlalchemy"): @@ -373,10 +369,9 @@ def mark_dirty(self): MUTABLE_TYPES += (DeclarativeBase,) if find_spec("pydantic"): - from pydantic import BaseModel as BaseModelV2 - from pydantic.v1 import BaseModel as BaseModelV1 + from pydantic import BaseModel - MUTABLE_TYPES += (BaseModelV1, BaseModelV2) + MUTABLE_TYPES += (BaseModel,) class MutableProxy(wrapt.ObjectProxy): @@ -577,10 +572,7 @@ def __getattr__(self, __name: str) -> Any: ) if ( - ( - not isinstance(self.__wrapped__, Base) - or __name not in NEVER_WRAP_BASE_ATTRS - ) + __name not in NEVER_WRAP_BASE_ATTRS and (func := getattr(value, "__func__", None)) is not None and not inspect.isclass(getattr(value, "__self__", None)) # skip SQLAlchemy instrumented methods diff --git a/reflex/state.py b/reflex/state.py index affe7324376..7062affeda3 100644 --- a/reflex/state.py +++ b/reflex/state.py @@ -1922,12 +1922,9 @@ async def _process_event( elif dataclasses.is_dataclass(hinted_args): payload[arg] = hinted_args(**value) elif find_spec("pydantic"): - from pydantic import BaseModel as BaseModelV2 - from pydantic.v1 import BaseModel as BaseModelV1 + from pydantic import BaseModel - if issubclass(hinted_args, BaseModelV1): - payload[arg] = hinted_args.parse_obj(value) - elif issubclass(hinted_args, BaseModelV2): + if issubclass(hinted_args, BaseModel): payload[arg] = hinted_args.model_validate(value) elif isinstance(value, list) and (hinted_args is set or hinted_args is set): payload[arg] = set(value) diff --git a/reflex/utils/compat.py b/reflex/utils/compat.py index 7a466aefc4d..cb9f35485a3 100644 --- a/reflex/utils/compat.py +++ b/reflex/utils/compat.py @@ -2,7 +2,6 @@ import sys from collections.abc import Mapping -from importlib.util import find_spec from typing import TYPE_CHECKING, Any if TYPE_CHECKING: @@ -54,31 +53,6 @@ def annotations_from_namespace(namespace: Mapping[str, Any]) -> dict[str, Any]: return namespace.get("__annotations__", {}) -if find_spec("pydantic") and find_spec("pydantic.v1"): - from pydantic.v1.main import ModelMetaclass - - class ModelMetaclassLazyAnnotations(ModelMetaclass): - """Compatibility metaclass to resolve python3.14 style lazy annotations.""" - - def __new__(mcs, name: str, bases: tuple, namespace: dict, **kwargs): - """Resolve python3.14 style lazy annotations before passing off to pydantic v1. - - Args: - name: The class name. - bases: The base classes. - namespace: The class namespace. - **kwargs: Additional keyword arguments. - - Returns: - The created class. - """ - namespace["__annotations__"] = annotations_from_namespace(namespace) - return super().__new__(mcs, name, bases, namespace, **kwargs) - -else: - ModelMetaclassLazyAnnotations = type # type: ignore[assignment] - - def sqlmodel_field_has_primary_key(field_info: "FieldInfo") -> bool: """Determines if a field is a primary. diff --git a/reflex/utils/pyi_generator.py b/reflex/utils/pyi_generator.py index bf5601913eb..5db65f9ee52 100644 --- a/reflex/utils/pyi_generator.py +++ b/reflex/utils/pyi_generator.py @@ -24,9 +24,35 @@ from typing import Any, get_args, get_origin from reflex.components.component import Component -from reflex.utils import types as rx_types from reflex.vars.base import Var + +def _is_union(cls: Any) -> bool: + origin = getattr(cls, "__origin__", None) + if origin is typing.Union: + return True + return origin is None and isinstance(cls, UnionType) + + +def _is_optional(cls: Any) -> bool: + return ( + cls is None + or cls is type(None) + or (_is_union(cls) and type(None) in get_args(cls)) + ) + + +def _is_literal(cls: Any) -> bool: + return getattr(cls, "__origin__", None) is typing.Literal + + +def _safe_issubclass(cls: Any, cls_check: Any | tuple[Any, ...]) -> bool: + try: + return issubclass(cls, cls_check) + except TypeError: + return False + + logger = logging.getLogger("pyi_generator") PWD = Path.cwd() @@ -150,10 +176,10 @@ def _get_type_hint( if value is type(None) or value is None: return "None" - if rx_types.is_union(value): + if _is_union(value): if type(None) in value.__args__: res_args = [ - _get_type_hint(arg, type_hint_globals, rx_types.is_optional(arg)) + _get_type_hint(arg, type_hint_globals, _is_optional(arg)) for arg in value.__args__ if arg is not type(None) ] @@ -164,7 +190,7 @@ def _get_type_hint( return f"{res} | None" res_args = [ - _get_type_hint(arg, type_hint_globals, rx_types.is_optional(arg)) + _get_type_hint(arg, type_hint_globals, _is_optional(arg)) for arg in value.__args__ ] res_args.sort() @@ -173,7 +199,7 @@ def _get_type_hint( if args: inner_container_type_args = ( sorted(repr(arg) for arg in args) - if rx_types.is_literal(value) + if _is_literal(value) else [ _get_type_hint(arg, type_hint_globals, is_optional=False) for arg in args @@ -196,7 +222,7 @@ def _get_type_hint( if value.__name__ == "Var": args = list( chain.from_iterable([ - get_args(arg) if rx_types.is_union(arg) else [arg] for arg in args + get_args(arg) if _is_union(arg) else [arg] for arg in args ]) ) @@ -211,12 +237,12 @@ def _get_type_hint( elif isinstance(value, str): ev = eval(value, type_hint_globals) - if rx_types.is_optional(ev): + if _is_optional(ev): return _get_type_hint(ev, type_hint_globals, is_optional=False) - if rx_types.is_union(ev): + if _is_union(ev): res = [ - _get_type_hint(arg, type_hint_globals, rx_types.is_optional(arg)) + _get_type_hint(arg, type_hint_globals, _is_optional(arg)) for arg in ev.__args__ ] return f"{' | '.join(res)}" @@ -227,8 +253,7 @@ def _get_type_hint( ) elif isinstance(value, list): res = [ - _get_type_hint(arg, type_hint_globals, rx_types.is_optional(arg)) - for arg in value + _get_type_hint(arg, type_hint_globals, _is_optional(arg)) for arg in value ] return f"[{', '.join(res)}]" else: @@ -876,7 +901,7 @@ def _generate_staticmethod_call_functiondef( id=_get_type_hint( anno := fullspec.annotations[name], type_hint_globals, - is_optional=rx_types.is_optional(anno), + is_optional=_is_optional(anno), ) ), ) @@ -1417,10 +1442,7 @@ def _scan_file(module_path: Path) -> tuple[str, str] | None: name: obj for name, obj in vars(module).items() if isinstance(obj, type) - and ( - rx_types.safe_issubclass(obj, Component) - or rx_types.safe_issubclass(obj, SimpleNamespace) - ) + and (_safe_issubclass(obj, Component) or _safe_issubclass(obj, SimpleNamespace)) and obj != Component and inspect.getmodule(obj) == module } diff --git a/reflex/utils/serializers.py b/reflex/utils/serializers.py index 9693601ec5d..25aca8a5dab 100644 --- a/reflex/utils/serializers.py +++ b/reflex/utils/serializers.py @@ -17,7 +17,6 @@ from typing import Any, Literal, TypeVar, get_type_hints, overload from uuid import UUID -from reflex.base import Base from reflex.constants.colors import Color from reflex.utils import console, types @@ -262,30 +261,12 @@ def serialize_type(value: type) -> str: return value.__name__ -@serializer(to=dict) -def serialize_base(value: Base) -> dict: - """Serialize a Base instance. - - Args: - value : The Base to serialize. - - Returns: - The serialized Base. - """ - from reflex.vars.base import Var - - return { - k: v for k, v in value.dict().items() if isinstance(v, Var) or not callable(v) - } - - if find_spec("pydantic"): - from pydantic import BaseModel as BaseModelV2 - from pydantic.v1 import BaseModel as BaseModelV1 + from pydantic import BaseModel @serializer(to=dict) - def serialize_base_model_v1(model: BaseModelV1) -> dict: - """Serialize a pydantic v1 BaseModel instance. + def serialize_base_model(model: BaseModel) -> dict: + """Serialize a pydantic v2 BaseModel instance. Args: model: The BaseModel to serialize. @@ -293,21 +274,7 @@ def serialize_base_model_v1(model: BaseModelV1) -> dict: Returns: The serialized BaseModel. """ - return model.dict() - - if BaseModelV1 is not BaseModelV2: - - @serializer(to=dict) - def serialize_base_model_v2(model: BaseModelV2) -> dict: - """Serialize a pydantic v2 BaseModel instance. - - Args: - model: The BaseModel to serialize. - - Returns: - The serialized BaseModel. - """ - return model.model_dump() + return model.model_dump() @serializer diff --git a/reflex/utils/types.py b/reflex/utils/types.py index 0005f3fb7fd..f7dea4e2983 100644 --- a/reflex/utils/types.py +++ b/reflex/utils/types.py @@ -40,7 +40,6 @@ import reflex from reflex import constants -from reflex.base import Base from reflex.utils import console # Potential GenericAlias types for isinstance checks. @@ -54,7 +53,7 @@ # Valid state var types. PrimitiveTypes = (int, float, bool, str, list, dict, set, tuple) -StateVarTypes = (*PrimitiveTypes, Base, type(None)) +StateVarTypes = (*PrimitiveTypes, type(None)) if TYPE_CHECKING: from reflex.state import BaseState @@ -503,11 +502,6 @@ def get_attribute_access_type(cls: GenericType, name: str) -> GenericType | None type_origin = get_origin(type_) if isinstance(type_origin, type) and issubclass(type_origin, Mapped): return get_args(type_)[0] # SQLAlchemy v2 - if find_spec("pydantic"): - from pydantic.v1.fields import ModelField - - if isinstance(type_, ModelField): - return type_.type_ # SQLAlchemy v1.4 return type_ if is_union(cls): # Check in each arg of the annotation. diff --git a/reflex/vars/base.py b/reflex/vars/base.py index 9f51b7c4d90..0c86f4b93d4 100644 --- a/reflex/vars/base.py +++ b/reflex/vars/base.py @@ -851,9 +851,6 @@ def guess_type(self: Var[bool]) -> BooleanVar: ... @overload def guess_type(self: Var[int] | Var[float] | Var[int | float]) -> NumberVar: ... - @overload - def guess_type(self: Var[BASE_TYPE]) -> ObjectVar[BASE_TYPE]: ... - @overload def guess_type(self) -> Self: ... @@ -2327,13 +2324,6 @@ def __get__( 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], @@ -2600,13 +2590,6 @@ def __get__( 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], @@ -3269,10 +3252,7 @@ def dispatch( from _typeshed import DataclassInstance from sqlalchemy.orm import DeclarativeBase - from reflex.base import Base - SQLA_TYPE = TypeVar("SQLA_TYPE", bound=DeclarativeBase | None) - BASE_TYPE = TypeVar("BASE_TYPE", bound=Base | None) DATACLASS_TYPE = TypeVar("DATACLASS_TYPE", bound=DataclassInstance | None) MAPPING_TYPE = TypeVar("MAPPING_TYPE", bound=Mapping | None) V = TypeVar("V") @@ -3432,11 +3412,6 @@ def __get__( owner: Any, ) -> ObjectVar[MAPPING_TYPE]: ... - @overload - def __get__( - self: Field[BASE_TYPE] | Field[BASE_TYPE | None], instance: None, owner: Any - ) -> ObjectVar[BASE_TYPE]: ... - @overload def __get__( self: Field[SQLA_TYPE] | Field[SQLA_TYPE | None], instance: None, owner: Any diff --git a/reflex/vars/object.py b/reflex/vars/object.py index d8e21b642db..af5efc9bc7a 100644 --- a/reflex/vars/object.py +++ b/reflex/vars/object.py @@ -76,9 +76,8 @@ def _determine_value_type(var_type: GenericType): PYTHON_TYPES = (Mapping,) if find_spec("pydantic"): import pydantic - import pydantic.v1 - PYTHON_TYPES += (pydantic.BaseModel, pydantic.v1.BaseModel) + PYTHON_TYPES += (pydantic.BaseModel,) class ObjectVar(Var[OBJECT_TYPE], python_types=PYTHON_TYPES): diff --git a/reflex/vars/sequence.py b/reflex/vars/sequence.py index 3824ba8760b..c0475a9b00f 100644 --- a/reflex/vars/sequence.py +++ b/reflex/vars/sequence.py @@ -41,7 +41,7 @@ ) if TYPE_CHECKING: - from .base import BASE_TYPE, DATACLASS_TYPE, SQLA_TYPE + from .base import DATACLASS_TYPE, SQLA_TYPE from .function import FunctionVar from .object import ObjectVar @@ -189,12 +189,6 @@ def __getitem__( i: int | NumberVar, ) -> ObjectVar[MAPPING_VAR_TYPE]: ... - @overload - def __getitem__( - self: ArrayVar[Sequence[BASE_TYPE]], - i: int | NumberVar, - ) -> ObjectVar[BASE_TYPE]: ... - @overload def __getitem__( self: ArrayVar[Sequence[SQLA_TYPE]], diff --git a/tests/integration/test_var_operations.py b/tests/integration/test_var_operations.py index f07ddc75295..4988e2e7dc1 100644 --- a/tests/integration/test_var_operations.py +++ b/tests/integration/test_var_operations.py @@ -12,11 +12,13 @@ def VarOperations(): """App with var operations.""" from typing import TypedDict + import pydantic + import reflex as rx from reflex.vars.base import LiteralVar from reflex.vars.sequence import ArrayVar - class Object(rx.Base): + class Object(pydantic.BaseModel): name: str = "hello" optional_none: str | None = None optional_str: str | None = "hello" diff --git a/tests/units/components/core/test_foreach.py b/tests/units/components/core/test_foreach.py index ee64712cc0b..af070f6d1ad 100644 --- a/tests/units/components/core/test_foreach.py +++ b/tests/units/components/core/test_foreach.py @@ -1,3 +1,4 @@ +import pydantic import pytest from reflex_components_core.core.foreach import ( Foreach, @@ -10,7 +11,6 @@ import reflex as rx from reflex import el -from reflex.base import Base from reflex.components.component import Component from reflex.constants.state import FIELD_MARKER from reflex.state import BaseState, ComponentState @@ -18,7 +18,7 @@ from reflex.vars.sequence import ArrayVar -class ForEachTag(Base): +class ForEachTag(pydantic.BaseModel): """A tag for testing the ForEach component.""" name: str = "" diff --git a/tests/units/components/test_component.py b/tests/units/components/test_component.py index 69c2ff2d169..97b189f7d51 100644 --- a/tests/units/components/test_component.py +++ b/tests/units/components/test_component.py @@ -1,13 +1,13 @@ from contextlib import nullcontext from typing import Any, ClassVar +import pydantic import pytest from reflex_components_core.base.bare import Bare from reflex_components_core.base.fragment import Fragment from reflex_components_radix.themes.layout.box import Box import reflex as rx -from reflex.base import Base from reflex.compiler.utils import compile_custom_component from reflex.components.component import ( CUSTOM_COMPONENTS, @@ -792,7 +792,7 @@ def test_component_create_unpack_tuple_child(test_component, element, expected): assert fragment_wrapper.render() == expected -class _Obj(Base): +class _Obj(pydantic.BaseModel): custom: int = 0 diff --git a/tests/units/docgen/test_class_and_component.py b/tests/units/docgen/test_class_and_component.py index 3d2adbf38bc..09536f707cb 100644 --- a/tests/units/docgen/test_class_and_component.py +++ b/tests/units/docgen/test_class_and_component.py @@ -262,11 +262,11 @@ def test_rx_state_fields(): def test_class_with_class_vars(): """Class variables are extracted from __class_vars__.""" - from reflex.base import Base + from pydantic import BaseModel - # Base has __class_vars__ (from pydantic v1) - doc = generate_class_documentation(Base) - # Just verify the attribute is populated (may be empty if Base has no class vars) + # BaseModel has __class_vars__ (from pydantic) + doc = generate_class_documentation(BaseModel) + # Just verify the attribute is populated (may be empty if BaseModel has no class vars) assert isinstance(doc.class_fields, tuple) diff --git a/tests/units/states/mutation.py b/tests/units/states/mutation.py index 4075f192d4d..74f2434d7c3 100644 --- a/tests/units/states/mutation.py +++ b/tests/units/states/mutation.py @@ -1,23 +1,24 @@ """Test states for mutable vars.""" -import reflex as rx +import pydantic + from reflex.state import BaseState -class OtherBase(rx.Base): - """A Base model with a str field.""" +class OtherBase(pydantic.BaseModel): + """A BaseModel with a str field.""" - bar: str = "" + bar: str = pydantic.Field(default="") -class CustomVar(rx.Base): - """A Base model with multiple fields.""" +class CustomVar(pydantic.BaseModel): + """A BaseModel with multiple fields.""" - foo: str = "" - array: list[str] = [] - hashmap: dict[str, str] = {} - test_set: set[str] = set() - custom: OtherBase = OtherBase() + foo: str = pydantic.Field(default="") + array: list[str] = pydantic.Field(default_factory=list) + hashmap: dict[str, str] = pydantic.Field(default_factory=dict) + test_set: set[str] = pydantic.Field(default_factory=set) + custom: OtherBase = pydantic.Field(default_factory=OtherBase) class MutableTestState(BaseState): diff --git a/tests/units/test_attribute_access_type.py b/tests/units/test_attribute_access_type.py index dd81961ef4d..b8dd4431b2a 100644 --- a/tests/units/test_attribute_access_type.py +++ b/tests/units/test_attribute_access_type.py @@ -12,7 +12,7 @@ pytest.importorskip("sqlmodel") pytest.importorskip("pydantic") -import pydantic.v1 +import pydantic import sqlalchemy import sqlmodel from sqlalchemy import JSON, TypeDecorator @@ -217,19 +217,21 @@ def first_label(self) -> SQLALabel | None: return self.labels[0] if self.labels else None -class BaseClass(rx.Base): - """Test rx.Base class.""" +class BaseClass(pydantic.BaseModel): + """Test pydantic BaseModel class.""" - no_default: int | None = pydantic.v1.Field(required=False) - count: int = 0 - name: str = "test" - int_list: list[int] = [] - str_list: list[str] = [] - optional_int: int | None = None - sqla_tag: SQLATag | None = None - labels: list[SQLALabel] = [] - dict_str_str: dict[str, str] = {} - default_factory: list[int] = pydantic.v1.Field(default_factory=list) + model_config = pydantic.ConfigDict(arbitrary_types_allowed=True) + + no_default: int | None = pydantic.Field(default=None) + count: int = pydantic.Field(default=0) + name: str = pydantic.Field(default="test") + int_list: list[int] = pydantic.Field(default_factory=list) + str_list: list[str] = pydantic.Field(default_factory=list) + optional_int: int | None = pydantic.Field(default=None) + sqla_tag: SQLATag | None = pydantic.Field(default=None) + labels: list[SQLALabel] = pydantic.Field(default_factory=list) + dict_str_str: dict[str, str] = pydantic.Field(default_factory=dict) + default_factory: list[int] = pydantic.Field(default_factory=list) @property def str_property(self) -> str: diff --git a/tests/units/test_base.py b/tests/units/test_base.py deleted file mode 100644 index 796fa9fd86d..00000000000 --- a/tests/units/test_base.py +++ /dev/null @@ -1,77 +0,0 @@ -import pytest - -from reflex.base import Base - -pytest.importorskip("pydantic") - - -@pytest.fixture -def child() -> Base: - """A child class. - - Returns: - A child class. - """ - - class Child(Base): - num: float - key: str - - return Child(num=3.15, key="pi") - - -def test_get_fields(child): - """Test that the fields are set correctly. - - Args: - child: A child class. - """ - assert child.__fields__.keys() == {"num", "key"} - - -def test_json(child): - """Test converting to json. - - Args: - child: A child class. - """ - assert child.json().replace(" ", "") == '{"num":3.15,"key":"pi"}' - - -@pytest.fixture -def complex_child() -> Base: - """A child class. - - Returns: - A child class. - """ - - class Child(Base): - num: float - key: str - name: str - age: int - active: bool - - return Child(num=3.15, key="pi", name="John Doe", age=30, active=True) - - -def test_complex_get_fields(complex_child): - """Test that the fields are set correctly. - - Args: - complex_child: A child class. - """ - assert complex_child.__fields__.keys() == {"num", "key", "name", "age", "active"} - - -def test_complex_json(complex_child): - """Test converting to json. - - Args: - complex_child: A child class. - """ - assert ( - complex_child.json().replace(" ", "") - == '{"num":3.15,"key":"pi","name":"JohnDoe","age":30,"active":true}' - ) diff --git a/tests/units/test_state.py b/tests/units/test_state.py index d02940d24bd..7be18207f6a 100644 --- a/tests/units/test_state.py +++ b/tests/units/test_state.py @@ -18,13 +18,13 @@ import pytest import pytest_asyncio from plotly.graph_objects import Figure +from pydantic import BaseModel as Base from pytest_mock import MockerFixture import reflex as rx import reflex.config from reflex import constants from reflex.app import App -from reflex.base import Base from reflex.constants import CompileVars, RouteVar, SocketEvent from reflex.constants.state import FIELD_MARKER from reflex.environment import environment @@ -66,8 +66,7 @@ pytest.importorskip("pydantic") -from pydantic import BaseModel as BaseModelV2 -from pydantic.v1 import BaseModel as BaseModelV1 +from pydantic import BaseModel from tests.units.states.mutation import MutableTestState @@ -3861,7 +3860,7 @@ class Child(State): class Obj(Base): """A object containing a callable for testing fallback pickle.""" - _f: Callable + f: Callable def test_fallback_pickle(): @@ -3873,7 +3872,7 @@ class DillState(BaseState): _g: Any = None state = DillState(_reflex_internal_init=True) # pyright: ignore [reportCallIssue] - state._o = Obj(_f=lambda: 42) + state._o = Obj(f=lambda: 42) state._f = lambda: 420 pk = state._serialize() @@ -3883,7 +3882,7 @@ class DillState(BaseState): assert unpickled_state._f is not None assert unpickled_state._f() == 420 assert unpickled_state._o is not None - assert unpickled_state._o._f() == 42 + assert unpickled_state._o.f() == 42 # Threading locks are unpicklable normally, and raise TypeError instead of PicklingError. state2 = DillState(_reflex_internal_init=True) # pyright: ignore [reportCallIssue] @@ -3908,29 +3907,7 @@ class TypedState(rx.State): _ = TypedState(field="str") -class ModelV1(BaseModelV1): - """A pydantic BaseModel v1.""" - - foo: str = "bar" - - def set_foo(self, val: str): - """Set the attribute foo. - - Args: - val: The value to set. - """ - self.foo = val - - def double_foo(self) -> str: - """Concatenate foo with foo. - - Returns: - foo + foo - """ - return self.foo + self.foo - - -class ModelV2(BaseModelV2): +class ModelV2(BaseModel): """A pydantic BaseModel v2.""" foo: str = "bar" @@ -3955,7 +3932,6 @@ def double_foo(self) -> str: class PydanticState(rx.State): """A state with pydantic BaseModel vars.""" - v1: ModelV1 = ModelV1() v2: ModelV2 = ModelV2() dc: ModelDC = ModelDC() @@ -3963,17 +3939,6 @@ class PydanticState(rx.State): def test_mutable_models(): """Test that dataclass and pydantic BaseModel v1 and v2 use dep tracking.""" state = PydanticState() - assert isinstance(state.v1, MutableProxy) - state.v1.foo = "baz" - assert state.dirty_vars == {"v1"} - state.dirty_vars.clear() - state.v1.set_foo("quuc") - assert state.dirty_vars == {"v1"} - state.dirty_vars.clear() - assert state.v1.double_foo() == "quucquuc" - assert state.dirty_vars == set() - state.v1.copy(update={"foo": "larp"}) - assert state.dirty_vars == set() assert isinstance(state.v2, MutableProxy) state.v2.foo = "baz" @@ -4144,10 +4109,6 @@ def rx_base_or_none(self, o: Object | None): # noqa: D102 assert isinstance(o, Object) self.passed = True - def rx_basemodelv1(self, m: ModelV1): # noqa: D102 - assert isinstance(m, ModelV1) - self.passed = True - def rx_basemodelv2(self, m: ModelV2): # noqa: D102 assert isinstance(m, ModelV2) self.passed = True @@ -4197,7 +4158,6 @@ def py_unresolvable(self, u: Unresolvable): # noqa: D102, F821 # pyright: ignor (UpcastState.rx_base, {"o": {"foo": "bar"}}), (UpcastState.rx_base_or_none, {"o": {"foo": "bar"}}), (UpcastState.rx_base_or_none, {"o": None}), - (UpcastState.rx_basemodelv1, {"m": {"foo": "bar"}}), (UpcastState.rx_basemodelv2, {"m": {"foo": "bar"}}), (UpcastState.rx_dataclass, {"dc": {"foo": "bar"}}), (UpcastState.py_set, {"s": ["foo", "foo"]}), diff --git a/tests/units/test_var.py b/tests/units/test_var.py index f1c1da56b52..96205cdb83a 100644 --- a/tests/units/test_var.py +++ b/tests/units/test_var.py @@ -7,10 +7,10 @@ import pytest from pandas import DataFrame +from pydantic import BaseModel as Base from pytest_mock import MockerFixture import reflex as rx -from reflex.base import Base from reflex.constants.base import REFLEX_VAR_CLOSING_TAG, REFLEX_VAR_OPENING_TAG from reflex.constants.state import FIELD_MARKER from reflex.environment import PerformanceMode @@ -392,14 +392,14 @@ def test_list_tuple_contains(var, expected): assert str(var.contains(other_var)) == f"{expected}.includes(other)" -class Foo(rx.Base): +class Foo(Base): """Foo class.""" bar: int baz: str -class Bar(rx.Base): +class Bar(Base): """Bar class.""" bar: str @@ -1177,7 +1177,7 @@ class Foo(Base): baz: int parent_obj = LiteralObjectVar.create( - Foo(bar=Boo(foo="bar", bar=5), baz=5).dict(), Foo + Foo(bar=Boo(foo="bar", bar=5), baz=5).model_dump(), Foo ) assert ( diff --git a/tests/units/utils/test_serializers.py b/tests/units/utils/test_serializers.py index c3a01bae508..1a39bcb1811 100644 --- a/tests/units/utils/test_serializers.py +++ b/tests/units/utils/test_serializers.py @@ -6,9 +6,9 @@ from typing import Any import pytest +from pydantic import BaseModel as Base from reflex_components_core.core.colors import Color -from reflex.base import Base from reflex.utils import serializers from reflex.utils.format import json_dumps from reflex.vars.base import LiteralVar diff --git a/tests/units/vars/test_object.py b/tests/units/vars/test_object.py index 817c6e786ba..40ee1f17e4a 100644 --- a/tests/units/vars/test_object.py +++ b/tests/units/vars/test_object.py @@ -1,6 +1,7 @@ import dataclasses from collections.abc import Sequence +import pydantic import pytest from typing_extensions import assert_type @@ -35,8 +36,8 @@ def serialize_bare(obj: Bare) -> dict: return {"quantity": obj.quantity} -class Base(rx.Base): - """A reflex base class with a single attribute.""" +class Base(pydantic.BaseModel): + """A pydantic BaseModel class with a single attribute.""" quantity: int = 0 diff --git a/uv.lock b/uv.lock index ce618c353e8..fb7f48721ae 100644 --- a/uv.lock +++ b/uv.lock @@ -13,6 +13,7 @@ resolution-markers = [ [manifest] members = [ + "hatch-reflex-pyi", "reflex", "reflex-components-code", "reflex-components-core", @@ -702,6 +703,16 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" }, ] +[[package]] +name = "hatch-reflex-pyi" +source = { editable = "packages/hatch-reflex-pyi" } +dependencies = [ + { name = "hatchling" }, +] + +[package.metadata] +requires-dist = [{ name = "hatchling" }] + [[package]] name = "hatchling" version = "1.29.0" @@ -2128,8 +2139,8 @@ requires-dist = [ { name = "packaging", specifier = ">=24.2,<27" }, { name = "platformdirs", specifier = ">=4.3.7,<5.0" }, { name = "psutil", marker = "sys_platform == 'win32'", specifier = ">=7.0.0,<8.0" }, - { name = "pydantic", specifier = ">=1.10.21,<3.0" }, - { name = "pydantic", marker = "extra == 'db'", specifier = ">=1.10.21,<3.0" }, + { name = "pydantic", specifier = ">=2.12.0,<3.0" }, + { name = "pydantic", marker = "extra == 'db'", specifier = ">=2.12.0,<3.0" }, { name = "python-multipart", specifier = ">=0.0.20,<1.0" }, { name = "python-socketio", specifier = ">=5.12.0,<6.0" }, { name = "redis", specifier = ">=5.2.1,<8.0" }, @@ -2194,14 +2205,42 @@ dev = [ [[package]] name = "reflex-components-code" source = { editable = "packages/reflex-components-code" } +dependencies = [ + { name = "reflex-components-core" }, + { name = "reflex-components-lucide" }, + { name = "reflex-components-radix" }, +] + +[package.metadata] +requires-dist = [ + { name = "reflex-components-core", editable = "packages/reflex-components-core" }, + { name = "reflex-components-lucide", editable = "packages/reflex-components-lucide" }, + { name = "reflex-components-radix", editable = "packages/reflex-components-radix" }, +] [[package]] name = "reflex-components-core" source = { editable = "packages/reflex-components-core" } +dependencies = [ + { name = "reflex-components-lucide" }, + { name = "reflex-components-sonner" }, +] + +[package.metadata] +requires-dist = [ + { name = "reflex-components-lucide", editable = "packages/reflex-components-lucide" }, + { name = "reflex-components-sonner", editable = "packages/reflex-components-sonner" }, +] [[package]] name = "reflex-components-dataeditor" source = { editable = "packages/reflex-components-dataeditor" } +dependencies = [ + { name = "reflex-components-core" }, +] + +[package.metadata] +requires-dist = [{ name = "reflex-components-core", editable = "packages/reflex-components-core" }] [[package]] name = "reflex-components-gridjs" @@ -2214,6 +2253,18 @@ source = { editable = "packages/reflex-components-lucide" } [[package]] name = "reflex-components-markdown" source = { editable = "packages/reflex-components-markdown" } +dependencies = [ + { name = "reflex-components-code" }, + { name = "reflex-components-core" }, + { name = "reflex-components-radix" }, +] + +[package.metadata] +requires-dist = [ + { name = "reflex-components-code", editable = "packages/reflex-components-code" }, + { name = "reflex-components-core", editable = "packages/reflex-components-core" }, + { name = "reflex-components-radix", editable = "packages/reflex-components-radix" }, +] [[package]] name = "reflex-components-moment" @@ -2222,18 +2273,48 @@ source = { editable = "packages/reflex-components-moment" } [[package]] name = "reflex-components-plotly" source = { editable = "packages/reflex-components-plotly" } +dependencies = [ + { name = "reflex-components-core" }, +] + +[package.metadata] +requires-dist = [{ name = "reflex-components-core", editable = "packages/reflex-components-core" }] [[package]] name = "reflex-components-radix" source = { editable = "packages/reflex-components-radix" } +dependencies = [ + { name = "reflex-components-core" }, + { name = "reflex-components-lucide" }, + { name = "reflex-components-react-router" }, +] + +[package.metadata] +requires-dist = [ + { name = "reflex-components-core", editable = "packages/reflex-components-core" }, + { name = "reflex-components-lucide", editable = "packages/reflex-components-lucide" }, + { name = "reflex-components-react-router", editable = "packages/reflex-components-react-router" }, +] [[package]] name = "reflex-components-react-player" source = { editable = "packages/reflex-components-react-player" } +dependencies = [ + { name = "reflex-components-core" }, +] + +[package.metadata] +requires-dist = [{ name = "reflex-components-core", editable = "packages/reflex-components-core" }] [[package]] name = "reflex-components-react-router" source = { editable = "packages/reflex-components-react-router" } +dependencies = [ + { name = "reflex-components-core" }, +] + +[package.metadata] +requires-dist = [{ name = "reflex-components-core", editable = "packages/reflex-components-core" }] [[package]] name = "reflex-components-recharts" @@ -2242,6 +2323,12 @@ source = { editable = "packages/reflex-components-recharts" } [[package]] name = "reflex-components-sonner" source = { editable = "packages/reflex-components-sonner" } +dependencies = [ + { name = "reflex-components-lucide" }, +] + +[package.metadata] +requires-dist = [{ name = "reflex-components-lucide", editable = "packages/reflex-components-lucide" }] [[package]] name = "reflex-docgen" From 23e1ced08e0cd0da5de576776388cf46cb3427a2 Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Thu, 26 Mar 2026 13:01:33 -0700 Subject: [PATCH 036/113] ah did this guy wrong --- reflex/istate/proxy.py | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/reflex/istate/proxy.py b/reflex/istate/proxy.py index 87ed0a14fd7..91074356ef3 100644 --- a/reflex/istate/proxy.py +++ b/reflex/istate/proxy.py @@ -350,13 +350,6 @@ def mark_dirty(self): raise NotImplementedError(msg) -if find_spec("pydantic"): - import pydantic - - NEVER_WRAP_BASE_ATTRS = set(pydantic.BaseModel.__dict__) -else: - NEVER_WRAP_BASE_ATTRS = {} - MUTABLE_TYPES = ( list, dict, @@ -572,8 +565,7 @@ def __getattr__(self, __name: str) -> Any: ) if ( - __name not in NEVER_WRAP_BASE_ATTRS - and (func := getattr(value, "__func__", None)) is not None + (func := getattr(value, "__func__", None)) is not None and not inspect.isclass(getattr(value, "__self__", None)) # skip SQLAlchemy instrumented methods and not getattr(value, "_sa_instrumented", False) From 3f41cda1775a7d06c964de5e0f63279d3839a021 Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Thu, 26 Mar 2026 13:17:05 -0700 Subject: [PATCH 037/113] remove breakpoints from types --- .../themes/components/radio_group.py | 3 +- reflex/utils/types.py | 85 +------------------ tests/units/utils/test_types.py | 2 +- tests/units/utils/test_utils.py | 6 +- 4 files changed, 7 insertions(+), 89 deletions(-) diff --git a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/radio_group.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/radio_group.py index 4137e813878..7bffcd12bfe 100644 --- a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/radio_group.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/radio_group.py @@ -159,7 +159,8 @@ def create( default_value = props.pop("default_value", "") if not isinstance(items, (list, Var)) or ( - isinstance(items, Var) and not types._issubclass(items._var_type, list) + isinstance(items, Var) + and not types.typehint_issubclass(items._var_type, list) ): items_type = type(items) if not isinstance(items, Var) else items._var_type msg = f"The radio group component takes in a list, got {items_type} instead" diff --git a/reflex/utils/types.py b/reflex/utils/types.py index f7dea4e2983..ba67b9c81b9 100644 --- a/reflex/utils/types.py +++ b/reflex/utils/types.py @@ -34,11 +34,9 @@ from typing import get_origin as get_origin_og from typing import get_type_hints as get_type_hints_og -from reflex_components_core.core.breakpoints import Breakpoints from typing_extensions import Self as Self from typing_extensions import override as override -import reflex from reflex import constants from reflex.utils import console @@ -547,87 +545,6 @@ def get_base_class(cls: GenericType) -> type: return get_base_class(cls.__origin__) if is_generic_alias(cls) else cls -def _breakpoints_satisfies_typing(cls_check: GenericType, instance: Any) -> bool: - """Check if the breakpoints instance satisfies the typing. - - Args: - cls_check: The class to check against. - instance: The instance to check. - - Returns: - Whether the breakpoints instance satisfies the typing. - """ - cls_check_base = get_base_class(cls_check) - - if cls_check_base == Breakpoints: - _, expected_type = get_args(cls_check) - if is_literal(expected_type): - for value in instance.values(): - if not isinstance(value, str) or value not in get_args(expected_type): - return False - return True - if isinstance(cls_check_base, tuple): - # union type, so check all types - return any( - _breakpoints_satisfies_typing(type_to_check, instance) - for type_to_check in get_args(cls_check) - ) - if cls_check_base == reflex.vars.Var and "__args__" in cls_check.__dict__: - return _breakpoints_satisfies_typing(get_args(cls_check)[0], instance) - - return False - - -def _issubclass(cls: GenericType, cls_check: GenericType, instance: Any = None) -> bool: - """Check if a class is a subclass of another class. - - Args: - cls: The class to check. - cls_check: The class to check against. - instance: An instance of cls to aid in checking generics. - - Returns: - Whether the class is a subclass of the other class. - - Raises: - TypeError: If the base class is not valid for issubclass. - """ - # Special check for Any. - if cls_check == Any: - return True - if cls in [Any, Callable, None]: - return False - - # Get the base classes. - cls_base = get_base_class(cls) - cls_check_base = get_base_class(cls_check) - - # The class we're checking should not be a union. - if isinstance(cls_base, tuple): - return False - - # Check that fields of breakpoints match the expected values. - if isinstance(instance, Breakpoints): - return _breakpoints_satisfies_typing(cls_check, instance) - - if isinstance(cls_check_base, tuple): - cls_check_base = tuple( - cls_check_one if not is_typeddict(cls_check_one) else dict - for cls_check_one in cls_check_base - ) - if is_typeddict(cls_check_base): - cls_check_base = dict - - # Check if the types match. - try: - return cls_check_base == Any or issubclass(cls_base, cls_check_base) - except TypeError as te: - # These errors typically arise from bad annotations and are hard to - # debug without knowing the type that we tried to compare. - msg = f"Invalid type for issubclass: {cls_base}" - raise TypeError(msg) from te - - def does_obj_satisfy_typed_dict( obj: Any, cls: GenericType, @@ -815,7 +732,7 @@ def _isinstance( for item, arg in zip(obj, args, strict=True) ) ) - if origin in (dict, Mapping, Breakpoints): + if origin is dict or safe_issubclass(origin, Mapping): expected_class = ( dict if origin is dict and not treat_mutable_obj_as_immutable diff --git a/tests/units/utils/test_types.py b/tests/units/utils/test_types.py index bf1a77c967b..c92f1156c5f 100644 --- a/tests/units/utils/test_types.py +++ b/tests/units/utils/test_types.py @@ -45,7 +45,7 @@ def test_validate_literal_error_msg(params, allowed_value_str, value_str): def test_issubclass( cls: types.GenericType, cls_check: types.GenericType, expected: bool ) -> None: - assert types._issubclass(cls, cls_check) == expected + assert types.typehint_issubclass(cls, cls_check) == expected class CustomDict(dict[str, str]): diff --git a/tests/units/utils/test_utils.py b/tests/units/utils/test_utils.py index c1dfc37190a..a5176bc4c7b 100644 --- a/tests/units/utils/test_utils.py +++ b/tests/units/utils/test_utils.py @@ -283,8 +283,8 @@ def test_is_backend_base_variable( (float, int | float, True), (str, int | float, False), (list[int], list[int], True), - (list[int], list[float], True), - (int | float, int | float, False), + (list[int], list[float], False), + (int | float, int | float, True), (int | Var[int], Var[int], False), (int, Any, True), (Any, Any, True), @@ -296,7 +296,7 @@ def test_is_backend_base_variable( ], ) def test_issubclass(cls: type, cls_check: type, expected: bool): - assert types._issubclass(cls, cls_check) == expected + assert types.typehint_issubclass(cls, cls_check) == expected @pytest.mark.parametrize("cls", [Literal["test", 1], Literal[1, "test"]]) From bbd781ce028a97bedd7c45742b378bdc6b7961f1 Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Thu, 26 Mar 2026 13:18:17 -0700 Subject: [PATCH 038/113] delete that since that works --- reflex/utils/types.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/reflex/utils/types.py b/reflex/utils/types.py index ba67b9c81b9..2c82d6929fb 100644 --- a/reflex/utils/types.py +++ b/reflex/utils/types.py @@ -732,7 +732,7 @@ def _isinstance( for item, arg in zip(obj, args, strict=True) ) ) - if origin is dict or safe_issubclass(origin, Mapping): + if safe_issubclass(origin, Mapping): expected_class = ( dict if origin is dict and not treat_mutable_obj_as_immutable From 03e9063539b46f721014a85c08599aee513b2737 Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Thu, 26 Mar 2026 13:33:01 -0700 Subject: [PATCH 039/113] move var into reflex-core --- packages/hatch-reflex-pyi/pyproject.toml | 1 + .../src/hatch_reflex_pyi/plugin.py | 6 +- packages/reflex-core/README.md | 3 + packages/reflex-core/pyproject.toml | 25 + .../reflex-core/src/reflex_core/__init__.py | 1 + .../src/reflex_core/constants/__init__.py | 19 + .../src/reflex_core/constants/base.py | 278 ++ .../src/reflex_core/constants/colors.py | 99 + .../src/reflex_core/constants/compiler.py | 210 + .../src/reflex_core/constants/state.py | 19 + .../src/reflex_core/utils/__init__.py | 1 + .../src/reflex_core/utils/compat.py | 69 + .../src/reflex_core/utils/console.py | 473 +++ .../src/reflex_core/utils/decorator.py | 148 + .../src/reflex_core/utils/exceptions.py | 286 ++ .../src/reflex_core/utils/format.py | 774 ++++ .../src/reflex_core/utils/imports.py | 150 + .../src/reflex_core/utils/pyi_generator.py | 1652 ++++++++ .../src/reflex_core/utils/serializers.py | 498 +++ .../src/reflex_core/utils/types.py | 1197 ++++++ .../src/reflex_core/vars/__init__.py | 59 + .../reflex-core/src/reflex_core/vars/base.py | 3692 ++++++++++++++++ .../reflex-core/src/reflex_core/vars/color.py | 166 + .../src/reflex_core/vars/datetime.py | 202 + .../src/reflex_core/vars/dep_tracking.py | 485 +++ .../src/reflex_core/vars/function.py | 555 +++ .../src/reflex_core/vars/number.py | 1148 +++++ .../src/reflex_core/vars/object.py | 647 +++ .../src/reflex_core/vars/sequence.py | 1841 ++++++++ pyproject.toml | 2 + reflex/constants/base.py | 279 +- reflex/constants/colors.py | 100 +- reflex/constants/compiler.py | 211 +- reflex/constants/state.py | 20 +- reflex/utils/compat.py | 70 +- reflex/utils/console.py | 474 +-- reflex/utils/decorator.py | 149 +- reflex/utils/exceptions.py | 287 +- reflex/utils/format.py | 775 +--- reflex/utils/imports.py | 151 +- reflex/utils/pyi_generator.py | 1653 +------- reflex/utils/serializers.py | 499 +-- reflex/utils/types.py | 1198 +----- reflex/vars/__init__.py | 59 +- reflex/vars/base.py | 3693 +---------------- reflex/vars/color.py | 167 +- reflex/vars/datetime.py | 203 +- reflex/vars/dep_tracking.py | 486 +-- reflex/vars/function.py | 556 +-- reflex/vars/number.py | 1149 +---- reflex/vars/object.py | 648 +-- reflex/vars/sequence.py | 1842 +------- scripts/hatch_build.py | 2 +- uv.lock | 27 +- 54 files changed, 14774 insertions(+), 14630 deletions(-) create mode 100644 packages/reflex-core/README.md create mode 100644 packages/reflex-core/pyproject.toml create mode 100644 packages/reflex-core/src/reflex_core/__init__.py create mode 100644 packages/reflex-core/src/reflex_core/constants/__init__.py create mode 100644 packages/reflex-core/src/reflex_core/constants/base.py create mode 100644 packages/reflex-core/src/reflex_core/constants/colors.py create mode 100644 packages/reflex-core/src/reflex_core/constants/compiler.py create mode 100644 packages/reflex-core/src/reflex_core/constants/state.py create mode 100644 packages/reflex-core/src/reflex_core/utils/__init__.py create mode 100644 packages/reflex-core/src/reflex_core/utils/compat.py create mode 100644 packages/reflex-core/src/reflex_core/utils/console.py create mode 100644 packages/reflex-core/src/reflex_core/utils/decorator.py create mode 100644 packages/reflex-core/src/reflex_core/utils/exceptions.py create mode 100644 packages/reflex-core/src/reflex_core/utils/format.py create mode 100644 packages/reflex-core/src/reflex_core/utils/imports.py create mode 100644 packages/reflex-core/src/reflex_core/utils/pyi_generator.py create mode 100644 packages/reflex-core/src/reflex_core/utils/serializers.py create mode 100644 packages/reflex-core/src/reflex_core/utils/types.py create mode 100644 packages/reflex-core/src/reflex_core/vars/__init__.py create mode 100644 packages/reflex-core/src/reflex_core/vars/base.py create mode 100644 packages/reflex-core/src/reflex_core/vars/color.py create mode 100644 packages/reflex-core/src/reflex_core/vars/datetime.py create mode 100644 packages/reflex-core/src/reflex_core/vars/dep_tracking.py create mode 100644 packages/reflex-core/src/reflex_core/vars/function.py create mode 100644 packages/reflex-core/src/reflex_core/vars/number.py create mode 100644 packages/reflex-core/src/reflex_core/vars/object.py create mode 100644 packages/reflex-core/src/reflex_core/vars/sequence.py diff --git a/packages/hatch-reflex-pyi/pyproject.toml b/packages/hatch-reflex-pyi/pyproject.toml index a2754ad8a0c..68817a928f1 100644 --- a/packages/hatch-reflex-pyi/pyproject.toml +++ b/packages/hatch-reflex-pyi/pyproject.toml @@ -8,6 +8,7 @@ maintainers = [{ name = "Khaleel Al-Adhami", email = "khaleel@reflex.dev" }] requires-python = ">=3.10" dependencies = [ "hatchling", + "reflex-core", ] [project.entry-points.hatch] diff --git a/packages/hatch-reflex-pyi/src/hatch_reflex_pyi/plugin.py b/packages/hatch-reflex-pyi/src/hatch_reflex_pyi/plugin.py index 4f3b1effde0..c8b03ec4016 100644 --- a/packages/hatch-reflex-pyi/src/hatch_reflex_pyi/plugin.py +++ b/packages/hatch-reflex-pyi/src/hatch_reflex_pyi/plugin.py @@ -55,9 +55,9 @@ def initialize(self, version: str, build_data: dict[str, Any]) -> None: return try: - from reflex.utils.pyi_generator import PyiGenerator # noqa: F401 + from reflex_core.utils.pyi_generator import PyiGenerator # noqa: F401 except ImportError: - # reflex is not installed — skip pyi generation. + # reflex-core is not installed — skip pyi generation. # Pre-generated .pyi files in the sdist will be used. return @@ -68,7 +68,7 @@ def initialize(self, version: str, build_data: dict[str, Any]) -> None: # (e.g. "reflex_components_core.core.banner" instead of # "packages.reflex-components-core.src.reflex_components_core.core.banner"). subprocess.run( - [sys.executable, "-m", "reflex.utils.pyi_generator", src_dir.name], + [sys.executable, "-m", "reflex_core.utils.pyi_generator", src_dir.name], cwd=src_dir.parent, check=True, ) diff --git a/packages/reflex-core/README.md b/packages/reflex-core/README.md new file mode 100644 index 00000000000..77f3c868ca6 --- /dev/null +++ b/packages/reflex-core/README.md @@ -0,0 +1,3 @@ +# reflex-core + +Core types for the Reflex framework. diff --git a/packages/reflex-core/pyproject.toml b/packages/reflex-core/pyproject.toml new file mode 100644 index 00000000000..fd821d49e1a --- /dev/null +++ b/packages/reflex-core/pyproject.toml @@ -0,0 +1,25 @@ +[project] +name = "reflex-core" +dynamic = ["version"] +description = "Core types for the Reflex framework." +readme = "README.md" +authors = [{ name = "Khaleel Al-Adhami", email = "khaleel@reflex.dev" }] +maintainers = [{ name = "Khaleel Al-Adhami", email = "khaleel@reflex.dev" }] +requires-python = ">=3.10" +dependencies = [ + "packaging >=24.2,<27", + "pydantic >=1.10.21,<3.0", + "rich >=13,<15", + "typing_extensions >=4.13.0", +] + +[tool.hatch.version] +source = "uv-dynamic-versioning" + +[tool.uv-dynamic-versioning] +pattern-prefix = "reflex-core-" +fallback-version = "0.0.0dev0" + +[build-system] +requires = ["hatchling", "uv-dynamic-versioning"] +build-backend = "hatchling.build" diff --git a/packages/reflex-core/src/reflex_core/__init__.py b/packages/reflex-core/src/reflex_core/__init__.py new file mode 100644 index 00000000000..4311653df9d --- /dev/null +++ b/packages/reflex-core/src/reflex_core/__init__.py @@ -0,0 +1 @@ +"""Reflex core types.""" diff --git a/packages/reflex-core/src/reflex_core/constants/__init__.py b/packages/reflex-core/src/reflex_core/constants/__init__.py new file mode 100644 index 00000000000..6a6e2a55f1f --- /dev/null +++ b/packages/reflex-core/src/reflex_core/constants/__init__.py @@ -0,0 +1,19 @@ +"""Reflex core constants.""" + +from .base import ( + REFLEX_VAR_CLOSING_TAG, + REFLEX_VAR_OPENING_TAG, + ColorMode, + Dirs, + Env, + LogLevel, + Reflex, +) +from .compiler import ( + Hooks, + Imports, + MemoizationDisposition, + MemoizationMode, + SETTER_PREFIX, +) +from .state import StateManagerMode diff --git a/packages/reflex-core/src/reflex_core/constants/base.py b/packages/reflex-core/src/reflex_core/constants/base.py new file mode 100644 index 00000000000..c8a9a1dfde5 --- /dev/null +++ b/packages/reflex-core/src/reflex_core/constants/base.py @@ -0,0 +1,278 @@ +"""Base file for constants that don't fit any other categories.""" + +from __future__ import annotations + +import platform +from enum import Enum +from importlib import metadata +from pathlib import Path +from types import SimpleNamespace +from typing import Literal + +from platformdirs import PlatformDirs + +IS_WINDOWS = platform.system() == "Windows" +IS_MACOS = platform.system() == "Darwin" +IS_LINUX = platform.system() == "Linux" + + +class Dirs(SimpleNamespace): + """Various directories/paths used by Reflex.""" + + # The frontend directories in a project. + # The web folder where the frontend app is compiled to. + WEB = ".web" + # The directory where uploaded files are stored. + UPLOADED_FILES = "uploaded_files" + # The name of the assets directory. + APP_ASSETS = "assets" + # The name of the assets directory for external resources (a subfolder of APP_ASSETS). + EXTERNAL_APP_ASSETS = "external" + # The name of the utils file. + UTILS = "utils" + # The name of the state file. + STATE_PATH = UTILS + "/state" + # The name of the components file. + COMPONENTS_PATH = UTILS + "/components" + # The name of the contexts file. + CONTEXTS_PATH = UTILS + "/context" + # The name of the output directory. + BUILD_DIR = "build" + # The name of the static files directory. + STATIC = BUILD_DIR + "/client" + # The name of the public html directory served at "/" + PUBLIC = "public" + # The directory where styles are located. + STYLES = "styles" + # The name of the pages directory. + PAGES = "app" + # The name of the routes directory. + ROUTES = "routes" + # The name of the env json file. + ENV_JSON = "env.json" + # The name of the reflex json file. + REFLEX_JSON = "reflex.json" + # The name of the postcss config file. + POSTCSS_JS = "postcss.config.js" + # The name of the states directory. + STATES = ".states" + # Where compilation artifacts for the backend are stored. + BACKEND = "backend" + # JSON-encoded list of page routes that need to be evaluated on the backend. + STATEFUL_PAGES = "stateful_pages.json" + # Marker file indicating that upload component was used in the frontend. + UPLOAD_IS_USED = "upload_is_used" + + +def _reflex_version() -> str: + """Get the Reflex version. + + Returns: + The Reflex version. + """ + try: + return metadata.version("reflex") + except metadata.PackageNotFoundError: + return "unknown" + + +class Reflex(SimpleNamespace): + """Base constants concerning Reflex.""" + + # App names and versions. + # The name of the Reflex package. + MODULE_NAME = "reflex" + # The current version of Reflex. + VERSION = _reflex_version() + + # The reflex json file. + JSON = "reflex.json" + + # Files and directories used to init a new project. + # The directory to store reflex dependencies. + # on windows, we use C:/Users//AppData/Local/reflex. + # on macOS, we use ~/Library/Application Support/reflex. + # on linux, we use ~/.local/share/reflex. + # If user sets REFLEX_DIR envroment variable use that instead. + DIR = PlatformDirs(MODULE_NAME, False).user_data_path + + LOGS_DIR = DIR / "logs" + + # The root directory of the reflex library. + ROOT_DIR = Path(__file__).parents[2] + + RELEASES_URL = "https://api.github.com/repos/reflex-dev/templates/releases" + + # The reflex stylesheet language supported + STYLESHEETS_SUPPORTED = ["css", "sass", "scss"] + + +class ReflexHostingCLI(SimpleNamespace): + """Base constants concerning Reflex Hosting CLI.""" + + # The name of the Reflex Hosting CLI package. + MODULE_NAME = "reflex-hosting-cli" + + +class Templates(SimpleNamespace): + """Constants related to Templates.""" + + # The default template + DEFAULT = "blank" + + # The AI template + AI = "ai" + + # The option for the user to choose a remote template. + CHOOSE_TEMPLATES = "choose-templates" + + # The URL to find reflex templates. + REFLEX_TEMPLATES_URL = ( + "https://reflex.dev/docs/getting-started/open-source-templates/" + ) + + # The reflex.build frontend host + REFLEX_BUILD_FRONTEND = "https://build.reflex.dev" + + # The reflex.build frontend with referrer + REFLEX_BUILD_FRONTEND_WITH_REFERRER = ( + f"{REFLEX_BUILD_FRONTEND}/?utm_source=reflex_cli" + ) + + class Dirs(SimpleNamespace): + """Folders used by the template system of Reflex.""" + + # The template directory used during reflex init. + BASE = Reflex.ROOT_DIR / Reflex.MODULE_NAME / ".templates" + # The web subdirectory of the template directory. + WEB_TEMPLATE = BASE / "web" + # Where the code for the templates is stored. + CODE = "code" + + +class Javascript(SimpleNamespace): + """Constants related to Javascript.""" + + # The node modules directory. + NODE_MODULES = "node_modules" + + +class ReactRouter(Javascript): + """Constants related to React Router.""" + + # The react router config file + CONFIG_FILE = "react-router.config.js" + + # The associated Vite config file + VITE_CONFIG_FILE = "vite.config.js" + + # Regex to check for message displayed when frontend comes up + DEV_FRONTEND_LISTENING_REGEX = r"Local:[\s]+" + + # Regex to pattern the route path in the config file + # INFO Accepting connections at http://localhost:3000 + PROD_FRONTEND_LISTENING_REGEX = r"Accepting connections at[\s]+" + + FRONTEND_LISTENING_REGEX = ( + rf"(?:{DEV_FRONTEND_LISTENING_REGEX}|{PROD_FRONTEND_LISTENING_REGEX})(.*)" + ) + + SPA_FALLBACK = "__spa-fallback.html" + + +# Color mode variables +class ColorMode(SimpleNamespace): + """Constants related to ColorMode.""" + + NAME = "rawColorMode" + RESOLVED_NAME = "resolvedColorMode" + USE = "useColorMode" + TOGGLE = "toggleColorMode" + SET = "setColorMode" + + +LITERAL_ENV = Literal["dev", "prod"] + + +# Env modes +class Env(str, Enum): + """The environment modes.""" + + DEV = "dev" + PROD = "prod" + + +# Log levels +class LogLevel(str, Enum): + """The log levels.""" + + DEBUG = "debug" + DEFAULT = "default" + INFO = "info" + WARNING = "warning" + ERROR = "error" + CRITICAL = "critical" + + @classmethod + def from_string(cls, level: str | None) -> LogLevel | None: + """Convert a string to a log level. + + Args: + level: The log level as a string. + + Returns: + The log level. + """ + if not level: + return None + try: + return LogLevel[level.upper()] + except KeyError: + return None + + def __le__(self, other: LogLevel) -> bool: + """Compare log levels. + + Args: + other: The other log level. + + Returns: + True if the log level is less than or equal to the other log level. + """ + levels = list(LogLevel) + return levels.index(self) <= levels.index(other) + + def subprocess_level(self): + """Return the log level for the subprocess. + + Returns: + The log level for the subprocess + """ + return self if self != LogLevel.DEFAULT else LogLevel.WARNING + + +# Server socket configuration variables +POLLING_MAX_HTTP_BUFFER_SIZE = 1000 * 1000 + + +class Ping(SimpleNamespace): + """PING constants.""" + + # The 'ping' interval + INTERVAL = 25 + # The 'ping' timeout + TIMEOUT = 120 + + +# Keys in the client_side_storage dict +COOKIES = "cookies" +LOCAL_STORAGE = "local_storage" +SESSION_STORAGE = "session_storage" + +# Testing variables. +# Testing os env set by pytest when running a test case. +PYTEST_CURRENT_TEST = "PYTEST_CURRENT_TEST" +APP_HARNESS_FLAG = "APP_HARNESS_FLAG" + +REFLEX_VAR_OPENING_TAG = "" +REFLEX_VAR_CLOSING_TAG = "" diff --git a/packages/reflex-core/src/reflex_core/constants/colors.py b/packages/reflex-core/src/reflex_core/constants/colors.py new file mode 100644 index 00000000000..d31b9cd72e3 --- /dev/null +++ b/packages/reflex-core/src/reflex_core/constants/colors.py @@ -0,0 +1,99 @@ +"""The colors used in Reflex are a wrapper around https://www.radix-ui.com/colors.""" + +from __future__ import annotations + +from dataclasses import dataclass +from typing import TYPE_CHECKING, Literal, get_args + +if TYPE_CHECKING: + from reflex_core.vars import Var + +ColorType = Literal[ + "gray", + "mauve", + "slate", + "sage", + "olive", + "sand", + "tomato", + "red", + "ruby", + "crimson", + "pink", + "plum", + "purple", + "violet", + "iris", + "indigo", + "blue", + "cyan", + "teal", + "jade", + "green", + "grass", + "brown", + "orange", + "sky", + "mint", + "lime", + "yellow", + "amber", + "gold", + "bronze", + "accent", + "black", + "white", +] + +COLORS = frozenset(get_args(ColorType)) + +ShadeType = Literal[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12] +MIN_SHADE_VALUE = 1 +MAX_SHADE_VALUE = 12 + + +def format_color( + color: ColorType | Var[str], shade: ShadeType | Var[int], alpha: bool | Var[bool] +) -> str: + """Format a color as a CSS color string. + + Args: + color: The color to use. + shade: The shade of the color to use. + alpha: Whether to use the alpha variant of the color. + + Returns: + The formatted color. + """ + if isinstance(alpha, bool): + return f"var(--{color}-{'a' if alpha else ''}{shade})" + + from reflex_components_core.core import cond + + alpha_var = cond(alpha, "a", "") + return f"var(--{color}-{alpha_var}{shade})" + + +@dataclass +class Color: + """A color in the Reflex color palette.""" + + # The color palette to use + color: ColorType | Var[str] + + # The shade of the color to use + shade: ShadeType | Var[int] = 7 + + # Whether to use the alpha variant of the color + alpha: bool | Var[bool] = False + + def __format__(self, format_spec: str) -> str: + """Format the color as a CSS color string. + + Args: + format_spec: The format specifier to use. + + Returns: + The formatted color. + """ + return format_color(self.color, self.shade, self.alpha) diff --git a/packages/reflex-core/src/reflex_core/constants/compiler.py b/packages/reflex-core/src/reflex_core/constants/compiler.py new file mode 100644 index 00000000000..74e90083ae0 --- /dev/null +++ b/packages/reflex-core/src/reflex_core/constants/compiler.py @@ -0,0 +1,210 @@ +"""Compiler variables.""" + +import dataclasses +import enum +from enum import Enum +from types import SimpleNamespace + +from reflex_core.constants import Dirs +from reflex_core.utils.imports import ImportVar + +# The prefix used to create setters for state vars. +SETTER_PREFIX = "set_" + +# The file used to specify no compilation. +NOCOMPILE_FILE = "nocompile" + + +class Ext(SimpleNamespace): + """Extension used in Reflex.""" + + # The extension for JS files. + JS = ".js" + # The extension for JSX files. + JSX = ".jsx" + # The extension for python files. + PY = ".py" + # The extension for css files. + CSS = ".css" + # The extension for zip files. + ZIP = ".zip" + # The extension for executable files on Windows. + EXE = ".exe" + # The extension for markdown files. + MD = ".md" + + +class CompileVars(SimpleNamespace): + """The variables used during compilation.""" + + # The expected variable name where the rx.App is stored. + APP = "app" + # The expected variable name where the API object is stored for deployment. + API = "api" + # The name of the router variable. + ROUTER = "router" + # The name of the socket variable. + SOCKET = "socket" + # The name of the variable to hold API results. + RESULT = "result" + # The name of the final variable. + FINAL = "final" + # The name of the process variable. + PROCESSING = "processing" + # The name of the state variable. + STATE = "state" + # The name of the events variable. + EVENTS = "events" + # The name of the initial hydrate event. + HYDRATE = "hydrate" + # The name of the is_hydrated variable. + IS_HYDRATED = "is_hydrated" + # The name of the function to add events to the queue. + ADD_EVENTS = "addEvents" + # The name of the function to apply event actions before invoking a target. + APPLY_EVENT_ACTIONS = "applyEventActions" + # The name of the var storing any connection error. + CONNECT_ERROR = "connectErrors" + # The name of the function for converting a dict to an event. + TO_EVENT = "ReflexEvent" + # The name of the internal on_load event. + ON_LOAD_INTERNAL = "reflex___state____on_load_internal_state.on_load_internal" + # The name of the internal event to update generic state vars. + UPDATE_VARS_INTERNAL = ( + "reflex___state____update_vars_internal_state.update_vars_internal" + ) + # The name of the frontend event exception state + FRONTEND_EXCEPTION_STATE = "reflex___state____frontend_event_exception_state" + # The full name of the frontend exception state + FRONTEND_EXCEPTION_STATE_FULL = ( + f"reflex___state____state.{FRONTEND_EXCEPTION_STATE}" + ) + + +class PageNames(SimpleNamespace): + """The name of basic pages deployed in the frontend.""" + + # The name of the index page. + INDEX_ROUTE = "index" + # The name of the app root page. + APP_ROOT = "root.jsx" + # The root stylesheet filename. + STYLESHEET_ROOT = "__reflex_global_styles" + # The name of the document root page. + DOCUMENT_ROOT = "_document.js" + # The name of the theme page. + THEME = "theme" + # The module containing components. + COMPONENTS = "components" + # The module containing shared stateful components + STATEFUL_COMPONENTS = "stateful_components" + + +class ComponentName(Enum): + """Component names.""" + + BACKEND = "Backend" + FRONTEND = "Frontend" + + def zip(self): + """Give the zip filename for the component. + + Returns: + The lower-case filename with zip extension. + """ + return self.value.lower() + Ext.ZIP + + +class CompileContext(str, Enum): + """The context in which the compiler is running.""" + + RUN = "run" + EXPORT = "export" + DEPLOY = "deploy" + UNDEFINED = "undefined" + + +class Imports(SimpleNamespace): + """Common sets of import vars.""" + + EVENTS = { + "react": [ImportVar(tag="useContext")], + f"$/{Dirs.CONTEXTS_PATH}": [ImportVar(tag="EventLoopContext")], + f"$/{Dirs.STATE_PATH}": [ + ImportVar(tag=CompileVars.TO_EVENT), + ImportVar(tag=CompileVars.APPLY_EVENT_ACTIONS), + ], + } + + +class Hooks(SimpleNamespace): + """Common sets of hook declarations.""" + + EVENTS = f"const [{CompileVars.ADD_EVENTS}, {CompileVars.CONNECT_ERROR}] = useContext(EventLoopContext);" + + class HookPosition(enum.Enum): + """The position of the hook in the component.""" + + INTERNAL = "internal" + PRE_TRIGGER = "pre_trigger" + POST_TRIGGER = "post_trigger" + + +class MemoizationDisposition(enum.Enum): + """The conditions under which a component should be memoized.""" + + # If the component uses state or events, it should be memoized. + STATEFUL = "stateful" + ALWAYS = "always" + NEVER = "never" + + +@dataclasses.dataclass(frozen=True) +class MemoizationMode: + """The mode for memoizing a Component.""" + + # The conditions under which the component should be memoized. + disposition: MemoizationDisposition = MemoizationDisposition.STATEFUL + + # Whether children of this component should be memoized first. + recursive: bool = True + + +DATA_UNDERSCORE = "data_" +DATA_DASH = "data-" +ARIA_UNDERSCORE = "aria_" +ARIA_DASH = "aria-" + +SPECIAL_ATTRS = ( + DATA_UNDERSCORE, + DATA_DASH, + ARIA_UNDERSCORE, + ARIA_DASH, +) + + +class SpecialAttributes(enum.Enum): + """Special attributes for components. + + These are placed in custom_attrs and rendered as-is rather than converting + to a style prop. + """ + + @classmethod + def is_special(cls, attr: str) -> bool: + """Check if the attribute is special. + + Args: + attr: the attribute to check + + Returns: + True if the attribute is special. + """ + return attr.startswith(SPECIAL_ATTRS) + + +class ResetStylesheet(SimpleNamespace): + """Constants for CSS reset stylesheet.""" + + # The filename of the CSS reset file. + FILENAME = "__reflex_style_reset.css" diff --git a/packages/reflex-core/src/reflex_core/constants/state.py b/packages/reflex-core/src/reflex_core/constants/state.py new file mode 100644 index 00000000000..3f6ebec2f17 --- /dev/null +++ b/packages/reflex-core/src/reflex_core/constants/state.py @@ -0,0 +1,19 @@ +"""State-related constants.""" + +from enum import Enum + + +class StateManagerMode(str, Enum): + """State manager constants.""" + + DISK = "disk" + MEMORY = "memory" + REDIS = "redis" + + +# Used for things like console_log, etc. +FRONTEND_EVENT_STATE = "__reflex_internal_frontend_event_state" + +FIELD_MARKER = "_rx_state_" +MEMO_MARKER = "_rx_memo_" +CAMEL_CASE_MEMO_MARKER = "RxMemo" diff --git a/packages/reflex-core/src/reflex_core/utils/__init__.py b/packages/reflex-core/src/reflex_core/utils/__init__.py new file mode 100644 index 00000000000..d8eb4c260ed --- /dev/null +++ b/packages/reflex-core/src/reflex_core/utils/__init__.py @@ -0,0 +1 @@ +"""Reflex core utilities.""" diff --git a/packages/reflex-core/src/reflex_core/utils/compat.py b/packages/reflex-core/src/reflex_core/utils/compat.py new file mode 100644 index 00000000000..cb9f35485a3 --- /dev/null +++ b/packages/reflex-core/src/reflex_core/utils/compat.py @@ -0,0 +1,69 @@ +"""Compatibility hacks and helpers.""" + +import sys +from collections.abc import Mapping +from typing import TYPE_CHECKING, Any + +if TYPE_CHECKING: + from pydantic.fields import FieldInfo + + +async def windows_hot_reload_lifespan_hack(): + """[REF-3164] A hack to fix hot reload on Windows. + + Uvicorn has an issue stopping itself on Windows after detecting changes in + the filesystem. + + This workaround repeatedly prints and flushes null characters to stderr, + which seems to allow the uvicorn server to exit when the CTRL-C signal is + sent from the reloader process. + + Don't ask me why this works, I discovered it by accident - masenf. + """ + import asyncio + import sys + + try: + while True: + sys.stderr.write("\0") + sys.stderr.flush() + await asyncio.sleep(0.5) + except asyncio.CancelledError: + pass + + +def annotations_from_namespace(namespace: Mapping[str, Any]) -> dict[str, Any]: + """Get the annotations from a class namespace. + + Args: + namespace: The class namespace. + + Returns: + The (forward-ref) annotations from the class namespace. + """ + if sys.version_info >= (3, 14) and "__annotations__" not in namespace: + from annotationlib import ( + Format, + call_annotate_function, + get_annotate_from_class_namespace, + ) + + if annotate := get_annotate_from_class_namespace(namespace): + return call_annotate_function(annotate, format=Format.FORWARDREF) + return namespace.get("__annotations__", {}) + + +def sqlmodel_field_has_primary_key(field_info: "FieldInfo") -> bool: + """Determines if a field is a primary. + + Args: + field_info: a rx.model field + + Returns: + If field_info is a primary key (Bool) + """ + if getattr(field_info, "primary_key", None) is True: + return True + if getattr(field_info, "sa_column", None) is None: + return False + return bool(getattr(field_info.sa_column, "primary_key", None)) # pyright: ignore[reportAttributeAccessIssue] diff --git a/packages/reflex-core/src/reflex_core/utils/console.py b/packages/reflex-core/src/reflex_core/utils/console.py new file mode 100644 index 00000000000..f620dfd69c3 --- /dev/null +++ b/packages/reflex-core/src/reflex_core/utils/console.py @@ -0,0 +1,473 @@ +"""Functions to communicate to the user via console.""" + +from __future__ import annotations + +import contextlib +import datetime +import inspect +import os +import shutil +import sys +import time +from pathlib import Path +from types import FrameType + +from rich.console import Console +from rich.progress import MofNCompleteColumn, Progress, TaskID, TimeElapsedColumn +from rich.prompt import Prompt + +from reflex_core.constants import LogLevel +from reflex_core.constants.base import Reflex +from reflex_core.utils.decorator import once + +# Console for pretty printing. +_console = Console(highlight=False) +_console_stderr = Console(stderr=True, highlight=False) + +# The current log level. +_LOG_LEVEL = LogLevel.INFO + +# Deprecated features who's warning has been printed. +_EMITTED_DEPRECATION_WARNINGS = set() + +# Info messages which have been printed. +_EMITTED_INFO = set() + +# Warnings which have been printed. +_EMITTED_WARNINGS = set() + +# Errors which have been printed. +_EMITTED_ERRORS = set() + +# Success messages which have been printed. +_EMITTED_SUCCESS = set() + +# Debug messages which have been printed. +_EMITTED_DEBUG = set() + +# Logs which have been printed. +_EMITTED_LOGS = set() + +# Prints which have been printed. +_EMITTED_PRINTS = set() + + +def set_log_level(log_level: LogLevel | None): + """Set the log level. + + Args: + log_level: The log level to set. + + Raises: + TypeError: If the log level is a string. + """ + if log_level is None: + return + if not isinstance(log_level, LogLevel): + msg = f"log_level must be a LogLevel enum value, got {log_level} of type {type(log_level)} instead." + raise TypeError(msg) + global _LOG_LEVEL + if log_level != _LOG_LEVEL: + # Set the loglevel persistenly for subprocesses. + os.environ["REFLEX_LOGLEVEL"] = log_level.value + _LOG_LEVEL = log_level + + +def is_debug() -> bool: + """Check if the log level is debug. + + Returns: + True if the log level is debug. + """ + return _LOG_LEVEL <= LogLevel.DEBUG + + +def print(msg: str, *, dedupe: bool = False, **kwargs): + """Print a message. + + Args: + msg: The message to print. + dedupe: If True, suppress multiple console logs of print message. + kwargs: Keyword arguments to pass to the print function. + """ + if dedupe: + if msg in _EMITTED_PRINTS: + return + _EMITTED_PRINTS.add(msg) + _console.print(msg, **kwargs) + + +def _print_stderr(msg: str, *, dedupe: bool = False, **kwargs): + """Print a message to stderr. + + Args: + msg: The message to print. + dedupe: If True, suppress multiple console logs of print message. + kwargs: Keyword arguments to pass to the print function. + """ + if dedupe: + if msg in _EMITTED_PRINTS: + return + _EMITTED_PRINTS.add(msg) + _console_stderr.print(msg, **kwargs) + + +@once +def log_file_console(): + """Create a console that logs to a file. + + Returns: + A Console object that logs to a file. + """ + from reflex.environment import environment + + if not (env_log_file := environment.REFLEX_LOG_FILE.get()): + subseconds = int((time.time() % 1) * 1000) + timestamp = time.strftime("%Y-%m-%d_%H-%M-%S") + f"_{subseconds:03d}" + log_file = Reflex.DIR / "logs" / (timestamp + ".log") + log_file.parent.mkdir(parents=True, exist_ok=True) + else: + log_file = env_log_file + if log_file.exists(): + log_file.unlink() + log_file.touch() + return Console(file=log_file.open("a", encoding="utf-8")) + + +@once +def should_use_log_file_console() -> bool: + """Check if the log file console should be used. + + Returns: + True if the log file console should be used, False otherwise. + """ + from reflex.environment import environment + + return environment.REFLEX_ENABLE_FULL_LOGGING.get() + + +def print_to_log_file(msg: str, *, dedupe: bool = False, **kwargs): + """Print a message to the log file. + + Args: + msg: The message to print. + dedupe: If True, suppress multiple console logs of print message. + kwargs: Keyword arguments to pass to the print function. + """ + log_file_console().print(f"[{datetime.datetime.now()}] {msg}", **kwargs) + + +def debug(msg: str, *, dedupe: bool = False, **kwargs): + """Print a debug message. + + Args: + msg: The debug message. + dedupe: If True, suppress multiple console logs of debug message. + kwargs: Keyword arguments to pass to the print function. + """ + if is_debug(): + msg_ = f"[purple]Debug: {msg}[/purple]" + if dedupe: + if msg_ in _EMITTED_DEBUG: + return + _EMITTED_DEBUG.add(msg_) + if progress := kwargs.pop("progress", None): + progress.console.print(msg_, **kwargs) + else: + print(msg_, **kwargs) + if should_use_log_file_console() and kwargs.pop("progress", None) is None: + print_to_log_file(f"[purple]Debug: {msg}[/purple]", **kwargs) + + +def info(msg: str, *, dedupe: bool = False, **kwargs): + """Print an info message. + + Args: + msg: The info message. + dedupe: If True, suppress multiple console logs of info message. + kwargs: Keyword arguments to pass to the print function. + """ + if _LOG_LEVEL <= LogLevel.INFO: + if dedupe: + if msg in _EMITTED_INFO: + return + _EMITTED_INFO.add(msg) + print(f"[cyan]Info: {msg}[/cyan]", **kwargs) + if should_use_log_file_console(): + print_to_log_file(f"[cyan]Info: {msg}[/cyan]", **kwargs) + + +def success(msg: str, *, dedupe: bool = False, **kwargs): + """Print a success message. + + Args: + msg: The success message. + dedupe: If True, suppress multiple console logs of success message. + kwargs: Keyword arguments to pass to the print function. + """ + if _LOG_LEVEL <= LogLevel.INFO: + if dedupe: + if msg in _EMITTED_SUCCESS: + return + _EMITTED_SUCCESS.add(msg) + print(f"[green]Success: {msg}[/green]", **kwargs) + if should_use_log_file_console(): + print_to_log_file(f"[green]Success: {msg}[/green]", **kwargs) + + +def log(msg: str, *, dedupe: bool = False, **kwargs): + """Takes a string and logs it to the console. + + Args: + msg: The message to log. + dedupe: If True, suppress multiple console logs of log message. + kwargs: Keyword arguments to pass to the print function. + """ + if _LOG_LEVEL <= LogLevel.INFO: + if dedupe: + if msg in _EMITTED_LOGS: + return + _EMITTED_LOGS.add(msg) + _console.log(msg, **kwargs) + if should_use_log_file_console(): + print_to_log_file(msg, **kwargs) + + +def rule(title: str, **kwargs): + """Prints a horizontal rule with a title. + + Args: + title: The title of the rule. + kwargs: Keyword arguments to pass to the print function. + """ + _console.rule(title, **kwargs) + + +def warn(msg: str, *, dedupe: bool = False, **kwargs): + """Print a warning message. + + Args: + msg: The warning message. + dedupe: If True, suppress multiple console logs of warning message. + kwargs: Keyword arguments to pass to the print function. + """ + if _LOG_LEVEL <= LogLevel.WARNING: + if dedupe: + if msg in _EMITTED_WARNINGS: + return + _EMITTED_WARNINGS.add(msg) + print(f"[orange1]Warning: {msg}[/orange1]", **kwargs) + if should_use_log_file_console(): + print_to_log_file(f"[orange1]Warning: {msg}[/orange1]", **kwargs) + + +@once +def _exclude_paths_from_frame_info() -> list[Path]: + import importlib.util + + import click + import granian + import socketio + import typing_extensions + + import reflex as rx + + # Exclude utility modules that should never be the source of deprecated reflex usage. + exclude_modules = [click, rx, typing_extensions, socketio, granian] + modules_paths = [file for m in exclude_modules if (file := m.__file__)] + [ + spec.origin + for m in [*sys.builtin_module_names, *sys.stdlib_module_names] + if (spec := importlib.util.find_spec(m)) and spec.origin + ] + exclude_roots = [ + p.parent.resolve() if (p := Path(file)).name == "__init__.py" else p.resolve() + for file in modules_paths + ] + # Specifically exclude the reflex cli module. + if reflex_bin := shutil.which(b"reflex"): + exclude_roots.append(Path(reflex_bin.decode())) + + return exclude_roots + + +def _get_first_non_framework_frame() -> FrameType | None: + exclude_roots = _exclude_paths_from_frame_info() + + frame = inspect.currentframe() + while frame := frame and frame.f_back: + frame_path = Path(inspect.getfile(frame)).resolve() + if not any(frame_path.is_relative_to(root) for root in exclude_roots): + break + return frame + + +def deprecate( + *, + feature_name: str, + reason: str, + deprecation_version: str, + removal_version: str, + dedupe: bool = True, + **kwargs, +): + """Print a deprecation warning. + + Args: + feature_name: The feature to deprecate. + reason: The reason for deprecation. + deprecation_version: The version the feature was deprecated + removal_version: The version the deprecated feature will be removed + dedupe: If True, suppress multiple console logs of deprecation message. + kwargs: Keyword arguments to pass to the print function. + """ + dedupe_key = feature_name + loc = "" + + # See if we can find where the deprecation exists in "user code" + origin_frame = _get_first_non_framework_frame() + if origin_frame is not None: + filename = Path(origin_frame.f_code.co_filename) + if filename.is_relative_to(Path.cwd()): + filename = filename.relative_to(Path.cwd()) + loc = f" ({filename}:{origin_frame.f_lineno})" + dedupe_key = f"{dedupe_key} {loc}" + + if dedupe_key not in _EMITTED_DEPRECATION_WARNINGS: + msg = ( + f"{feature_name} has been deprecated in version {deprecation_version}. {reason.rstrip('.').lstrip('. ')}. It will be completely " + f"removed in {removal_version}.{loc}" + ) + if _LOG_LEVEL <= LogLevel.WARNING: + print(f"[yellow]DeprecationWarning: {msg}[/yellow]", **kwargs) + if should_use_log_file_console(): + print_to_log_file(f"[yellow]DeprecationWarning: {msg}[/yellow]", **kwargs) + if dedupe: + _EMITTED_DEPRECATION_WARNINGS.add(dedupe_key) + + +def error(msg: str, *, dedupe: bool = False, **kwargs): + """Print an error message. + + Args: + msg: The error message. + dedupe: If True, suppress multiple console logs of error message. + kwargs: Keyword arguments to pass to the print function. + """ + if _LOG_LEVEL <= LogLevel.ERROR: + if dedupe: + if msg in _EMITTED_ERRORS: + return + _EMITTED_ERRORS.add(msg) + _print_stderr(f"[red]{msg}[/red]", **kwargs) + if should_use_log_file_console(): + print_to_log_file(f"[red]{msg}[/red]", **kwargs) + + +def ask( + question: str, + choices: list[str] | None = None, + default: str | None = None, + show_choices: bool = True, +) -> str | None: + """Takes a prompt question and optionally a list of choices + and returns the user input. + + Args: + question: The question to ask the user. + choices: A list of choices to select from. + default: The default option selected. + show_choices: Whether to show the choices. + + Returns: + A string with the user input. + """ + return Prompt.ask( + question, choices=choices, default=default, show_choices=show_choices + ) + + +def progress(): + """Create a new progress bar. + + Returns: + A new progress bar. + """ + return Progress( + *Progress.get_default_columns()[:-1], + MofNCompleteColumn(), + TimeElapsedColumn(), + ) + + +def status(*args, **kwargs): + """Create a status with a spinner. + + Args: + *args: Args to pass to the status. + **kwargs: Kwargs to pass to the status. + + Returns: + 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]") + + +class PoorProgress: + """A poor man's progress bar.""" + + def __init__(self): + """Initialize the progress bar.""" + super().__init__() + self.tasks = {} + self.progress = 0 + self.total = 0 + + def add_task(self, task: str, total: int): + """Add a task to the progress bar. + + Args: + task: The task name. + total: The total number of steps for the task. + + Returns: + The task ID. + """ + self.total += total + task_id = TaskID(len(self.tasks)) + self.tasks[task_id] = {"total": total, "current": 0} + return task_id + + def advance(self, task: TaskID, advance: int = 1): + """Advance the progress of a task. + + Args: + task: The task ID. + advance: The number of steps to advance. + """ + if task in self.tasks: + self.tasks[task]["current"] += advance + self.progress += advance + _console.print(f"Progress: {self.progress}/{self.total}") + + def start(self): + """Start the progress bar.""" + + def stop(self): + """Stop the progress bar.""" diff --git a/packages/reflex-core/src/reflex_core/utils/decorator.py b/packages/reflex-core/src/reflex_core/utils/decorator.py new file mode 100644 index 00000000000..4129b18a2ee --- /dev/null +++ b/packages/reflex-core/src/reflex_core/utils/decorator.py @@ -0,0 +1,148 @@ +"""Decorator utilities.""" + +import functools +from collections.abc import Callable +from pathlib import Path +from typing import ParamSpec, TypeVar, cast + +T = TypeVar("T") + + +def once(f: Callable[[], T]) -> Callable[[], T]: + """A decorator that calls the function once and caches the result. + + Args: + f: The function to call. + + Returns: + A function that calls the function once and caches the result. + """ + unset = object() + value: object | T = unset + + @functools.wraps(f) + def wrapper() -> T: + nonlocal value + value = f() if value is unset else value + return value # pyright: ignore[reportReturnType] + + return wrapper + + +def once_unless_none(f: Callable[[], T | None]) -> Callable[[], T | None]: + """A decorator that calls the function once and caches the result unless it is None. + + Args: + f: The function to call. + + Returns: + A function that calls the function once and caches the result unless it is None. + """ + value: T | None = None + + @functools.wraps(f) + def wrapper() -> T | None: + nonlocal value + value = f() if value is None else value + return value + + return wrapper + + +P = ParamSpec("P") + + +def debug(f: Callable[P, T]) -> Callable[P, T]: + """A decorator that prints the function name, arguments, and result. + + Args: + f: The function to call. + + Returns: + A function that prints the function name, arguments, and result. + """ + + @functools.wraps(f) + def wrapper(*args: P.args, **kwargs: P.kwargs) -> T: + result = f(*args, **kwargs) + print( # noqa: T201 + f"Calling {f.__name__} with args: {args} and kwargs: {kwargs}, result: {result}" + ) + return result + + return wrapper + + +def _write_cached_procedure_file(payload: str, cache_file: Path, value: object): + import pickle + + cache_file.parent.mkdir(parents=True, exist_ok=True) + cache_file.write_bytes(pickle.dumps((payload, value))) + + +def _read_cached_procedure_file(cache_file: Path) -> tuple[str | None, object]: + import pickle + + if cache_file.exists(): + with cache_file.open("rb") as f: + return pickle.loads(f.read()) + + return None, None + + +P = ParamSpec("P") +Picklable = TypeVar("Picklable") + + +def cached_procedure( + cache_file_path: Callable[[], Path], + payload_fn: Callable[P, str], +) -> Callable[[Callable[P, Picklable]], Callable[P, Picklable]]: + """Decorator to cache the result of a function based on its arguments. + + Args: + cache_file_path: Function that computes the cache file path. + payload_fn: Function that computes cache payload from function args. + + Returns: + The decorated function. + """ + + def _inner_decorator(func: Callable[P, Picklable]) -> Callable[P, Picklable]: + def _inner(*args: P.args, **kwargs: P.kwargs) -> Picklable: + cache_file = cache_file_path() + + payload, value = _read_cached_procedure_file(cache_file) + new_payload = payload_fn(*args, **kwargs) + + if payload != new_payload: + new_value = func(*args, **kwargs) + _write_cached_procedure_file(new_payload, cache_file, new_value) + return new_value + + from reflex_core.utils import console + + console.debug( + f"Using cached value for {func.__name__} with payload: {new_payload}" + ) + return cast("Picklable", value) + + return _inner + + return _inner_decorator + + +def cache_result_in_disk( + cache_file_path: Callable[[], Path], +) -> Callable[[Callable[[], Picklable]], Callable[[], Picklable]]: + """Decorator to cache the result of a function on disk. + + Args: + cache_file_path: Function that computes the cache file path. + + Returns: + The decorated function. + """ + return cached_procedure( + cache_file_path=cache_file_path, payload_fn=lambda: "constant" + ) diff --git a/packages/reflex-core/src/reflex_core/utils/exceptions.py b/packages/reflex-core/src/reflex_core/utils/exceptions.py new file mode 100644 index 00000000000..edfafd3dc55 --- /dev/null +++ b/packages/reflex-core/src/reflex_core/utils/exceptions.py @@ -0,0 +1,286 @@ +"""Custom Exceptions.""" + +from __future__ import annotations + +from typing import TYPE_CHECKING, Any + +if TYPE_CHECKING: + from reflex_core.vars import Var + + +class ReflexError(Exception): + """Base exception for all Reflex exceptions.""" + + +class ConfigError(ReflexError): + """Custom exception for config related errors.""" + + +class InvalidStateManagerModeError(ReflexError, ValueError): + """Raised when an invalid state manager mode is provided.""" + + +class ReflexRuntimeError(ReflexError, RuntimeError): + """Custom RuntimeError for Reflex.""" + + +class UploadTypeError(ReflexError, TypeError): + """Custom TypeError for upload related errors.""" + + +class EnvVarValueError(ReflexError, ValueError): + """Custom ValueError raised when unable to convert env var to expected type.""" + + +class ComponentTypeError(ReflexError, TypeError): + """Custom TypeError for component related errors.""" + + +class ChildrenTypeError(ComponentTypeError): + """Raised when the children prop of a component is not a valid type.""" + + def __init__(self, component: str, child: Any): + """Initialize the exception. + + Args: + component: The name of the component. + child: The child that caused the error. + """ + super().__init__( + f"Component {component} received child {child} of type {type(child)}. " + "Accepted types are other components, state vars, or primitive Python types (dict excluded)." + ) + + +class EventHandlerTypeError(ReflexError, TypeError): + """Custom TypeError for event handler related errors.""" + + +class EventHandlerValueError(ReflexError, ValueError): + """Custom ValueError for event handler related errors.""" + + +class StateValueError(ReflexError, ValueError): + """Custom ValueError for state related errors.""" + + +class VarNameError(ReflexError, NameError): + """Custom NameError for when a state var has been shadowed by a substate var.""" + + +class VarTypeError(ReflexError, TypeError): + """Custom TypeError for var related errors.""" + + +class VarValueError(ReflexError, ValueError): + """Custom ValueError for var related errors.""" + + +class VarAttributeError(ReflexError, AttributeError): + """Custom AttributeError for var related errors.""" + + +class UntypedVarError(ReflexError, TypeError): + """Custom TypeError for untyped var errors.""" + + def __init__(self, var: Var, action: str, doc_link: str = ""): + """Create an UntypedVarError from a var. + + Args: + var: The var. + action: The action that caused the error. + doc_link: The link to the documentation. + """ + var_data = var._get_all_var_data() + is_state_var = ( + var_data + and var_data.state + and var_data.field_name + and var_data.state + "." + var_data.field_name == str(var) + ) + super().__init__( + f"Cannot {action} on untyped var '{var!s}' of type '{var._var_type!s}'." + + ( + " Please add a type annotation to the var in the state class." + if is_state_var + else " You can call the var's .to(desired_type) method to convert it to the desired type." + ) + + (f" See {doc_link}" if doc_link else "") + ) + + +class UntypedComputedVarError(ReflexError, TypeError): + """Custom TypeError for untyped computed var errors.""" + + def __init__(self, var_name: str): + """Initialize the UntypedComputedVarError. + + Args: + var_name: The name of the computed var. + """ + super().__init__(f"Computed var '{var_name}' must have a type annotation.") + + +class ComputedVarSignatureError(ReflexError, TypeError): + """Custom TypeError for computed var signature errors.""" + + def __init__(self, var_name: str, signature: str): + """Initialize the ComputedVarSignatureError. + + Args: + var_name: The name of the var. + signature: The invalid signature. + """ + super().__init__(f"Computed var `{var_name}{signature}` cannot take arguments.") + + +class MissingAnnotationError(ReflexError, TypeError): + """Custom TypeError for missing annotations.""" + + def __init__(self, var_name: str): + """Initialize the MissingAnnotationError. + + Args: + var_name: The name of the var. + """ + super().__init__(f"Var '{var_name}' must have a type annotation.") + + +class UploadValueError(ReflexError, ValueError): + """Custom ValueError for upload related errors.""" + + +class PageValueError(ReflexError, ValueError): + """Custom ValueError for page related errors.""" + + +class RouteValueError(ReflexError, ValueError): + """Custom ValueError for route related errors.""" + + +class VarOperationTypeError(ReflexError, TypeError): + """Custom TypeError for when unsupported operations are performed on vars.""" + + +class VarDependencyError(ReflexError, ValueError): + """Custom ValueError for when a var depends on a non-existent var.""" + + +class InvalidStylePropError(ReflexError, TypeError): + """Custom Type Error when style props have invalid values.""" + + +class ImmutableStateError(ReflexError): + """Raised when a background task attempts to modify state outside of context.""" + + +class LockExpiredError(ReflexError): + """Raised when the state lock expires while an event is being processed.""" + + +class MatchTypeError(ReflexError, TypeError): + """Raised when the return types of match cases are different.""" + + +class EventHandlerArgTypeMismatchError(ReflexError, TypeError): + """Raised when the annotations of args accepted by an EventHandler differs from the spec of the event trigger.""" + + +class EventFnArgMismatchError(ReflexError, TypeError): + """Raised when the number of args required by an event handler is more than provided by the event trigger.""" + + +class DynamicRouteArgShadowsStateVarError(ReflexError, NameError): + """Raised when a dynamic route arg shadows a state var.""" + + +class ComputedVarShadowsStateVarError(ReflexError, NameError): + """Raised when a computed var shadows a state var.""" + + +class ComputedVarShadowsBaseVarsError(ReflexError, NameError): + """Raised when a computed var shadows a base var.""" + + +class EventHandlerShadowsBuiltInStateMethodError(ReflexError, NameError): + """Raised when an event handler shadows a built-in state method.""" + + +class GeneratedCodeHasNoFunctionDefsError(ReflexError): + """Raised when refactored code generated with flexgen has no functions defined.""" + + +class PrimitiveUnserializableToJSONError(ReflexError, ValueError): + """Raised when a primitive type is unserializable to JSON. Usually with NaN and Infinity.""" + + +class InvalidLifespanTaskTypeError(ReflexError, TypeError): + """Raised when an invalid task type is registered as a lifespan task.""" + + +class DynamicComponentMissingLibraryError(ReflexError, ValueError): + """Raised when a dynamic component is missing a library.""" + + +class SetUndefinedStateVarError(ReflexError, AttributeError): + """Raised when setting the value of a var without first declaring it.""" + + +class StateSchemaMismatchError(ReflexError, TypeError): + """Raised when the serialized schema of a state class does not match the current schema.""" + + +class EnvironmentVarValueError(ReflexError, ValueError): + """Raised when an environment variable is set to an invalid value.""" + + +class DynamicComponentInvalidSignatureError(ReflexError, TypeError): + """Raised when a dynamic component has an invalid signature.""" + + +class InvalidPropValueError(ReflexError): + """Raised when a prop value is invalid.""" + + +class StateTooLargeError(ReflexError): + """Raised when the state is too large to be serialized.""" + + +class StateSerializationError(ReflexError): + """Raised when the state cannot be serialized.""" + + +class StateMismatchError(ReflexError, ValueError): + """Raised when the state retrieved does not match the expected state.""" + + +class SystemPackageMissingError(ReflexError): + """Raised when a system package is missing.""" + + def __init__(self, package: str): + """Initialize the SystemPackageMissingError. + + Args: + package: The missing package. + """ + from reflex_core.constants import IS_MACOS + + extra = ( + f" You can do so by running 'brew install {package}'." if IS_MACOS else "" + ) + super().__init__( + f"System package '{package}' is missing." + f" Please install it through your system package manager.{extra}" + ) + + +class EventDeserializationError(ReflexError, ValueError): + """Raised when an event cannot be deserialized.""" + + +class InvalidLockWarningThresholdError(ReflexError): + """Raised when an invalid lock warning threshold is provided.""" + + +class UnretrievableVarValueError(ReflexError): + """Raised when the value of a var is not retrievable.""" diff --git a/packages/reflex-core/src/reflex_core/utils/format.py b/packages/reflex-core/src/reflex_core/utils/format.py new file mode 100644 index 00000000000..f23caef748b --- /dev/null +++ b/packages/reflex-core/src/reflex_core/utils/format.py @@ -0,0 +1,774 @@ +"""Formatting operations.""" + +from __future__ import annotations + +import inspect +import json +import os +import re +from typing import TYPE_CHECKING, Any + +from reflex import constants +from reflex_core.constants.state import FRONTEND_EVENT_STATE +from reflex_core.utils import exceptions + +if TYPE_CHECKING: + from reflex.components.component import ComponentStyle + from reflex.event import ArgsSpec, EventChain, EventHandler, EventSpec, EventType + +WRAP_MAP = { + "{": "}", + "(": ")", + "[": "]", + "<": ">", + '"': '"', + "'": "'", + "`": "`", +} + + +def length_of_largest_common_substring(str1: str, str2: str) -> int: + """Find the length of the largest common substring between two strings. + + Args: + str1: The first string. + str2: The second string. + + Returns: + The length of the largest common substring. + """ + if not str1 or not str2: + return 0 + + # Create a matrix of size (len(str1) + 1) x (len(str2) + 1) + dp = [[0] * (len(str2) + 1) for _ in range(len(str1) + 1)] + + # Variables to keep track of maximum length and ending position + max_length = 0 + + # Fill the dp matrix + for i in range(1, len(str1) + 1): + for j in range(1, len(str2) + 1): + if str1[i - 1] == str2[j - 1]: + dp[i][j] = dp[i - 1][j - 1] + 1 + if dp[i][j] > max_length: + max_length = dp[i][j] + + return max_length + + +def get_close_char(open: str, close: str | None = None) -> str: + """Check if the given character is a valid brace. + + Args: + open: The open character. + close: The close character if provided. + + Returns: + The close character. + + Raises: + ValueError: If the open character is not a valid brace. + """ + if close is not None: + return close + if open not in WRAP_MAP: + msg = f"Invalid wrap open: {open}, must be one of {WRAP_MAP.keys()}" + raise ValueError(msg) + return WRAP_MAP[open] + + +def is_wrapped(text: str, open: str, close: str | None = None) -> bool: + """Check if the given text is wrapped in the given open and close characters. + + "(a) + (b)" --> False + "((abc))" --> True + "(abc)" --> True + + Args: + text: The text to check. + open: The open character. + close: The close character. + + Returns: + Whether the text is wrapped. + """ + close = get_close_char(open, close) + if not (text.startswith(open) and text.endswith(close)): + return False + + depth = 0 + for ch in text[:-1]: + if ch == open: + depth += 1 + if ch == close: + depth -= 1 + if depth == 0: # it shouldn't close before the end + return False + return True + + +def wrap( + text: str, + open: str, + close: str | None = None, + check_first: bool = True, + num: int = 1, +) -> str: + """Wrap the given text in the given open and close characters. + + Args: + text: The text to wrap. + open: The open character. + close: The close character. + check_first: Whether to check if the text is already wrapped. + num: The number of times to wrap the text. + + Returns: + The wrapped text. + """ + close = get_close_char(open, close) + + # If desired, check if the text is already wrapped in braces. + if check_first and is_wrapped(text=text, open=open, close=close): + return text + + # Wrap the text in braces. + return f"{open * num}{text}{close * num}" + + +def indent(text: str, indent_level: int = 2) -> str: + """Indent the given text by the given indent level. + + Args: + text: The text to indent. + indent_level: The indent level. + + Returns: + The indented text. + """ + lines = text.splitlines() + if len(lines) < 2: + return text + return os.linesep.join(f"{' ' * indent_level}{line}" for line in lines) + os.linesep + + +def to_snake_case(text: str) -> str: + """Convert a string to snake case. + + The words in the text are converted to lowercase and + separated by underscores. + + Args: + text: The string to convert. + + Returns: + The snake case string. + """ + s1 = re.sub(r"(.)([A-Z][a-z]+)", r"\1_\2", text) + return re.sub(r"([a-z0-9])([A-Z])", r"\1_\2", s1).lower().replace("-", "_") + + +def to_camel_case(text: str, treat_hyphens_as_underscores: bool = True) -> str: + """Convert a string to camel case. + + The first word in the text is converted to lowercase and + the rest of the words are converted to title case, removing underscores. + + Args: + text: The string to convert. + treat_hyphens_as_underscores: Whether to allow hyphens in the string. + + Returns: + The camel case string. + """ + if treat_hyphens_as_underscores: + text = text.replace("-", "_") + words = text.split("_") + # Capitalize the first letter of each word except the first one + if len(words) == 1: + return words[0] + return words[0] + "".join([w.capitalize() for w in words[1:]]) + + +def to_title_case(text: str, sep: str = "") -> str: + """Convert a string from snake case to title case. + + Args: + text: The string to convert. + sep: The separator to use to join the words. + + Returns: + The title case string. + """ + return sep.join(word.title() for word in text.split("_")) + + +def to_kebab_case(text: str) -> str: + """Convert a string to kebab case. + + The words in the text are converted to lowercase and + separated by hyphens. + + Args: + text: The string to convert. + + Returns: + The title case string. + """ + return to_snake_case(text).replace("_", "-") + + +def make_default_page_title(app_name: str, route: str) -> str: + """Make a default page title from a route. + + Args: + app_name: The name of the app owning the page. + route: The route to make the title from. + + Returns: + The default page title. + """ + route_parts = [ + part + for part in route.split("/") + if part and not (part.startswith("[") and part.endswith("]")) + ] + + title = constants.DefaultPage.TITLE.format( + app_name, route_parts[-1] if route_parts else constants.PageNames.INDEX_ROUTE + ) + return to_title_case(title) + + +def _escape_js_string(string: str) -> str: + """Escape the string for use as a JS string literal. + + Args: + string: The string to escape. + + Returns: + The escaped string. + """ + + # TODO: we may need to re-visit this logic after new Var API is implemented. + def escape_outside_segments(segment: str): + """Escape backticks in segments outside of `${}`. + + Args: + segment: The part of the string to escape. + + Returns: + The escaped or unescaped segment. + """ + if segment.startswith("${") and segment.endswith("}"): + # Return the `${}` segment unchanged + return segment + # Escape backticks in the segment + return segment.replace(r"\`", "`").replace("`", r"\`") + + # Split the string into parts, keeping the `${}` segments + parts = re.split(r"(\$\{.*?\})", string) + escaped_parts = [escape_outside_segments(part) for part in parts] + return "".join(escaped_parts) + + +def _wrap_js_string(string: str) -> str: + """Wrap string so it looks like {`string`}. + + Args: + string: The string to wrap. + + Returns: + The wrapped string. + """ + string = wrap(string, "`") + return wrap(string, "{") + + +def format_string(string: str) -> str: + """Format the given string as a JS string literal.. + + Args: + string: The string to format. + + Returns: + The formatted string. + """ + return _wrap_js_string(_escape_js_string(string)) + + +def format_var(var: Var) -> str: + """Format the given Var as a javascript value. + + Args: + var: The Var to format. + + Returns: + The formatted Var. + """ + return str(var) + + +def format_route(route: str) -> str: + """Format the given route. + + Args: + route: The route to format. + + Returns: + The formatted route. + """ + route = route.strip("/") + + # If the route is empty, return the index route. + if route == "": + return constants.PageNames.INDEX_ROUTE + + return route + + +def format_match( + cond: str | Var, + match_cases: list[tuple[list[Var], Var]], + default: Var, +) -> str: + """Format a match expression whose return type is a Var. + + Args: + cond: The condition. + match_cases: The list of cases to match. + default: The default case. + + Returns: + The formatted match expression + + """ + switch_code = f"(() => {{ switch (JSON.stringify({cond})) {{" + + for case in match_cases: + conditions, return_value = case + + case_conditions = " ".join([ + f"case JSON.stringify({condition!s}):" for condition in conditions + ]) + case_code = f"{case_conditions} return ({return_value!s}); break;" + switch_code += case_code + + switch_code += f"default: return ({default!s}); break;" + switch_code += "};})()" + + return switch_code + + +def format_prop( + prop: Var | EventChain | ComponentStyle | str, +) -> int | float | str: + """Format a prop. + + Args: + prop: The prop to format. + + Returns: + The formatted prop to display within a tag. + + Raises: + exceptions.InvalidStylePropError: If the style prop value is not a valid type. + TypeError: If the prop is not valid. + ValueError: If the prop is not a string. + """ + # import here to avoid circular import. + from reflex.event import EventChain + from reflex_core.utils import serializers + from reflex_core.vars import Var + + try: + # Handle var props. + if isinstance(prop, Var): + return str(prop) + + # Handle event props. + if isinstance(prop, EventChain): + return str(Var.create(prop)) + + # Handle other types. + if isinstance(prop, str): + if is_wrapped(prop, "{"): + return prop + return json_dumps(prop) + + # For dictionaries, convert any properties to strings. + if isinstance(prop, dict): + prop = serializers.serialize_dict(prop) # pyright: ignore [reportAttributeAccessIssue] + + else: + # Dump the prop as JSON. + prop = json_dumps(prop) + except exceptions.InvalidStylePropError: + raise + except TypeError as e: + msg = f"Could not format prop: {prop} of type {type(prop)}" + raise TypeError(msg) from e + + # Wrap the variable in braces. + if not isinstance(prop, str): + msg = f"Invalid prop: {prop}. Expected a string." + raise ValueError(msg) + return wrap(prop, "{", check_first=False) + + +def format_props(*single_props, **key_value_props) -> list[str]: + """Format the tag's props. + + Args: + single_props: Props that are not key-value pairs. + key_value_props: Props that are key-value pairs. + + Returns: + The formatted props list. + """ + # Format all the props. + from reflex_core.vars import LiteralStringVar, LiteralVar, Var + + return [ + (str(LiteralStringVar.create(name)) if "-" in name else name) + + ":" + + str(format_prop(prop if isinstance(prop, Var) else LiteralVar.create(prop))) + for name, prop in sorted(key_value_props.items()) + if prop is not None + ] + [(f"...{LiteralVar.create(prop)!s}") for prop in single_props] + + +def get_event_handler_parts(handler: EventHandler) -> tuple[str, str]: + """Get the state and function name of an event handler. + + Args: + handler: The event handler to get the parts of. + + Returns: + The state and function name. + """ + # Get the class that defines the event handler. + parts = handler.fn.__qualname__.split(".") + + # Get the state full name + state_full_name = handler.state_full_name + + # If there's no enclosing class, just return the function name. + if not state_full_name: + return ("", parts[-1]) + + # Get the function name + name = parts[-1] + + from reflex.state import State + + if state_full_name == FRONTEND_EVENT_STATE and name not in State.__dict__: + return ("", to_snake_case(handler.fn.__qualname__)) + + return (state_full_name, name) + + +def format_event_handler(handler: EventHandler) -> str: + """Format an event handler. + + Args: + handler: The event handler to format. + + Returns: + The formatted function. + """ + state, name = get_event_handler_parts(handler) + if state == "": + return name + return f"{state}.{name}" + + +def format_event(event_spec: EventSpec) -> str: + """Format an event. + + Args: + event_spec: The event to format. + + Returns: + The compiled event. + """ + args = ",".join([ + ":".join(( + name._js_expr, + ( + wrap( + json.dumps(val._js_expr).strip('"').replace("`", "\\`"), + "`", + ) + if val._var_is_string + else str(val) + ), + )) + for name, val in event_spec.args + ]) + event_args = [ + wrap(format_event_handler(event_spec.handler), '"'), + ] + event_args.append(wrap(args, "{")) + + if event_spec.client_handler_name: + event_args.append(wrap(event_spec.client_handler_name, '"')) + return f"ReflexEvent({', '.join(event_args)})" + + +if TYPE_CHECKING: + from reflex_core.vars import Var + + +def format_queue_events( + events: EventType[Any] | None = None, + args_spec: ArgsSpec | None = None, +) -> Var[EventChain]: + """Format a list of event handler / event spec as a javascript callback. + + The resulting code can be passed to interfaces that expect a callback + function and when triggered it will directly call queueEvents. + + It is intended to be executed in the rx.call_script context, where some + existing API needs a callback to trigger a backend event handler. + + Args: + events: The events to queue. + args_spec: The argument spec for the callback. + + Returns: + The compiled javascript callback to queue the given events on the frontend. + + Raises: + ValueError: If a lambda function is given which returns a Var. + """ + from reflex.event import ( + EventChain, + EventHandler, + EventSpec, + call_event_fn, + call_event_handler, + ) + from reflex_core.vars import FunctionVar, Var + + if not events: + return Var("(() => null)").to(FunctionVar, EventChain) + + # If no spec is provided, the function will take no arguments. + def _default_args_spec(): + return [] + + # Construct the arguments that the function accepts. + sig = inspect.signature(args_spec or _default_args_spec) + if sig.parameters: + arg_def = ",".join(f"_{p}" for p in sig.parameters) + arg_def = f"({arg_def})" + else: + arg_def = "()" + + payloads = [] + if not isinstance(events, list): + events = [events] + + # Process each event/spec/lambda (similar to Component._create_event_chain). + for spec in events: + specs: list[EventSpec] = [] + if isinstance(spec, (EventHandler, EventSpec)): + specs = [call_event_handler(spec, args_spec or _default_args_spec)] + elif isinstance(spec, type(lambda: None)): + specs = call_event_fn(spec, args_spec or _default_args_spec) # pyright: ignore [reportAssignmentType, reportArgumentType] + if isinstance(specs, Var): + msg = f"Invalid event spec: {specs}. Expected a list of EventSpecs." + raise ValueError(msg) + payloads.extend(format_event(s) for s in specs) + + # Return the final code snippet, expecting queueEvents, processEvent, and socket to be in scope. + # Typically this snippet will _only_ run from within an rx.call_script eval context. + return Var( + f"{arg_def} => {{queueEvents([{','.join(payloads)}], {constants.CompileVars.SOCKET}, false, navigate, params);" + f"processEvent({constants.CompileVars.SOCKET}, navigate, params);}}", + ).to(FunctionVar, EventChain) + + +def format_query_params(router_data: dict[str, Any]) -> dict[str, str]: + """Convert back query params name to python-friendly case. + + Args: + router_data: the router_data dict containing the query params + + Returns: + The reformatted query params + """ + params = router_data[constants.RouteVar.QUERY] + return {k.replace("-", "_"): v for k, v in params.items()} + + +def format_state_name(state_name: str) -> str: + """Format a state name, replacing dots with double underscore. + + This allows individual substates to be accessed independently as javascript vars + without using dot notation. + + Args: + state_name: The state name to format. + + Returns: + The formatted state name. + """ + return state_name.replace(".", "__") + + +def format_ref(ref: str) -> str: + """Format a ref. + + Args: + ref: The ref to format. + + Returns: + The formatted ref. + """ + # Replace all non-word characters with underscores. + clean_ref = re.sub(r"[^\w]+", "_", ref) + return f"ref_{clean_ref}" + + +def format_library_name(library_fullname: str | dict[str, Any]) -> str: + """Format the name of a library. + + Args: + library_fullname: The library reference, either as a string or a dictionary with a 'name' key. + + Returns: + The name without the @version if it was part of the name + + Raises: + KeyError: If library_fullname is a dictionary without a 'name' key. + TypeError: If library_fullname or its 'name' value is not a string. + """ + # If input is a dictionary, extract the 'name' key + if isinstance(library_fullname, dict): + if "name" not in library_fullname: + msg = "Dictionary input must contain a 'name' key" + raise KeyError(msg) + library_fullname = library_fullname["name"] + + # Process the library name as a string + if not isinstance(library_fullname, str): + msg = "Library name must be a string" + raise TypeError(msg) + + if library_fullname.startswith("https://"): + return library_fullname + + lib, at, version = library_fullname.rpartition("@") + if not lib: + lib = at + version + + return lib + + +def json_dumps(obj: Any, **kwargs) -> str: + """Takes an object and returns a jsonified string. + + Args: + obj: The object to be serialized. + kwargs: Additional keyword arguments to pass to json.dumps. + + Returns: + A string + """ + from reflex_core.utils import serializers + + kwargs.setdefault("ensure_ascii", False) + kwargs.setdefault("default", serializers.serialize) + + return json.dumps(obj, **kwargs) + + +def collect_form_dict_names(form_dict: dict[str, Any]) -> dict[str, Any]: + """Collapse keys with consecutive suffixes into a single list value. + + Separators dash and underscore are removed, unless this would overwrite an existing key. + + Args: + form_dict: The dict to collapse. + + Returns: + The collapsed dict. + """ + ending_digit_regex = re.compile(r"^(.*?)[_-]?(\d+)$") + collapsed = {} + for k in sorted(form_dict): + m = ending_digit_regex.match(k) + if m: + collapsed.setdefault(m.group(1), []).append(form_dict[k]) + # collapsing never overwrites valid data from the form_dict + collapsed.update(form_dict) + return collapsed + + +def format_array_ref(refs: str, idx: Var | None) -> str: + """Format a ref accessed by array. + + Args: + refs : The ref array to access. + idx : The index of the ref in the array. + + Returns: + The formatted ref. + """ + clean_ref = re.sub(r"[^\w]+", "_", refs) + if idx is not None: + return f"refs_{clean_ref}[{idx!s}]" + return f"refs_{clean_ref}" + + +def format_data_editor_column(col: str | dict): + """Format a given column into the proper format. + + Args: + col: The column. + + Returns: + The formatted column. + + Raises: + ValueError: invalid type provided for column. + """ + from reflex_core.vars import Var + + if isinstance(col, str): + return {"title": col, "id": col.lower(), "type": "str"} + + if isinstance(col, (dict,)): + if "id" not in col: + col["id"] = col["title"].lower() + if "type" not in col: + col["type"] = "str" + if "overlayIcon" not in col: + col["overlayIcon"] = None + return col + + if isinstance(col, Var): + return col + + msg = f"unexpected type ({(type(col).__name__)}: {col}) for column header in data_editor" + raise ValueError(msg) + + +def format_data_editor_cell(cell: Any): + """Format a given data into a renderable cell for data_editor. + + Args: + cell: The data to format. + + Returns: + The formatted cell. + """ + from reflex_core.vars.base import Var + + return { + "kind": Var(_js_expr="GridCellKind.Text"), + "data": cell, + } diff --git a/packages/reflex-core/src/reflex_core/utils/imports.py b/packages/reflex-core/src/reflex_core/utils/imports.py new file mode 100644 index 00000000000..e4ec1935739 --- /dev/null +++ b/packages/reflex-core/src/reflex_core/utils/imports.py @@ -0,0 +1,150 @@ +"""Import operations.""" + +from __future__ import annotations + +import dataclasses +from collections import defaultdict +from collections.abc import Mapping, Sequence + + +def merge_parsed_imports( + *imports: ImmutableParsedImportDict, +) -> ParsedImportDict: + """Merge multiple parsed import dicts together. + + Args: + *imports: The list of import dicts to merge. + + Returns: + The merged import dicts. + """ + all_imports: defaultdict[str, list[ImportVar]] = defaultdict(list) + for import_dict in imports: + for lib, fields in import_dict.items(): + all_imports[lib].extend(fields) + return all_imports + + +def merge_imports( + *imports: ImportDict | ParsedImportDict | ParsedImportTuple, +) -> ParsedImportDict: + """Merge multiple import dicts together. + + Args: + *imports: The list of import dicts to merge. + + Returns: + The merged import dicts. + """ + all_imports: defaultdict[str, list[ImportVar]] = defaultdict(list) + for import_dict in imports: + for lib, fields in ( + import_dict if isinstance(import_dict, tuple) else import_dict.items() + ): + # If the lib is an absolute path, we need to prefix it with a $ + lib = ( + "$" + lib + if lib.startswith(("/utils/", "/components/", "/styles/", "/public/")) + else lib + ) + if isinstance(fields, (list, tuple, set)): + all_imports[lib].extend( + ImportVar(field) if isinstance(field, str) else field + for field in fields + ) + else: + all_imports[lib].append( + ImportVar(fields) if isinstance(fields, str) else fields + ) + return all_imports + + +def parse_imports( + imports: ImmutableImportDict | ImmutableParsedImportDict, +) -> ParsedImportDict: + """Parse the import dict into a standard format. + + Args: + imports: The import dict to parse. + + Returns: + The parsed import dict. + """ + return { + package: [maybe_tags] + if isinstance(maybe_tags, ImportVar) + else [ImportVar(tag=maybe_tags)] + if isinstance(maybe_tags, str) + else [ImportVar(tag=tag) if isinstance(tag, str) else tag for tag in maybe_tags] + for package, maybe_tags in imports.items() + } + + +def collapse_imports( + imports: ParsedImportDict | ParsedImportTuple, +) -> ParsedImportDict: + """Remove all duplicate ImportVar within an ImportDict. + + Args: + imports: The import dict to collapse. + + Returns: + The collapsed import dict. + """ + return { + lib: ( + list(set(import_vars)) + if isinstance(import_vars, list) + else list(import_vars) + ) + for lib, import_vars in ( + imports if isinstance(imports, tuple) else imports.items() + ) + } + + +@dataclasses.dataclass(frozen=True) +class ImportVar: + """An import var.""" + + # The name of the import tag. + tag: str | None + + # whether the import is default or named. + is_default: bool | None = False + + # The tag alias. + alias: str | None = None + + # Whether this import need to install the associated lib + install: bool | None = True + + # whether this import should be rendered or not + render: bool | None = True + + # The path of the package to import from. + package_path: str = "/" + + @property + def name(self) -> str: + """The name of the import. + + Returns: + The name(tag name with alias) of tag. + """ + if self.alias: + return ( + self.alias + if self.is_default and self.tag != "*" + else (self.tag + " as " + self.alias if self.tag else self.alias) + ) + return self.tag or "" + + +ImportTypes = str | ImportVar | list[str | ImportVar] | list[ImportVar] +ImmutableImportTypes = str | ImportVar | Sequence[str | ImportVar] +ImportDict = dict[str, ImportTypes] +ImmutableImportDict = Mapping[str, ImmutableImportTypes] +ParsedImportDict = dict[str, list[ImportVar]] +ImmutableParsedImportDict = Mapping[str, Sequence[ImportVar]] +ParsedImportTuple = tuple[tuple[str, tuple[ImportVar, ...]], ...] diff --git a/packages/reflex-core/src/reflex_core/utils/pyi_generator.py b/packages/reflex-core/src/reflex_core/utils/pyi_generator.py new file mode 100644 index 00000000000..562bd742e3b --- /dev/null +++ b/packages/reflex-core/src/reflex_core/utils/pyi_generator.py @@ -0,0 +1,1652 @@ +"""The pyi generator module.""" + +from __future__ import annotations + +import ast +import contextlib +import importlib +import inspect +import json +import logging +import multiprocessing +import re +import subprocess +import sys +import typing +from collections.abc import Callable, Iterable, Mapping, Sequence +from concurrent.futures import ProcessPoolExecutor +from functools import cache +from hashlib import md5 +from inspect import getfullargspec +from itertools import chain +from pathlib import Path +from types import MappingProxyType, ModuleType, SimpleNamespace, UnionType +from typing import Any, get_args, get_origin + +from reflex.components.component import Component +from reflex_core.vars.base import Var + + +def _is_union(cls: Any) -> bool: + origin = getattr(cls, "__origin__", None) + if origin is typing.Union: + return True + return origin is None and isinstance(cls, UnionType) + + +def _is_optional(cls: Any) -> bool: + return ( + cls is None + or cls is type(None) + or (_is_union(cls) and type(None) in get_args(cls)) + ) + + +def _is_literal(cls: Any) -> bool: + return getattr(cls, "__origin__", None) is typing.Literal + + +def _safe_issubclass(cls: Any, cls_check: Any | tuple[Any, ...]) -> bool: + try: + return issubclass(cls, cls_check) + except TypeError: + return False + + +logger = logging.getLogger("pyi_generator") + +PWD = Path.cwd() + +PYI_HASHES = "pyi_hashes.json" + +EXCLUDED_FILES = [ + "app.py", + "component.py", + "bare.py", + "foreach.py", + "cond.py", + "match.py", + "multiselect.py", + "literals.py", +] + +# These props exist on the base component, but should not be exposed in create methods. +EXCLUDED_PROPS = [ + "alias", + "children", + "event_triggers", + "library", + "lib_dependencies", + "tag", + "is_default", + "special_props", + "_is_tag_in_global_scope", + "_invalid_children", + "_memoization_mode", + "_rename_props", + "_valid_children", + "_valid_parents", + "State", +] + +OVERWRITE_TYPES = { + "style": "Sequence[Mapping[str, Any]] | Mapping[str, Any] | Var[Mapping[str, Any]] | Breakpoints | None", +} + +DEFAULT_TYPING_IMPORTS = { + "Any", + "Callable", + "Dict", + # "List", + "Sequence", + "Mapping", + "Literal", + "Optional", + "Union", + "Annotated", +} + +# TODO: fix import ordering and unused imports with ruff later +DEFAULT_IMPORTS = { + "typing": sorted(DEFAULT_TYPING_IMPORTS), + "reflex_components_core.core.breakpoints": ["Breakpoints"], + "reflex.event": [ + "EventChain", + "EventHandler", + "EventSpec", + "EventType", + "KeyInputInfo", + "PointerEventInfo", + ], + "reflex.style": ["Style"], + "reflex.vars.base": ["Var"], +} + + +def _walk_files(path: str | Path): + """Walk all files in a path. + This can be replaced with Path.walk() in python3.12. + + Args: + path: The path to walk. + + Yields: + The next file in the path. + """ + for p in Path(path).iterdir(): + if p.is_dir(): + yield from _walk_files(p) + continue + yield p.resolve() + + +def _relative_to_pwd(path: Path) -> Path: + """Get the relative path of a path to the current working directory. + + Args: + path: The path to get the relative path for. + + Returns: + The relative path. + """ + if path.is_absolute(): + return path.relative_to(PWD) + return path + + +def _get_type_hint( + value: Any, type_hint_globals: dict, is_optional: bool = True +) -> str: + """Resolve the type hint for value. + + Args: + value: The type annotation as a str or actual types/aliases. + type_hint_globals: The globals to use to resolving a type hint str. + is_optional: Whether the type hint should be wrapped in Optional. + + Returns: + The resolved type hint as a str. + + Raises: + TypeError: If the value name is not visible in the type hint globals. + """ + res = "" + args = get_args(value) + + if value is type(None) or value is None: + return "None" + + if _is_union(value): + if type(None) in value.__args__: + res_args = [ + _get_type_hint(arg, type_hint_globals, _is_optional(arg)) + for arg in value.__args__ + if arg is not type(None) + ] + res_args.sort() + if len(res_args) == 1: + return f"{res_args[0]} | None" + res = f"{' | '.join(res_args)}" + return f"{res} | None" + + res_args = [ + _get_type_hint(arg, type_hint_globals, _is_optional(arg)) + for arg in value.__args__ + ] + res_args.sort() + return f"{' | '.join(res_args)}" + + if args: + inner_container_type_args = ( + sorted(repr(arg) for arg in args) + if _is_literal(value) + else [ + _get_type_hint(arg, type_hint_globals, is_optional=False) + for arg in args + if arg is not type(None) + ] + ) + + if ( + value.__module__ not in ["builtins", "__builtins__"] + and value.__name__ not in type_hint_globals + ): + msg = ( + f"{value.__module__ + '.' + value.__name__} is not a default import, " + "add it to DEFAULT_IMPORTS in pyi_generator.py" + ) + raise TypeError(msg) + + res = f"{value.__name__}[{', '.join(inner_container_type_args)}]" + + if value.__name__ == "Var": + args = list( + chain.from_iterable([ + get_args(arg) if _is_union(arg) else [arg] for arg in args + ]) + ) + + # For Var types, Union with the inner args so they can be passed directly. + types = [res] + [ + _get_type_hint(arg, type_hint_globals, is_optional=False) + for arg in args + if arg is not type(None) + ] + if len(types) > 1: + res = " | ".join(sorted(types)) + + elif isinstance(value, str): + ev = eval(value, type_hint_globals) + if _is_optional(ev): + return _get_type_hint(ev, type_hint_globals, is_optional=False) + + if _is_union(ev): + res = [ + _get_type_hint(arg, type_hint_globals, _is_optional(arg)) + for arg in ev.__args__ + ] + return f"{' | '.join(res)}" + res = ( + _get_type_hint(ev, type_hint_globals, is_optional=False) + if ev.__name__ == "Var" + else value + ) + elif isinstance(value, list): + res = [ + _get_type_hint(arg, type_hint_globals, _is_optional(arg)) for arg in value + ] + return f"[{', '.join(res)}]" + else: + res = value.__name__ + if is_optional and not res.startswith("Optional") and not res.endswith("| None"): + res = f"{res} | None" + return res + + +@cache +def _get_source(obj: Any) -> str: + """Get and cache the source for a Python object. + + Args: + obj: The object whose source should be retrieved. + + Returns: + The source code for the object. + """ + return inspect.getsource(obj) + + +@cache +def _get_class_prop_comments(clz: type[Component]) -> Mapping[str, tuple[str, ...]]: + """Parse and cache prop comments for a component class. + + Args: + clz: The class to extract prop comments from. + + Returns: + An immutable mapping of prop name to comment lines. + """ + props_comments: dict[str, tuple[str, ...]] = {} + comments = [] + for line in _get_source(clz).splitlines(): + reached_functions = re.search(r"def ", line) + if reached_functions: + # We've reached the functions, so stop. + break + + if line == "": + # We hit a blank line, so clear comments to avoid commented out prop appearing in next prop docs. + comments.clear() + continue + + # Get comments for prop + if line.strip().startswith("#"): + # Remove noqa from the comments. + line = line.partition(" # noqa")[0] + comments.append(line) + continue + + # Check if this line has a prop. + match = re.search(r"\w+:", line) + if match is None: + # This line doesn't have a var, so continue. + continue + + # Get the prop. + prop = match.group(0).strip(":") + if comments: + props_comments[prop] = tuple( + comment.strip().strip("#") for comment in comments + ) + comments.clear() + + return MappingProxyType(props_comments) + + +@cache +def _get_full_argspec(func: Callable) -> inspect.FullArgSpec: + """Get and cache the full argspec for a callable. + + Args: + func: The callable to inspect. + + Returns: + The full argument specification. + """ + return getfullargspec(func) + + +@cache +def _get_signature_return_annotation(func: Callable) -> Any: + """Get and cache a callable's return annotation. + + Args: + func: The callable to inspect. + + Returns: + The callable's return annotation. + """ + return inspect.signature(func).return_annotation + + +@cache +def _get_module_star_imports(module_name: str) -> Mapping[str, Any]: + """Resolve names imported by `from module import *`. + + Args: + module_name: The module to inspect. + + Returns: + An immutable mapping of imported names to values. + """ + module = importlib.import_module(module_name) + exported_names = getattr(module, "__all__", None) + if exported_names is not None: + return MappingProxyType({ + name: getattr(module, name) for name in exported_names + }) + return MappingProxyType({ + name: value for name, value in vars(module).items() if not name.startswith("_") + }) + + +@cache +def _get_module_selected_imports( + module_name: str, imported_names: tuple[str, ...] +) -> Mapping[str, Any]: + """Resolve a set of imported names from a module. + + Args: + module_name: The module to import from. + imported_names: The names to resolve. + + Returns: + An immutable mapping of imported names to values. + """ + module = importlib.import_module(module_name) + return MappingProxyType({name: getattr(module, name) for name in imported_names}) + + +@cache +def _get_class_annotation_globals(target_class: type) -> Mapping[str, Any]: + """Get globals needed to resolve class annotations. + + Args: + target_class: The class whose annotation globals should be resolved. + + Returns: + An immutable mapping of globals for the class MRO. + """ + available_vars: dict[str, Any] = {} + for module_name in {cls.__module__ for cls in target_class.__mro__}: + available_vars.update(sys.modules[module_name].__dict__) + return MappingProxyType(available_vars) + + +@cache +def _get_class_event_triggers(target_class: type) -> frozenset[str]: + """Get and cache event trigger names for a class. + + Args: + target_class: The class to inspect. + + Returns: + The event trigger names defined on the class. + """ + return frozenset(target_class.get_event_triggers()) + + +def _generate_imports( + typing_imports: Iterable[str], +) -> list[ast.ImportFrom | ast.Import]: + """Generate the import statements for the stub file. + + Args: + typing_imports: The typing imports to include. + + Returns: + The list of import statements. + """ + return [ + *[ + ast.ImportFrom(module=name, names=[ast.alias(name=val) for val in values]) # pyright: ignore [reportCallIssue] + for name, values in DEFAULT_IMPORTS.items() + ], + ast.Import([ast.alias("reflex")]), + ] + + +def _generate_docstrings(clzs: list[type[Component]], props: list[str]) -> str: + """Generate the docstrings for the create method. + + Args: + clzs: The classes to generate docstrings for. + props: The props to generate docstrings for. + + Returns: + The docstring for the create method. + """ + props_comments = {} + for clz in clzs: + for prop, comment_lines in _get_class_prop_comments(clz).items(): + if prop in props: + props_comments[prop] = list(comment_lines) + clz = clzs[0] + new_docstring = [] + for line in (clz.create.__doc__ or "").splitlines(): + if "**" in line: + indent = line.split("**")[0] + new_docstring.extend([ + f"{indent}{n}:{' '.join(c)}" for n, c in props_comments.items() + ]) + new_docstring.append(line) + return "\n".join(new_docstring) + + +def _extract_func_kwargs_as_ast_nodes( + func: Callable, + type_hint_globals: dict[str, Any], +) -> list[tuple[ast.arg, ast.Constant | None]]: + """Get the kwargs already defined on the function. + + Args: + func: The function to extract kwargs from. + type_hint_globals: The globals to use to resolving a type hint str. + + Returns: + The list of kwargs as ast arg nodes. + """ + spec = _get_full_argspec(func) + kwargs = [] + + for kwarg in spec.kwonlyargs: + arg = ast.arg(arg=kwarg) + if kwarg in spec.annotations: + arg.annotation = ast.Name( + id=_get_type_hint(spec.annotations[kwarg], type_hint_globals) + ) + default = None + if spec.kwonlydefaults is not None and kwarg in spec.kwonlydefaults: + default = ast.Constant(value=spec.kwonlydefaults[kwarg]) + kwargs.append((arg, default)) + return kwargs + + +def _extract_class_props_as_ast_nodes( + func: Callable, + clzs: list[type], + type_hint_globals: dict[str, Any], + extract_real_default: bool = False, +) -> list[tuple[ast.arg, ast.Constant | None]]: + """Get the props defined on the class and all parents. + + Args: + func: The function that kwargs will be added to. + clzs: The classes to extract props from. + type_hint_globals: The globals to use to resolving a type hint str. + extract_real_default: Whether to extract the real default value from the + pydantic field definition. + + Returns: + The list of props as ast arg nodes + """ + spec = _get_full_argspec(func) + func_kwonlyargs = set(spec.kwonlyargs) + all_props: set[str] = set() + kwargs = [] + for target_class in clzs: + event_triggers = _get_class_event_triggers(target_class) + # Import from the target class to ensure type hints are resolvable. + type_hint_globals.update(_get_module_star_imports(target_class.__module__)) + annotation_globals = { + **type_hint_globals, + **_get_class_annotation_globals(target_class), + } + for name, value in target_class.__annotations__.items(): + if ( + name in func_kwonlyargs + or name in EXCLUDED_PROPS + or name in all_props + or name in event_triggers + or (isinstance(value, str) and "ClassVar" in value) + ): + continue + all_props.add(name) + + default = None + if extract_real_default: + # TODO: This is not currently working since the default is not type compatible + # with the annotation in some cases. + with contextlib.suppress(AttributeError, KeyError): + # Try to get default from pydantic field definition. + default = target_class.__fields__[name].default + if isinstance(default, Var): + default = default._decode() + + kwargs.append(( + ast.arg( + arg=name, + annotation=ast.Name( + id=OVERWRITE_TYPES.get( + name, + _get_type_hint( + value, + annotation_globals, + ), + ) + ), + ), + ast.Constant(value=default), # pyright: ignore [reportArgumentType] + )) + return kwargs + + +def _get_visible_type_name( + typ: Any, type_hint_globals: Mapping[str, Any] | None +) -> str | None: + """Get a visible identifier for a type in the current module. + + Args: + typ: The type annotation to resolve. + type_hint_globals: The globals visible in the current module. + + Returns: + The visible identifier if one exists, otherwise None. + """ + if type_hint_globals is None: + return None + + type_name = getattr(typ, "__name__", None) + if ( + type_name is not None + and type_name in type_hint_globals + and type_hint_globals[type_name] is typ + ): + return type_name + + for name, value in type_hint_globals.items(): + if name.isidentifier() and value is typ: + return name + + return None + + +def type_to_ast( + typ: Any, + cls: type, + type_hint_globals: Mapping[str, Any] | None = None, +) -> ast.expr: + """Converts any type annotation into its AST representation. + Handles nested generic types, unions, etc. + + Args: + typ: The type annotation to convert. + cls: The class where the type annotation is used. + type_hint_globals: The globals visible where the annotation is used. + + Returns: + The AST representation of the type annotation. + """ + if typ is type(None) or typ is None: + return ast.Name(id="None") + + origin = get_origin(typ) + if origin is typing.Literal: + return ast.Subscript( + value=ast.Name(id="Literal"), + slice=ast.Tuple( + elts=[ast.Constant(value=val) for val in get_args(typ)], ctx=ast.Load() + ), + ctx=ast.Load(), + ) + if origin is UnionType: + origin = typing.Union + + # Handle plain types (int, str, custom classes, etc.) + if origin is None: + if hasattr(typ, "__name__"): + if typ.__module__.startswith("reflex."): + typ_parts = typ.__module__.split(".") + cls_parts = cls.__module__.split(".") + + zipped = list(zip(typ_parts, cls_parts, strict=False)) + + if all(a == b for a, b in zipped) and len(typ_parts) == len(cls_parts): + return ast.Name(id=typ.__name__) + if visible_name := _get_visible_type_name(typ, type_hint_globals): + return ast.Name(id=visible_name) + if ( + typ.__module__ in DEFAULT_IMPORTS + and typ.__name__ in DEFAULT_IMPORTS[typ.__module__] + ): + return ast.Name(id=typ.__name__) + return ast.Name(id=typ.__module__ + "." + typ.__name__) + return ast.Name(id=typ.__name__) + if hasattr(typ, "_name"): + return ast.Name(id=typ._name) + return ast.Name(id=str(typ)) + + # Get the base type name (List, Dict, Optional, etc.) + base_name = getattr(origin, "_name", origin.__name__) + + # Get type arguments + args = get_args(typ) + + # Handle empty type arguments + if not args: + return ast.Name(id=base_name) + + # Convert all type arguments recursively + arg_nodes = [type_to_ast(arg, cls, type_hint_globals) for arg in args] + + # Special case for single-argument types (like list[T] or Optional[T]) + if len(arg_nodes) == 1: + slice_value = arg_nodes[0] + else: + slice_value = ast.Tuple(elts=arg_nodes, ctx=ast.Load()) + + return ast.Subscript( + value=ast.Name(id=base_name), + slice=slice_value, + ctx=ast.Load(), + ) + + +@cache +def _get_parent_imports(func: Callable) -> Mapping[str, tuple[str, ...]]: + """Get parent imports needed to resolve forwarded type hints. + + Args: + func: The callable whose annotations are being analyzed. + + Returns: + An immutable mapping of module names to imported symbol names. + """ + imports_: dict[str, set[str]] = {"reflex.vars": {"Var"}} + module_dir = set(dir(importlib.import_module(func.__module__))) + for type_hint in inspect.get_annotations(func).values(): + try: + match = re.match(r"\w+\[([\w\d]+)\]", type_hint) + except TypeError: + continue + if match: + type_hint = match.group(1) + if type_hint in module_dir: + imports_.setdefault(func.__module__, set()).add(type_hint) + return MappingProxyType({ + module_name: tuple(sorted(imported_names)) + for module_name, imported_names in imports_.items() + }) + + +def _generate_component_create_functiondef( + clz: type[Component], + type_hint_globals: dict[str, Any], + lineno: int, + decorator_list: Sequence[ast.expr] = (ast.Name(id="classmethod"),), +) -> ast.FunctionDef: + """Generate the create function definition for a Component. + + Args: + clz: The Component class to generate the create functiondef for. + type_hint_globals: The globals to use to resolving a type hint str. + lineno: The line number to use for the ast nodes. + decorator_list: The list of decorators to apply to the create functiondef. + + Returns: + The create functiondef node for the ast. + + Raises: + TypeError: If clz is not a subclass of Component. + """ + if not issubclass(clz, Component): + msg = f"clz must be a subclass of Component, not {clz!r}" + raise TypeError(msg) + + # add the imports needed by get_type_hint later + type_hint_globals.update({ + name: getattr(typing, name) for name in DEFAULT_TYPING_IMPORTS + }) + + if clz.__module__ != clz.create.__module__: + imports_ = _get_parent_imports(clz.create) + for name, values in imports_.items(): + type_hint_globals.update(_get_module_selected_imports(name, values)) + + kwargs = _extract_func_kwargs_as_ast_nodes(clz.create, type_hint_globals) + + # kwargs associated with props defined in the class and its parents + all_classes = [c for c in clz.__mro__ if issubclass(c, Component)] + prop_kwargs = _extract_class_props_as_ast_nodes( + clz.create, all_classes, type_hint_globals + ) + all_props = [arg[0].arg for arg in prop_kwargs] + kwargs.extend(prop_kwargs) + + def figure_out_return_type(annotation: Any): + if isinstance(annotation, type) and issubclass(annotation, inspect._empty): + return ast.Name(id="EventType[Any]") + + if not isinstance(annotation, str) and get_origin(annotation) is tuple: + arguments = get_args(annotation) + + arguments_without_var = [ + get_args(argument)[0] if get_origin(argument) == Var else argument + for argument in arguments + ] + + # Convert each argument type to its AST representation + type_args = [ + type_to_ast(arg, cls=clz, type_hint_globals=type_hint_globals) + for arg in arguments_without_var + ] + + # Get all prefixes of the type arguments + all_count_args_type = [ + ast.Name( + f"EventType[{', '.join([ast.unparse(arg) for arg in type_args[:i]])}]" + ) + if i > 0 + else ast.Name("EventType[()]") + for i in range(len(type_args) + 1) + ] + + # Create EventType using the joined string + return ast.Name(id=f"{' | '.join(map(ast.unparse, all_count_args_type))}") + + if isinstance(annotation, str) and annotation.lower().startswith("tuple["): + inside_of_tuple = ( + annotation + .removeprefix("tuple[") + .removeprefix("Tuple[") + .removesuffix("]") + ) + + if inside_of_tuple == "()": + return ast.Name(id="EventType[()]") + + arguments = [""] + + bracket_count = 0 + + for char in inside_of_tuple: + if char == "[": + bracket_count += 1 + elif char == "]": + bracket_count -= 1 + + if char == "," and bracket_count == 0: + arguments.append("") + else: + arguments[-1] += char + + arguments = [argument.strip() for argument in arguments] + + arguments_without_var = [ + argument.removeprefix("Var[").removesuffix("]") + if argument.startswith("Var[") + else argument + for argument in arguments + ] + + all_count_args_type = [ + ast.Name(f"EventType[{', '.join(arguments_without_var[:i])}]") + if i > 0 + else ast.Name("EventType[()]") + for i in range(len(arguments) + 1) + ] + + return ast.Name(id=f"{' | '.join(map(ast.unparse, all_count_args_type))}") + return ast.Name(id="EventType[Any]") + + event_triggers = clz.get_event_triggers() + + # event handler kwargs + kwargs.extend( + ( + ast.arg( + arg=trigger, + annotation=ast.Subscript( + ast.Name("Optional"), + ast.Name( + id=ast.unparse( + figure_out_return_type( + _get_signature_return_annotation(event_specs) + ) + if not isinstance( + event_specs := event_triggers[trigger], Sequence + ) + else ast.Subscript( + ast.Name("Union"), + ast.Tuple([ + figure_out_return_type( + _get_signature_return_annotation(event_spec) + ) + for event_spec in event_specs + ]), + ) + ) + ), + ), + ), + ast.Constant(value=None), + ) + for trigger in sorted(event_triggers) + ) + + logger.debug(f"Generated {clz.__name__}.create method with {len(kwargs)} kwargs") + create_args = ast.arguments( + args=[ast.arg(arg="cls")], + posonlyargs=[], + vararg=ast.arg(arg="children"), + kwonlyargs=[arg[0] for arg in kwargs], + kw_defaults=[arg[1] for arg in kwargs], + kwarg=ast.arg(arg="props"), + defaults=[], + ) + + return ast.FunctionDef( # pyright: ignore [reportCallIssue] + name="create", + args=create_args, + body=[ + ast.Expr( + value=ast.Constant( + value=_generate_docstrings( + all_classes, [*all_props, *event_triggers] + ) + ), + ), + ast.Expr( + value=ast.Constant(value=Ellipsis), + ), + ], + decorator_list=list(decorator_list), + lineno=lineno, + returns=ast.Constant(value=clz.__name__), + ) + + +def _generate_staticmethod_call_functiondef( + node: ast.ClassDef, + clz: type[Component] | type[SimpleNamespace], + type_hint_globals: dict[str, Any], +) -> ast.FunctionDef | None: + fullspec = _get_full_argspec(clz.__call__) + + call_args = ast.arguments( + args=[ + ast.arg( + name, + annotation=ast.Name( + id=_get_type_hint( + anno := fullspec.annotations[name], + type_hint_globals, + is_optional=_is_optional(anno), + ) + ), + ) + for name in fullspec.args + ], + posonlyargs=[], + kwonlyargs=[], + kw_defaults=[], + kwarg=ast.arg(arg="props"), + defaults=( + [ast.Constant(value=default) for default in fullspec.defaults] + if fullspec.defaults + else [] + ), + ) + return ast.FunctionDef( # pyright: ignore [reportCallIssue] + name="__call__", + args=call_args, + body=[ + ast.Expr(value=ast.Constant(value=clz.__call__.__doc__)), + ast.Expr( + value=ast.Constant(...), + ), + ], + decorator_list=[ast.Name(id="staticmethod")], + lineno=node.lineno, + returns=ast.Constant( + value=_get_type_hint( + typing.get_type_hints(clz.__call__).get("return", None), + type_hint_globals, + is_optional=False, + ) + ), + ) + + +def _generate_namespace_call_functiondef( + node: ast.ClassDef, + clz_name: str, + classes: dict[str, type[Component] | type[SimpleNamespace]], + type_hint_globals: dict[str, Any], +) -> ast.FunctionDef | None: + """Generate the __call__ function definition for a SimpleNamespace. + + Args: + node: The existing __call__ classdef parent node from the ast + clz_name: The name of the SimpleNamespace class to generate the __call__ functiondef for. + classes: Map name to actual class definition. + type_hint_globals: The globals to use to resolving a type hint str. + + Returns: + The create functiondef node for the ast. + """ + # add the imports needed by get_type_hint later + type_hint_globals.update({ + name: getattr(typing, name) for name in DEFAULT_TYPING_IMPORTS + }) + + clz = classes[clz_name] + + if not hasattr(clz.__call__, "__self__"): + return _generate_staticmethod_call_functiondef(node, clz, type_hint_globals) + + # Determine which class is wrapped by the namespace __call__ method + component_clz = clz.__call__.__self__ + + if clz.__call__.__func__.__name__ != "create": # pyright: ignore [reportFunctionMemberAccess] + return None + + if not issubclass(component_clz, Component): + return None + + definition = _generate_component_create_functiondef( + clz=component_clz, + type_hint_globals=type_hint_globals, + lineno=node.lineno, + decorator_list=[], + ) + definition.name = "__call__" + + # Turn the definition into a staticmethod + del definition.args.args[0] # remove `cls` arg + definition.decorator_list = [ast.Name(id="staticmethod")] + + return definition + + +class StubGenerator(ast.NodeTransformer): + """A node transformer that will generate the stubs for a given module.""" + + def __init__( + self, + module: ModuleType, + classes: dict[str, type[Component | SimpleNamespace]], + ): + """Initialize the stub generator. + + Args: + module: The actual module object module to generate stubs for. + classes: The actual Component class objects to generate stubs for. + """ + super().__init__() + # Dict mapping class name to actual class object. + self.classes = classes + # Track the last class node that was visited. + self.current_class = None + # These imports will be included in the AST of stub files. + self.typing_imports = DEFAULT_TYPING_IMPORTS.copy() + # Whether those typing imports have been inserted yet. + self.inserted_imports = False + # This dict is used when evaluating type hints. + self.type_hint_globals = module.__dict__.copy() + + @staticmethod + def _remove_docstring( + node: ast.Module | ast.ClassDef | ast.FunctionDef, + ) -> ast.Module | ast.ClassDef | ast.FunctionDef: + """Removes any docstring in place. + + Args: + node: The node to remove the docstring from. + + Returns: + The modified node. + """ + if ( + node.body + and isinstance(node.body[0], ast.Expr) + and isinstance(node.body[0].value, ast.Constant) + ): + node.body.pop(0) + return node + + def _current_class_is_component(self) -> type[Component] | None: + """Check if the current class is a Component. + + Returns: + Whether the current class is a Component. + """ + if ( + self.current_class is not None + and self.current_class in self.classes + and issubclass((clz := self.classes[self.current_class]), Component) + ): + return clz + return None + + def visit_Module(self, node: ast.Module) -> ast.Module: + """Visit a Module node and remove docstring from body. + + Args: + node: The Module node to visit. + + Returns: + The modified Module node. + """ + self.generic_visit(node) + return self._remove_docstring(node) # pyright: ignore [reportReturnType] + + def visit_Import( + self, node: ast.Import | ast.ImportFrom + ) -> ast.Import | ast.ImportFrom | list[ast.Import | ast.ImportFrom]: + """Collect import statements from the module. + + If this is the first import statement, insert the typing imports before it. + + Args: + node: The import node to visit. + + Returns: + The modified import node(s). + """ + if not self.inserted_imports: + self.inserted_imports = True + default_imports = _generate_imports(self.typing_imports) + return [*default_imports, node] + return node + + def visit_ImportFrom( + self, node: ast.ImportFrom + ) -> ast.Import | ast.ImportFrom | list[ast.Import | ast.ImportFrom] | None: + """Visit an ImportFrom node. + + Remove any `from __future__ import *` statements, and hand off to visit_Import. + + Args: + node: The ImportFrom node to visit. + + Returns: + The modified ImportFrom node. + """ + if node.module == "__future__": + return None # ignore __future__ imports: https://docs.astral.sh/ruff/rules/future-annotations-in-stub/ + return self.visit_Import(node) + + def visit_ClassDef(self, node: ast.ClassDef) -> ast.ClassDef: + """Visit a ClassDef node. + + Remove all assignments in the class body, and add a create functiondef + if one does not exist. + + Args: + node: The ClassDef node to visit. + + Returns: + The modified ClassDef node. + """ + self.current_class = node.name + self._remove_docstring(node) + + # Define `__call__` as a real function so the docstring appears in the stub. + call_definition = None + for child in node.body[:]: + found_call = False + if ( + isinstance(child, ast.AnnAssign) + and isinstance(child.target, ast.Name) + and child.target.id.startswith("_") + ): + node.body.remove(child) + if isinstance(child, ast.Assign): + for target in child.targets[:]: + if isinstance(target, ast.Name) and target.id == "__call__": + child.targets.remove(target) + found_call = True + if not found_call: + continue + if not child.targets[:]: + node.body.remove(child) + call_definition = _generate_namespace_call_functiondef( + node, + self.current_class, + self.classes, + type_hint_globals=self.type_hint_globals, + ) + break + + self.generic_visit(node) # Visit child nodes. + + if ( + not any( + isinstance(child, ast.FunctionDef) and child.name == "create" + for child in node.body + ) + and (clz := self._current_class_is_component()) is not None + ): + # Add a new .create FunctionDef since one does not exist. + node.body.append( + _generate_component_create_functiondef( + clz=clz, + type_hint_globals=self.type_hint_globals, + lineno=node.lineno, + ) + ) + if call_definition is not None: + node.body.append(call_definition) + if not node.body: + # We should never return an empty body. + node.body.append(ast.Expr(value=ast.Constant(value=Ellipsis))) + self.current_class = None + return node + + def visit_FunctionDef(self, node: ast.FunctionDef) -> Any: + """Visit a FunctionDef node. + + Special handling for `.create` functions to add type hints for all props + defined on the component class. + + Remove all private functions and blank out the function body of the + remaining public functions. + + Args: + node: The FunctionDef node to visit. + + Returns: + The modified FunctionDef node (or None). + """ + if ( + node.name == "create" + and self.current_class in self.classes + and issubclass((clz := self.classes[self.current_class]), Component) + ): + node = _generate_component_create_functiondef( + clz=clz, + type_hint_globals=self.type_hint_globals, + lineno=node.lineno, + decorator_list=node.decorator_list, + ) + else: + if node.name.startswith("_") and node.name != "__call__": + return None # remove private methods + + if node.body[-1] != ast.Expr(value=ast.Constant(value=Ellipsis)): + # Blank out the function body for public functions. + node.body = [ast.Expr(value=ast.Constant(value=Ellipsis))] + return node + + def visit_Assign(self, node: ast.Assign) -> ast.Assign | None: + """Remove non-annotated assignment statements. + + Args: + node: The Assign node to visit. + + Returns: + The modified Assign node (or None). + """ + # Special case for assignments to `typing.Any` as fallback. + if ( + node.value is not None + and isinstance(node.value, ast.Name) + and node.value.id == "Any" + ): + return node + + if self._current_class_is_component(): + # Remove annotated assignments in Component classes (props) + return None + + # remove dunder method assignments for lazy_loader.attach + for target in node.targets: + if isinstance(target, ast.Tuple): + for name in target.elts: + if isinstance(name, ast.Name) and name.id.startswith("_"): + return None + + return node + + def visit_AnnAssign(self, node: ast.AnnAssign) -> ast.AnnAssign | None: + """Visit an AnnAssign node (Annotated assignment). + + Remove private target and remove the assignment value in the stub. + + Args: + node: The AnnAssign node to visit. + + Returns: + The modified AnnAssign node (or None). + """ + # skip ClassVars + if ( + isinstance(node.annotation, ast.Subscript) + and isinstance(node.annotation.value, ast.Name) + and node.annotation.value.id == "ClassVar" + ): + return node + if isinstance(node.target, ast.Name) and node.target.id.startswith("_"): + return None + if self._current_class_is_component(): + # Remove annotated assignments in Component classes (props) + return None + # Blank out assignments in type stubs. + node.value = None + return node + + +class InitStubGenerator(StubGenerator): + """A node transformer that will generate the stubs for a given init file.""" + + def visit_Import( + self, node: ast.Import | ast.ImportFrom + ) -> ast.Import | ast.ImportFrom | list[ast.Import | ast.ImportFrom]: + """Collect import statements from the init module. + + Args: + node: The import node to visit. + + Returns: + The modified import node(s). + """ + return [node] + + +def _path_to_module_name(path: Path) -> str: + """Convert a file path to a dotted module name. + + Args: + path: The file path to convert. + + Returns: + The dotted module name. + """ + return _relative_to_pwd(path).with_suffix("").as_posix().replace("/", ".") + + +def _write_pyi_file(module_path: Path, source: str) -> str: + relpath = str(_relative_to_pwd(module_path)).replace("\\", "/") + pyi_content = ( + "\n".join([ + f'"""Stub file for {relpath}"""', + "# ------------------- DO NOT EDIT ----------------------", + "# This file was generated by `reflex/utils/pyi_generator.py`!", + "# ------------------------------------------------------", + "", + ]) + + source + ) + + pyi_path = module_path.with_suffix(".pyi") + pyi_path.write_text(pyi_content) + logger.info(f"Wrote {relpath}") + return md5(pyi_content.encode()).hexdigest() + + +# Mapping from component subpackage name to its target Python package. +_COMPONENT_SUBPACKAGE_TARGETS: dict[str, str] = { + # reflex-components (base package) + "base": "reflex_components_core.base", + "core": "reflex_components_core.core", + "datadisplay": "reflex_components_core.datadisplay", + "el": "reflex_components_core.el", + "gridjs": "reflex_components_gridjs", + "lucide": "reflex_components_lucide", + "moment": "reflex_components_moment", + # Deep overrides (datadisplay split) + "datadisplay.code": "reflex_components_code.code", + "datadisplay.shiki_code_block": "reflex_components_code.shiki_code_block", + "datadisplay.dataeditor": "reflex_components_dataeditor.dataeditor", + # Standalone packages + "markdown": "reflex_components_markdown", + "plotly": "reflex_components_plotly", + "radix": "reflex_components_radix", + "react_player": "reflex_components_react_player", + "react_router": "reflex_components_react_router", + "recharts": "reflex_components_recharts", + "sonner": "reflex_components_sonner", +} + + +def _rewrite_component_import(module: str) -> str: + """Rewrite a lazy-loader module path to the correct absolute package import. + + Args: + module: The module path from ``_SUBMOD_ATTRS`` (e.g. ``"components.radix.themes.base"``). + + Returns: + An absolute import path (``"reflex_components_radix.themes.base"``) for moved + components, or a relative path (``".components.component"``) for everything else. + """ + if module == "components": + # "components": ["el", "radix", ...] — these are re-exported submodules. + # Can't map to a single package, but the pyi generator handles each attr individually. + return "reflex_components_core" + if module.startswith("components."): + rest = module[len("components.") :] + # Try progressively deeper matches (e.g. "datadisplay.code" before "datadisplay"). + parts = rest.split(".") + for depth in range(min(len(parts), 2), 0, -1): + key = ".".join(parts[:depth]) + target = _COMPONENT_SUBPACKAGE_TARGETS.get(key) + if target is not None: + remainder = ".".join(parts[depth:]) + return f"{target}.{remainder}" if remainder else target + return f".{module}" + + +def _get_init_lazy_imports(mod: tuple | ModuleType, new_tree: ast.AST): + # retrieve the _SUBMODULES and _SUBMOD_ATTRS from an init file if present. + sub_mods: set[str] | None = getattr(mod, "_SUBMODULES", None) + sub_mod_attrs: dict[str, list[str | tuple[str, str]]] | None = getattr( + mod, "_SUBMOD_ATTRS", None + ) + extra_mappings: dict[str, str] | None = getattr(mod, "_EXTRA_MAPPINGS", None) + + if not sub_mods and not sub_mod_attrs and not extra_mappings: + return None + sub_mods_imports = [] + sub_mod_attrs_imports = [] + extra_mappings_imports = [] + + if sub_mods: + sub_mods_imports = [f"from . import {mod}" for mod in sorted(sub_mods)] + sub_mods_imports.append("") + + if sub_mod_attrs: + flattened_sub_mod_attrs = { + imported: module + for module, attrs in sub_mod_attrs.items() + for imported in attrs + } + # construct the import statement and handle special cases for aliases + for imported, module in flattened_sub_mod_attrs.items(): + # For "components": ["el", "radix", ...], resolve each attr to its package. + if ( + module == "components" + and isinstance(imported, str) + and imported in _COMPONENT_SUBPACKAGE_TARGETS + ): + target = _COMPONENT_SUBPACKAGE_TARGETS[imported] + sub_mod_attrs_imports.append(f"import {target} as {imported}") + continue + + rewritten = _rewrite_component_import(module) + if isinstance(imported, tuple): + suffix = ( + (imported[0] + " as " + imported[1]) + if imported[0] != imported[1] + else imported[0] + ) + else: + suffix = imported + sub_mod_attrs_imports.append(f"from {rewritten} import {suffix}") + sub_mod_attrs_imports.append("") + + if extra_mappings: + for alias, import_path in extra_mappings.items(): + module_name, import_name = import_path.rsplit(".", 1) + extra_mappings_imports.append( + f"from {module_name} import {import_name} as {alias}" + ) + + text = ( + "\n" + + "\n".join([ + *sub_mods_imports, + *sub_mod_attrs_imports, + *extra_mappings_imports, + ]) + + "\n" + ) + text += ast.unparse(new_tree) + "\n\n" + text += f"__all__ = {getattr(mod, '__all__', [])!r}\n" + return text + + +def _scan_file(module_path: Path) -> tuple[str, str] | None: + """Process a single Python file and generate its .pyi stub. + + Args: + module_path: Path to the Python source file. + + Returns: + Tuple of (pyi_path, content_hash) or None if no stub needed. + """ + module_import = _path_to_module_name(module_path) + module = importlib.import_module(module_import) + logger.debug(f"Read {module_path}") + class_names = { + name: obj + for name, obj in vars(module).items() + if isinstance(obj, type) + and (_safe_issubclass(obj, Component) or _safe_issubclass(obj, SimpleNamespace)) + and obj != Component + and inspect.getmodule(obj) == module + } + is_init_file = _relative_to_pwd(module_path).name == "__init__.py" + if not class_names and not is_init_file: + return None + + if is_init_file: + new_tree = InitStubGenerator(module, class_names).visit( + ast.parse(_get_source(module)) + ) + init_imports = _get_init_lazy_imports(module, new_tree) + if not init_imports: + return None + content_hash = _write_pyi_file(module_path, init_imports) + else: + new_tree = StubGenerator(module, class_names).visit( + ast.parse(_get_source(module)) + ) + content_hash = _write_pyi_file(module_path, ast.unparse(new_tree)) + return str(module_path.with_suffix(".pyi").resolve()), content_hash + + +class PyiGenerator: + """A .pyi file generator that will scan all defined Component in Reflex and + generate the appropriate stub. + """ + + modules: list = [] + root: str = "" + current_module: Any = {} + written_files: list[tuple[str, str]] = [] + + def _scan_files(self, files: list[Path]): + max_workers = min(multiprocessing.cpu_count() or 1, len(files), 8) + use_parallel = ( + max_workers > 1 and "fork" in multiprocessing.get_all_start_methods() + ) + + if not use_parallel: + # Serial fallback: _scan_file handles its own imports. + for file in files: + result = _scan_file(file) + if result is not None: + self.written_files.append(result) + return + + # Pre-import all modules sequentially to populate sys.modules + # so forked workers inherit the cache and skip redundant imports. + importable_files: list[Path] = [] + for file in files: + module_import = _path_to_module_name(file) + try: + importlib.import_module(module_import) + importable_files.append(file) + except Exception: + logger.exception(f"Failed to import {module_import}") + + # Generate stubs in parallel using forked worker processes. + ctx = multiprocessing.get_context("fork") + with ProcessPoolExecutor(max_workers=max_workers, mp_context=ctx) as executor: + self.written_files.extend( + r for r in executor.map(_scan_file, importable_files) if r is not None + ) + + def scan_all( + self, + targets: list, + changed_files: list[Path] | None = None, + use_json: bool = False, + ): + """Scan all targets for class inheriting Component and generate the .pyi files. + + Args: + targets: the list of file/folders to scan. + changed_files (optional): the list of changed files since the last run. + use_json: whether to use json to store the hashes. + """ + file_targets = [] + for target in targets: + target_path = Path(target) + if ( + target_path.is_file() + and target_path.suffix == ".py" + and target_path.name not in EXCLUDED_FILES + ): + file_targets.append(target_path) + continue + if not target_path.is_dir(): + continue + for file_path in _walk_files(target_path): + relative = _relative_to_pwd(file_path) + if relative.name in EXCLUDED_FILES or file_path.suffix != ".py": + continue + if ( + changed_files is not None + and _relative_to_pwd(file_path) not in changed_files + ): + continue + file_targets.append(file_path) + + # check if pyi changed but not the source + if changed_files is not None: + for changed_file in changed_files: + if changed_file.suffix != ".pyi": + continue + py_file_path = changed_file.with_suffix(".py") + if not py_file_path.exists() and changed_file.exists(): + changed_file.unlink() + if py_file_path in file_targets: + continue + subprocess.run(["git", "checkout", changed_file]) + + self._scan_files(file_targets) + + file_paths, hashes = ( + [f[0] for f in self.written_files], + [f[1] for f in self.written_files], + ) + + # Fix generated pyi files with ruff. + if file_paths: + subprocess.run(["ruff", "check", "--fix", *file_paths]) + subprocess.run(["ruff", "format", *file_paths]) + + if use_json: + if file_paths and changed_files is None: + file_paths = list(map(Path, file_paths)) + top_dir = file_paths[0].parent + for file_path in file_paths: + file_parent = file_path.parent + while len(file_parent.parts) > len(top_dir.parts): + file_parent = file_parent.parent + while len(top_dir.parts) > len(file_parent.parts): + top_dir = top_dir.parent + while not file_parent.samefile(top_dir): + file_parent = file_parent.parent + top_dir = top_dir.parent + + while ( + not top_dir.samefile(top_dir.parent) + and not (top_dir / PYI_HASHES).exists() + ): + top_dir = top_dir.parent + + pyi_hashes_file = top_dir / PYI_HASHES + + if pyi_hashes_file.exists(): + pyi_hashes_file.write_text( + json.dumps( + dict( + zip( + [ + f.relative_to(pyi_hashes_file.parent).as_posix() + for f in file_paths + ], + hashes, + strict=True, + ) + ), + indent=2, + sort_keys=True, + ) + + "\n", + ) + elif file_paths: + file_paths = list(map(Path, file_paths)) + pyi_hashes_parent = file_paths[0].parent + while ( + not pyi_hashes_parent.samefile(pyi_hashes_parent.parent) + and not (pyi_hashes_parent / PYI_HASHES).exists() + ): + pyi_hashes_parent = pyi_hashes_parent.parent + + pyi_hashes_file = pyi_hashes_parent / PYI_HASHES + if pyi_hashes_file.exists(): + pyi_hashes = json.loads(pyi_hashes_file.read_text()) + for file_path, hashed_content in zip( + file_paths, hashes, strict=False + ): + formatted_path = file_path.relative_to( + pyi_hashes_parent + ).as_posix() + pyi_hashes[formatted_path] = hashed_content + + pyi_hashes_file.write_text( + json.dumps(pyi_hashes, indent=2, sort_keys=True) + "\n" + ) + + +if __name__ == "__main__": + import argparse + + parser = argparse.ArgumentParser(description="Generate .pyi stub files") + parser.add_argument( + "targets", + nargs="*", + default=["reflex/components", "reflex/experimental", "reflex/__init__.py"], + help="Target directories/files to process", + ) + args = parser.parse_args() + + logging.basicConfig(level=logging.INFO) + logging.getLogger("blib2to3.pgen2.driver").setLevel(logging.INFO) + + gen = PyiGenerator() + gen.scan_all(args.targets, None, use_json=True) diff --git a/packages/reflex-core/src/reflex_core/utils/serializers.py b/packages/reflex-core/src/reflex_core/utils/serializers.py new file mode 100644 index 00000000000..49b3233c838 --- /dev/null +++ b/packages/reflex-core/src/reflex_core/utils/serializers.py @@ -0,0 +1,498 @@ +"""Serializers used to convert Var types to JSON strings.""" + +from __future__ import annotations + +import contextlib +import dataclasses +import decimal +import functools +import inspect +import json +import warnings +from collections.abc import Callable, Mapping, Sequence +from datetime import date, datetime, time, timedelta +from enum import Enum +from importlib.util import find_spec +from pathlib import Path +from typing import Any, Literal, TypeVar, get_type_hints, overload +from uuid import UUID + +from reflex_core.constants.colors import Color +from reflex_core.utils import console, types + +# Mapping from type to a serializer. +# The serializer should convert the type to a JSON object. +SerializedType = str | bool | int | float | list | dict | None + + +Serializer = Callable[[Any], SerializedType] + + +SERIALIZERS: dict[type, Serializer] = {} +SERIALIZER_TYPES: dict[type, type] = {} + +SERIALIZED_FUNCTION = TypeVar("SERIALIZED_FUNCTION", bound=Serializer) + + +@overload +def serializer( + fn: None = None, + to: type[SerializedType] | None = None, + overwrite: bool | None = None, +) -> Callable[[SERIALIZED_FUNCTION], SERIALIZED_FUNCTION]: ... + + +@overload +def serializer( + fn: SERIALIZED_FUNCTION, + to: type[SerializedType] | None = None, + overwrite: bool | None = None, +) -> SERIALIZED_FUNCTION: ... + + +def serializer( + fn: SERIALIZED_FUNCTION | None = None, + to: Any = None, + overwrite: bool | None = None, +) -> SERIALIZED_FUNCTION | Callable[[SERIALIZED_FUNCTION], SERIALIZED_FUNCTION]: + """Decorator to add a serializer for a given type. + + Args: + fn: The function to decorate. + to: The type returned by the serializer. If this is `str`, then any Var created from this type will be treated as a string. + overwrite: Whether to overwrite the existing serializer. + + Returns: + The decorated function. + """ + + def wrapper(fn: SERIALIZED_FUNCTION) -> SERIALIZED_FUNCTION: + # Check the type hints to get the type of the argument. + type_hints = get_type_hints(fn) + args = [arg for arg in type_hints if arg != "return"] + + # Make sure the function takes a single argument. + if len(args) != 1: + msg = "Serializer must take a single argument." + raise ValueError(msg) + + # Get the type of the argument. + type_ = type_hints[args[0]] + + # Make sure the type is not already registered. + registered_fn = SERIALIZERS.get(type_) + if registered_fn is not None and registered_fn != fn and overwrite is not True: + message = f"Overwriting serializer for type {type_} from {registered_fn.__module__}:{registered_fn.__qualname__} to {fn.__module__}:{fn.__qualname__}." + if overwrite is False: + raise ValueError(message) + caller_frame = next( + filter( + lambda frame: frame.filename != __file__, + inspect.getouterframes(inspect.currentframe()), + ), + None, + ) + file_info = ( + f"(at {caller_frame.filename}:{caller_frame.lineno})" + if caller_frame + else "" + ) + console.warn( + f"{message} Call rx.serializer with `overwrite=True` if this is intentional. {file_info}" + ) + + to_type = to or type_hints.get("return") + + # Apply type transformation if requested + if to_type: + SERIALIZER_TYPES[type_] = to_type + get_serializer_type.cache_clear() + + # Register the serializer. + SERIALIZERS[type_] = fn + get_serializer.cache_clear() + + # Return the function. + return fn + + if fn is not None: + return wrapper(fn) + return wrapper + + +@overload +def serialize( + value: Any, get_type: Literal[True] +) -> tuple[SerializedType | None, types.GenericType | None]: ... + + +@overload +def serialize(value: Any, get_type: Literal[False]) -> SerializedType | None: ... + + +@overload +def serialize(value: Any) -> SerializedType | None: ... + + +def serialize( + value: Any, get_type: bool = False +) -> SerializedType | tuple[SerializedType | None, types.GenericType | None] | None: + """Serialize the value to a JSON string. + + Args: + value: The value to serialize. + get_type: Whether to return the type of the serialized value. + + Returns: + The serialized value, or None if a serializer is not found. + """ + # Get the serializer for the type. + serializer = get_serializer(type(value)) + + # If there is no serializer, return None. + if serializer is None: + if dataclasses.is_dataclass(value) and not isinstance(value, type): + return {k.name: getattr(value, k.name) for k in dataclasses.fields(value)} + + if get_type: + return None, None + return None + + # Serialize the value. + serialized = serializer(value) + + # Return the serialized value and the type. + if get_type: + return serialized, get_serializer_type(type(value)) + return serialized + + +@functools.lru_cache +def get_serializer(type_: type) -> Serializer | None: + """Get the serializer for the type. + + Args: + type_: The type to get the serializer for. + + Returns: + The serializer for the type, or None if there is no serializer. + """ + # First, check if the type is registered. + serializer = SERIALIZERS.get(type_) + if serializer is not None: + return serializer + + # If the type is not registered, check if it is a subclass of a registered type. + for registered_type, serializer in reversed(SERIALIZERS.items()): + if issubclass(type_, registered_type): + return serializer + + # If there is no serializer, return None. + return None + + +@functools.lru_cache +def get_serializer_type(type_: type) -> type | None: + """Get the converted type for the type after serializing. + + Args: + type_: The type to get the serializer type for. + + Returns: + The serialized type for the type, or None if there is no type conversion registered. + """ + # First, check if the type is registered. + serializer = SERIALIZER_TYPES.get(type_) + if serializer is not None: + return serializer + + # If the type is not registered, check if it is a subclass of a registered type. + for registered_type, serializer in reversed(SERIALIZER_TYPES.items()): + if issubclass(type_, registered_type): + return serializer + + # If there is no serializer, return None. + return None + + +def has_serializer(type_: type, into_type: type | None = None) -> bool: + """Check if there is a serializer for the type. + + Args: + type_: The type to check. + into_type: The type to serialize into. + + Returns: + Whether there is a serializer for the type. + """ + serializer_for_type = get_serializer(type_) + return serializer_for_type is not None and ( + into_type is None or get_serializer_type(type_) == into_type + ) + + +def can_serialize(type_: type, into_type: type | None = None) -> bool: + """Check if there is a serializer for the type. + + Args: + type_: The type to check. + into_type: The type to serialize into. + + Returns: + Whether there is a serializer for the type. + """ + return ( + isinstance(type_, type) + and dataclasses.is_dataclass(type_) + and (into_type is None or into_type is dict) + ) or has_serializer(type_, into_type) + + +@serializer(to=str) +def serialize_type(value: type) -> str: + """Serialize a python type. + + Args: + value: the type to serialize. + + Returns: + The serialized type. + """ + return value.__name__ + + +if find_spec("pydantic"): + from pydantic import BaseModel + + @serializer(to=dict) + def serialize_base_model(model: BaseModel) -> dict: + """Serialize a pydantic v2 BaseModel instance. + + Args: + model: The BaseModel to serialize. + + Returns: + The serialized BaseModel. + """ + return model.model_dump() + + +@serializer +def serialize_set(value: set) -> list: + """Serialize a set to a JSON serializable list. + + Args: + value: The set to serialize. + + Returns: + The serialized list. + """ + return list(value) + + +@serializer +def serialize_sequence(value: Sequence) -> list: + """Serialize a sequence to a JSON serializable list. + + Args: + value: The sequence to serialize. + + Returns: + The serialized list. + """ + return list(value) + + +@serializer(to=dict) +def serialize_mapping(value: Mapping) -> dict: + """Serialize a mapping type to a dictionary. + + Args: + value: The mapping instance to serialize. + + Returns: + A new dictionary containing the same key-value pairs as the input mapping. + """ + return {**value} + + +@serializer(to=str) +def serialize_datetime(dt: date | datetime | time | timedelta) -> str: + """Serialize a datetime to a JSON string. + + Args: + dt: The datetime to serialize. + + Returns: + The serialized datetime. + """ + return str(dt) + + +@serializer(to=str) +def serialize_path(path: Path) -> str: + """Serialize a pathlib.Path to a JSON string. + + Args: + path: The path to serialize. + + Returns: + The serialized path. + """ + return str(path.as_posix()) + + +@serializer +def serialize_enum(en: Enum) -> str: + """Serialize a enum to a JSON string. + + Args: + en: The enum to serialize. + + Returns: + The serialized enum. + """ + return en.value + + +@serializer(to=str) +def serialize_uuid(uuid: UUID) -> str: + """Serialize a UUID to a JSON string. + + Args: + uuid: The UUID to serialize. + + Returns: + The serialized UUID. + """ + return str(uuid) + + +@serializer(to=float) +def serialize_decimal(value: decimal.Decimal) -> float: + """Serialize a Decimal to a float. + + Args: + value: The Decimal to serialize. + + Returns: + The serialized Decimal as a float. + """ + return float(value) + + +@serializer(to=str) +def serialize_color(color: Color) -> str: + """Serialize a color. + + Args: + color: The color to serialize. + + Returns: + The serialized color. + """ + return color.__format__("") + + +with contextlib.suppress(ImportError): + from pandas import DataFrame + + def format_dataframe_values(df: DataFrame) -> list[list[Any]]: + """Format dataframe values to a list of lists. + + Args: + df: The dataframe to format. + + Returns: + The dataframe as a list of lists. + """ + return [ + [str(d) if isinstance(d, (list, tuple)) else d for d in data] + for data in list(df.to_numpy().tolist()) + ] + + @serializer + def serialize_dataframe(df: DataFrame) -> dict: + """Serialize a pandas dataframe. + + Args: + df: The dataframe to serialize. + + Returns: + The serialized dataframe. + """ + return { + "columns": df.columns.tolist(), + "data": format_dataframe_values(df), + } + + +with contextlib.suppress(ImportError): + from plotly.graph_objects import Figure, layout + from plotly.io import to_json + + @serializer + def serialize_figure(figure: Figure) -> dict: + """Serialize a plotly figure. + + Args: + figure: The figure to serialize. + + Returns: + The serialized figure. + """ + return json.loads(str(to_json(figure))) + + @serializer + def serialize_template(template: layout.Template) -> dict: + """Serialize a plotly template. + + Args: + template: The template to serialize. + + Returns: + The serialized template. + """ + return { + "data": json.loads(str(to_json(template.data))), + "layout": json.loads(str(to_json(template.layout))), + } + + +with contextlib.suppress(ImportError): + import base64 + import io + + from PIL.Image import MIME + from PIL.Image import Image as Img + + @serializer + def serialize_image(image: Img) -> str: + """Serialize a plotly figure. + + Args: + image: The image to serialize. + + Returns: + The serialized image. + """ + buff = io.BytesIO() + image_format = getattr(image, "format", None) or "PNG" + image.save(buff, format=image_format) + image_bytes = buff.getvalue() + base64_image = base64.b64encode(image_bytes).decode("utf-8") + try: + # Newer method to get the mime type, but does not always work. + mime_type = image.get_format_mimetype() # pyright: ignore [reportAttributeAccessIssue] + except AttributeError: + try: + # Fallback method + mime_type = MIME[image_format] + except KeyError: + # Unknown mime_type: warn and return image/png and hope the browser can sort it out. + warnings.warn( # noqa: B028 + f"Unknown mime type for {image} {image_format}. Defaulting to image/png" + ) + mime_type = "image/png" + + return f"data:{mime_type};base64,{base64_image}" diff --git a/packages/reflex-core/src/reflex_core/utils/types.py b/packages/reflex-core/src/reflex_core/utils/types.py new file mode 100644 index 00000000000..c698c4fef44 --- /dev/null +++ b/packages/reflex-core/src/reflex_core/utils/types.py @@ -0,0 +1,1197 @@ +"""Contains custom types and methods to check types.""" + +from __future__ import annotations + +import dataclasses +import sys +import types +from collections.abc import Callable, Iterable, Mapping, Sequence +from enum import Enum +from functools import cached_property, lru_cache +from importlib.util import find_spec +from types import GenericAlias +from typing import ( # noqa: UP035 + TYPE_CHECKING, + Any, + Awaitable, + ClassVar, + Dict, + ForwardRef, + List, + Literal, + MutableMapping, + NoReturn, + Protocol, + Tuple, + TypeVar, + Union, + _eval_type, # pyright: ignore [reportAttributeAccessIssue] + _GenericAlias, # pyright: ignore [reportAttributeAccessIssue] + _SpecialGenericAlias, # pyright: ignore [reportAttributeAccessIssue] + get_args, + is_typeddict, +) +from typing import get_origin as get_origin_og +from typing import get_type_hints as get_type_hints_og + +from typing_extensions import Self as Self +from typing_extensions import override as override + +from reflex import constants +from reflex_core.utils import console + +# Potential GenericAlias types for isinstance checks. +GenericAliasTypes = (_GenericAlias, GenericAlias, _SpecialGenericAlias) + +# Potential Union types for isinstance checks. +UnionTypes = (Union, types.UnionType) + +# Union of generic types. +GenericType = type | _GenericAlias + +# Valid state var types. +PrimitiveTypes = (int, float, bool, str, list, dict, set, tuple) +StateVarTypes = (*PrimitiveTypes, type(None)) + +if TYPE_CHECKING: + from reflex.state import BaseState + from reflex_core.vars.base import Var + +VAR1 = TypeVar("VAR1", bound="Var") +VAR2 = TypeVar("VAR2", bound="Var") +VAR3 = TypeVar("VAR3", bound="Var") +VAR4 = TypeVar("VAR4", bound="Var") +VAR5 = TypeVar("VAR5", bound="Var") +VAR6 = TypeVar("VAR6", bound="Var") +VAR7 = TypeVar("VAR7", bound="Var") + + +class _ArgsSpec0(Protocol): + def __call__(self) -> Sequence[Var]: ... + + +class _ArgsSpec1(Protocol): + def __call__(self, var1: VAR1, /) -> Sequence[Var]: ... # pyright: ignore [reportInvalidTypeVarUse] + + +class _ArgsSpec2(Protocol): + def __call__(self, var1: VAR1, var2: VAR2, /) -> Sequence[Var]: ... # pyright: ignore [reportInvalidTypeVarUse] + + +class _ArgsSpec3(Protocol): + def __call__(self, var1: VAR1, var2: VAR2, var3: VAR3, /) -> Sequence[Var]: ... # pyright: ignore [reportInvalidTypeVarUse] + + +class _ArgsSpec4(Protocol): + def __call__( + self, + var1: VAR1, # pyright: ignore [reportInvalidTypeVarUse] + var2: VAR2, # pyright: ignore [reportInvalidTypeVarUse] + var3: VAR3, # pyright: ignore [reportInvalidTypeVarUse] + var4: VAR4, # pyright: ignore [reportInvalidTypeVarUse] + /, + ) -> Sequence[Var]: ... + + +class _ArgsSpec5(Protocol): + def __call__( + self, + var1: VAR1, # pyright: ignore [reportInvalidTypeVarUse] + var2: VAR2, # pyright: ignore [reportInvalidTypeVarUse] + var3: VAR3, # pyright: ignore [reportInvalidTypeVarUse] + var4: VAR4, # pyright: ignore [reportInvalidTypeVarUse] + var5: VAR5, # pyright: ignore [reportInvalidTypeVarUse] + /, + ) -> Sequence[Var]: ... + + +class _ArgsSpec6(Protocol): + def __call__( + self, + var1: VAR1, # pyright: ignore [reportInvalidTypeVarUse] + var2: VAR2, # pyright: ignore [reportInvalidTypeVarUse] + var3: VAR3, # pyright: ignore [reportInvalidTypeVarUse] + var4: VAR4, # pyright: ignore [reportInvalidTypeVarUse] + var5: VAR5, # pyright: ignore [reportInvalidTypeVarUse] + var6: VAR6, # pyright: ignore [reportInvalidTypeVarUse] + /, + ) -> Sequence[Var]: ... + + +class _ArgsSpec7(Protocol): + def __call__( + self, + var1: VAR1, # pyright: ignore [reportInvalidTypeVarUse] + var2: VAR2, # pyright: ignore [reportInvalidTypeVarUse] + var3: VAR3, # pyright: ignore [reportInvalidTypeVarUse] + var4: VAR4, # pyright: ignore [reportInvalidTypeVarUse] + var5: VAR5, # pyright: ignore [reportInvalidTypeVarUse] + var6: VAR6, # pyright: ignore [reportInvalidTypeVarUse] + var7: VAR7, # pyright: ignore [reportInvalidTypeVarUse] + /, + ) -> Sequence[Var]: ... + + +ArgsSpec = ( + _ArgsSpec0 + | _ArgsSpec1 + | _ArgsSpec2 + | _ArgsSpec3 + | _ArgsSpec4 + | _ArgsSpec5 + | _ArgsSpec6 + | _ArgsSpec7 +) + +Scope = MutableMapping[str, Any] +Message = MutableMapping[str, Any] + +Receive = Callable[[], Awaitable[Message]] +Send = Callable[[Message], Awaitable[None]] + +ASGIApp = Callable[[Scope, Receive, Send], Awaitable[None]] + +PrimitiveToAnnotation = { + list: List, # noqa: UP006 + tuple: Tuple, # noqa: UP006 + dict: Dict, # noqa: UP006 +} + +RESERVED_BACKEND_VAR_NAMES = {"_abc_impl", "_backend_vars", "_was_touched", "_mixin"} + + +class Unset: + """A class to represent an unset value. + + This is used to differentiate between a value that is not set and a value that is set to None. + """ + + def __repr__(self) -> str: + """Return the string representation of the class. + + Returns: + The string representation of the class. + """ + return "Unset" + + def __bool__(self) -> bool: + """Return False when the class is used in a boolean context. + + Returns: + False + """ + return False + + +@lru_cache +def _get_origin_cached(tp: Any): + return get_origin_og(tp) + + +def get_origin(tp: Any): + """Get the origin of a class. + + Args: + tp: The class to get the origin of. + + Returns: + The origin of the class. + """ + return ( + origin + if (origin := getattr(tp, "__origin__", None)) is not None + else _get_origin_cached(tp) + ) + + +@lru_cache +def is_generic_alias(cls: GenericType) -> bool: + """Check whether the class is a generic alias. + + Args: + cls: The class to check. + + Returns: + Whether the class is a generic alias. + """ + return isinstance(cls, GenericAliasTypes) + + +@lru_cache +def get_type_hints(obj: Any) -> dict[str, Any]: + """Get the type hints of a class. + + Args: + obj: The class to get the type hints of. + + Returns: + The type hints of the class. + """ + return get_type_hints_og(obj) + + +def _unionize(args: list[GenericType]) -> GenericType: + if not args: + return Any # pyright: ignore [reportReturnType] + if len(args) == 1: + return args[0] + return Union[tuple(args)] # noqa: UP007 + + +def unionize(*args: GenericType) -> type: + """Unionize the types. + + Args: + args: The types to unionize. + + Returns: + The unionized types. + """ + return _unionize([arg for arg in args if arg is not NoReturn]) + + +def is_none(cls: GenericType) -> bool: + """Check if a class is None. + + Args: + cls: The class to check. + + Returns: + Whether the class is None. + """ + return cls is type(None) or cls is None + + +def is_union(cls: GenericType) -> bool: + """Check if a class is a Union. + + Args: + cls: The class to check. + + Returns: + Whether the class is a Union. + """ + origin = getattr(cls, "__origin__", None) + if origin is Union: + return True + return origin is None and isinstance(cls, types.UnionType) + + +def is_literal(cls: GenericType) -> bool: + """Check if a class is a Literal. + + Args: + cls: The class to check. + + Returns: + Whether the class is a literal. + """ + return getattr(cls, "__origin__", None) is Literal + + +@lru_cache +def has_args(cls: type) -> bool: + """Check if the class has generic parameters. + + Args: + cls: The class to check. + + Returns: + Whether the class has generic + """ + if get_args(cls): + return True + + # Check if the class inherits from a generic class (using __orig_bases__) + if hasattr(cls, "__orig_bases__"): + for base in cls.__orig_bases__: + if get_args(base): + return True + + return False + + +def is_optional(cls: GenericType) -> bool: + """Check if a class is an Optional. + + Args: + cls: The class to check. + + Returns: + Whether the class is an Optional. + """ + return ( + cls is None + or cls is type(None) + or (is_union(cls) and type(None) in get_args(cls)) + ) + + +def is_classvar(a_type: Any) -> bool: + """Check if a type is a ClassVar. + + Args: + a_type: The type to check. + + Returns: + Whether the type is a ClassVar. + """ + return ( + a_type is ClassVar + or (type(a_type) is _GenericAlias and a_type.__origin__ is ClassVar) + or ( + type(a_type) is ForwardRef and a_type.__forward_arg__.startswith("ClassVar") + ) + ) + + +def value_inside_optional(cls: GenericType) -> GenericType: + """Get the value inside an Optional type or the original type. + + Args: + cls: The class to check. + + Returns: + The value inside the Optional type or the original type. + """ + if is_union(cls) and len(args := get_args(cls)) >= 2 and type(None) in args: + if len(args) == 2: + return args[0] if args[1] is type(None) else args[1] + return unionize(*[arg for arg in args if arg is not type(None)]) + return cls + + +def get_field_type(cls: GenericType, field_name: str) -> GenericType | None: + """Get the type of a field in a class. + + Args: + cls: The class to check. + field_name: The name of the field to check. + + Returns: + The type of the field, if it exists, else None. + """ + if (fields := getattr(cls, "_fields", None)) is not None and field_name in fields: + return fields[field_name].annotated_type + if ( + hasattr(cls, "__fields__") + and field_name in cls.__fields__ + and hasattr(cls.__fields__[field_name], "annotation") + and not isinstance(cls.__fields__[field_name].annotation, (str, ForwardRef)) + ): + return cls.__fields__[field_name].annotation + type_hints = get_type_hints(cls) + return type_hints.get(field_name, None) + + +PROPERTY_CLASSES = (property,) +if find_spec("sqlalchemy") and find_spec("sqlalchemy.ext"): + from sqlalchemy.ext.hybrid import hybrid_property + + PROPERTY_CLASSES += (hybrid_property,) + + +def get_property_hint(attr: Any | None) -> GenericType | None: + """Check if an attribute is a property and return its type hint. + + Args: + attr: The descriptor to check. + + Returns: + The type hint of the property, if it is a property, else None. + """ + if not isinstance(attr, PROPERTY_CLASSES): + return None + hints = get_type_hints(attr.fget) + return hints.get("return", None) + + +def get_attribute_access_type(cls: GenericType, name: str) -> GenericType | None: + """Check if an attribute can be accessed on the cls and return its type. + + Supports pydantic models, unions, and annotated attributes on rx.Model. + + Args: + cls: The class to check. + name: The name of the attribute to check. + + Returns: + The type of the attribute, if accessible, or None + """ + try: + attr = getattr(cls, name, None) + except NotImplementedError: + attr = None + + if hint := get_property_hint(attr): + return hint + + if hasattr(cls, "__fields__") and name in cls.__fields__: + # pydantic models + return get_field_type(cls, name) + if find_spec("sqlalchemy") and find_spec("sqlalchemy.orm"): + import sqlalchemy + from sqlalchemy.ext.associationproxy import AssociationProxyInstance + from sqlalchemy.orm import ( + DeclarativeBase, + Mapped, + QueryableAttribute, + Relationship, + ) + + from reflex.model import Model + + if find_spec("sqlmodel"): + from sqlmodel import SQLModel + + sqlmodel_types = (Model, SQLModel) + else: + sqlmodel_types = (Model,) + + if isinstance(cls, type) and issubclass(cls, DeclarativeBase): + insp = sqlalchemy.inspect(cls) + if name in insp.columns: + # check for list types + column = insp.columns[name] + column_type = column.type + try: + type_ = insp.columns[name].type.python_type + except NotImplementedError: + type_ = None + if type_ is not None: + if hasattr(column_type, "item_type"): + try: + item_type = column_type.item_type.python_type # pyright: ignore [reportAttributeAccessIssue] + except NotImplementedError: + item_type = None + if item_type is not None: + if type_ in PrimitiveToAnnotation: + type_ = PrimitiveToAnnotation[type_] + type_ = type_[item_type] # pyright: ignore [reportIndexIssue] + if hasattr(column, "nullable") and column.nullable: + type_ = type_ | None + return type_ + if name in insp.all_orm_descriptors: + descriptor = insp.all_orm_descriptors[name] + if hint := get_property_hint(descriptor): + return hint + if isinstance(descriptor, QueryableAttribute): + prop = descriptor.property + if isinstance(prop, Relationship): + type_ = prop.mapper.class_ + # TODO: check for nullable? + return list[type_] if prop.uselist else type_ | None + if isinstance(attr, AssociationProxyInstance): + return list[ + get_attribute_access_type( + attr.target_class, + attr.remote_attr.key, # pyright: ignore [reportAttributeAccessIssue] + ) + ] + elif ( + isinstance(cls, type) + and not is_generic_alias(cls) + and issubclass(cls, sqlmodel_types) + ): + # Check in the annotations directly (for sqlmodel.Relationship) + hints = get_type_hints(cls) # pyright: ignore [reportArgumentType] + if name in hints: + type_ = hints[name] + type_origin = get_origin(type_) + if isinstance(type_origin, type) and issubclass(type_origin, Mapped): + return get_args(type_)[0] # SQLAlchemy v2 + return type_ + if is_union(cls): + # Check in each arg of the annotation. + return unionize( + *(get_attribute_access_type(arg, name) for arg in get_args(cls)) + ) + if isinstance(cls, type): + # Bare class + exceptions = NameError + try: + hints = get_type_hints(cls) # pyright: ignore [reportArgumentType] + if name in hints: + return hints[name] + except exceptions as e: + console.warn(f"Failed to resolve ForwardRefs for {cls}.{name} due to {e}") + return None # Attribute is not accessible. + + +@lru_cache +def get_base_class(cls: GenericType) -> type: + """Get the base class of a class. + + Args: + cls: The class. + + Returns: + The base class of the class. + + Raises: + TypeError: If a literal has multiple types. + """ + if is_literal(cls): + # only literals of the same type are supported. + arg_type = type(get_args(cls)[0]) + if not all(type(arg) is arg_type for arg in get_args(cls)): + msg = "only literals of the same type are supported" + raise TypeError(msg) + return type(get_args(cls)[0]) + + if is_union(cls): + return tuple(get_base_class(arg) for arg in get_args(cls)) # pyright: ignore [reportReturnType] + + return get_base_class(cls.__origin__) if is_generic_alias(cls) else cls + + +def does_obj_satisfy_typed_dict( + obj: Any, + cls: GenericType, + *, + nested: int = 0, + treat_var_as_type: bool = True, + treat_mutable_obj_as_immutable: bool = False, +) -> bool: + """Check if an object satisfies a typed dict. + + Args: + obj: The object to check. + cls: The typed dict to check against. + nested: How many levels deep to check. + treat_var_as_type: Whether to treat Var as the type it represents, i.e. _var_type. + treat_mutable_obj_as_immutable: Whether to treat mutable objects as immutable. Useful if a component declares a mutable object as a prop, but the value is not expected to change. + + Returns: + Whether the object satisfies the typed dict. + """ + if not isinstance(obj, Mapping): + return False + + key_names_to_values = get_type_hints(cls) + required_keys: frozenset[str] = getattr(cls, "__required_keys__", frozenset()) + is_closed = getattr(cls, "__closed__", False) + extra_items_type = getattr(cls, "__extra_items__", Any) + + for key, value in obj.items(): + if is_closed and key not in key_names_to_values: + return False + if nested: + if key in key_names_to_values: + expected_type = key_names_to_values[key] + if not _isinstance( + value, + expected_type, + nested=nested - 1, + treat_var_as_type=treat_var_as_type, + treat_mutable_obj_as_immutable=treat_mutable_obj_as_immutable, + ): + return False + else: + if not _isinstance( + value, + extra_items_type, + nested=nested - 1, + treat_var_as_type=treat_var_as_type, + treat_mutable_obj_as_immutable=treat_mutable_obj_as_immutable, + ): + return False + + # required keys are all present + return required_keys.issubset(frozenset(obj)) + + +def _isinstance( + obj: Any, + cls: GenericType, + *, + nested: int = 0, + treat_var_as_type: bool = True, + treat_mutable_obj_as_immutable: bool = False, +) -> bool: + """Check if an object is an instance of a class. + + Args: + obj: The object to check. + cls: The class to check against. + nested: How many levels deep to check. + treat_var_as_type: Whether to treat Var as the type it represents, i.e. _var_type. + treat_mutable_obj_as_immutable: Whether to treat mutable objects as immutable. Useful if a component declares a mutable object as a prop, but the value is not expected to change. + + Returns: + Whether the object is an instance of the class. + """ + if cls is Any: + return True + + from reflex_core.vars import LiteralVar, Var + + if cls is Var: + return isinstance(obj, Var) + if isinstance(obj, LiteralVar): + return treat_var_as_type and _isinstance( + obj._var_value, cls, nested=nested, treat_var_as_type=True + ) + if isinstance(obj, Var): + return treat_var_as_type and typehint_issubclass( + obj._var_type, + cls, + treat_mutable_superclasss_as_immutable=treat_mutable_obj_as_immutable, + treat_literals_as_union_of_types=True, + treat_any_as_subtype_of_everything=True, + ) + + if cls is None or cls is type(None): + return obj is None + + if cls is not None and is_union(cls): + return any( + _isinstance(obj, arg, nested=nested, treat_var_as_type=treat_var_as_type) + for arg in get_args(cls) + ) + + if is_literal(cls): + return obj in get_args(cls) + + origin = get_origin(cls) + + if origin is None: + # cls is a typed dict + if is_typeddict(cls): + if nested: + return does_obj_satisfy_typed_dict( + obj, + cls, + nested=nested - 1, + treat_var_as_type=treat_var_as_type, + treat_mutable_obj_as_immutable=treat_mutable_obj_as_immutable, + ) + return isinstance(obj, dict) + + # cls is a float + if cls is float: + return isinstance(obj, (float, int)) + + # cls is a simple class + return isinstance(obj, cls) + + args = get_args(cls) + + if not args: + if treat_mutable_obj_as_immutable: + if origin is dict: + origin = Mapping + elif origin is list or origin is set: + origin = Sequence + # cls is a simple generic class + return isinstance(obj, origin) + + if origin is Var and args: + # cls is a Var + return _isinstance( + obj, + args[0], + nested=nested, + treat_var_as_type=treat_var_as_type, + treat_mutable_obj_as_immutable=treat_mutable_obj_as_immutable, + ) + + if nested > 0 and args: + if origin is list: + expected_class = Sequence if treat_mutable_obj_as_immutable else list + return isinstance(obj, expected_class) and all( + _isinstance( + item, + args[0], + nested=nested - 1, + treat_var_as_type=treat_var_as_type, + ) + for item in obj + ) + if origin is tuple: + if args[-1] is Ellipsis: + return isinstance(obj, tuple) and all( + _isinstance( + item, + args[0], + nested=nested - 1, + treat_var_as_type=treat_var_as_type, + ) + for item in obj + ) + return ( + isinstance(obj, tuple) + and len(obj) == len(args) + and all( + _isinstance( + item, + arg, + nested=nested - 1, + treat_var_as_type=treat_var_as_type, + ) + for item, arg in zip(obj, args, strict=True) + ) + ) + if safe_issubclass(origin, Mapping): + expected_class = ( + dict + if origin is dict and not treat_mutable_obj_as_immutable + else Mapping + ) + return isinstance(obj, expected_class) and all( + _isinstance( + key, args[0], nested=nested - 1, treat_var_as_type=treat_var_as_type + ) + and _isinstance( + value, + args[1], + nested=nested - 1, + treat_var_as_type=treat_var_as_type, + ) + for key, value in obj.items() + ) + if origin is set: + expected_class = Sequence if treat_mutable_obj_as_immutable else set + return isinstance(obj, expected_class) and all( + _isinstance( + item, + args[0], + nested=nested - 1, + treat_var_as_type=treat_var_as_type, + ) + for item in obj + ) + + if args: + from reflex_core.vars import Field + + if origin is Field: + return _isinstance( + obj, args[0], nested=nested, treat_var_as_type=treat_var_as_type + ) + + return isinstance(obj, get_base_class(cls)) + + +def is_dataframe(value: type) -> bool: + """Check if the given value is a dataframe. + + Args: + value: The value to check. + + Returns: + Whether the value is a dataframe. + """ + if is_generic_alias(value) or value == Any: + return False + return value.__name__ == "DataFrame" + + +def is_valid_var_type(type_: type) -> bool: + """Check if the given type is a valid prop type. + + Args: + type_: The type to check. + + Returns: + Whether the type is a valid prop type. + """ + from reflex_core.utils import serializers + + if is_union(type_): + return all(is_valid_var_type(arg) for arg in get_args(type_)) + + if is_literal(type_): + types = {type(value) for value in get_args(type_)} + return all(is_valid_var_type(type_) for type_ in types) + + type_ = origin if (origin := get_origin(type_)) is not None else type_ + + return ( + issubclass(type_, StateVarTypes) + or serializers.has_serializer(type_) + or dataclasses.is_dataclass(type_) + ) + + +def is_backend_base_variable(name: str, cls: type[BaseState]) -> bool: + """Check if this variable name correspond to a backend variable. + + Args: + name: The name of the variable to check + cls: The class of the variable to check (must be a BaseState subclass) + + Returns: + bool: The result of the check + """ + if name in RESERVED_BACKEND_VAR_NAMES: + return False + + if not name.startswith("_"): + return False + + if name.startswith("__"): + return False + + if name.startswith(f"_{cls.__name__}__"): + return False + + hints = cls._get_type_hints() + if name in hints: + hint = get_origin(hints[name]) + if hint == ClassVar: + return False + + if name in cls.inherited_backend_vars: + return False + + from reflex_core.vars.base import is_computed_var + + if name in cls.__dict__: + value = cls.__dict__[name] + if type(value) is classmethod: + return False + if callable(value): + return False + + if isinstance( + value, + ( + types.FunctionType, + property, + cached_property, + ), + ) or is_computed_var(value): + return False + + return True + + +def check_type_in_allowed_types(value_type: type, allowed_types: Iterable) -> bool: + """Check that a value type is found in a list of allowed types. + + Args: + value_type: Type of value. + allowed_types: Iterable of allowed types. + + Returns: + If the type is found in the allowed types. + """ + return get_base_class(value_type) in allowed_types + + +def check_prop_in_allowed_types(prop: Any, allowed_types: Iterable) -> bool: + """Check that a prop value is in a list of allowed types. + Does the check in a way that works regardless if it's a raw value or a state Var. + + Args: + prop: The prop to check. + allowed_types: The list of allowed types. + + Returns: + If the prop type match one of the allowed_types. + """ + from reflex_core.vars import Var + + type_ = prop._var_type if isinstance(prop, Var) else type(prop) + return type_ in allowed_types + + +def is_encoded_fstring(value: Any) -> bool: + """Check if a value is an encoded Var f-string. + + Args: + value: The value string to check. + + Returns: + Whether the value is an f-string + """ + return isinstance(value, str) and constants.REFLEX_VAR_OPENING_TAG in value + + +def validate_literal(key: str, value: Any, expected_type: type, comp_name: str): + """Check that a value is a valid literal. + + Args: + key: The prop name. + value: The prop value to validate. + expected_type: The expected type(literal type). + comp_name: Name of the component. + + Raises: + ValueError: When the value is not a valid literal. + """ + from reflex_core.vars import Var + + if ( + is_literal(expected_type) + and not isinstance(value, Var) # validating vars is not supported yet. + and not is_encoded_fstring(value) # f-strings are not supported. + and value not in expected_type.__args__ + ): + allowed_values = expected_type.__args__ + if value not in allowed_values: + allowed_value_str = ",".join([ + str(v) if not isinstance(v, str) else f"'{v}'" for v in allowed_values + ]) + value_str = f"'{value}'" if isinstance(value, str) else value + msg = f"prop value for {key!s} of the `{comp_name}` component should be one of the following: {allowed_value_str}. Got {value_str} instead" + raise ValueError(msg) + + +def safe_issubclass(cls: Any, cls_check: Any | tuple[Any, ...]): + """Check if a class is a subclass of another class. Returns False if internal error occurs. + + Args: + cls: The class to check. + cls_check: The class to check against. + + Returns: + Whether the class is a subclass of the other class. + """ + try: + return issubclass(cls, cls_check) + except TypeError: + return False + + +def typehint_issubclass( + possible_subclass: Any, + possible_superclass: Any, + *, + treat_mutable_superclasss_as_immutable: bool = False, + treat_literals_as_union_of_types: bool = True, + treat_any_as_subtype_of_everything: bool = False, +) -> bool: + """Check if a type hint is a subclass of another type hint. + + Args: + possible_subclass: The type hint to check. + possible_superclass: The type hint to check against. + treat_mutable_superclasss_as_immutable: Whether to treat target classes as immutable. + treat_literals_as_union_of_types: Whether to treat literals as a union of their types. + treat_any_as_subtype_of_everything: Whether to treat Any as a subtype of everything. This is the default behavior in Python. + + Returns: + Whether the type hint is a subclass of the other type hint. + """ + if possible_subclass is possible_superclass or possible_superclass is Any: + return True + if possible_subclass is Any: + return treat_any_as_subtype_of_everything + if possible_subclass is NoReturn: + return True + + provided_type_origin = get_origin(possible_subclass) + accepted_type_origin = get_origin(possible_superclass) + + if provided_type_origin is None and accepted_type_origin is None: + # In this case, we are dealing with a non-generic type, so we can use issubclass + return issubclass(possible_subclass, possible_superclass) + + if treat_literals_as_union_of_types and is_literal(possible_superclass): + args = get_args(possible_superclass) + return any( + typehint_issubclass( + possible_subclass, + type(arg), + treat_mutable_superclasss_as_immutable=treat_mutable_superclasss_as_immutable, + treat_literals_as_union_of_types=treat_literals_as_union_of_types, + treat_any_as_subtype_of_everything=treat_any_as_subtype_of_everything, + ) + for arg in args + ) + + if is_literal(possible_subclass): + args = get_args(possible_subclass) + return all( + _isinstance( + arg, + possible_superclass, + treat_mutable_obj_as_immutable=treat_mutable_superclasss_as_immutable, + nested=2, + ) + for arg in args + ) + + provided_type_origin = ( + Union if provided_type_origin is types.UnionType else provided_type_origin + ) + accepted_type_origin = ( + Union if accepted_type_origin is types.UnionType else accepted_type_origin + ) + + # Get type arguments (e.g., [float, int] for dict[float, int]) + provided_args = get_args(possible_subclass) + accepted_args = get_args(possible_superclass) + + if accepted_type_origin is Union: + if provided_type_origin is not Union: + return any( + typehint_issubclass( + possible_subclass, + accepted_arg, + treat_mutable_superclasss_as_immutable=treat_mutable_superclasss_as_immutable, + treat_literals_as_union_of_types=treat_literals_as_union_of_types, + treat_any_as_subtype_of_everything=treat_any_as_subtype_of_everything, + ) + for accepted_arg in accepted_args + ) + return all( + any( + typehint_issubclass( + provided_arg, + accepted_arg, + treat_mutable_superclasss_as_immutable=treat_mutable_superclasss_as_immutable, + treat_literals_as_union_of_types=treat_literals_as_union_of_types, + treat_any_as_subtype_of_everything=treat_any_as_subtype_of_everything, + ) + for accepted_arg in accepted_args + ) + for provided_arg in provided_args + ) + if provided_type_origin is Union: + return all( + typehint_issubclass( + provided_arg, + possible_superclass, + treat_mutable_superclasss_as_immutable=treat_mutable_superclasss_as_immutable, + treat_literals_as_union_of_types=treat_literals_as_union_of_types, + treat_any_as_subtype_of_everything=treat_any_as_subtype_of_everything, + ) + for provided_arg in provided_args + ) + + provided_type_origin = provided_type_origin or possible_subclass + accepted_type_origin = accepted_type_origin or possible_superclass + + if treat_mutable_superclasss_as_immutable: + if accepted_type_origin is dict: + accepted_type_origin = Mapping + elif accepted_type_origin is list or accepted_type_origin is set: + accepted_type_origin = Sequence + + # Check if the origin of both types is the same (e.g., list for list[int]) + if not safe_issubclass( + provided_type_origin or possible_subclass, + accepted_type_origin or possible_superclass, + ): + return False + + # Ensure all specific types are compatible with accepted types + # Note this is not necessarily correct, as it doesn't check against contravariance and covariance + # It also ignores when the length of the arguments is different + return all( + typehint_issubclass( + provided_arg, + accepted_arg, + treat_mutable_superclasss_as_immutable=treat_mutable_superclasss_as_immutable, + treat_literals_as_union_of_types=treat_literals_as_union_of_types, + treat_any_as_subtype_of_everything=treat_any_as_subtype_of_everything, + ) + for provided_arg, accepted_arg in zip( + provided_args, accepted_args, strict=False + ) + if accepted_arg is not Any + ) + + +def resolve_annotations( + raw_annotations: Mapping[str, type[Any]], module_name: str | None +) -> dict[str, type[Any]]: + """Partially taken from typing.get_type_hints. + + Resolve string or ForwardRef annotations into type objects if possible. + + Args: + raw_annotations: The raw annotations to resolve. + module_name: The name of the module. + + Returns: + The resolved annotations. + """ + module = sys.modules.get(module_name, None) if module_name is not None else None + + base_globals: dict[str, Any] | None = ( + module.__dict__ if module is not None else None + ) + + annotations = {} + for name, value in raw_annotations.items(): + if isinstance(value, str): + if sys.version_info == (3, 10, 0): + value = ForwardRef(value, is_argument=False) + else: + value = ForwardRef(value, is_argument=False, is_class=True) + try: + if sys.version_info >= (3, 13): + value = _eval_type(value, base_globals, None, type_params=()) + else: + value = _eval_type(value, base_globals, None) + except NameError: + # this is ok, it can be fixed with update_forward_refs + pass + annotations[name] = value + return annotations + + +TYPES_THAT_HAS_DEFAULT_VALUE = (int, float, tuple, list, set, dict, str) + + +def get_default_value_for_type(t: GenericType) -> Any: + """Get the default value of the var. + + Args: + t: The type of the var. + + Returns: + The default value of the var, if it has one, else None. + + Raises: + ImportError: If the var is a dataframe and pandas is not installed. + """ + if is_optional(t): + return None + + origin = get_origin(t) if is_generic_alias(t) else t + if origin is Literal: + args = get_args(t) + return args[0] if args else None + if safe_issubclass(origin, TYPES_THAT_HAS_DEFAULT_VALUE): + return origin() + if safe_issubclass(origin, Mapping): + return {} + if is_dataframe(origin): + try: + import pandas as pd + + return pd.DataFrame() + except ImportError as e: + msg = "Please install pandas to use dataframes in your app." + raise ImportError(msg) from e + return None + + +IMMUTABLE_TYPES = ( + int, + float, + bool, + str, + bytes, + frozenset, + tuple, + type(None), + Enum, +) + + +def is_immutable(i: Any) -> bool: + """Check if a value is immutable. + + Args: + i: The value to check. + + Returns: + Whether the value is immutable. + """ + return isinstance(i, IMMUTABLE_TYPES) diff --git a/packages/reflex-core/src/reflex_core/vars/__init__.py b/packages/reflex-core/src/reflex_core/vars/__init__.py new file mode 100644 index 00000000000..c1989e5733d --- /dev/null +++ b/packages/reflex-core/src/reflex_core/vars/__init__.py @@ -0,0 +1,59 @@ +"""Immutable-Based Var System.""" + +from .base import ( + BaseStateMeta, + EvenMoreBasicBaseState, + Field, + LiteralVar, + Var, + VarData, + field, + get_unique_variable_name, + get_uuid_string_var, + var_operation, + var_operation_return, +) +from .color import ColorVar, LiteralColorVar +from .datetime import DateTimeVar +from .function import FunctionStringVar, FunctionVar, VarOperationCall +from .number import BooleanVar, LiteralBooleanVar, LiteralNumberVar, NumberVar +from .object import LiteralObjectVar, ObjectVar, RestProp +from .sequence import ( + ArrayVar, + ConcatVarOperation, + LiteralArrayVar, + LiteralStringVar, + StringVar, +) + +__all__ = [ + "ArrayVar", + "BaseStateMeta", + "BooleanVar", + "ColorVar", + "ConcatVarOperation", + "DateTimeVar", + "EvenMoreBasicBaseState", + "Field", + "FunctionStringVar", + "FunctionVar", + "LiteralArrayVar", + "LiteralBooleanVar", + "LiteralColorVar", + "LiteralNumberVar", + "LiteralObjectVar", + "LiteralStringVar", + "LiteralVar", + "NumberVar", + "ObjectVar", + "RestProp", + "StringVar", + "Var", + "VarData", + "VarOperationCall", + "field", + "get_unique_variable_name", + "get_uuid_string_var", + "var_operation", + "var_operation_return", +] diff --git a/packages/reflex-core/src/reflex_core/vars/base.py b/packages/reflex-core/src/reflex_core/vars/base.py new file mode 100644 index 00000000000..e91e8ff8f5c --- /dev/null +++ b/packages/reflex-core/src/reflex_core/vars/base.py @@ -0,0 +1,3692 @@ +"""Collection of base classes.""" + +from __future__ import annotations + +import contextlib +import copy +import dataclasses +import datetime +import functools +import inspect +import json +import re +import string +import uuid +import warnings +from abc import ABCMeta +from collections.abc import Callable, Coroutine, Iterable, Mapping, Sequence +from dataclasses import _MISSING_TYPE, MISSING +from decimal import Decimal +from types import CodeType, FunctionType +from typing import ( + TYPE_CHECKING, + Annotated, + Any, + ClassVar, + Generic, + Literal, + NoReturn, + ParamSpec, + Protocol, + TypeGuard, + TypeVar, + cast, + get_args, + get_type_hints, + overload, +) + +from rich.markup import escape +from typing_extensions import dataclass_transform, override + +from reflex import constants +from reflex_core.constants.compiler import Hooks +from reflex_core.constants.state import FIELD_MARKER +from reflex_core.utils import console, exceptions, imports, serializers, types +from reflex_core.utils.compat import annotations_from_namespace +from reflex_core.utils.decorator import once +from reflex_core.utils.exceptions import ( + ComputedVarSignatureError, + UntypedComputedVarError, + VarAttributeError, + VarDependencyError, + VarTypeError, +) +from reflex_core.utils.format import format_state_name +from reflex_core.utils.imports import ( + ImmutableImportDict, + ImmutableParsedImportDict, + ImportDict, + ImportVar, + ParsedImportTuple, + parse_imports, +) +from reflex_core.utils.types import ( + GenericType, + Self, + _isinstance, + get_origin, + has_args, + safe_issubclass, + unionize, +) + +if TYPE_CHECKING: + from reflex.components.component import BaseComponent + from reflex.state import BaseState + from reflex_core.constants.colors import Color + + from .color import LiteralColorVar + 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) +OTHER_VAR_TYPE = TypeVar("OTHER_VAR_TYPE") +STRING_T = TypeVar("STRING_T", bound=str) +SEQUENCE_TYPE = TypeVar("SEQUENCE_TYPE", bound=Sequence) + +warnings.filterwarnings("ignore", message="fields may not start with an underscore") + +_PYDANTIC_VALIDATE_VALUES = "__pydantic_validate_values__" + + +def _pydantic_validator(*args, **kwargs): + return None + + +@dataclasses.dataclass( + eq=False, + frozen=True, +) +class VarSubclassEntry: + """Entry for a Var subclass.""" + + var_subclass: type[Var] + to_var_subclass: type[ToOperation] + python_types: tuple[GenericType, ...] + + +_var_subclasses: list[VarSubclassEntry] = [] +_var_literal_subclasses: list[tuple[type[LiteralVar], VarSubclassEntry]] = [] + + +@dataclasses.dataclass( + eq=True, + frozen=True, +) +class VarData: + """Metadata associated with a x.""" + + # The name of the enclosing state. + state: str = dataclasses.field(default="") + + # The name of the field in the state. + field_name: str = dataclasses.field(default="") + + # Imports needed to render this var + imports: ParsedImportTuple = dataclasses.field(default_factory=tuple) + + # Hooks that need to be present in the component to render this var + hooks: tuple[str, ...] = dataclasses.field(default_factory=tuple) + + # Dependencies of the var + deps: tuple[Var, ...] = dataclasses.field(default_factory=tuple) + + # Position of the hook in the component + position: Hooks.HookPosition | None = None + + # Components that are part of this var + components: tuple[BaseComponent, ...] = dataclasses.field(default_factory=tuple) + + def __init__( + self, + state: str = "", + field_name: str = "", + imports: ImmutableImportDict | ImmutableParsedImportDict | None = None, + hooks: Mapping[str, VarData | None] | Sequence[str] | str | None = None, + deps: list[Var] | None = None, + position: Hooks.HookPosition | None = None, + components: Iterable[BaseComponent] | None = None, + ): + """Initialize the var data. + + Args: + state: The name of the enclosing state. + field_name: The name of the field in the state. + imports: Imports needed to render this var. + hooks: Hooks that need to be present in the component to render this var. + deps: Dependencies of the var for useCallback. + position: Position of the hook in the component. + components: Components that are part of this var. + """ + if isinstance(hooks, str): + hooks = [hooks] + if not isinstance(hooks, dict): + hooks = dict.fromkeys(hooks or []) + immutable_imports: ParsedImportTuple = tuple( + (k, tuple(v)) for k, v in parse_imports(imports or {}).items() + ) + object.__setattr__(self, "state", state) + object.__setattr__(self, "field_name", field_name) + object.__setattr__(self, "imports", immutable_imports) + object.__setattr__(self, "hooks", tuple(hooks or {})) + object.__setattr__(self, "deps", tuple(deps or [])) + object.__setattr__(self, "position", position or None) + object.__setattr__(self, "components", tuple(components or [])) + + if hooks and any(hooks.values()): + # Merge our dependencies first, so they can be referenced. + merged_var_data = VarData.merge(*hooks.values(), self) + if merged_var_data is not None: + object.__setattr__(self, "state", merged_var_data.state) + object.__setattr__(self, "field_name", merged_var_data.field_name) + object.__setattr__(self, "imports", merged_var_data.imports) + object.__setattr__(self, "hooks", merged_var_data.hooks) + object.__setattr__(self, "deps", merged_var_data.deps) + object.__setattr__(self, "position", merged_var_data.position) + object.__setattr__(self, "components", merged_var_data.components) + + def old_school_imports(self) -> ImportDict: + """Return the imports as a mutable dict. + + Returns: + The imports as a mutable dict. + """ + return {k: list(v) for k, v in self.imports} + + def merge(*all: VarData | None) -> VarData | None: + """Merge multiple var data objects. + + Args: + *all: The var data objects to merge. + + Returns: + The merged var data object. + + Raises: + ReflexError: If trying to merge VarData with different positions. + + # noqa: DAR102 *all + """ + all_var_datas = list(filter(None, all)) + + if not all_var_datas: + return None + + if len(all_var_datas) == 1: + return all_var_datas[0] + + # Get the first non-empty field name or default to empty string. + field_name = next( + (var_data.field_name for var_data in all_var_datas if var_data.field_name), + "", + ) + + # Get the first non-empty state or default to empty string. + state = next( + (var_data.state for var_data in all_var_datas if var_data.state), "" + ) + + hooks: dict[str, VarData | None] = { + hook: None for var_data in all_var_datas for hook in var_data.hooks + } + + imports_ = imports.merge_imports( + *(var_data.imports for var_data in all_var_datas) + ) + + deps = [dep for var_data in all_var_datas for dep in var_data.deps] + + positions = list( + dict.fromkeys( + var_data.position + for var_data in all_var_datas + if var_data.position is not None + ) + ) + if positions: + if len(positions) > 1: + msg = f"Cannot merge var data with different positions: {positions}" + raise exceptions.ReflexError(msg) + position = positions[0] + else: + position = None + + components = tuple( + component for var_data in all_var_datas for component in var_data.components + ) + + return VarData( + state=state, + field_name=field_name, + imports=imports_, + hooks=hooks, + deps=deps, + position=position, + components=components, + ) + + def __bool__(self) -> bool: + """Check if the var data is non-empty. + + Returns: + True if any field is set to a non-default value. + """ + return bool( + self.state + or self.imports + or self.hooks + or self.field_name + or self.deps + or self.position + or self.components + ) + + @classmethod + def from_state(cls, state: type[BaseState] | str, field_name: str = "") -> VarData: + """Set the state of the var. + + Args: + state: The state to set or the full name of the state. + field_name: The name of the field in the state. Optional. + + Returns: + The var with the set state. + """ + from reflex_core.utils import format + + state_name = state if isinstance(state, str) else state.get_full_name() + return VarData( + state=state_name, + field_name=field_name, + hooks={ + "const {0} = useContext(StateContexts.{0})".format( + format.format_state_name(state_name) + ): None + }, + imports={ + f"$/{constants.Dirs.CONTEXTS_PATH}": [ImportVar(tag="StateContexts")], + "react": [ImportVar(tag="useContext")], + }, + ) + + +def _decode_var_immutable(value: str) -> tuple[VarData | None, str]: + """Decode the state name from a formatted var. + + Args: + value: The value to extract the state name from. + + Returns: + The extracted state name and the value without the state name. + """ + var_datas = [] + if isinstance(value, str): + # fast path if there is no encoded VarData + if constants.REFLEX_VAR_OPENING_TAG not in value: + return None, value + + offset = 0 + + # Find all tags. + while m := _decode_var_pattern.search(value): + start, end = m.span() + value = value[:start] + value[end:] + + serialized_data = m.group(1) + + if serialized_data.isnumeric() or ( + serialized_data[0] == "-" and serialized_data[1:].isnumeric() + ): + # This is a global immutable var. + var = _global_vars[int(serialized_data)] + var_data = var._get_all_var_data() + + if var_data is not None: + var_datas.append(var_data) + offset += end - start + + return VarData.merge(*var_datas) if var_datas else None, value + + +def can_use_in_object_var(cls: GenericType) -> bool: + """Check if the class can be used in an ObjectVar. + + Args: + cls: The class to check. + + Returns: + Whether the class can be used in an ObjectVar. + """ + if types.is_union(cls): + return all(can_use_in_object_var(t) for t in types.get_args(cls)) + return ( + isinstance(cls, type) + and not safe_issubclass(cls, Var) + and serializers.can_serialize(cls, dict) + ) + + +class MetaclassVar(type): + """Metaclass for the Var class.""" + + def __setattr__(cls, name: str, value: Any): + """Set an attribute on the class. + + Args: + name: The name of the attribute. + value: The value of the attribute. + """ + super().__setattr__( + name, value if name != _PYDANTIC_VALIDATE_VALUES else _pydantic_validator + ) + + +@dataclasses.dataclass( + eq=False, + frozen=True, +) +class Var(Generic[VAR_TYPE], metaclass=MetaclassVar): + """Base class for immutable vars.""" + + # The name of the var. + _js_expr: str = dataclasses.field() + + # The type of the var. + _var_type: types.GenericType = dataclasses.field(default=Any) + + # Extra metadata associated with the Var + _var_data: VarData | None = dataclasses.field(default=None) + + def __str__(self) -> str: + """String representation of the var. Guaranteed to be a valid Javascript expression. + + Returns: + The name of the var. + """ + return self._js_expr + + @property + def _var_is_local(self) -> bool: + """Whether this is a local javascript variable. + + Returns: + False + """ + return False + + @property + def _var_is_string(self) -> bool: + """Whether the var is a string literal. + + Returns: + False + """ + return False + + def __init_subclass__( + cls, + python_types: tuple[GenericType, ...] | GenericType = types.Unset(), + default_type: GenericType = types.Unset(), + **kwargs, + ): + """Initialize the subclass. + + Args: + python_types: The python types that the var represents. + default_type: The default type of the var. Defaults to the first python type. + **kwargs: Additional keyword arguments. + """ + super().__init_subclass__(**kwargs) + + if python_types or default_type: + python_types = ( + (python_types if isinstance(python_types, tuple) else (python_types,)) + if python_types + else () + ) + + default_type = default_type or (python_types[0] if python_types else Any) + + @dataclasses.dataclass( + eq=False, + frozen=True, + slots=True, + ) + class ToVarOperation(ToOperation, cls): + """Base class of converting a var to another var type.""" + + _original: Var = dataclasses.field( + default=Var(_js_expr="null", _var_type=None), + ) + + _default_var_type: ClassVar[GenericType] = default_type + + new_to_var_operation_name = f"{cls.__name__.removesuffix('Var')}CastedVar" + ToVarOperation.__qualname__ = ( + ToVarOperation.__qualname__.removesuffix(ToVarOperation.__name__) + + new_to_var_operation_name + ) + ToVarOperation.__name__ = new_to_var_operation_name + + _var_subclasses.append(VarSubclassEntry(cls, ToVarOperation, python_types)) + + def __post_init__(self): + """Post-initialize the var. + + Raises: + TypeError: If _js_expr is not a string. + """ + if not isinstance(self._js_expr, str): + msg = f"Expected _js_expr to be a string, got value {self._js_expr!r} of type {type(self._js_expr).__name__}" + raise TypeError(msg) + + if self._var_data is not None and not isinstance(self._var_data, VarData): + msg = f"Expected _var_data to be a VarData, got value {self._var_data!r} of type {type(self._var_data).__name__}" + raise TypeError(msg) + + # Decode any inline Var markup and apply it to the instance + var_data_, js_expr_ = _decode_var_immutable(self._js_expr) + + if var_data_ or js_expr_ != self._js_expr: + self.__init__( + _js_expr=js_expr_, + _var_type=self._var_type, + _var_data=VarData.merge(self._var_data, var_data_), + ) + + def __hash__(self) -> int: + """Define a hash function for the var. + + Returns: + The hash of the var. + """ + return hash((self._js_expr, self._var_type, self._var_data)) + + def _get_all_var_data(self) -> VarData | None: + """Get all VarData associated with the Var. + + Returns: + The VarData of the components and all of its children. + """ + return self._var_data + + def __deepcopy__(self, memo: dict[int, Any]) -> Self: + """Deepcopy the var. + + Args: + memo: The memo dictionary to use for the deepcopy. + + Returns: + A deepcopy of the var. + """ + return self + + def equals(self, other: Var) -> bool: + """Check if two vars are equal. + + Args: + other: The other var to compare. + + Returns: + Whether the vars are equal. + """ + return ( + self._js_expr == other._js_expr + and self._var_type == other._var_type + and self._get_all_var_data() == other._get_all_var_data() + ) + + @overload + def _replace( + self, + _var_type: type[OTHER_VAR_TYPE], + merge_var_data: VarData | None = None, + **kwargs: Any, + ) -> Var[OTHER_VAR_TYPE]: ... + + @overload + def _replace( + self, + _var_type: GenericType | None = None, + merge_var_data: VarData | None = None, + **kwargs: Any, + ) -> Self: ... + + def _replace( + self, + _var_type: GenericType | None = None, + merge_var_data: VarData | None = None, + **kwargs: Any, + ) -> Self | Var: + """Make a copy of this Var with updated fields. + + Args: + _var_type: The new type of the Var. + merge_var_data: VarData to merge into the existing VarData. + **kwargs: Var fields to update. + + Returns: + A new Var with the updated fields overwriting the corresponding fields in this Var. + + Raises: + TypeError: If _var_is_local, _var_is_string, or _var_full_name_needs_state_prefix is not None. + """ + if kwargs.get("_var_is_local", False) is not False: + msg = "The _var_is_local argument is not supported for Var." + raise TypeError(msg) + + if kwargs.get("_var_is_string", False) is not False: + msg = "The _var_is_string argument is not supported for Var." + raise TypeError(msg) + + if kwargs.get("_var_full_name_needs_state_prefix", False) is not False: + msg = "The _var_full_name_needs_state_prefix argument is not supported for Var." + raise TypeError(msg) + value_with_replaced = dataclasses.replace( + self, + _var_type=_var_type or self._var_type, + _var_data=VarData.merge( + kwargs.get("_var_data", self._var_data), merge_var_data + ), + **kwargs, + ) + + if (js_expr := kwargs.get("_js_expr")) is not None: + object.__setattr__(value_with_replaced, "_js_expr", js_expr) + + 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, + ) -> LiteralBooleanVar: ... + + @overload + @classmethod + def create( + cls, + value: int, + _var_data: VarData | None = None, + ) -> LiteralNumberVar[int]: ... + + @overload + @classmethod + def create( + cls, + value: float, + _var_data: VarData | None = None, + ) -> LiteralNumberVar[float]: ... + + @overload + @classmethod + def create( + cls, + value: Decimal, + _var_data: VarData | None = None, + ) -> LiteralNumberVar[Decimal]: ... + + @overload + @classmethod + def create( # pyright: ignore [reportOverlappingOverload] + cls, + value: Color, + _var_data: VarData | None = None, + ) -> LiteralColorVar: ... + + @overload + @classmethod + def create( # pyright: ignore [reportOverlappingOverload] + cls, + value: str, + _var_data: VarData | None = None, + ) -> LiteralStringVar: ... + + @overload + @classmethod + def create( # pyright: ignore [reportOverlappingOverload] + cls, + value: STRING_T, + _var_data: VarData | None = None, + ) -> StringVar[STRING_T]: ... + + @overload + @classmethod + def create( # pyright: ignore[reportOverlappingOverload] + cls, + value: None, + _var_data: VarData | None = None, + ) -> LiteralNoneVar: ... + + @overload + @classmethod + def create( + cls, + value: MAPPING_TYPE, + _var_data: VarData | None = None, + ) -> LiteralObjectVar[MAPPING_TYPE]: ... + + @overload + @classmethod + def create( + cls, + value: SEQUENCE_TYPE, + _var_data: VarData | None = None, + ) -> LiteralArrayVar[SEQUENCE_TYPE]: ... + + @overload + @classmethod + def create( + cls, + value: OTHER_VAR_TYPE, + _var_data: VarData | None = None, + ) -> Var[OTHER_VAR_TYPE]: ... + + @classmethod + def create( + cls, + value: OTHER_VAR_TYPE, + _var_data: VarData | None = None, + ) -> Var[OTHER_VAR_TYPE]: + """Create a var from a value. + + Args: + value: The value to create the var from. + _var_data: Additional hooks and imports associated with the Var. + + Returns: + The var. + """ + # If the value is already a var, do nothing. + if isinstance(value, Var): + return value + + return LiteralVar.create(value, _var_data=_var_data) + + def __format__(self, format_spec: str) -> str: + """Format the var into a Javascript equivalent to an f-string. + + Args: + format_spec: The format specifier (Ignored for now). + + Returns: + The formatted var. + """ + hashed_var = hash(self) + + _global_vars[hashed_var] = self + + # Encode the _var_data into the formatted output for tracking purposes. + return f"{constants.REFLEX_VAR_OPENING_TAG}{hashed_var}{constants.REFLEX_VAR_CLOSING_TAG}{self._js_expr}" + + @overload + def to(self, output: type[str]) -> StringVar: ... # pyright: ignore[reportOverlappingOverload] + + @overload + def to(self, output: type[bool]) -> BooleanVar: ... + + @overload + def to(self, output: type[int]) -> NumberVar[int]: ... + + @overload + def to(self, output: type[float]) -> NumberVar[float]: ... + + @overload + def to(self, output: type[Decimal]) -> NumberVar[Decimal]: ... + + @overload + def to( + self, + output: type[SEQUENCE_TYPE], + ) -> ArrayVar[SEQUENCE_TYPE]: ... + + @overload + def to( + self, + output: type[MAPPING_TYPE], + ) -> ObjectVar[MAPPING_TYPE]: ... + + @overload + def to( + self, output: type[ObjectVar], var_type: type[VAR_INSIDE] + ) -> ObjectVar[VAR_INSIDE]: ... + + @overload + def to( + self, output: type[ObjectVar], var_type: None = None + ) -> ObjectVar[VAR_TYPE]: ... + + @overload + def to(self, output: VAR_SUBCLASS, var_type: None = None) -> VAR_SUBCLASS: ... + + @overload + def to( + self, + output: type[OUTPUT] | types.GenericType, + var_type: types.GenericType | None = None, + ) -> OUTPUT: ... + + def to( + self, + output: type[OUTPUT] | types.GenericType, + var_type: types.GenericType | None = None, + ) -> Var: + """Convert the var to a different type. + + Args: + output: The output type. + var_type: The type of the var. + + Returns: + The converted var. + """ + from .object import ObjectVar + + fixed_output_type = get_origin(output) or output + + # If the first argument is a python type, we map it to the corresponding Var type. + for var_subclass in _var_subclasses[::-1]: + if fixed_output_type in var_subclass.python_types or safe_issubclass( + fixed_output_type, var_subclass.python_types + ): + return self.to(var_subclass.var_subclass, output) + + if fixed_output_type is None: + return get_to_operation(NoneVar).create(self) # pyright: ignore [reportReturnType] + + # Handle fixed_output_type being Base or a dataclass. + if can_use_in_object_var(output): + return self.to(ObjectVar, output) + + if isinstance(output, type): + for var_subclass in _var_subclasses[::-1]: + if safe_issubclass(output, var_subclass.var_subclass): + current_var_type = self._var_type + if current_var_type is Any: + new_var_type = var_type + else: + new_var_type = var_type or current_var_type + return var_subclass.to_var_subclass.create( # pyright: ignore [reportReturnType] + value=self, _var_type=new_var_type + ) + + # If we can't determine the first argument, we just replace the _var_type. + if not safe_issubclass(output, Var) or var_type is None: + return dataclasses.replace( + self, + _var_type=output, + ) + + # We couldn't determine the output type to be any other Var type, so we replace the _var_type. + if var_type is not None: + return dataclasses.replace( + self, + _var_type=var_type, + ) + + return self + + @overload + def guess_type(self: Var[NoReturn]) -> Var[Any]: ... # pyright: ignore [reportOverlappingOverload] + + @overload + def guess_type(self: Var[str]) -> StringVar: ... + + @overload + def guess_type(self: Var[bool]) -> BooleanVar: ... + + @overload + def guess_type(self: Var[int] | Var[float] | Var[int | float]) -> NumberVar: ... + + @overload + def guess_type(self) -> Self: ... + + def guess_type(self) -> Var: + """Guesses the type of the variable based on its `_var_type` attribute. + + Returns: + Var: The guessed type of the variable. + + Raises: + TypeError: If the type is not supported for guessing. + """ + from .object import ObjectVar + + var_type = self._var_type + if var_type is None: + return self.to(None) + if var_type is NoReturn: + return self.to(Any) + + var_type = types.value_inside_optional(var_type) + + if var_type is Any: + return self + + fixed_type = get_origin(var_type) or var_type + + if fixed_type in types.UnionTypes: + inner_types = get_args(var_type) + non_optional_inner_types = [ + types.value_inside_optional(inner_type) for inner_type in inner_types + ] + fixed_inner_types = [ + get_origin(inner_type) or inner_type + for inner_type in non_optional_inner_types + ] + + for var_subclass in _var_subclasses[::-1]: + if all( + safe_issubclass(t, var_subclass.python_types) + for t in fixed_inner_types + ): + return self.to(var_subclass.var_subclass, self._var_type) + + if can_use_in_object_var(var_type): + return self.to(ObjectVar, self._var_type) + + return self + + if fixed_type is Literal: + args = get_args(var_type) + fixed_type = unionize(*(type(arg) for arg in args)) + + if not isinstance(fixed_type, type): + msg = f"Unsupported type {var_type} for guess_type." + raise TypeError(msg) + + if fixed_type is None: + return self.to(None) + + for var_subclass in _var_subclasses[::-1]: + if safe_issubclass(fixed_type, var_subclass.python_types): + return self.to(var_subclass.var_subclass, self._var_type) + + if can_use_in_object_var(fixed_type): + return self.to(ObjectVar, self._var_type) + + return self + + @staticmethod + def _get_setter_name_for_name( + name: str, + ) -> str: + """Get the name of the var's generated setter function. + + Args: + name: The name of the var. + + Returns: + The name of the setter function. + """ + return constants.SETTER_PREFIX + name + + def _get_setter(self, name: str) -> Callable[[BaseState, Any], None]: + """Get the var's setter function. + + Args: + name: The name of the var. + + Returns: + A function that that creates a setter for the var. + """ + setter_name = Var._get_setter_name_for_name(name) + + def setter(state: Any, value: Any): + """Get the setter for the var. + + Args: + state: The state within which we add the setter function. + value: The value to set. + """ + if self._var_type in [int, float]: + try: + value = self._var_type(value) + setattr(state, name, value) + except ValueError: + console.debug( + f"{type(state).__name__}.{self._js_expr}: Failed conversion of {value!s} to '{self._var_type.__name__}'. Value not set.", + ) + else: + setattr(state, name, value) + + setter.__annotations__["value"] = self._var_type + + setter.__qualname__ = setter_name + + return setter + + def _var_set_state(self, state: type[BaseState] | str) -> Self: + """Set the state of the var. + + Args: + state: The state to set. + + Returns: + The var with the state set. + """ + formatted_state_name = ( + state + if isinstance(state, str) + else format_state_name(state.get_full_name()) + ) + + return StateOperation.create( # pyright: ignore [reportReturnType] + formatted_state_name, + self, + _var_data=VarData.merge( + VarData.from_state(state, self._js_expr), self._var_data + ), + ).guess_type() + + def __eq__(self, other: Var | Any) -> BooleanVar: + """Check if the current variable is equal to the given variable. + + Args: + other (Var | Any): The variable to compare with. + + Returns: + BooleanVar: A BooleanVar object representing the result of the equality check. + """ + from .number import equal_operation + + return equal_operation(self, other) + + def __ne__(self, other: Var | Any) -> BooleanVar: + """Check if the current object is not equal to the given object. + + Parameters: + other (Var | Any): The object to compare with. + + Returns: + BooleanVar: A BooleanVar object representing the result of the comparison. + """ + from .number import equal_operation + + return ~equal_operation(self, other) + + def bool(self) -> BooleanVar: + """Convert the var to a boolean. + + Returns: + The boolean var. + """ + from .number import boolify + + return boolify(self) + + def is_none(self) -> BooleanVar: + """Check if the var is None. + + Returns: + A BooleanVar object representing the result of the check. + """ + from .number import is_not_none_operation + + return ~is_not_none_operation(self) + + def is_not_none(self) -> BooleanVar: + """Check if the var is not None. + + Returns: + A BooleanVar object representing the result of the check. + """ + from .number import is_not_none_operation + + return is_not_none_operation(self) + + def __and__( + self, other: Var[OTHER_VAR_TYPE] | Any + ) -> Var[VAR_TYPE | OTHER_VAR_TYPE]: + """Perform a logical AND operation on the current instance and another variable. + + Args: + other: The variable to perform the logical AND operation with. + + Returns: + A `BooleanVar` object representing the result of the logical AND operation. + """ + return and_operation(self, other) + + def __rand__( + self, other: Var[OTHER_VAR_TYPE] | Any + ) -> Var[VAR_TYPE | OTHER_VAR_TYPE]: + """Perform a logical AND operation on the current instance and another variable. + + Args: + other: The variable to perform the logical AND operation with. + + Returns: + A `BooleanVar` object representing the result of the logical AND operation. + """ + return and_operation(other, self) + + def __or__( + self, other: Var[OTHER_VAR_TYPE] | Any + ) -> Var[VAR_TYPE | OTHER_VAR_TYPE]: + """Perform a logical OR operation on the current instance and another variable. + + Args: + other: The variable to perform the logical OR operation with. + + Returns: + A `BooleanVar` object representing the result of the logical OR operation. + """ + return or_operation(self, other) + + def __ror__( + self, other: Var[OTHER_VAR_TYPE] | Any + ) -> Var[VAR_TYPE | OTHER_VAR_TYPE]: + """Perform a logical OR operation on the current instance and another variable. + + Args: + other: The variable to perform the logical OR operation with. + + Returns: + A `BooleanVar` object representing the result of the logical OR operation. + """ + return or_operation(other, self) + + def __invert__(self) -> BooleanVar: + """Perform a logical NOT operation on the current instance. + + Returns: + A `BooleanVar` object representing the result of the logical NOT operation. + """ + return ~self.bool() + + def to_string(self, use_json: bool = True) -> StringVar: + """Convert the var to a string. + + Args: + use_json: Whether to use JSON stringify. If False, uses Object.prototype.toString. + + Returns: + The string var. + """ + from .function import JSON_STRINGIFY, PROTOTYPE_TO_STRING + from .sequence import StringVar + + return ( + JSON_STRINGIFY.call(self).to(StringVar) + if use_json + else PROTOTYPE_TO_STRING.call(self).to(StringVar) + ) + + def _as_ref(self) -> Var: + """Get a reference to the var. + + Returns: + The reference to the var. + """ + return Var( + _js_expr=f"refs[{Var.create(str(self))}]", + _var_data=VarData( + imports={ + f"$/{constants.Dirs.STATE_PATH}": [imports.ImportVar(tag="refs")] + } + ), + ).to(str) + + def js_type(self) -> StringVar: + """Returns the javascript type of the object. + + This method uses the `typeof` function from the `FunctionStringVar` class + to determine the type of the object. + + Returns: + StringVar: A string variable representing the type of the object. + """ + from .function import FunctionStringVar + from .sequence import StringVar + + type_of = FunctionStringVar("typeof") + return type_of.call(self).to(StringVar) + + def _without_data(self): + """Create a copy of the var without the data. + + Returns: + The var without the data. + """ + return dataclasses.replace(self, _var_data=None) + + def _decode(self) -> Any: + """Decode Var as a python value. + + Note that Var with state set cannot be decoded python-side and will be + returned as full_name. + + Returns: + The decoded value or the Var name. + """ + if isinstance(self, LiteralVar): + return self._var_value + try: + return json.loads(str(self)) + except ValueError: + return str(self) + + @property + def _var_state(self) -> str: + """Compat method for getting the state. + + Returns: + The state name associated with the var. + """ + var_data = self._get_all_var_data() + return var_data.state if var_data else "" + + @overload + @classmethod + def range(cls, stop: int | NumberVar, /) -> ArrayVar[Sequence[int]]: ... + + @overload + @classmethod + def range( + cls, + start: int | NumberVar, + end: int | NumberVar, + step: int | NumberVar = 1, + /, + ) -> ArrayVar[Sequence[int]]: ... + + @classmethod + def range( + cls, + first_endpoint: int | NumberVar, + second_endpoint: int | NumberVar | None = None, + step: int | NumberVar | None = None, + ) -> ArrayVar[Sequence[int]]: + """Create a range of numbers. + + Args: + first_endpoint: The end of the range if second_endpoint is not provided, otherwise the start of the range. + second_endpoint: The end of the range. + step: The step of the range. + + Returns: + The range of numbers. + """ + from .sequence import ArrayVar + + return ArrayVar.range(first_endpoint, second_endpoint, step) + + if not TYPE_CHECKING: + + def __getitem__(self, key: Any) -> Var: + """Get the item from the var. + + Args: + key: The key to get. + + Raises: + UntypedVarError: If the var type is Any. + TypeError: If the var type is Any. + + # noqa: DAR101 self + """ + if self._var_type is Any: + raise exceptions.UntypedVarError( + self, + f"access the item '{key}'", + ) + msg = f"Var of type {self._var_type} does not support item access." + raise TypeError(msg) + + def __getattr__(self, name: str): + """Get an attribute of the var. + + Args: + name: The name of the attribute. + + Raises: + VarAttributeError: If the attribute does not exist. + UntypedVarError: If the var type is Any. + TypeError: If the var type is Any. + + # noqa: DAR101 self + """ + if name.startswith("_"): + msg = f"Attribute {name} not found." + raise VarAttributeError(msg) + + if name == "contains": + msg = f"Var of type {self._var_type} does not support contains check." + raise TypeError(msg) + if name == "reverse": + msg = "Cannot reverse non-list var." + raise TypeError(msg) + + if self._var_type is Any: + raise exceptions.UntypedVarError( + self, + f"access the attribute '{name}'", + ) + + msg = f"The State var {escape(self._js_expr)} of type {escape(str(self._var_type))} has no attribute '{name}' or may have been annotated wrongly." + raise VarAttributeError(msg) + + def __bool__(self) -> bool: + """Raise exception if using Var in a boolean context. + + Raises: + VarTypeError: when attempting to bool-ify the Var. + + # noqa: DAR101 self + """ + msg = ( + f"Cannot convert Var {str(self)!r} to bool for use with `if`, `and`, `or`, and `not`. " + "Instead use `rx.cond` and bitwise operators `&` (and), `|` (or), `~` (invert)." + ) + raise VarTypeError(msg) + + def __iter__(self) -> Any: + """Raise exception if using Var in an iterable context. + + Raises: + VarTypeError: when attempting to iterate over the Var. + + # noqa: DAR101 self + """ + msg = f"Cannot iterate over Var {str(self)!r}. Instead use `rx.foreach`." + raise VarTypeError(msg) + + def __contains__(self, _: Any) -> Var: + """Override the 'in' operator to alert the user that it is not supported. + + Raises: + VarTypeError: the operation is not supported + + # noqa: DAR101 self + """ + msg = ( + "'in' operator not supported for Var types, use Var.contains() instead." + ) + raise VarTypeError(msg) + + +OUTPUT = TypeVar("OUTPUT", bound=Var) + +VAR_SUBCLASS = TypeVar("VAR_SUBCLASS", bound=Var) +VAR_INSIDE = TypeVar("VAR_INSIDE") + + +class ToOperation: + """A var operation that converts a var to another type.""" + + def __getattr__(self, name: str) -> Any: + """Get an attribute of the var. + + Args: + name: The name of the attribute. + + Returns: + The attribute of the var. + """ + from .object import ObjectVar + + if isinstance(self, ObjectVar) and name != "_js_expr": + return ObjectVar.__getattr__(self, name) + return getattr(self._original, name) + + def __post_init__(self): + """Post initialization.""" + object.__delattr__(self, "_js_expr") + + def __hash__(self) -> int: + """Calculate the hash value of the object. + + Returns: + int: The hash value of the object. + """ + return hash(self._original) + + def _get_all_var_data(self) -> VarData | None: + """Get all the var data. + + Returns: + The var data. + """ + return VarData.merge( + self._original._get_all_var_data(), + self._var_data, + ) + + @classmethod + def create( + cls, + value: Var, + _var_type: GenericType | None = None, + _var_data: VarData | None = None, + ): + """Create a ToOperation. + + Args: + value: The value of the var. + _var_type: The type of the Var. + _var_data: Additional hooks and imports associated with the Var. + + Returns: + The ToOperation. + """ + return cls( + _js_expr="", # pyright: ignore [reportCallIssue] + _var_data=_var_data, # pyright: ignore [reportCallIssue] + _var_type=_var_type or cls._default_var_type, # pyright: ignore [reportCallIssue, reportAttributeAccessIssue] + _original=value, # pyright: ignore [reportCallIssue] + ) + + +class LiteralVar(Var): + """Base class for immutable literal vars.""" + + def __init_subclass__(cls, **kwargs): + """Initialize the subclass. + + Args: + **kwargs: Additional keyword arguments. + + Raises: + TypeError: If the LiteralVar subclass does not have a corresponding Var subclass. + """ + super().__init_subclass__(**kwargs) + + bases = cls.__bases__ + + bases_normalized = [ + base if isinstance(base, type) else get_origin(base) for base in bases + ] + + possible_bases = [ + base + for base in bases_normalized + if safe_issubclass(base, Var) and base != LiteralVar + ] + + if not possible_bases: + msg = f"LiteralVar subclass {cls} must have a base class that is a subclass of Var and not LiteralVar." + raise TypeError(msg) + + var_subclasses = [ + var_subclass + for var_subclass in _var_subclasses + if var_subclass.var_subclass in possible_bases + ] + + if not var_subclasses: + msg = f"LiteralVar {cls} must have a base class annotated with `python_types`." + raise TypeError(msg) + + if len(var_subclasses) != 1: + msg = f"LiteralVar {cls} must have exactly one base class annotated with `python_types`." + raise TypeError(msg) + + var_subclass = var_subclasses[0] + + # Remove the old subclass, happens because __init_subclass__ is called twice + # for each subclass. This is because of __slots__ in dataclasses. + for var_literal_subclass in list(_var_literal_subclasses): + if var_literal_subclass[1] is var_subclass: + _var_literal_subclasses.remove(var_literal_subclass) + + _var_literal_subclasses.append((cls, var_subclass)) + + @classmethod + def _create_literal_var( + cls, + value: Any, + _var_data: VarData | None = None, + ) -> Var: + """Create a var from a value. + + Args: + value: The value to create the var from. + _var_data: Additional hooks and imports associated with the Var. + + Returns: + The var. + + Raises: + TypeError: If the value is not a supported type for LiteralVar. + """ + from .object import LiteralObjectVar + from .sequence import ArrayVar, LiteralStringVar + + if isinstance(value, Var): + if _var_data is None: + return value + return value._replace(merge_var_data=_var_data) + + for literal_subclass, var_subclass in _var_literal_subclasses[::-1]: + if isinstance(value, var_subclass.python_types): + return literal_subclass.create(value, _var_data=_var_data) + + if ( + (as_var_method := getattr(value, "_as_var", None)) is not None + and callable(as_var_method) + and isinstance((resulting_var := as_var_method()), Var) + ): + return resulting_var + + from reflex.event import EventHandler + from reflex_core.utils.format import get_event_handler_parts + + if isinstance(value, EventHandler): + return Var(_js_expr=".".join(filter(None, get_event_handler_parts(value)))) + + serialized_value = serializers.serialize(value) + if serialized_value is not None: + if isinstance(serialized_value, Mapping): + return LiteralObjectVar.create( + serialized_value, + _var_type=type(value), + _var_data=_var_data, + ) + if isinstance(serialized_value, str): + return LiteralStringVar.create( + serialized_value, _var_type=type(value), _var_data=_var_data + ) + return LiteralVar.create(serialized_value, _var_data=_var_data) + + if dataclasses.is_dataclass(value) and not isinstance(value, type): + return LiteralObjectVar.create( + { + k.name: (None if callable(v := getattr(value, k.name)) else v) + for k in dataclasses.fields(value) + }, + _var_type=type(value), + _var_data=_var_data, + ) + + if isinstance(value, range): + return ArrayVar.range(value.start, value.stop, value.step) + + msg = f"Unsupported type {type(value)} for LiteralVar. Tried to create a LiteralVar from {value}." + raise TypeError(msg) + + if not TYPE_CHECKING: + create = _create_literal_var + + def __post_init__(self): + """Post-initialize the var.""" + + @classmethod + def _get_all_var_data_without_creating_var( + cls, + value: Any, + ) -> VarData | None: + return cls.create(value)._get_all_var_data() + + @classmethod + def _get_all_var_data_without_creating_var_dispatch( + cls, + value: Any, + ) -> VarData | None: + """Get all the var data without creating a var. + + Args: + value: The value to get the var data from. + + Returns: + The var data or None. + + Raises: + TypeError: If the value is not a supported type for LiteralVar. + """ + from .object import LiteralObjectVar + from .sequence import LiteralStringVar + + if isinstance(value, Var): + return value._get_all_var_data() + + for literal_subclass, var_subclass in _var_literal_subclasses[::-1]: + if isinstance(value, var_subclass.python_types): + return literal_subclass._get_all_var_data_without_creating_var(value) + + if ( + (as_var_method := getattr(value, "_as_var", None)) is not None + and callable(as_var_method) + and isinstance((resulting_var := as_var_method()), Var) + ): + return resulting_var._get_all_var_data() + + from reflex.event import EventHandler + from reflex_core.utils.format import get_event_handler_parts + + if isinstance(value, EventHandler): + return Var( + _js_expr=".".join(filter(None, get_event_handler_parts(value))) + )._get_all_var_data() + + serialized_value = serializers.serialize(value) + if serialized_value is not None: + if isinstance(serialized_value, Mapping): + return LiteralObjectVar._get_all_var_data_without_creating_var( + serialized_value + ) + if isinstance(serialized_value, str): + return LiteralStringVar._get_all_var_data_without_creating_var( + serialized_value + ) + return LiteralVar._get_all_var_data_without_creating_var_dispatch( + serialized_value + ) + + if dataclasses.is_dataclass(value) and not isinstance(value, type): + return LiteralObjectVar._get_all_var_data_without_creating_var({ + k.name: (None if callable(v := getattr(value, k.name)) else v) + for k in dataclasses.fields(value) + }) + + if isinstance(value, range): + return None + + msg = f"Unsupported type {type(value)} for LiteralVar. Tried to create a LiteralVar from {value}." + raise TypeError(msg) + + @property + def _var_value(self) -> Any: + msg = "LiteralVar subclasses must implement the _var_value property." + raise NotImplementedError(msg) + + def json(self) -> str: + """Serialize the var to a JSON string. + + Raises: + NotImplementedError: If the method is not implemented. + """ + msg = "LiteralVar subclasses must implement the json method." + raise NotImplementedError(msg) + + +@serializers.serializer +def serialize_literal(value: LiteralVar): + """Serialize a Literal type. + + Args: + value: The Literal to serialize. + + Returns: + The serialized Literal. + """ + return value._var_value + + +def get_python_literal(value: LiteralVar | Any) -> Any | None: + """Get the Python literal value. + + Args: + value: The value to get the Python literal value of. + + Returns: + The Python literal value. + """ + if isinstance(value, LiteralVar): + return value._var_value + if isinstance(value, Var): + return None + return value + + +P = ParamSpec("P") +T = TypeVar("T") + + +# NoReturn is used to match CustomVarOperationReturn with no type hint. +@overload +def var_operation( # pyright: ignore [reportOverlappingOverload] + func: Callable[P, CustomVarOperationReturn[NoReturn]], +) -> Callable[P, Var]: ... + + +@overload +def var_operation( + func: Callable[P, CustomVarOperationReturn[None]], +) -> Callable[P, NoneVar]: ... + + +@overload +def var_operation( # pyright: ignore [reportOverlappingOverload] + func: Callable[P, CustomVarOperationReturn[bool]] + | Callable[P, CustomVarOperationReturn[bool | None]], +) -> Callable[P, BooleanVar]: ... + + +NUMBER_T = TypeVar("NUMBER_T", int, float, int | float) + + +@overload +def var_operation( + func: Callable[P, CustomVarOperationReturn[NUMBER_T]] + | Callable[P, CustomVarOperationReturn[NUMBER_T | None]], +) -> Callable[P, NumberVar[NUMBER_T]]: ... + + +@overload +def var_operation( + func: Callable[P, CustomVarOperationReturn[str]] + | Callable[P, CustomVarOperationReturn[str | None]], +) -> Callable[P, StringVar]: ... + + +LIST_T = TypeVar("LIST_T", bound=Sequence) + + +@overload +def var_operation( + func: Callable[P, CustomVarOperationReturn[LIST_T]] + | Callable[P, CustomVarOperationReturn[LIST_T | None]], +) -> Callable[P, ArrayVar[LIST_T]]: ... + + +OBJECT_TYPE = TypeVar("OBJECT_TYPE", bound=Mapping) + + +@overload +def var_operation( + func: Callable[P, CustomVarOperationReturn[OBJECT_TYPE]] + | Callable[P, CustomVarOperationReturn[OBJECT_TYPE | None]], +) -> Callable[P, ObjectVar[OBJECT_TYPE]]: ... + + +@overload +def var_operation( + func: Callable[P, CustomVarOperationReturn[T]] + | Callable[P, CustomVarOperationReturn[T | None]], +) -> Callable[P, Var[T]]: ... + + +def var_operation( # pyright: ignore [reportInconsistentOverload] + func: Callable[P, CustomVarOperationReturn[T]], +) -> Callable[P, Var[T]]: + """Decorator for creating a var operation. + + Example: + ```python + @var_operation + def add(a: NumberVar, b: NumberVar): + return custom_var_operation(f"{a} + {b}") + ``` + + Args: + func: The function to decorate. + + Returns: + The decorated function. + """ + func_args = list(inspect.signature(func).parameters) + + @functools.wraps(func) + def wrapper(*args: P.args, **kwargs: P.kwargs) -> Var[T]: + args_vars = { + func_args[i]: (LiteralVar.create(arg) if not isinstance(arg, Var) else arg) + for i, arg in enumerate(args) + } + kwargs_vars = { + key: LiteralVar.create(value) if not isinstance(value, Var) else value + for key, value in kwargs.items() + } + + return CustomVarOperation.create( + name=func.__name__, + args=tuple(list(args_vars.items()) + list(kwargs_vars.items())), + return_var=func(*args_vars.values(), **kwargs_vars), # pyright: ignore [reportCallIssue, reportReturnType] + ).guess_type() + + return wrapper + + +def figure_out_type(value: Any) -> types.GenericType: + """Figure out the type of the value. + + Args: + value: The value to figure out the type of. + + Returns: + The type of the value. + """ + if isinstance(value, (list, set, tuple, Mapping, Var)): + if isinstance(value, Var): + return value._var_type + if has_args(value_type := type(value)): + return value_type + if isinstance(value, list): + if not value: + return Sequence[NoReturn] + return Sequence[unionize(*{figure_out_type(v) for v in value[:100]})] + if isinstance(value, set): + return set[unionize(*{figure_out_type(v) for v in value})] + if isinstance(value, tuple): + if not value: + return tuple[NoReturn, ...] + if len(value) <= 5: + return tuple[tuple(figure_out_type(v) for v in value)] + return tuple[unionize(*{figure_out_type(v) for v in value[:100]}), ...] + if isinstance(value, Mapping): + if not value: + return Mapping[NoReturn, NoReturn] + return Mapping[ + unionize(*{figure_out_type(k) for k in list(value.keys())[:100]}), + unionize(*{figure_out_type(v) for v in list(value.values())[:100]}), + ] + return type(value) + + +GLOBAL_CACHE = {} + + +class cached_property: # noqa: N801 + """A cached property that caches the result of the function.""" + + def __init__(self, func: Callable): + """Initialize the cached_property. + + Args: + func: The function to cache. + """ + self._func = func + self._attrname = None + + def __set_name__(self, owner: Any, name: str): + """Set the name of the cached property. + + Args: + owner: The owner of the cached property. + name: The name of the cached property. + + Raises: + TypeError: If the cached property is assigned to two different names. + """ + if self._attrname is None: + self._attrname = name + + original_del = getattr(owner, "__del__", None) + + def delete_property(this: Any): + """Delete the cached property. + + Args: + this: The object to delete the cached property from. + """ + cached_field_name = "_reflex_cache_" + name + try: + unique_id = object.__getattribute__(this, cached_field_name) + except AttributeError: + if original_del is not None: + original_del(this) + return + GLOBAL_CACHE.pop(unique_id, None) + + if original_del is not None: + original_del(this) + + owner.__del__ = delete_property + + elif name != self._attrname: + msg = ( + "Cannot assign the same cached_property to two different names " + f"({self._attrname!r} and {name!r})." + ) + raise TypeError(msg) + + def __get__(self, instance: Any, owner: type | None = None): + """Get the cached property. + + Args: + instance: The instance to get the cached property from. + owner: The owner of the cached property. + + Returns: + The cached property. + + Raises: + TypeError: If the class does not have __set_name__. + """ + if self._attrname is None: + msg = "Cannot use cached_property on a class without __set_name__." + raise TypeError(msg) + cached_field_name = "_reflex_cache_" + self._attrname + try: + unique_id = object.__getattribute__(instance, cached_field_name) + except AttributeError: + unique_id = uuid.uuid4().int + object.__setattr__(instance, cached_field_name, unique_id) + if unique_id not in GLOBAL_CACHE: + GLOBAL_CACHE[unique_id] = self._func(instance) + return GLOBAL_CACHE[unique_id] + + +cached_property_no_lock = cached_property + + +class VarProtocol(Protocol): + """A protocol for Var.""" + + __dataclass_fields__: ClassVar[dict[str, dataclasses.Field[Any]]] + + @property + def _js_expr(self) -> str: ... + + @property + def _var_type(self) -> types.GenericType: ... + + @property + def _var_data(self) -> VarData: ... + + +class CachedVarOperation: + """Base class for cached var operations to lower boilerplate code.""" + + def __post_init__(self): + """Post-initialize the CachedVarOperation.""" + object.__delattr__(self, "_js_expr") + + def __getattr__(self, name: str) -> Any: + """Get an attribute of the var. + + Args: + name: The name of the attribute. + + Returns: + The attribute. + """ + if name == "_js_expr": + return self._cached_var_name + + parent_classes = inspect.getmro(type(self)) + + next_class = parent_classes[parent_classes.index(CachedVarOperation) + 1] + + return next_class.__getattr__(self, name) + + def _get_all_var_data(self) -> VarData | None: + """Get all VarData associated with the Var. + + Returns: + The VarData of the components and all of its children. + """ + return self._cached_get_all_var_data + + @cached_property_no_lock + def _cached_get_all_var_data(self: VarProtocol) -> VarData | None: + """Get the cached VarData. + + Returns: + The cached VarData. + """ + return VarData.merge( + *( + value._get_all_var_data() if isinstance(value, Var) else None + for value in ( + getattr(self, field.name) for field in dataclasses.fields(self) + ) + ), + self._var_data, + ) + + def __hash__(self: DataclassInstance) -> int: + """Calculate the hash of the object. + + Returns: + The hash of the object. + """ + return hash(( + type(self).__name__, + *[ + getattr(self, field.name) + for field in dataclasses.fields(self) + if field.name not in ["_js_expr", "_var_data", "_var_type"] + ], + )) + + +def and_operation( + a: Var[VAR_TYPE] | Any, b: Var[OTHER_VAR_TYPE] | Any +) -> Var[VAR_TYPE | OTHER_VAR_TYPE]: + """Perform a logical AND operation on two variables. + + Args: + a: The first variable. + b: The second variable. + + Returns: + The result of the logical AND operation. + """ + return _and_operation(a, b) + + +@var_operation +def _and_operation(a: Var, b: Var): + """Perform a logical AND operation on two variables. + + Args: + a: The first variable. + b: The second variable. + + Returns: + The result of the logical AND operation. + """ + return var_operation_return( + js_expression=f"({a} && {b})", + var_type=unionize(a._var_type, b._var_type), + ) + + +def or_operation( + a: Var[VAR_TYPE] | Any, b: Var[OTHER_VAR_TYPE] | Any +) -> Var[VAR_TYPE | OTHER_VAR_TYPE]: + """Perform a logical OR operation on two variables. + + Args: + a: The first variable. + b: The second variable. + + Returns: + The result of the logical OR operation. + """ + return _or_operation(a, b) + + +@var_operation +def _or_operation(a: Var, b: Var): + """Perform a logical OR operation on two variables. + + Args: + a: The first variable. + b: The second variable. + + Returns: + The result of the logical OR operation. + """ + return var_operation_return( + js_expression=f"({a} || {b})", + var_type=unionize(a._var_type, b._var_type), + ) + + +RETURN_TYPE = TypeVar("RETURN_TYPE") + +DICT_KEY = TypeVar("DICT_KEY") +DICT_VAL = TypeVar("DICT_VAL") + +LIST_INSIDE = TypeVar("LIST_INSIDE") + + +class FakeComputedVarBaseClass(property): + """A fake base class for ComputedVar to avoid inheriting from property.""" + + __pydantic_run_validation__ = False + + +def is_computed_var(obj: Any) -> TypeGuard[ComputedVar]: + """Check if the object is a ComputedVar. + + Args: + obj: The object to check. + + Returns: + Whether the object is a ComputedVar. + """ + return isinstance(obj, FakeComputedVarBaseClass) + + +@dataclasses.dataclass( + eq=False, + frozen=True, + slots=True, +) +class ComputedVar(Var[RETURN_TYPE]): + """A field with computed getters.""" + + # Whether to track dependencies and cache computed values + _cache: bool = dataclasses.field(default=False) + + # Whether the computed var is a backend var + _backend: bool = dataclasses.field(default=False) + + # The initial value of the computed var + _initial_value: RETURN_TYPE | types.Unset = dataclasses.field(default=types.Unset()) + + # Explicit var dependencies to track + _static_deps: dict[str | None, set[str]] = dataclasses.field(default_factory=dict) + + # Whether var dependencies should be auto-determined + _auto_deps: bool = dataclasses.field(default=True) + + # Interval at which the computed var should be updated + _update_interval: datetime.timedelta | None = dataclasses.field(default=None) + + _fget: Callable[[BaseState], RETURN_TYPE] = dataclasses.field( + default_factory=lambda: lambda _: None + ) # pyright: ignore [reportAssignmentType] + + _name: str = dataclasses.field(default="") + + def __init__( + self, + fget: Callable[[BASE_STATE], RETURN_TYPE], + initial_value: RETURN_TYPE | types.Unset = types.Unset(), + cache: bool = True, + deps: list[str | Var] | None = None, + auto_deps: bool = True, + interval: int | datetime.timedelta | None = None, + backend: bool | None = None, + **kwargs, + ): + """Initialize a ComputedVar. + + Args: + fget: The getter function. + initial_value: The initial value of the computed var. + cache: Whether to cache the computed value. + deps: Explicit var dependencies to track. + auto_deps: Whether var dependencies should be auto-determined. + interval: Interval at which the computed var should be updated. + backend: Whether the computed var is a backend var. + **kwargs: additional attributes to set on the instance + + Raises: + TypeError: If the computed var dependencies are not Var instances or var names. + UntypedComputedVarError: If the computed var is untyped. + """ + hint = kwargs.pop("return_type", None) or get_type_hints(fget).get( + "return", Any + ) + + if hint is Any: + raise UntypedComputedVarError(var_name=fget.__name__) + is_using_fget_name = "_js_expr" not in kwargs + js_expr = kwargs.pop("_js_expr", fget.__name__ + FIELD_MARKER) + kwargs.setdefault("_var_type", hint) + + Var.__init__( + self, + _js_expr=js_expr, + _var_type=kwargs.pop("_var_type"), + _var_data=kwargs.pop( + "_var_data", + VarData(field_name=fget.__name__) if is_using_fget_name else None, + ), + ) + + if kwargs: + msg = f"Unexpected keyword arguments: {tuple(kwargs)}" + raise TypeError(msg) + + if backend is None: + backend = fget.__name__.startswith("_") + + object.__setattr__(self, "_backend", backend) + object.__setattr__(self, "_initial_value", initial_value) + object.__setattr__(self, "_cache", cache) + object.__setattr__(self, "_name", fget.__name__) + + if isinstance(interval, int): + interval = datetime.timedelta(seconds=interval) + + object.__setattr__(self, "_update_interval", interval) + + object.__setattr__( + self, + "_static_deps", + self._calculate_static_deps(deps), + ) + object.__setattr__(self, "_auto_deps", auto_deps) + + object.__setattr__(self, "_fget", fget) + + def _calculate_static_deps( + self, + deps: list[str | Var] | dict[str | None, set[str]] | None = None, + ) -> dict[str | None, set[str]]: + """Calculate the static dependencies of the computed var from user input or existing dependencies. + + Args: + deps: The user input dependencies or existing dependencies. + + Returns: + The static dependencies. + """ + if isinstance(deps, dict): + # Assume a dict is coming from _replace, so no special processing. + return deps + static_deps = {} + if deps is not None: + for dep in deps: + static_deps = self._add_static_dep(dep, static_deps) + return static_deps + + def _add_static_dep( + self, dep: str | Var, deps: dict[str | None, set[str]] | None = None + ) -> dict[str | None, set[str]]: + """Add a static dependency to the computed var or existing dependency set. + + Args: + dep: The dependency to add. + deps: The existing dependency set. + + Returns: + The updated dependency set. + + Raises: + TypeError: If the computed var dependencies are not Var instances or var names. + """ + if deps is None: + deps = self._static_deps + if isinstance(dep, Var): + state_name = ( + all_var_data.state + if (all_var_data := dep._get_all_var_data()) and all_var_data.state + else None + ) + if all_var_data is not None: + var_name = all_var_data.field_name + else: + var_name = dep._js_expr + deps.setdefault(state_name, set()).add(var_name) + elif isinstance(dep, str) and dep != "": + deps.setdefault(None, set()).add(dep) + else: + msg = "ComputedVar dependencies must be Var instances or var names (non-empty strings)." + raise TypeError(msg) + return deps + + @override + def _replace( + self, + merge_var_data: VarData | None = None, + **kwargs: Any, + ) -> Self: + """Replace the attributes of the ComputedVar. + + Args: + merge_var_data: VarData to merge into the existing VarData. + **kwargs: Var fields to update. + + Returns: + The new ComputedVar instance. + + Raises: + TypeError: If kwargs contains keys that are not allowed. + """ + if "deps" in kwargs: + kwargs["deps"] = self._calculate_static_deps(kwargs["deps"]) + field_values = { + "fget": kwargs.pop("fget", self._fget), + "initial_value": kwargs.pop("initial_value", self._initial_value), + "cache": kwargs.pop("cache", self._cache), + "deps": kwargs.pop("deps", copy.copy(self._static_deps)), + "auto_deps": kwargs.pop("auto_deps", self._auto_deps), + "interval": kwargs.pop("interval", self._update_interval), + "backend": kwargs.pop("backend", self._backend), + "_js_expr": kwargs.pop("_js_expr", self._js_expr), + "_var_type": kwargs.pop("_var_type", self._var_type), + "_var_data": kwargs.pop( + "_var_data", VarData.merge(self._var_data, merge_var_data) + ), + "return_type": kwargs.pop("return_type", self._var_type), + } + + if kwargs: + unexpected_kwargs = ", ".join(kwargs.keys()) + msg = f"Unexpected keyword arguments: {unexpected_kwargs}" + raise TypeError(msg) + + return type(self)(**field_values) + + @property + def _cache_attr(self) -> str: + """Get the attribute used to cache the value on the instance. + + Returns: + An attribute name. + """ + return f"__cached_{self._js_expr}" + + @property + def _last_updated_attr(self) -> str: + """Get the attribute used to store the last updated timestamp. + + Returns: + An attribute name. + """ + return f"__last_updated_{self._js_expr}" + + def needs_update(self, instance: BaseState) -> bool: + """Check if the computed var needs to be updated. + + Args: + instance: The state instance that the computed var is attached to. + + Returns: + True if the computed var needs to be updated, False otherwise. + """ + if self._update_interval is None: + return False + last_updated = getattr(instance, self._last_updated_attr, None) + if last_updated is None: + return True + return datetime.datetime.now() - last_updated > self._update_interval + + @overload + def __get__( + self: ComputedVar[bool], + instance: None, + owner: type, + ) -> BooleanVar: ... + + @overload + def __get__( + self: ComputedVar[int] | ComputedVar[float], + instance: None, + owner: type, + ) -> NumberVar: ... + + @overload + def __get__( + self: ComputedVar[str], + instance: None, + owner: type, + ) -> StringVar: ... + + @overload + def __get__( + self: ComputedVar[MAPPING_TYPE], + instance: None, + owner: type, + ) -> ObjectVar[MAPPING_TYPE]: ... + + @overload + def __get__( + self: ComputedVar[list[LIST_INSIDE]], + instance: None, + owner: type, + ) -> ArrayVar[list[LIST_INSIDE]]: ... + + @overload + def __get__( + self: ComputedVar[tuple[LIST_INSIDE, ...]], + instance: None, + owner: type, + ) -> ArrayVar[tuple[LIST_INSIDE, ...]]: ... + + @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]: ... + + @overload + def __get__(self, instance: BaseState, owner: type) -> RETURN_TYPE: ... + + def __get__(self, instance: BaseState | None, owner: type): + """Get the ComputedVar value. + + If the value is already cached on the instance, return the cached value. + + Args: + instance: the instance of the class accessing this computed var. + owner: the class that this descriptor is attached to. + + Returns: + The value of the var for the given instance. + """ + if instance is None: + state_where_defined = owner + while self._name in state_where_defined.inherited_vars: + state_where_defined = state_where_defined.get_parent_state() + + field_name = ( + format_state_name(state_where_defined.get_full_name()) + + "." + + self._js_expr + ) + + return dispatch( + field_name, + var_data=VarData.from_state(state_where_defined, self._name), + result_var_type=self._var_type, + existing_var=self, + ) + + if not self._cache: + value = self.fget(instance) + else: + # handle caching + if not hasattr(instance, self._cache_attr) or self.needs_update(instance): + # Set cache attr on state instance. + setattr(instance, self._cache_attr, self.fget(instance)) + # Ensure the computed var gets serialized to redis. + instance._was_touched = True + # Set the last updated timestamp on the state instance. + setattr(instance, self._last_updated_attr, datetime.datetime.now()) + value = getattr(instance, self._cache_attr) + + self._check_deprecated_return_type(instance, value) + + return value + + def _check_deprecated_return_type(self, instance: BaseState, value: Any) -> None: + if not _isinstance(value, self._var_type, nested=1, treat_var_as_type=False): + console.error( + f"Computed var '{type(instance).__name__}.{self._name}' must return" + f" a value of type '{escape(str(self._var_type))}', got '{value!s}' of type {type(value)}." + ) + + def _deps( + self, + objclass: type[BaseState], + obj: FunctionType | CodeType | None = None, + ) -> dict[str, set[str]]: + """Determine var dependencies of this ComputedVar. + + Save references to attributes accessed on "self" or other fetched states. + + Recursively called when the function makes a method call on "self" or + define comprehensions or nested functions that may reference "self". + + Args: + objclass: the class obj this ComputedVar is attached to. + obj: the object to disassemble (defaults to the fget function). + + Returns: + A dictionary mapping state names to the set of variable names + accessed by the given obj. + """ + from .dep_tracking import DependencyTracker + + d = {} + if self._static_deps: + d.update(self._static_deps) + # None is a placeholder for the current state class. + if None in d: + d[objclass.get_full_name()] = d.pop(None) + + if not self._auto_deps: + return d + + if obj is None: + fget = self._fget + if fget is not None: + obj = cast(FunctionType, fget) + else: + return d + + try: + return DependencyTracker( + func=obj, state_cls=objclass, dependencies=d + ).dependencies + except Exception as e: + console.warn( + "Failed to automatically determine dependencies for computed var " + f"{objclass.__name__}.{self._name}: {e}. " + "Set auto_deps=False and provide accurate deps=['var1', 'var2'] to suppress this warning." + ) + return d + + def mark_dirty(self, instance: BaseState) -> None: + """Mark this ComputedVar as dirty. + + Args: + instance: the state instance that needs to recompute the value. + """ + with contextlib.suppress(AttributeError): + delattr(instance, self._cache_attr) + + def add_dependency(self, objclass: type[BaseState], dep: Var): + """Explicitly add a dependency to the ComputedVar. + + After adding the dependency, when the `dep` changes, this computed var + will be marked dirty. + + Args: + objclass: The class obj this ComputedVar is attached to. + dep: The dependency to add. + + Raises: + VarDependencyError: If the dependency is not a Var instance with a + state and field name + """ + if all_var_data := dep._get_all_var_data(): + state_name = all_var_data.state + if state_name: + var_name = all_var_data.field_name + if var_name: + self._static_deps.setdefault(state_name, set()).add(var_name) + target_state_class = objclass.get_root_state().get_class_substate( + state_name + ) + target_state_class._var_dependencies.setdefault( + var_name, set() + ).add(( + objclass.get_full_name(), + self._name, + )) + target_state_class._potentially_dirty_states.add( + objclass.get_full_name() + ) + return + msg = ( + "ComputedVar dependencies must be Var instances with a state and " + f"field name, got {dep!r}." + ) + raise VarDependencyError(msg) + + def _determine_var_type(self) -> type: + """Get the type of the var. + + Returns: + The type of the var. + """ + hints = get_type_hints(self._fget) + if "return" in hints: + return hints["return"] + return Any # pyright: ignore [reportReturnType] + + @property + def __class__(self) -> type: + """Get the class of the var. + + Returns: + The class of the var. + """ + return FakeComputedVarBaseClass + + @property + def fget(self) -> Callable[[BaseState], RETURN_TYPE]: + """Get the getter function. + + Returns: + The getter function. + """ + return self._fget + + +class DynamicRouteVar(ComputedVar[str | list[str]]): + """A ComputedVar that represents a dynamic route.""" + + +async def _default_async_computed_var(_self: BaseState) -> Any: # noqa: RUF029 + return None + + +@dataclasses.dataclass( + eq=False, + frozen=True, + init=False, + slots=True, +) +class AsyncComputedVar(ComputedVar[RETURN_TYPE]): + """A computed var that wraps a coroutinefunction.""" + + _fget: Callable[[BaseState], Coroutine[None, None, RETURN_TYPE]] = ( + dataclasses.field(default=_default_async_computed_var) + ) + + @overload + def __get__( + self: AsyncComputedVar[bool], + instance: None, + owner: type, + ) -> BooleanVar: ... + + @overload + def __get__( + self: AsyncComputedVar[int] | ComputedVar[float], + instance: None, + owner: type, + ) -> NumberVar: ... + + @overload + def __get__( + self: AsyncComputedVar[str], + instance: None, + owner: type, + ) -> StringVar: ... + + @overload + def __get__( + self: AsyncComputedVar[MAPPING_TYPE], + instance: None, + owner: type, + ) -> ObjectVar[MAPPING_TYPE]: ... + + @overload + def __get__( + self: AsyncComputedVar[list[LIST_INSIDE]], + instance: None, + owner: type, + ) -> ArrayVar[list[LIST_INSIDE]]: ... + + @overload + def __get__( + self: AsyncComputedVar[tuple[LIST_INSIDE, ...]], + instance: None, + owner: type, + ) -> ArrayVar[tuple[LIST_INSIDE, ...]]: ... + + @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]: ... + + @overload + def __get__( + self, instance: BaseState, owner: type + ) -> Coroutine[None, None, RETURN_TYPE]: ... + + def __get__( + self, instance: BaseState | None, owner + ) -> Var | Coroutine[None, None, RETURN_TYPE]: + """Get the ComputedVar value. + + If the value is already cached on the instance, return the cached value. + + Args: + instance: the instance of the class accessing this computed var. + owner: the class that this descriptor is attached to. + + Returns: + The value of the var for the given instance. + """ + if instance is None: + return super(AsyncComputedVar, self).__get__(instance, owner) + + if not self._cache: + + async def _awaitable_result(instance: BaseState = instance) -> RETURN_TYPE: + value = await self.fget(instance) + self._check_deprecated_return_type(instance, value) + return value + + return _awaitable_result() + + # handle caching + async def _awaitable_result(instance: BaseState = instance) -> RETURN_TYPE: + if not hasattr(instance, self._cache_attr) or self.needs_update(instance): + # Set cache attr on state instance. + setattr(instance, self._cache_attr, await self.fget(instance)) + # Ensure the computed var gets serialized to redis. + instance._was_touched = True + # Set the last updated timestamp on the state instance. + setattr(instance, self._last_updated_attr, datetime.datetime.now()) + value = getattr(instance, self._cache_attr) + self._check_deprecated_return_type(instance, value) + return value + + return _awaitable_result() + + @property + def fget(self) -> Callable[[BaseState], Coroutine[None, None, RETURN_TYPE]]: + """Get the getter function. + + Returns: + The getter function. + """ + return self._fget + + +if TYPE_CHECKING: + BASE_STATE = TypeVar("BASE_STATE", bound=BaseState) + + +class _ComputedVarDecorator(Protocol): + """A protocol for the ComputedVar decorator.""" + + @overload + def __call__( + self, + fget: Callable[[BASE_STATE], Coroutine[Any, Any, RETURN_TYPE]], + ) -> AsyncComputedVar[RETURN_TYPE]: ... + + @overload + def __call__( + self, + fget: Callable[[BASE_STATE], RETURN_TYPE], + ) -> ComputedVar[RETURN_TYPE]: ... + + def __call__( + self, + fget: Callable[[BASE_STATE], Any], + ) -> ComputedVar[Any]: ... + + +@overload +def computed_var( + fget: None = None, + initial_value: Any | types.Unset = types.Unset(), + cache: bool = True, + deps: list[str | Var] | None = None, + auto_deps: bool = True, + interval: datetime.timedelta | int | None = None, + backend: bool | None = None, + **kwargs, +) -> _ComputedVarDecorator: ... + + +@overload +def computed_var( + fget: Callable[[BASE_STATE], Coroutine[Any, Any, RETURN_TYPE]], + initial_value: RETURN_TYPE | types.Unset = types.Unset(), + cache: bool = True, + deps: list[str | Var] | None = None, + auto_deps: bool = True, + interval: datetime.timedelta | int | None = None, + backend: bool | None = None, + **kwargs, +) -> AsyncComputedVar[RETURN_TYPE]: ... + + +@overload +def computed_var( + fget: Callable[[BASE_STATE], RETURN_TYPE], + initial_value: RETURN_TYPE | types.Unset = types.Unset(), + cache: bool = True, + deps: list[str | Var] | None = None, + auto_deps: bool = True, + interval: datetime.timedelta | int | None = None, + backend: bool | None = None, + **kwargs, +) -> ComputedVar[RETURN_TYPE]: ... + + +def computed_var( + fget: Callable[[BASE_STATE], Any] | None = None, + initial_value: Any | types.Unset = types.Unset(), + cache: bool = True, + deps: list[str | Var] | None = None, + auto_deps: bool = True, + interval: datetime.timedelta | int | None = None, + backend: bool | None = None, + **kwargs, +) -> ComputedVar | Callable[[Callable[[BASE_STATE], Any]], ComputedVar]: + """A ComputedVar decorator with or without kwargs. + + Args: + fget: The getter function. + initial_value: The initial value of the computed var. + cache: Whether to cache the computed value. + deps: Explicit var dependencies to track. + auto_deps: Whether var dependencies should be auto-determined. + interval: Interval at which the computed var should be updated. + backend: Whether the computed var is a backend var. + **kwargs: additional attributes to set on the instance + + Returns: + A ComputedVar instance. + + Raises: + ValueError: If caching is disabled and an update interval is set. + VarDependencyError: If user supplies dependencies without caching. + ComputedVarSignatureError: If the getter function has more than one argument. + """ + if cache is False and interval is not None: + msg = "Cannot set update interval without caching." + raise ValueError(msg) + + if cache is False and (deps is not None or auto_deps is False): + msg = "Cannot track dependencies without caching." + raise VarDependencyError(msg) + + if fget is not None: + sign = inspect.signature(fget) + if len(sign.parameters) != 1: + raise ComputedVarSignatureError(fget.__name__, signature=str(sign)) + + if inspect.iscoroutinefunction(fget): + computed_var_cls = AsyncComputedVar + else: + computed_var_cls = ComputedVar + return computed_var_cls( + fget, + initial_value=initial_value, + cache=cache, + deps=deps, + auto_deps=auto_deps, + interval=interval, + backend=backend, + **kwargs, + ) + + def wrapper(fget: Callable[[BASE_STATE], Any]) -> ComputedVar: + if inspect.iscoroutinefunction(fget): + computed_var_cls = AsyncComputedVar + else: + computed_var_cls = ComputedVar + return computed_var_cls( + fget, + initial_value=initial_value, + cache=cache, + deps=deps, + auto_deps=auto_deps, + interval=interval, + backend=backend, + **kwargs, + ) + + return wrapper + + +RETURN = TypeVar("RETURN") + + +class CustomVarOperationReturn(Var[RETURN]): + """Base class for custom var operations.""" + + @classmethod + def create( + cls, + js_expression: str, + _var_type: type[RETURN] | None = None, + _var_data: VarData | None = None, + ) -> CustomVarOperationReturn[RETURN]: + """Create a CustomVarOperation. + + Args: + js_expression: The JavaScript expression to evaluate. + _var_type: The type of the var. + _var_data: Additional hooks and imports associated with the Var. + + Returns: + The CustomVarOperation. + """ + return CustomVarOperationReturn( + _js_expr=js_expression, + _var_type=_var_type or Any, + _var_data=_var_data, + ) + + +def var_operation_return( + js_expression: str, + var_type: type[RETURN] | GenericType | None = None, + var_data: VarData | None = None, +) -> CustomVarOperationReturn[RETURN]: + """Shortcut for creating a CustomVarOperationReturn. + + Args: + js_expression: The JavaScript expression to evaluate. + var_type: The type of the var. + var_data: Additional hooks and imports associated with the Var. + + Returns: + The CustomVarOperationReturn. + """ + return CustomVarOperationReturn.create( + js_expression, + var_type, + var_data, + ) + + +@dataclasses.dataclass( + eq=False, + frozen=True, + slots=True, +) +class CustomVarOperation(CachedVarOperation, Var[T]): + """Base class for custom var operations.""" + + _name: str = dataclasses.field(default="") + + _args: tuple[tuple[str, Var], ...] = dataclasses.field(default_factory=tuple) + + _return: CustomVarOperationReturn[T] = dataclasses.field( + default_factory=lambda: CustomVarOperationReturn.create("") + ) + + @cached_property_no_lock + def _cached_var_name(self) -> str: + """Get the cached var name. + + Returns: + The cached var name. + """ + return str(self._return) + + @cached_property_no_lock + def _cached_get_all_var_data(self) -> VarData | None: + """Get the cached VarData. + + Returns: + The cached VarData. + """ + return VarData.merge( + *(arg[1]._get_all_var_data() for arg in self._args), + self._return._get_all_var_data(), + self._var_data, + ) + + @classmethod + def create( + cls, + name: str, + args: tuple[tuple[str, Var], ...], + return_var: CustomVarOperationReturn[T], + _var_data: VarData | None = None, + ) -> CustomVarOperation[T]: + """Create a CustomVarOperation. + + Args: + name: The name of the operation. + args: The arguments to the operation. + return_var: The return var. + _var_data: Additional hooks and imports associated with the Var. + + Returns: + The CustomVarOperation. + """ + return CustomVarOperation( + _js_expr="", + _var_type=return_var._var_type, + _var_data=_var_data, + _name=name, + _args=args, + _return=return_var, + ) + + +class NoneVar(Var[None], python_types=type(None)): + """A var representing None.""" + + +@dataclasses.dataclass( + eq=False, + frozen=True, + slots=True, +) +class LiteralNoneVar(LiteralVar, NoneVar): + """A var representing None.""" + + _var_value: None = None + + def json(self) -> str: + """Serialize the var to a JSON string. + + Returns: + The JSON string. + """ + return "null" + + @classmethod + def _get_all_var_data_without_creating_var(cls, value: None) -> VarData | None: + return None + + @classmethod + def create( + cls, + value: None = None, + _var_data: VarData | None = None, + ) -> LiteralNoneVar: + """Create a var from a value. + + Args: + value: The value of the var. Must be None. Existed for compatibility with LiteralVar. + _var_data: Additional hooks and imports associated with the Var. + + Returns: + The var. + """ + return LiteralNoneVar( + _js_expr="null", + _var_type=None, + _var_data=_var_data, + ) + + +def get_to_operation(var_subclass: type[Var]) -> type[ToOperation]: + """Get the ToOperation class for a given Var subclass. + + Args: + var_subclass: The Var subclass. + + Returns: + The ToOperation class. + + Raises: + ValueError: If the ToOperation class cannot be found. + """ + possible_classes = [ + saved_var_subclass.to_var_subclass + for saved_var_subclass in _var_subclasses + if saved_var_subclass.var_subclass is var_subclass + ] + if not possible_classes: + msg = f"Could not find ToOperation for {var_subclass}." + raise ValueError(msg) + return possible_classes[0] + + +@dataclasses.dataclass( + eq=False, + frozen=True, + slots=True, +) +class StateOperation(CachedVarOperation, Var): + """A var operation that accesses a field on an object.""" + + _state_name: str = dataclasses.field(default="") + _field: Var = dataclasses.field(default_factory=lambda: LiteralNoneVar.create()) + + @cached_property_no_lock + def _cached_var_name(self) -> str: + """Get the cached var name. + + Returns: + The cached var name. + """ + return f"{self._state_name!s}.{self._field!s}" + + def __getattr__(self, name: str) -> Any: + """Get an attribute of the var. + + Args: + name: The name of the attribute. + + Returns: + The attribute. + """ + if name == "_js_expr": + return self._cached_var_name + + return getattr(self._field, name) + + @classmethod + def create( + cls, + state_name: str, + field: Var, + _var_data: VarData | None = None, + ) -> StateOperation: + """Create a DotOperation. + + Args: + state_name: The name of the state. + field: The field of the state. + _var_data: Additional hooks and imports associated with the Var. + + Returns: + The DotOperation. + """ + return StateOperation( + _js_expr="", + _var_type=field._var_type, + _var_data=_var_data, + _state_name=state_name, + _field=field, + ) + + +def get_uuid_string_var() -> Var: + """Return a Var that generates a single memoized UUID via .web/utils/state.js. + + useMemo with an empty dependency array ensures that the generated UUID is + consistent across re-renders of the component. + + Returns: + A Var that generates a UUID at runtime. + """ + from reflex_core.utils.imports import ImportVar + from reflex_core.vars import Var + + unique_uuid_var = get_unique_variable_name() + unique_uuid_var_data = VarData( + imports={ + f"$/{constants.Dirs.STATE_PATH}": ImportVar(tag="generateUUID"), + "react": "useMemo", + }, + hooks={f"const {unique_uuid_var} = useMemo(generateUUID, [])": None}, + ) + + return Var( + _js_expr=unique_uuid_var, + _var_type=str, + _var_data=unique_uuid_var_data, + ) + + +# Set of unique variable names. +USED_VARIABLES = set() + + +@once +def _rng(): + import random + + return random.Random(42) + + +def get_unique_variable_name() -> str: + """Get a unique variable name. + + Returns: + The unique variable name. + """ + name = "".join([_rng().choice(string.ascii_lowercase) for _ in range(8)]) + if name not in USED_VARIABLES: + USED_VARIABLES.add(name) + return name + return get_unique_variable_name() + + +# Compile regex for finding reflex var tags. +_decode_var_pattern_re = ( + rf"{constants.REFLEX_VAR_OPENING_TAG}(.*?){constants.REFLEX_VAR_CLOSING_TAG}" +) +_decode_var_pattern = re.compile(_decode_var_pattern_re, flags=re.DOTALL) + +# Defined global immutable vars. +_global_vars: dict[int, Var] = {} + + +dispatchers: dict[GenericType, Callable[[Var], Var]] = {} + + +def transform(fn: Callable[[Var], Var]) -> Callable[[Var], Var]: + """Register a function to transform a Var. + + Args: + fn: The function to register. + + Returns: + The decorator. + + Raises: + TypeError: If the return type of the function is not a Var. + TypeError: If the Var return type does not have a generic type. + ValueError: If a function for the generic type is already registered. + """ + types = get_type_hints(fn) + return_type = types["return"] + + origin = get_origin(return_type) + + if origin is not Var: + msg = f"Expected return type of {fn.__name__} to be a Var, got {origin}." + raise TypeError(msg) + + generic_args = get_args(return_type) + + if not generic_args: + msg = f"Expected Var return type of {fn.__name__} to have a generic type." + raise TypeError(msg) + + generic_type = get_origin(generic_args[0]) or generic_args[0] + + if generic_type in dispatchers: + msg = f"Function for {generic_type} already registered." + raise ValueError(msg) + + dispatchers[generic_type] = fn + + return fn + + +def dispatch( + field_name: str, + var_data: VarData, + result_var_type: GenericType, + existing_var: Var | None = None, +) -> Var: + """Dispatch a Var to the appropriate transformation function. + + Args: + field_name: The name of the field. + var_data: The VarData associated with the Var. + result_var_type: The type of the Var. + existing_var: The existing Var to transform. Optional. + + Returns: + The transformed Var. + + Raises: + TypeError: If the return type of the function is not a Var. + TypeError: If the Var return type does not have a generic type. + TypeError: If the first argument of the function is not a Var. + TypeError: If the first argument of the function does not have a generic type + """ + result_origin_var_type = get_origin(result_var_type) or result_var_type + + if result_origin_var_type in dispatchers: + fn = dispatchers[result_origin_var_type] + fn_types = get_type_hints(fn) + fn_first_arg_type = fn_types.get( + next(iter(inspect.signature(fn).parameters.values())).name, Any + ) + + fn_return = fn_types.get("return", Any) + + fn_return_origin = get_origin(fn_return) or fn_return + + if fn_return_origin is not Var: + msg = f"Expected return type of {fn.__name__} to be a Var, got {fn_return}." + raise TypeError(msg) + + fn_return_generic_args = get_args(fn_return) + + if not fn_return_generic_args: + msg = f"Expected generic type of {fn_return} to be a type." + raise TypeError(msg) + + arg_origin = get_origin(fn_first_arg_type) or fn_first_arg_type + + if arg_origin is not Var: + msg = f"Expected first argument of {fn.__name__} to be a Var, got {fn_first_arg_type}." + raise TypeError(msg) + + arg_generic_args = get_args(fn_first_arg_type) + + if not arg_generic_args: + msg = f"Expected generic type of {fn_first_arg_type} to be a type." + raise TypeError(msg) + + fn_return_type = fn_return_generic_args[0] + + var = ( + Var( + field_name, + _var_data=var_data, + _var_type=fn_return_type, + ).guess_type() + if existing_var is None + else existing_var._replace( + _var_type=fn_return_type, + _var_data=var_data, + _js_expr=field_name, + ).guess_type() + ) + + return fn(var) + + if existing_var is not None: + return existing_var._replace( + _js_expr=field_name, + _var_data=var_data, + _var_type=result_var_type, + ).guess_type() + return Var( + field_name, + _var_data=var_data, + _var_type=result_var_type, + ).guess_type() + + +if TYPE_CHECKING: + from _typeshed import DataclassInstance + from sqlalchemy.orm import DeclarativeBase + + SQLA_TYPE = TypeVar("SQLA_TYPE", bound=DeclarativeBase | None) + DATACLASS_TYPE = TypeVar("DATACLASS_TYPE", bound=DataclassInstance | None) + MAPPING_TYPE = TypeVar("MAPPING_TYPE", bound=Mapping | None) + V = TypeVar("V") + + +FIELD_TYPE = TypeVar("FIELD_TYPE") + + +class Field(Generic[FIELD_TYPE]): + """A field for a state.""" + + if TYPE_CHECKING: + type_: GenericType + default: FIELD_TYPE | _MISSING_TYPE + default_factory: Callable[[], FIELD_TYPE] | None + + def __init__( + self, + default: FIELD_TYPE | _MISSING_TYPE = MISSING, + default_factory: Callable[[], FIELD_TYPE] | None = None, + is_var: bool = True, + annotated_type: GenericType # pyright: ignore [reportRedeclaration] + | _MISSING_TYPE = MISSING, + ) -> None: + """Initialize the field. + + Args: + default: The default value for the field. + default_factory: The default factory for the field. + is_var: Whether the field is a Var. + annotated_type: The annotated type for the field. + """ + self.default = default + self.default_factory = default_factory + self.is_var = is_var + if annotated_type is not MISSING: + type_origin = get_origin(annotated_type) or annotated_type + if type_origin is Field and ( + args := getattr(annotated_type, "__args__", None) + ): + annotated_type: GenericType = args[0] + type_origin = get_origin(annotated_type) or annotated_type + + if self.default is MISSING and self.default_factory is None: + default_value = types.get_default_value_for_type(annotated_type) + if default_value is None and not types.is_optional(annotated_type): + annotated_type = annotated_type | None + if types.is_immutable(default_value): + self.default = default_value + else: + self.default_factory = functools.partial( + copy.deepcopy, default_value + ) + self.outer_type_ = self.annotated_type = annotated_type + + if type_origin is Annotated: + type_origin = annotated_type.__origin__ # pyright: ignore [reportAttributeAccessIssue] + + self.type_ = self.type_origin = type_origin + else: + self.outer_type_ = self.annotated_type = self.type_ = self.type_origin = Any + + def default_value(self) -> FIELD_TYPE: + """Get the default value for the field. + + Returns: + The default value for the field. + + Raises: + ValueError: If no default value or factory is provided. + """ + if self.default is not MISSING: + return self.default + if self.default_factory is not None: + return self.default_factory() + msg = "No default value or factory provided." + raise ValueError(msg) + + def __repr__(self) -> str: + """Represent the field in a readable format. + + Returns: + The string representation of the field. + """ + annotated_type_str = ( + f", annotated_type={self.annotated_type!r}" + if self.annotated_type is not MISSING + else "" + ) + if self.default is not MISSING: + return f"Field(default={self.default!r}, is_var={self.is_var}{annotated_type_str})" + return f"Field(default_factory={self.default_factory!r}, is_var={self.is_var}{annotated_type_str})" + + if TYPE_CHECKING: + + def __set__(self, instance: Any, value: FIELD_TYPE): + """Set the Var. + + Args: + instance: The instance of the class setting the Var. + value: The value to set the Var to. + + # noqa: DAR101 self + """ + + @overload + def __get__(self: Field[None], instance: None, owner: Any) -> NoneVar: ... + + @overload + def __get__( + self: Field[bool] | Field[bool | None], instance: None, owner: Any + ) -> BooleanVar: ... + + @overload + def __get__( + self: Field[int] | Field[int | None], + instance: None, + owner: Any, + ) -> NumberVar[int]: ... + + @overload + def __get__( + self: Field[float] + | Field[int | float] + | Field[float | None] + | Field[int | float | None], + instance: None, + owner: Any, + ) -> NumberVar: ... + + @overload + def __get__( + self: Field[str] | Field[str | None], instance: None, owner: Any + ) -> StringVar: ... + + @overload + def __get__( + self: Field[list[V]] + | Field[set[V]] + | Field[list[V] | None] + | Field[set[V] | None], + instance: None, + owner: Any, + ) -> ArrayVar[Sequence[V]]: ... + + @overload + def __get__( + self: Field[SEQUENCE_TYPE] | Field[SEQUENCE_TYPE | None], + instance: None, + owner: Any, + ) -> ArrayVar[SEQUENCE_TYPE]: ... + + @overload + def __get__( + self: Field[MAPPING_TYPE] | Field[MAPPING_TYPE | None], + instance: None, + owner: Any, + ) -> ObjectVar[MAPPING_TYPE]: ... + + @overload + def __get__( + self: Field[SQLA_TYPE] | Field[SQLA_TYPE | None], instance: None, owner: Any + ) -> ObjectVar[SQLA_TYPE]: ... + + if TYPE_CHECKING: + + @overload + def __get__( + self: Field[DATACLASS_TYPE] | Field[DATACLASS_TYPE | None], + instance: None, + owner: Any, + ) -> ObjectVar[DATACLASS_TYPE]: ... + + @overload + def __get__(self, instance: None, owner: Any) -> Var[FIELD_TYPE]: ... + + @overload + def __get__(self, instance: Any, owner: Any) -> FIELD_TYPE: ... + + def __get__(self, instance: Any, owner: Any): # pyright: ignore [reportInconsistentOverload] + """Get the Var. + + Args: + instance: The instance of the class accessing the Var. + owner: The class that the Var is attached to. + """ + + +@overload +def field( + default: FIELD_TYPE | _MISSING_TYPE = MISSING, + *, + is_var: Literal[False], + default_factory: Callable[[], FIELD_TYPE] | None = None, +) -> FIELD_TYPE: ... + + +@overload +def field( + default: FIELD_TYPE | _MISSING_TYPE = MISSING, + *, + default_factory: Callable[[], FIELD_TYPE] | None = None, + is_var: Literal[True] = True, +) -> Field[FIELD_TYPE]: ... + + +def field( + default: FIELD_TYPE | _MISSING_TYPE = MISSING, + *, + default_factory: Callable[[], FIELD_TYPE] | None = None, + is_var: bool = True, +) -> Field[FIELD_TYPE] | FIELD_TYPE: + """Create a field for a state. + + Args: + default: The default value for the field. + default_factory: The default factory for the field. + is_var: Whether the field is a Var. + + Returns: + The field for the state. + + Raises: + ValueError: If both default and default_factory are specified. + """ + if default is not MISSING and default_factory is not None: + msg = "cannot specify both default and default_factory" + raise ValueError(msg) + if default is not MISSING and not types.is_immutable(default): + console.warn( + "Mutable default values are not recommended. " + "Use default_factory instead to avoid unexpected behavior." + ) + return Field( + default_factory=functools.partial(copy.deepcopy, default), + is_var=is_var, + ) + return Field( + default=default, + default_factory=default_factory, + is_var=is_var, + ) + + +@dataclass_transform(kw_only_default=True, field_specifiers=(field,)) +class BaseStateMeta(ABCMeta): + """Meta class for BaseState.""" + + if TYPE_CHECKING: + __inherited_fields__: Mapping[str, Field] + __own_fields__: dict[str, Field] + __fields__: dict[str, Field] + + # Whether this state class is a mixin and should not be instantiated. + _mixin: bool = False + + def __new__( + cls, + name: str, + bases: tuple[type, ...], + namespace: dict[str, Any], + mixin: bool = False, + ) -> type: + """Create a new class. + + Args: + name: The name of the class. + bases: The bases of the class. + namespace: The namespace of the class. + mixin: Whether the class is a mixin and should not be instantiated. + + Returns: + The new class. + """ + state_bases = [ + base for base in bases if issubclass(base, EvenMoreBasicBaseState) + ] + mixin = mixin or ( + bool(state_bases) and all(base._mixin for base in state_bases) + ) + # Add the field to the class + inherited_fields: dict[str, Field] = {} + own_fields: dict[str, Field] = {} + resolved_annotations = types.resolve_annotations( + annotations_from_namespace(namespace), namespace["__module__"] + ) + + for base in bases[::-1]: + if hasattr(base, "__inherited_fields__"): + inherited_fields.update(base.__inherited_fields__) + for base in bases[::-1]: + if hasattr(base, "__own_fields__"): + inherited_fields.update(base.__own_fields__) + + for key, value in [ + (key, value) + for key, value in namespace.items() + if key not in resolved_annotations + ]: + if isinstance(value, Field): + if value.annotated_type is not Any: + new_value = value + elif value.default is not MISSING: + new_value = Field( + default=value.default, + is_var=value.is_var, + annotated_type=figure_out_type(value.default), + ) + else: + new_value = Field( + default_factory=value.default_factory, + is_var=value.is_var, + annotated_type=Any, + ) + elif ( + not key.startswith("__") + and not callable(value) + and not isinstance(value, (staticmethod, classmethod, property, Var)) + ): + if types.is_immutable(value): + new_value = Field( + default=value, + annotated_type=figure_out_type(value), + ) + else: + new_value = Field( + default_factory=functools.partial(copy.deepcopy, value), + annotated_type=figure_out_type(value), + ) + else: + continue + + own_fields[key] = new_value + + for key, annotation in resolved_annotations.items(): + value = namespace.get(key, MISSING) + + if types.is_classvar(annotation): + # If the annotation is a classvar, skip it. + continue + + if value is MISSING: + value = Field( + annotated_type=annotation, + ) + elif not isinstance(value, Field): + if types.is_immutable(value): + value = Field( + default=value, + annotated_type=annotation, + ) + else: + value = Field( + default_factory=functools.partial(copy.deepcopy, value), + annotated_type=annotation, + ) + else: + value = Field( + default=value.default, + default_factory=value.default_factory, + is_var=value.is_var, + annotated_type=annotation, + ) + + own_fields[key] = value + + namespace["__own_fields__"] = own_fields + namespace["__inherited_fields__"] = inherited_fields + namespace["__fields__"] = inherited_fields | own_fields + namespace["_mixin"] = mixin + return super().__new__(cls, name, bases, namespace) + + +class EvenMoreBasicBaseState(metaclass=BaseStateMeta): + """A simplified base state class that provides basic functionality.""" + + def __init__( + self, + **kwargs, + ): + """Initialize the state with the given kwargs. + + Args: + **kwargs: The kwargs to pass to the state. + """ + super().__init__() + for key, value in kwargs.items(): + object.__setattr__(self, key, value) + for name, value in type(self).get_fields().items(): + if name not in kwargs: + default_value = value.default_value() + object.__setattr__(self, name, default_value) + + def set(self, **kwargs): + """Mutate the state by setting the given kwargs. Returns the state. + + Args: + **kwargs: The kwargs to set. + + Returns: + The state with the fields set to the given kwargs. + """ + for key, value in kwargs.items(): + setattr(self, key, value) + return self + + @classmethod + def get_fields(cls) -> Mapping[str, Field]: + """Get the fields of the component. + + Returns: + The fields of the component. + """ + return cls.__fields__ + + @classmethod + def add_field(cls, name: str, var: Var, default_value: Any): + """Add a field to the class after class definition. + + Used by State.add_var() to correctly handle the new variable. + + Args: + name: The name of the field to add. + var: The variable to add a field for. + default_value: The default value of the field. + """ + if types.is_immutable(default_value): + new_field = Field( + default=default_value, + annotated_type=var._var_type, + ) + else: + new_field = Field( + default_factory=functools.partial(copy.deepcopy, default_value), + annotated_type=var._var_type, + ) + cls.__fields__[name] = new_field diff --git a/packages/reflex-core/src/reflex_core/vars/color.py b/packages/reflex-core/src/reflex_core/vars/color.py new file mode 100644 index 00000000000..7db57acde2e --- /dev/null +++ b/packages/reflex-core/src/reflex_core/vars/color.py @@ -0,0 +1,166 @@ +"""Vars for colors.""" + +import dataclasses + +from reflex_core.constants.colors import Color +from reflex_core.vars.base import ( + CachedVarOperation, + LiteralVar, + Var, + VarData, + cached_property_no_lock, + get_python_literal, +) +from reflex_core.vars.number import ternary_operation +from reflex_core.vars.sequence import ConcatVarOperation, LiteralStringVar, StringVar + + +class ColorVar(StringVar[Color], python_types=Color): + """Base class for immutable color vars.""" + + +@dataclasses.dataclass( + eq=False, + frozen=True, + slots=True, +) +class LiteralColorVar(CachedVarOperation, LiteralVar, ColorVar): + """Base class for immutable literal color vars.""" + + _var_value: Color = dataclasses.field(default_factory=lambda: Color(color="black")) + + @classmethod + def _get_all_var_data_without_creating_var( + cls, + value: Color, + ) -> VarData | None: + return VarData.merge( + LiteralStringVar._get_all_var_data_without_creating_var(value.color) + if isinstance(value.color, str) + else value.color._get_all_var_data(), + value.alpha._get_all_var_data() + if not isinstance(value.alpha, bool) + else None, + value.shade._get_all_var_data() + if not isinstance(value.shade, int) + else None, + ) + + @classmethod + def create( + cls, + value: Color, + _var_type: type[Color] | None = None, + _var_data: VarData | None = None, + ) -> ColorVar: + """Create a var from a string value. + + Args: + value: The value to create the var from. + _var_type: The type of the var. + _var_data: Additional hooks and imports associated with the Var. + + Returns: + The var. + """ + return cls( + _js_expr="", + _var_type=_var_type or Color, + _var_data=_var_data, + _var_value=value, + ) + + def __hash__(self) -> int: + """Get the hash of the var. + + Returns: + The hash of the var. + """ + return hash(( + self.__class__.__name__, + self._var_value.color, + self._var_value.alpha, + self._var_value.shade, + )) + + @cached_property_no_lock + def _cached_var_name(self) -> str: + """The name of the var. + + Returns: + The name of the var. + """ + alpha = self._var_value.alpha + alpha = ( + ternary_operation( + alpha, + LiteralStringVar.create("a"), + LiteralStringVar.create(""), + ) + if isinstance(alpha, Var) + else LiteralStringVar.create("a" if alpha else "") + ) + + shade = self._var_value.shade + shade = ( + shade.to_string(use_json=False) + if isinstance(shade, Var) + else LiteralStringVar.create(str(shade)) + ) + return str( + ConcatVarOperation.create( + LiteralStringVar.create("var(--"), + self._var_value.color, + LiteralStringVar.create("-"), + alpha, + shade, + LiteralStringVar.create(")"), + ) + ) + + @cached_property_no_lock + def _cached_get_all_var_data(self) -> VarData | None: + """Get all the var data. + + Returns: + The var data. + """ + return VarData.merge( + LiteralStringVar._get_all_var_data_without_creating_var( + self._var_value.color + ) + if isinstance(self._var_value.color, str) + else self._var_value.color._get_all_var_data(), + self._var_value.alpha._get_all_var_data() + if not isinstance(self._var_value.alpha, bool) + else None, + self._var_value.shade._get_all_var_data() + if not isinstance(self._var_value.shade, int) + else None, + self._var_data, + ) + + def json(self) -> str: + """Get the JSON representation of the var. + + Returns: + The JSON representation of the var. + + Raises: + TypeError: If the color is not a valid color. + """ + color, alpha, shade = map( + get_python_literal, + (self._var_value.color, self._var_value.alpha, self._var_value.shade), + ) + if color is None or alpha is None or shade is None: + msg = "Cannot serialize color that contains non-literal vars." + raise TypeError(msg) + if ( + not isinstance(color, str) + or not isinstance(alpha, bool) + or not isinstance(shade, int) + ): + msg = "Color is not a valid color." + raise TypeError(msg) + return f"var(--{color}-{'a' if alpha else ''}{shade})" diff --git a/packages/reflex-core/src/reflex_core/vars/datetime.py b/packages/reflex-core/src/reflex_core/vars/datetime.py new file mode 100644 index 00000000000..d528f678781 --- /dev/null +++ b/packages/reflex-core/src/reflex_core/vars/datetime.py @@ -0,0 +1,202 @@ +"""Immutable datetime and date vars.""" + +from __future__ import annotations + +import dataclasses +from datetime import date, datetime +from typing import Any, TypeVar + +from reflex_core.utils.exceptions import VarTypeError +from reflex_core.vars.number import BooleanVar + +from .base import ( + CustomVarOperationReturn, + LiteralVar, + Var, + VarData, + var_operation, + var_operation_return, +) + +DATETIME_T = TypeVar("DATETIME_T", datetime, date) + +datetime_types = datetime | date + + +def raise_var_type_error(): + """Raise a VarTypeError. + + Raises: + VarTypeError: Cannot compare a datetime object with a non-datetime object. + """ + msg = "Cannot compare a datetime object with a non-datetime object." + raise VarTypeError(msg) + + +class DateTimeVar(Var[DATETIME_T], python_types=(datetime, date)): + """A variable that holds a datetime or date object.""" + + def __lt__(self, other: datetime_types | DateTimeVar) -> BooleanVar: + """Less than comparison. + + Args: + other: The other datetime to compare. + + Returns: + The result of the comparison. + """ + if not isinstance(other, DATETIME_TYPES): + raise_var_type_error() + return date_lt_operation(self, other) + + def __le__(self, other: datetime_types | DateTimeVar) -> BooleanVar: + """Less than or equal comparison. + + Args: + other: The other datetime to compare. + + Returns: + The result of the comparison. + """ + if not isinstance(other, DATETIME_TYPES): + raise_var_type_error() + return date_le_operation(self, other) + + def __gt__(self, other: datetime_types | DateTimeVar) -> BooleanVar: + """Greater than comparison. + + Args: + other: The other datetime to compare. + + Returns: + The result of the comparison. + """ + if not isinstance(other, DATETIME_TYPES): + raise_var_type_error() + return date_gt_operation(self, other) + + def __ge__(self, other: datetime_types | DateTimeVar) -> BooleanVar: + """Greater than or equal comparison. + + Args: + other: The other datetime to compare. + + Returns: + The result of the comparison. + """ + if not isinstance(other, DATETIME_TYPES): + raise_var_type_error() + return date_ge_operation(self, other) + + +@var_operation +def date_gt_operation(lhs: DateTimeVar | Any, rhs: DateTimeVar | Any): + """Greater than comparison. + + Args: + lhs: The left-hand side of the operation. + rhs: The right-hand side of the operation. + + Returns: + The result of the operation. + """ + return date_compare_operation(rhs, lhs, strict=True) + + +@var_operation +def date_lt_operation(lhs: DateTimeVar | Any, rhs: DateTimeVar | Any): + """Less than comparison. + + Args: + lhs: The left-hand side of the operation. + rhs: The right-hand side of the operation. + + Returns: + The result of the operation. + """ + return date_compare_operation(lhs, rhs, strict=True) + + +@var_operation +def date_le_operation(lhs: DateTimeVar | Any, rhs: DateTimeVar | Any): + """Less than or equal comparison. + + Args: + lhs: The left-hand side of the operation. + rhs: The right-hand side of the operation. + + Returns: + The result of the operation. + """ + return date_compare_operation(lhs, rhs) + + +@var_operation +def date_ge_operation(lhs: DateTimeVar | Any, rhs: DateTimeVar | Any): + """Greater than or equal comparison. + + Args: + lhs: The left-hand side of the operation. + rhs: The right-hand side of the operation. + + Returns: + The result of the operation. + """ + return date_compare_operation(rhs, lhs) + + +def date_compare_operation( + lhs: DateTimeVar[DATETIME_T] | Any, + rhs: DateTimeVar[DATETIME_T] | Any, + strict: bool = False, +) -> CustomVarOperationReturn[bool]: + """Check if the value is less than the other value. + + Args: + lhs: The left-hand side of the operation. + rhs: The right-hand side of the operation. + strict: Whether to use strict comparison. + + Returns: + The result of the operation. + """ + return var_operation_return( + f"({lhs} {'<' if strict else '<='} {rhs})", + bool, + ) + + +@dataclasses.dataclass( + eq=False, + frozen=True, + slots=True, +) +class LiteralDatetimeVar(LiteralVar, DateTimeVar): + """Base class for immutable datetime and date vars.""" + + _var_value: date = dataclasses.field(default=datetime.now()) + + @classmethod + def _get_all_var_data_without_creating_var(cls, value: date) -> VarData | None: + return None + + @classmethod + def create(cls, value: date, _var_data: VarData | None = None): + """Create a new instance of the class. + + Args: + value: The value to set. + + Returns: + LiteralDatetimeVar: The new instance of the class. + """ + js_expr = f'"{value!s}"' + return cls( + _js_expr=js_expr, + _var_type=type(value), + _var_value=value, + _var_data=_var_data, + ) + + +DATETIME_TYPES = (datetime, date, DateTimeVar) diff --git a/packages/reflex-core/src/reflex_core/vars/dep_tracking.py b/packages/reflex-core/src/reflex_core/vars/dep_tracking.py new file mode 100644 index 00000000000..2dfea82eb85 --- /dev/null +++ b/packages/reflex-core/src/reflex_core/vars/dep_tracking.py @@ -0,0 +1,485 @@ +"""Collection of base classes.""" + +from __future__ import annotations + +import contextlib +import dataclasses +import dis +import enum +import importlib +import inspect +import sys +from types import CellType, CodeType, FunctionType, ModuleType +from typing import TYPE_CHECKING, Any, ClassVar, cast + +from reflex_core.utils.exceptions import VarValueError + +if TYPE_CHECKING: + from reflex.state import BaseState + + from .base import Var + + +CellEmpty = object() + + +def get_cell_value(cell: CellType) -> Any: + """Get the value of a cell object. + + Args: + cell: The cell object to get the value from. (func.__closure__ objects) + + Returns: + The value from the cell or CellEmpty if a ValueError is raised. + """ + try: + return cell.cell_contents + except ValueError: + return CellEmpty + + +class ScanStatus(enum.Enum): + """State of the dis instruction scanning loop.""" + + SCANNING = enum.auto() + GETTING_ATTR = enum.auto() + GETTING_STATE = enum.auto() + GETTING_STATE_POST_AWAIT = enum.auto() + GETTING_VAR = enum.auto() + GETTING_IMPORT = enum.auto() + + +class UntrackedLocalVarError(VarValueError): + """Raised when a local variable is referenced, but it is not tracked in the current scope.""" + + +def assert_base_state( + local_value: Any, + local_name: str | None = None, +) -> type[BaseState]: + """Assert that a local variable is a BaseState subclass. + + Args: + local_value: The value of the local variable to check. + local_name: The name of the local variable to check. + + Returns: + The local variable value if it is a BaseState subclass. + + Raises: + VarValueError: If the object is not a BaseState subclass. + """ + from reflex.state import BaseState + + if not isinstance(local_value, type) or not issubclass(local_value, BaseState): + msg = f"Cannot determine dependencies in fetched state {local_name!r}: {local_value!r} is not a BaseState." + raise VarValueError(msg) + return local_value + + +@dataclasses.dataclass +class DependencyTracker: + """State machine for identifying state attributes that are accessed by a function.""" + + func: FunctionType | CodeType = dataclasses.field() + state_cls: type[BaseState] = dataclasses.field() + + dependencies: dict[str, set[str]] = dataclasses.field(default_factory=dict) + + scan_status: ScanStatus = dataclasses.field(default=ScanStatus.SCANNING) + top_of_stack: str | None = dataclasses.field(default=None) + + tracked_locals: dict[str, type[BaseState] | ModuleType] = dataclasses.field( + default_factory=dict + ) + + _getting_state_class: type[BaseState] | ModuleType | None = dataclasses.field( + default=None + ) + _get_var_value_positions: dis.Positions | None = dataclasses.field(default=None) + _last_import_name: str | None = dataclasses.field(default=None) + + INVALID_NAMES: ClassVar[list[str]] = ["parent_state", "substates", "get_substate"] + + def __post_init__(self): + """After initializing, populate the dependencies dict.""" + with contextlib.suppress(AttributeError): + # unbox functools.partial + self.func = cast(FunctionType, self.func.func) # pyright: ignore[reportAttributeAccessIssue] + with contextlib.suppress(AttributeError): + # unbox EventHandler + self.func = cast(FunctionType, self.func.fn) # pyright: ignore[reportAttributeAccessIssue,reportFunctionMemberAccess] + + if isinstance(self.func, FunctionType): + with contextlib.suppress(AttributeError, IndexError): + # the first argument to the function is the name of "self" arg + self.tracked_locals[self.func.__code__.co_varnames[0]] = self.state_cls + + self._populate_dependencies() + + def _merge_deps(self, tracker: DependencyTracker) -> None: + """Merge dependencies from another DependencyTracker. + + Args: + tracker: The DependencyTracker to merge dependencies from. + """ + for state_name, dep_name in tracker.dependencies.items(): + self.dependencies.setdefault(state_name, set()).update(dep_name) + + def get_tracked_local(self, local_name: str) -> type[BaseState] | ModuleType: + """Get the value of a local name tracked in the current function scope. + + Args: + local_name: The name of the local variable to fetch. + + Returns: + The value of local name tracked in the current scope (a referenced + BaseState subclass or imported module). + + Raises: + UntrackedLocalVarError: If the local variable is not being tracked. + """ + try: + local_value = self.tracked_locals[local_name] + except KeyError as ke: + msg = f"{local_name!r} is not tracked in the current scope." + raise UntrackedLocalVarError(msg) from ke + return local_value + + def load_attr_or_method(self, instruction: dis.Instruction) -> None: + """Handle loading an attribute or method from the object on top of the stack. + + This method directly tracks attributes and recursively merges + dependencies from analyzing the dependencies of any methods called. + + Args: + instruction: The dis instruction to process. + + Raises: + VarValueError: if the attribute is an disallowed name or attribute + does not reference a BaseState. + """ + from .base import ComputedVar + + if instruction.argval in self.INVALID_NAMES: + msg = f"Cached var {self!s} cannot access arbitrary state via `{instruction.argval}`." + raise VarValueError(msg) + if instruction.argval == "get_state": + # Special case: arbitrary state access requested. + self.scan_status = ScanStatus.GETTING_STATE + return + if instruction.argval == "get_var_value": + # Special case: arbitrary var access requested. + if sys.version_info >= (3, 11): + self._get_var_value_positions = instruction.positions + self.scan_status = ScanStatus.GETTING_VAR + return + + # Reset status back to SCANNING after attribute is accessed. + self.scan_status = ScanStatus.SCANNING + if not self.top_of_stack: + return + target_obj = self.get_tracked_local(self.top_of_stack) + try: + target_state = assert_base_state(target_obj, local_name=self.top_of_stack) + except VarValueError: + # If the target state is not a BaseState, we cannot track dependencies on it. + return + try: + ref_obj = getattr(target_state, instruction.argval) + except AttributeError: + # Not found on this state class, maybe it is a dynamic attribute that will be picked up later. + ref_obj = None + + if isinstance(ref_obj, property) and not isinstance(ref_obj, ComputedVar): + # recurse into property fget functions + ref_obj = ref_obj.fget + if callable(ref_obj): + # recurse into callable attributes + self._merge_deps( + type(self)(func=cast(FunctionType, ref_obj), state_cls=target_state) + ) + elif ( + instruction.argval in target_state.backend_vars + or instruction.argval in target_state.vars + ): + # var access + self.dependencies.setdefault(target_state.get_full_name(), set()).add( + instruction.argval + ) + + def _get_globals(self) -> dict[str, Any]: + """Get the globals of the function. + + Returns: + The var names and values in the globals of the function. + """ + if isinstance(self.func, CodeType): + return {} + return self.func.__globals__ # pyright: ignore[reportAttributeAccessIssue] + + def _get_closure(self) -> dict[str, Any]: + """Get the closure of the function, with unbound values omitted. + + Returns: + The var names and values in the closure of the function. + """ + if isinstance(self.func, CodeType): + return {} + return { + var_name: get_cell_value(cell) + for var_name, cell in zip( + self.func.__code__.co_freevars, # pyright: ignore[reportAttributeAccessIssue] + self.func.__closure__ or (), + strict=False, + ) + if get_cell_value(cell) is not CellEmpty + } + + def handle_getting_state(self, instruction: dis.Instruction) -> None: + """Handle bytecode analysis when `get_state` was called in the function. + + If the wrapped function is getting an arbitrary state and saving it to a + local variable, this method associates the local variable name with the + state class in self.tracked_locals. + + When an attribute/method is accessed on a tracked local, it will be + associated with this state. + + Args: + instruction: The dis instruction to process. + + Raises: + VarValueError: if the state class cannot be determined from the instruction. + """ + if isinstance(self.func, CodeType): + msg = "Dependency detection cannot identify get_state class from a code object." + raise VarValueError(msg) + if instruction.opname in ("LOAD_FAST", "LOAD_FAST_BORROW"): + self._getting_state_class = self.get_tracked_local( + local_name=instruction.argval, + ) + elif instruction.opname == "LOAD_GLOBAL": + # Special case: referencing state class from global scope. + try: + self._getting_state_class = self._get_globals()[instruction.argval] + except (ValueError, KeyError) as ve: + msg = f"Cached var {self!s} cannot access arbitrary state `{instruction.argval}`, not found in globals." + raise VarValueError(msg) from ve + elif instruction.opname == "LOAD_DEREF": + # Special case: referencing state class from closure. + try: + self._getting_state_class = self._get_closure()[instruction.argval] + except (ValueError, KeyError) as ve: + msg = f"Cached var {self!s} cannot access arbitrary state `{instruction.argval}`, is it defined yet?" + raise VarValueError(msg) from ve + elif instruction.opname in ("LOAD_ATTR", "LOAD_METHOD"): + self._getting_state_class = getattr( + self._getting_state_class, + instruction.argval, + ) + elif instruction.opname == "GET_AWAITABLE": + # Now inside the `await` machinery, subsequent instructions + # operate on the result of the `get_state` call. + self.scan_status = ScanStatus.GETTING_STATE_POST_AWAIT + if self._getting_state_class is not None: + self.top_of_stack = "_" + self.tracked_locals[self.top_of_stack] = self._getting_state_class + self._getting_state_class = None + + def handle_getting_state_post_await(self, instruction: dis.Instruction) -> None: + """Handle bytecode analysis after `get_state` was called in the function. + + This function is called _after_ awaiting self.get_state to capture the + local variable holding the state instance or directly record access to + attributes accessed on the result of get_state. + + Args: + instruction: The dis instruction to process. + + Raises: + VarValueError: if the state class cannot be determined from the instruction. + """ + if instruction.opname == "STORE_FAST" and self.top_of_stack: + # Storing the result of get_state in a local variable. + self.tracked_locals[instruction.argval] = self.tracked_locals.pop( + self.top_of_stack + ) + self.top_of_stack = None + self.scan_status = ScanStatus.SCANNING + elif instruction.opname in ("LOAD_ATTR", "LOAD_METHOD"): + # Attribute access on an inline `get_state`, not assigned to a variable. + self.load_attr_or_method(instruction) + + def _eval_var(self, positions: dis.Positions) -> Var: + """Evaluate instructions from the wrapped function to get the Var object. + + Args: + positions: The disassembly positions of the get_var_value call. + + Returns: + The Var object. + + Raises: + VarValueError: if the source code for the var cannot be determined. + """ + # Get the original source code and eval it to get the Var. + module = inspect.getmodule(self.func) + if module is None or self._get_var_value_positions is None: + msg = f"Cannot determine the source code for the var in {self.func!r}." + raise VarValueError(msg) + start_line = self._get_var_value_positions.end_lineno + start_column = self._get_var_value_positions.end_col_offset + end_line = positions.end_lineno + end_column = positions.end_col_offset + if ( + start_line is None + or start_column is None + or end_line is None + or end_column is None + ): + msg = f"Cannot determine the source code for the var in {self.func!r}." + raise VarValueError(msg) + source = inspect.getsource(module).splitlines(True)[start_line - 1 : end_line] + # Create a python source string snippet. + if len(source) > 1: + snipped_source = "".join([ + *source[0][start_column:], + *source[1:-1], + *source[-1][:end_column], + ]) + else: + snipped_source = source[0][start_column:end_column] + # Evaluate the string in the context of the function's globals, closure and tracked local scope. + return eval( + f"({snipped_source})", + self._get_globals(), + {**self._get_closure(), **self.tracked_locals}, + ) + + def handle_getting_var(self, instruction: dis.Instruction) -> None: + """Handle bytecode analysis when `get_var_value` was called in the function. + + This only really works if the expression passed to `get_var_value` is + evaluable in the function's global scope or closure, so getting the var + value from a var saved in a local variable or in the class instance is + not possible. + + Args: + instruction: The dis instruction to process. + + Raises: + VarValueError: if the source code for the var cannot be determined. + """ + if instruction.opname == "CALL": + if instruction.positions is None: + msg = f"Cannot determine the source code for the var in {self.func!r}." + raise VarValueError(msg) + the_var = self._eval_var(instruction.positions) + the_var_data = the_var._get_all_var_data() + if the_var_data is None: + msg = f"Cannot determine the source code for the var in {self.func!r}." + raise VarValueError(msg) + self.dependencies.setdefault(the_var_data.state, set()).add( + the_var_data.field_name + ) + self.scan_status = ScanStatus.SCANNING + + def _populate_dependencies(self) -> None: + """Update self.dependencies based on the disassembly of self.func. + + Save references to attributes accessed on "self" or other fetched states. + + Recursively called when the function makes a method call on "self" or + define comprehensions or nested functions that may reference "self". + """ + for instruction in dis.get_instructions(self.func): + if self.scan_status == ScanStatus.GETTING_STATE: + self.handle_getting_state(instruction) + elif self.scan_status == ScanStatus.GETTING_STATE_POST_AWAIT: + self.handle_getting_state_post_await(instruction) + elif self.scan_status == ScanStatus.GETTING_VAR: + self.handle_getting_var(instruction) + elif ( + instruction.opname + in ( + "LOAD_FAST", + "LOAD_DEREF", + "LOAD_FAST_BORROW", + "LOAD_FAST_CHECK", + "LOAD_FAST_AND_CLEAR", + ) + and instruction.argval in self.tracked_locals + ): + # bytecode loaded the class instance to the top of stack, next load instruction + # is referencing an attribute on self + self.top_of_stack = instruction.argval + self.scan_status = ScanStatus.GETTING_ATTR + elif ( + instruction.opname + in ( + "LOAD_FAST_LOAD_FAST", + "LOAD_FAST_BORROW_LOAD_FAST_BORROW", + "STORE_FAST_LOAD_FAST", + ) + and instruction.argval[-1] in self.tracked_locals + ): + # Double LOAD_FAST family instructions load multiple values onto the stack, + # the last value in the argval list is the top of the stack. + self.top_of_stack = instruction.argval[-1] + self.scan_status = ScanStatus.GETTING_ATTR + elif self.scan_status == ScanStatus.GETTING_ATTR and instruction.opname in ( + "LOAD_ATTR", + "LOAD_METHOD", + ): + self.load_attr_or_method(instruction) + self.top_of_stack = None + elif instruction.opname == "LOAD_CONST" and isinstance( + instruction.argval, CodeType + ): + # recurse into nested functions / comprehensions, which can reference + # instance attributes from the outer scope + self._merge_deps( + type(self)( + func=instruction.argval, + state_cls=self.state_cls, + tracked_locals=self.tracked_locals, + ) + ) + elif instruction.opname == "IMPORT_NAME" and instruction.argval is not None: + self.scan_status = ScanStatus.GETTING_IMPORT + self._last_import_name = instruction.argval + importlib.import_module(instruction.argval) + top_module_name = instruction.argval.split(".")[0] + self.tracked_locals[instruction.argval] = sys.modules[top_module_name] + self.top_of_stack = instruction.argval + elif instruction.opname == "IMPORT_FROM": + if not self._last_import_name: + msg = f"Cannot find package associated with import {instruction.argval} in {self.func!r}." + raise VarValueError(msg) + if instruction.argval in self._last_import_name.split("."): + # `import ... as ...` case: + # import from interim package, update tracked_locals for the last imported name. + self.tracked_locals[self._last_import_name] = getattr( + self.tracked_locals[self._last_import_name], instruction.argval + ) + continue + # Importing a name from a package/module. + if self._last_import_name is not None and self.top_of_stack: + # The full import name does NOT end up in scope for a `from ... import`. + self.tracked_locals.pop(self._last_import_name) + self.tracked_locals[instruction.argval] = getattr( + importlib.import_module(self._last_import_name), + instruction.argval, + ) + # If we see a STORE_FAST, we can assign the top of stack to an aliased name. + self.top_of_stack = instruction.argval + elif ( + self.scan_status == ScanStatus.GETTING_IMPORT + and instruction.opname == "STORE_FAST" + and self.top_of_stack is not None + ): + self.tracked_locals[instruction.argval] = self.tracked_locals.pop( + self.top_of_stack + ) + self.top_of_stack = None diff --git a/packages/reflex-core/src/reflex_core/vars/function.py b/packages/reflex-core/src/reflex_core/vars/function.py new file mode 100644 index 00000000000..6efec1b5679 --- /dev/null +++ b/packages/reflex-core/src/reflex_core/vars/function.py @@ -0,0 +1,555 @@ +"""Immutable function vars.""" + +from __future__ import annotations + +import dataclasses +from collections.abc import Callable, Sequence +from typing import Any, Concatenate, Generic, ParamSpec, Protocol, TypeVar, overload + +from reflex_core.utils import format +from reflex_core.utils.types import GenericType + +from .base import CachedVarOperation, LiteralVar, Var, VarData, cached_property_no_lock + +P = ParamSpec("P") +V1 = TypeVar("V1") +V2 = TypeVar("V2") +V3 = TypeVar("V3") +V4 = TypeVar("V4") +V5 = TypeVar("V5") +V6 = TypeVar("V6") +R = TypeVar("R") + + +class ReflexCallable(Protocol[P, R]): + """Protocol for a callable.""" + + __call__: Callable[P, R] + + +CALLABLE_TYPE = TypeVar("CALLABLE_TYPE", bound=ReflexCallable, covariant=True) +OTHER_CALLABLE_TYPE = TypeVar( + "OTHER_CALLABLE_TYPE", bound=ReflexCallable, covariant=True +) + + +def _is_js_identifier_start(char: str) -> bool: + """Check whether a character can start a JavaScript identifier. + + Returns: + True if the character is valid as the first character of a JS identifier. + """ + return char == "$" or char == "_" or char.isalpha() + + +def _is_js_identifier_char(char: str) -> bool: + """Check whether a character can continue a JavaScript identifier. + + Returns: + True if the character is valid within a JS identifier. + """ + return _is_js_identifier_start(char) or char.isdigit() + + +def _starts_with_arrow_function(expr: str) -> bool: + """Check whether an expression starts with an inline arrow function. + + Returns: + True if the expression begins with an arrow function. + """ + if "=>" not in expr: + return False + + expr = expr.lstrip() + if not expr: + return False + + if expr.startswith("async"): + async_remainder = expr[len("async") :] + if async_remainder[:1].isspace(): + expr = async_remainder.lstrip() + + if not expr: + return False + + if _is_js_identifier_start(expr[0]): + end_index = 1 + while end_index < len(expr) and _is_js_identifier_char(expr[end_index]): + end_index += 1 + return expr[end_index:].lstrip().startswith("=>") + + if not expr.startswith("("): + return False + + depth = 0 + string_delimiter: str | None = None + escaped = False + + for index, char in enumerate(expr): + if string_delimiter is not None: + if escaped: + escaped = False + elif char == "\\": + escaped = True + elif char == string_delimiter: + string_delimiter = None + continue + + if char in {"'", '"', "`"}: + string_delimiter = char + continue + + if char == "(": + depth += 1 + continue + + if char == ")": + depth -= 1 + if depth == 0: + return expr[index + 1 :].lstrip().startswith("=>") + + return False + + +class FunctionVar(Var[CALLABLE_TYPE], default_type=ReflexCallable[Any, Any]): + """Base class for immutable function vars.""" + + @overload + def partial(self) -> FunctionVar[CALLABLE_TYPE]: ... + + @overload + def partial( + self: FunctionVar[ReflexCallable[Concatenate[V1, P], R]], + arg1: V1 | Var[V1], + ) -> FunctionVar[ReflexCallable[P, R]]: ... + + @overload + def partial( + self: FunctionVar[ReflexCallable[Concatenate[V1, V2, P], R]], + arg1: V1 | Var[V1], + arg2: V2 | Var[V2], + ) -> FunctionVar[ReflexCallable[P, R]]: ... + + @overload + def partial( + self: FunctionVar[ReflexCallable[Concatenate[V1, V2, V3, P], R]], + arg1: V1 | Var[V1], + arg2: V2 | Var[V2], + arg3: V3 | Var[V3], + ) -> FunctionVar[ReflexCallable[P, R]]: ... + + @overload + def partial( + self: FunctionVar[ReflexCallable[Concatenate[V1, V2, V3, V4, P], R]], + arg1: V1 | Var[V1], + arg2: V2 | Var[V2], + arg3: V3 | Var[V3], + arg4: V4 | Var[V4], + ) -> FunctionVar[ReflexCallable[P, R]]: ... + + @overload + def partial( + self: FunctionVar[ReflexCallable[Concatenate[V1, V2, V3, V4, V5, P], R]], + arg1: V1 | Var[V1], + arg2: V2 | Var[V2], + arg3: V3 | Var[V3], + arg4: V4 | Var[V4], + arg5: V5 | Var[V5], + ) -> FunctionVar[ReflexCallable[P, R]]: ... + + @overload + def partial( + self: FunctionVar[ReflexCallable[Concatenate[V1, V2, V3, V4, V5, V6, P], R]], + arg1: V1 | Var[V1], + arg2: V2 | Var[V2], + arg3: V3 | Var[V3], + arg4: V4 | Var[V4], + arg5: V5 | Var[V5], + arg6: V6 | Var[V6], + ) -> FunctionVar[ReflexCallable[P, R]]: ... + + @overload + def partial( + self: FunctionVar[ReflexCallable[P, R]], *args: Var | Any + ) -> FunctionVar[ReflexCallable[P, R]]: ... + + @overload + def partial(self, *args: Var | Any) -> FunctionVar: ... + + def partial(self, *args: Var | Any) -> FunctionVar: # pyright: ignore [reportInconsistentOverload] + """Partially apply the function with the given arguments. + + Args: + *args: The arguments to partially apply the function with. + + Returns: + The partially applied function. + """ + if not args: + return self + return ArgsFunctionOperation.create( + ("...args",), + VarOperationCall.create(self, *args, Var(_js_expr="...args")), + ) + + @overload + def call( + self: FunctionVar[ReflexCallable[[V1], R]], arg1: V1 | Var[V1] + ) -> VarOperationCall[[V1], R]: ... + + @overload + def call( + self: FunctionVar[ReflexCallable[[V1, V2], R]], + arg1: V1 | Var[V1], + arg2: V2 | Var[V2], + ) -> VarOperationCall[[V1, V2], R]: ... + + @overload + def call( + self: FunctionVar[ReflexCallable[[V1, V2, V3], R]], + arg1: V1 | Var[V1], + arg2: V2 | Var[V2], + arg3: V3 | Var[V3], + ) -> VarOperationCall[[V1, V2, V3], R]: ... + + @overload + def call( + self: FunctionVar[ReflexCallable[[V1, V2, V3, V4], R]], + arg1: V1 | Var[V1], + arg2: V2 | Var[V2], + arg3: V3 | Var[V3], + arg4: V4 | Var[V4], + ) -> VarOperationCall[[V1, V2, V3, V4], R]: ... + + @overload + def call( + self: FunctionVar[ReflexCallable[[V1, V2, V3, V4, V5], R]], + arg1: V1 | Var[V1], + arg2: V2 | Var[V2], + arg3: V3 | Var[V3], + arg4: V4 | Var[V4], + arg5: V5 | Var[V5], + ) -> VarOperationCall[[V1, V2, V3, V4, V5], R]: ... + + @overload + def call( + self: FunctionVar[ReflexCallable[[V1, V2, V3, V4, V5, V6], R]], + arg1: V1 | Var[V1], + arg2: V2 | Var[V2], + arg3: V3 | Var[V3], + arg4: V4 | Var[V4], + arg5: V5 | Var[V5], + arg6: V6 | Var[V6], + ) -> VarOperationCall[[V1, V2, V3, V4, V5, V6], R]: ... + + @overload + def call( + self: FunctionVar[ReflexCallable[P, R]], *args: Var | Any + ) -> VarOperationCall[P, R]: ... + + @overload + def call(self, *args: Var | Any) -> Var: ... + + def call(self, *args: Var | Any) -> Var: # pyright: ignore [reportInconsistentOverload] + """Call the function with the given arguments. + + Args: + *args: The arguments to call the function with. + + Returns: + The function call operation. + """ + return VarOperationCall.create(self, *args).guess_type() + + __call__ = call + + +class BuilderFunctionVar( + FunctionVar[CALLABLE_TYPE], default_type=ReflexCallable[Any, Any] +): + """Base class for immutable function vars with the builder pattern.""" + + __call__ = FunctionVar.partial + + +class FunctionStringVar(FunctionVar[CALLABLE_TYPE]): + """Base class for immutable function vars from a string.""" + + @classmethod + def create( + cls, + func: str, + _var_type: type[OTHER_CALLABLE_TYPE] = ReflexCallable[Any, Any], + _var_data: VarData | None = None, + ) -> FunctionStringVar[OTHER_CALLABLE_TYPE]: + """Create a new function var from a string. + + Args: + func: The function to call. + _var_type: The type of the Var. + _var_data: Additional hooks and imports associated with the Var. + + Returns: + The function var. + """ + return FunctionStringVar( + _js_expr=func, + _var_type=_var_type, + _var_data=_var_data, + ) + + +@dataclasses.dataclass( + eq=False, + frozen=True, + slots=True, +) +class VarOperationCall(Generic[P, R], CachedVarOperation, Var[R]): + """Base class for immutable vars that are the result of a function call.""" + + _func: FunctionVar[ReflexCallable[P, R]] | None = dataclasses.field(default=None) + _args: tuple[Var | Any, ...] = dataclasses.field(default_factory=tuple) + + @cached_property_no_lock + def _cached_var_name(self) -> str: + """The name of the var. + + Returns: + The name of the var. + """ + func_expr = str(self._func) + if _starts_with_arrow_function(func_expr) and not format.is_wrapped( + func_expr, "(" + ): + func_expr = format.wrap(func_expr, "(") + + return f"({func_expr}({', '.join([str(LiteralVar.create(arg)) for arg in self._args])}))" + + @cached_property_no_lock + def _cached_get_all_var_data(self) -> VarData | None: + """Get all the var data associated with the var. + + Returns: + All the var data associated with the var. + """ + return VarData.merge( + self._func._get_all_var_data() if self._func is not None else None, + *[LiteralVar.create(arg)._get_all_var_data() for arg in self._args], + self._var_data, + ) + + @classmethod + def create( + cls, + func: FunctionVar[ReflexCallable[P, R]], + *args: Var | Any, + _var_type: GenericType = Any, + _var_data: VarData | None = None, + ) -> VarOperationCall: + """Create a new function call var. + + Args: + func: The function to call. + *args: The arguments to call the function with. + _var_type: The type of the Var. + _var_data: Additional hooks and imports associated with the Var. + + Returns: + The function call var. + """ + function_return_type = ( + func._var_type.__args__[1] + if getattr(func._var_type, "__args__", None) + else Any + ) + var_type = _var_type if _var_type is not Any else function_return_type + return cls( + _js_expr="", + _var_type=var_type, + _var_data=_var_data, + _func=func, + _args=args, + ) + + +@dataclasses.dataclass(frozen=True) +class DestructuredArg: + """Class for destructured arguments.""" + + fields: tuple[str, ...] = () + rest: str | None = None + + def to_javascript(self) -> str: + """Convert the destructured argument to JavaScript. + + Returns: + The destructured argument in JavaScript. + """ + return format.wrap( + ", ".join(self.fields) + (f", ...{self.rest}" if self.rest else ""), + "{", + "}", + ) + + +@dataclasses.dataclass( + frozen=True, +) +class FunctionArgs: + """Class for function arguments.""" + + args: tuple[str | DestructuredArg, ...] = () + rest: str | None = None + + +def format_args_function_operation( + args: FunctionArgs, return_expr: Var | Any, explicit_return: bool +) -> str: + """Format an args function operation. + + Args: + args: The function arguments. + return_expr: The return expression. + explicit_return: Whether to use explicit return syntax. + + Returns: + The formatted args function operation. + """ + arg_names_str = ", ".join([ + arg if isinstance(arg, str) else arg.to_javascript() for arg in args.args + ]) + (f", ...{args.rest}" if args.rest else "") + + return_expr_str = str(LiteralVar.create(return_expr)) + + # Wrap return expression in curly braces if explicit return syntax is used. + return_expr_str_wrapped = ( + format.wrap(return_expr_str, "{", "}") if explicit_return else return_expr_str + ) + + return f"(({arg_names_str}) => {return_expr_str_wrapped})" + + +@dataclasses.dataclass( + eq=False, + frozen=True, + slots=True, +) +class ArgsFunctionOperation(CachedVarOperation, FunctionVar): + """Base class for immutable function defined via arguments and return expression.""" + + _args: FunctionArgs = dataclasses.field(default_factory=FunctionArgs) + _return_expr: Var | Any = dataclasses.field(default=None) + _explicit_return: bool = dataclasses.field(default=False) + + @cached_property_no_lock + def _cached_var_name(self) -> str: + """The name of the var. + + Returns: + The name of the var. + """ + return format_args_function_operation( + self._args, self._return_expr, self._explicit_return + ) + + @classmethod + def create( + cls, + args_names: Sequence[str | DestructuredArg], + return_expr: Var | Any, + rest: str | None = None, + explicit_return: bool = False, + _var_type: GenericType = Callable, + _var_data: VarData | None = None, + ): + """Create a new function var. + + Args: + args_names: The names of the arguments. + return_expr: The return expression of the function. + rest: The name of the rest argument. + explicit_return: Whether to use explicit return syntax. + _var_type: The type of the Var. + _var_data: Additional hooks and imports associated with the Var. + + Returns: + The function var. + """ + return_expr = Var.create(return_expr) + return cls( + _js_expr="", + _var_type=_var_type, + _var_data=_var_data, + _args=FunctionArgs(args=tuple(args_names), rest=rest), + _return_expr=return_expr, + _explicit_return=explicit_return, + ) + + +@dataclasses.dataclass( + eq=False, + frozen=True, + slots=True, +) +class ArgsFunctionOperationBuilder(CachedVarOperation, BuilderFunctionVar): + """Base class for immutable function defined via arguments and return expression with the builder pattern.""" + + _args: FunctionArgs = dataclasses.field(default_factory=FunctionArgs) + _return_expr: Var | Any = dataclasses.field(default=None) + _explicit_return: bool = dataclasses.field(default=False) + + @cached_property_no_lock + def _cached_var_name(self) -> str: + """The name of the var. + + Returns: + The name of the var. + """ + return format_args_function_operation( + self._args, self._return_expr, self._explicit_return + ) + + @classmethod + def create( + cls, + args_names: Sequence[str | DestructuredArg], + return_expr: Var | Any, + rest: str | None = None, + explicit_return: bool = False, + _var_type: GenericType = Callable, + _var_data: VarData | None = None, + ): + """Create a new function var. + + Args: + args_names: The names of the arguments. + return_expr: The return expression of the function. + rest: The name of the rest argument. + explicit_return: Whether to use explicit return syntax. + _var_type: The type of the Var. + _var_data: Additional hooks and imports associated with the Var. + + Returns: + The function var. + """ + return_expr = Var.create(return_expr) + return cls( + _js_expr="", + _var_type=_var_type, + _var_data=_var_data, + _args=FunctionArgs(args=tuple(args_names), rest=rest), + _return_expr=return_expr, + _explicit_return=explicit_return, + ) + + +JSON_STRINGIFY = FunctionStringVar.create( + "JSON.stringify", _var_type=ReflexCallable[[Any], str] +) +ARRAY_ISARRAY = FunctionStringVar.create( + "Array.isArray", _var_type=ReflexCallable[[Any], bool] +) +PROTOTYPE_TO_STRING = FunctionStringVar.create( + "((__to_string) => __to_string.toString())", + _var_type=ReflexCallable[[Any], str], +) diff --git a/packages/reflex-core/src/reflex_core/vars/number.py b/packages/reflex-core/src/reflex_core/vars/number.py new file mode 100644 index 00000000000..3652a81ab4a --- /dev/null +++ b/packages/reflex-core/src/reflex_core/vars/number.py @@ -0,0 +1,1148 @@ +"""Immutable number vars.""" + +from __future__ import annotations + +import dataclasses +import decimal +import json +import math +from collections.abc import Callable +from typing import TYPE_CHECKING, Any, NoReturn, TypeVar, overload + +from typing_extensions import TypeVar as TypeVarExt + +from reflex_core.constants.base import Dirs +from reflex_core.utils.exceptions import ( + PrimitiveUnserializableToJSONError, + VarTypeError, + VarValueError, +) +from reflex_core.utils.imports import ImportDict, ImportVar +from reflex_core.utils.types import safe_issubclass + +from .base import ( + CustomVarOperationReturn, + LiteralVar, + Var, + VarData, + unionize, + var_operation, + var_operation_return, +) + +NUMBER_T = TypeVarExt( + "NUMBER_T", + bound=(int | float | decimal.Decimal), + default=(int | float | decimal.Decimal), + covariant=True, +) + +if TYPE_CHECKING: + from .sequence import ArrayVar + + +def raise_unsupported_operand_types( + operator: str, operands_types: tuple[type, ...] +) -> NoReturn: + """Raise an unsupported operand types error. + + Args: + operator: The operator. + operands_types: The types of the operands. + + Raises: + VarTypeError: The operand types are unsupported. + """ + msg = f"Unsupported Operand type(s) for {operator}: {', '.join(t.__name__ for t in operands_types)}" + raise VarTypeError(msg) + + +class NumberVar(Var[NUMBER_T], python_types=(int, float, decimal.Decimal)): + """Base class for immutable number vars.""" + + def __add__(self, other: number_types) -> NumberVar: + """Add two numbers. + + Args: + other: The other number. + + Returns: + The number addition operation. + """ + if not isinstance(other, NUMBER_TYPES): + raise_unsupported_operand_types("+", (type(self), type(other))) + return number_add_operation(self, +other) + + def __radd__(self, other: number_types) -> NumberVar: + """Add two numbers. + + Args: + other: The other number. + + Returns: + The number addition operation. + """ + if not isinstance(other, NUMBER_TYPES): + raise_unsupported_operand_types("+", (type(other), type(self))) + return number_add_operation(+other, self) + + def __sub__(self, other: number_types) -> NumberVar: + """Subtract two numbers. + + Args: + other: The other number. + + Returns: + The number subtraction operation. + """ + if not isinstance(other, NUMBER_TYPES): + raise_unsupported_operand_types("-", (type(self), type(other))) + + return number_subtract_operation(self, +other) + + def __rsub__(self, other: number_types) -> NumberVar: + """Subtract two numbers. + + Args: + other: The other number. + + Returns: + The number subtraction operation. + """ + if not isinstance(other, NUMBER_TYPES): + raise_unsupported_operand_types("-", (type(other), type(self))) + + return number_subtract_operation(+other, self) + + def __abs__(self): + """Get the absolute value of the number. + + Returns: + The number absolute operation. + """ + return number_abs_operation(self) + + @overload + def __mul__(self, other: number_types | boolean_types) -> NumberVar: ... + + @overload + def __mul__(self, other: list | tuple | set | ArrayVar) -> ArrayVar: ... + + def __mul__(self, other: Any): + """Multiply two numbers. + + Args: + other: The other number. + + Returns: + The number multiplication operation. + """ + from .sequence import ArrayVar, LiteralArrayVar + + if isinstance(other, (list, tuple, ArrayVar)): + if isinstance(other, ArrayVar): + return other * self + return LiteralArrayVar.create(other) * self + + if not isinstance(other, NUMBER_TYPES): + raise_unsupported_operand_types("*", (type(self), type(other))) + + return number_multiply_operation(self, +other) + + @overload + def __rmul__(self, other: number_types | boolean_types) -> NumberVar: ... + + @overload + def __rmul__(self, other: list | tuple | set | ArrayVar) -> ArrayVar: ... + + def __rmul__(self, other: Any): + """Multiply two numbers. + + Args: + other: The other number. + + Returns: + The number multiplication operation. + """ + from .sequence import ArrayVar, LiteralArrayVar + + if isinstance(other, (list, tuple, ArrayVar)): + if isinstance(other, ArrayVar): + return other * self + return LiteralArrayVar.create(other) * self + + if not isinstance(other, NUMBER_TYPES): + raise_unsupported_operand_types("*", (type(other), type(self))) + + return number_multiply_operation(+other, self) + + def __truediv__(self, other: number_types) -> NumberVar: + """Divide two numbers. + + Args: + other: The other number. + + Returns: + The number true division operation. + """ + if not isinstance(other, NUMBER_TYPES): + raise_unsupported_operand_types("/", (type(self), type(other))) + + return number_true_division_operation(self, +other) + + def __rtruediv__(self, other: number_types) -> NumberVar: + """Divide two numbers. + + Args: + other: The other number. + + Returns: + The number true division operation. + """ + if not isinstance(other, NUMBER_TYPES): + raise_unsupported_operand_types("/", (type(other), type(self))) + + return number_true_division_operation(+other, self) + + def __floordiv__(self, other: number_types) -> NumberVar: + """Floor divide two numbers. + + Args: + other: The other number. + + Returns: + The number floor division operation. + """ + if not isinstance(other, NUMBER_TYPES): + raise_unsupported_operand_types("//", (type(self), type(other))) + + return number_floor_division_operation(self, +other) + + def __rfloordiv__(self, other: number_types) -> NumberVar: + """Floor divide two numbers. + + Args: + other: The other number. + + Returns: + The number floor division operation. + """ + if not isinstance(other, NUMBER_TYPES): + raise_unsupported_operand_types("//", (type(other), type(self))) + + return number_floor_division_operation(+other, self) + + def __mod__(self, other: number_types) -> NumberVar: + """Modulo two numbers. + + Args: + other: The other number. + + Returns: + The number modulo operation. + """ + if not isinstance(other, NUMBER_TYPES): + raise_unsupported_operand_types("%", (type(self), type(other))) + + return number_modulo_operation(self, +other) + + def __rmod__(self, other: number_types) -> NumberVar: + """Modulo two numbers. + + Args: + other: The other number. + + Returns: + The number modulo operation. + """ + if not isinstance(other, NUMBER_TYPES): + raise_unsupported_operand_types("%", (type(other), type(self))) + + return number_modulo_operation(+other, self) + + def __pow__(self, other: number_types) -> NumberVar: + """Exponentiate two numbers. + + Args: + other: The other number. + + Returns: + The number exponent operation. + """ + if not isinstance(other, NUMBER_TYPES): + raise_unsupported_operand_types("**", (type(self), type(other))) + + return number_exponent_operation(self, +other) + + def __rpow__(self, other: number_types) -> NumberVar: + """Exponentiate two numbers. + + Args: + other: The other number. + + Returns: + The number exponent operation. + """ + if not isinstance(other, NUMBER_TYPES): + raise_unsupported_operand_types("**", (type(other), type(self))) + + return number_exponent_operation(+other, self) + + def __neg__(self) -> NumberVar: + """Negate the number. + + Returns: + The number negation operation. + """ + return number_negate_operation(self) # pyright: ignore [reportReturnType] + + def __invert__(self): + """Boolean NOT the number. + + Returns: + The boolean NOT operation. + """ + return boolean_not_operation(self.bool()) + + def __pos__(self) -> NumberVar: + """Positive the number. + + Returns: + The number. + """ + return self + + def __round__(self, ndigits: int | NumberVar = 0) -> NumberVar: + """Round the number. + + Args: + ndigits: The number of digits to round. + + Returns: + The number round operation. + """ + if not isinstance(ndigits, NUMBER_TYPES): + raise_unsupported_operand_types("round", (type(self), type(ndigits))) + + return number_round_operation(self, +ndigits) + + def __ceil__(self): + """Ceil the number. + + Returns: + The number ceil operation. + """ + return number_ceil_operation(self) + + def __floor__(self): + """Floor the number. + + Returns: + The number floor operation. + """ + return number_floor_operation(self) + + def __trunc__(self): + """Trunc the number. + + Returns: + The number trunc operation. + """ + return number_trunc_operation(self) + + def __lt__(self, other: number_types) -> BooleanVar: + """Less than comparison. + + Args: + other: The other number. + + Returns: + The result of the comparison. + """ + if not isinstance(other, NUMBER_TYPES): + raise_unsupported_operand_types("<", (type(self), type(other))) + return less_than_operation(+self, +other) + + def __le__(self, other: number_types) -> BooleanVar: + """Less than or equal comparison. + + Args: + other: The other number. + + Returns: + The result of the comparison. + """ + if not isinstance(other, NUMBER_TYPES): + raise_unsupported_operand_types("<=", (type(self), type(other))) + return less_than_or_equal_operation(+self, +other) + + def __eq__(self, other: Any): + """Equal comparison. + + Args: + other: The other number. + + Returns: + The result of the comparison. + """ + if isinstance(other, NUMBER_TYPES): + return equal_operation(+self, +other) + return equal_operation(self, other) + + def __ne__(self, other: Any): + """Not equal comparison. + + Args: + other: The other number. + + Returns: + The result of the comparison. + """ + if isinstance(other, NUMBER_TYPES): + return not_equal_operation(+self, +other) + return not_equal_operation(self, other) + + def __gt__(self, other: number_types) -> BooleanVar: + """Greater than comparison. + + Args: + other: The other number. + + Returns: + The result of the comparison. + """ + if not isinstance(other, NUMBER_TYPES): + raise_unsupported_operand_types(">", (type(self), type(other))) + return greater_than_operation(+self, +other) + + def __ge__(self, other: number_types) -> BooleanVar: + """Greater than or equal comparison. + + Args: + other: The other number. + + Returns: + The result of the comparison. + """ + if not isinstance(other, NUMBER_TYPES): + raise_unsupported_operand_types(">=", (type(self), type(other))) + return greater_than_or_equal_operation(+self, +other) + + def _is_strict_float(self) -> bool: + """Check if the number is a float. + + Returns: + bool: True if the number is a float. + """ + return safe_issubclass(self._var_type, float) + + def _is_strict_int(self) -> bool: + """Check if the number is an int. + + Returns: + bool: True if the number is an int. + """ + return safe_issubclass(self._var_type, int) + + def __format__(self, format_spec: str) -> str: + """Format the number. + + Args: + format_spec: The format specifier. + + Returns: + The formatted number. + + Raises: + VarValueError: If the format specifier is not supported. + """ + from .sequence import ( + get_decimal_string_operation, + get_decimal_string_separator_operation, + ) + + separator = "" + + if format_spec and format_spec[:1] == ",": + separator = "," + format_spec = format_spec[1:] + elif format_spec and format_spec[:1] == "_": + separator = "_" + format_spec = format_spec[1:] + + if ( + format_spec + and format_spec[-1] == "f" + and format_spec[0] == "." + and format_spec[1:-1].isdigit() + ): + how_many_decimals = int(format_spec[1:-1]) + return f"{get_decimal_string_operation(self, Var.create(how_many_decimals), Var.create(separator))}" + + if not format_spec and separator: + return ( + f"{get_decimal_string_separator_operation(self, Var.create(separator))}" + ) + + if format_spec: + msg = ( + f"Unknown format code '{format_spec}' for object of type 'NumberVar'. It is only supported to use ',', '_', and '.f' for float numbers." + "If possible, use computed variables instead: https://reflex.dev/docs/vars/computed-vars/" + ) + raise VarValueError(msg) + + return super().__format__(format_spec) + + +def binary_number_operation( + func: Callable[[NumberVar, NumberVar], str], +) -> Callable[[number_types, number_types], NumberVar]: + """Decorator to create a binary number operation. + + Args: + func: The binary number operation function. + + Returns: + The binary number operation. + """ + + @var_operation + def operation(lhs: NumberVar, rhs: NumberVar): + return var_operation_return( + js_expression=func(lhs, rhs), + var_type=unionize(lhs._var_type, rhs._var_type), + ) + + def wrapper(lhs: number_types, rhs: number_types) -> NumberVar: + """Create the binary number operation. + + Args: + lhs: The first number. + rhs: The second number. + + Returns: + The binary number operation. + """ + return operation(lhs, rhs) # pyright: ignore [reportReturnType, reportArgumentType] + + return wrapper + + +@binary_number_operation +def number_add_operation(lhs: NumberVar, rhs: NumberVar): + """Add two numbers. + + Args: + lhs: The first number. + rhs: The second number. + + Returns: + The number addition operation. + """ + return f"({lhs} + {rhs})" + + +@binary_number_operation +def number_subtract_operation(lhs: NumberVar, rhs: NumberVar): + """Subtract two numbers. + + Args: + lhs: The first number. + rhs: The second number. + + Returns: + The number subtraction operation. + """ + return f"({lhs} - {rhs})" + + +@var_operation +def number_abs_operation(value: NumberVar): + """Get the absolute value of the number. + + Args: + value: The number. + + Returns: + The number absolute operation. + """ + return var_operation_return( + js_expression=f"Math.abs({value})", var_type=value._var_type + ) + + +@binary_number_operation +def number_multiply_operation(lhs: NumberVar, rhs: NumberVar): + """Multiply two numbers. + + Args: + lhs: The first number. + rhs: The second number. + + Returns: + The number multiplication operation. + """ + return f"({lhs} * {rhs})" + + +@var_operation +def number_negate_operation( + value: NumberVar[NUMBER_T], +) -> CustomVarOperationReturn[NUMBER_T]: + """Negate the number. + + Args: + value: The number. + + Returns: + The number negation operation. + """ + return var_operation_return(js_expression=f"-({value})", var_type=value._var_type) + + +@binary_number_operation +def number_true_division_operation(lhs: NumberVar, rhs: NumberVar): + """Divide two numbers. + + Args: + lhs: The first number. + rhs: The second number. + + Returns: + The number true division operation. + """ + return f"({lhs} / {rhs})" + + +@binary_number_operation +def number_floor_division_operation(lhs: NumberVar, rhs: NumberVar): + """Floor divide two numbers. + + Args: + lhs: The first number. + rhs: The second number. + + Returns: + The number floor division operation. + """ + return f"Math.floor({lhs} / {rhs})" + + +@binary_number_operation +def number_modulo_operation(lhs: NumberVar, rhs: NumberVar): + """Modulo two numbers. + + Args: + lhs: The first number. + rhs: The second number. + + Returns: + The number modulo operation. + """ + return f"({lhs} % {rhs})" + + +@binary_number_operation +def number_exponent_operation(lhs: NumberVar, rhs: NumberVar): + """Exponentiate two numbers. + + Args: + lhs: The first number. + rhs: The second number. + + Returns: + The number exponent operation. + """ + return f"({lhs} ** {rhs})" + + +@var_operation +def number_round_operation(value: NumberVar, ndigits: NumberVar | int): + """Round the number. + + Args: + value: The number. + ndigits: The number of digits. + + Returns: + The number round operation. + """ + if (isinstance(ndigits, LiteralNumberVar) and ndigits._var_value == 0) or ( + isinstance(ndigits, int) and ndigits == 0 + ): + return var_operation_return(js_expression=f"Math.round({value})", var_type=int) + return var_operation_return( + js_expression=f"(+{value}.toFixed({ndigits}))", var_type=float + ) + + +@var_operation +def number_ceil_operation(value: NumberVar): + """Ceil the number. + + Args: + value: The number. + + Returns: + The number ceil operation. + """ + return var_operation_return(js_expression=f"Math.ceil({value})", var_type=int) + + +@var_operation +def number_floor_operation(value: NumberVar): + """Floor the number. + + Args: + value: The number. + + Returns: + The number floor operation. + """ + return var_operation_return(js_expression=f"Math.floor({value})", var_type=int) + + +@var_operation +def number_trunc_operation(value: NumberVar): + """Trunc the number. + + Args: + value: The number. + + Returns: + The number trunc operation. + """ + return var_operation_return(js_expression=f"Math.trunc({value})", var_type=int) + + +class BooleanVar(NumberVar[bool], python_types=bool): + """Base class for immutable boolean vars.""" + + def __invert__(self): + """NOT the boolean. + + Returns: + The boolean NOT operation. + """ + return boolean_not_operation(self) + + def __int__(self): + """Convert the boolean to an int. + + Returns: + The boolean to int operation. + """ + return boolean_to_number_operation(self) + + def __pos__(self): + """Convert the boolean to an int. + + Returns: + The boolean to int operation. + """ + return boolean_to_number_operation(self) + + def bool(self) -> BooleanVar: + """Boolean conversion. + + Returns: + The boolean value of the boolean. + """ + return self + + def __lt__(self, other: Any): + """Less than comparison. + + Args: + other: The other boolean. + + Returns: + The result of the comparison. + """ + return +self < other + + def __le__(self, other: Any): + """Less than or equal comparison. + + Args: + other: The other boolean. + + Returns: + The result of the comparison. + """ + return +self <= other + + def __gt__(self, other: Any): + """Greater than comparison. + + Args: + other: The other boolean. + + Returns: + The result of the comparison. + """ + return +self > other + + def __ge__(self, other: Any): + """Greater than or equal comparison. + + Args: + other: The other boolean. + + Returns: + The result of the comparison. + """ + return +self >= other + + +@var_operation +def boolean_to_number_operation(value: BooleanVar): + """Convert the boolean to a number. + + Args: + value: The boolean. + + Returns: + The boolean to number operation. + """ + return var_operation_return(js_expression=f"Number({value})", var_type=int) + + +def comparison_operator( + func: Callable[[Var, Var], str], +) -> Callable[[Var | Any, Var | Any], BooleanVar]: + """Decorator to create a comparison operation. + + Args: + func: The comparison operation function. + + Returns: + The comparison operation. + """ + + @var_operation + def operation(lhs: Var, rhs: Var): + return var_operation_return( + js_expression=func(lhs, rhs), + var_type=bool, + ) + + def wrapper(lhs: Var | Any, rhs: Var | Any) -> BooleanVar: + """Create the comparison operation. + + Args: + lhs: The first value. + rhs: The second value. + + Returns: + The comparison operation. + """ + return operation(lhs, rhs) + + return wrapper + + +@comparison_operator +def greater_than_operation(lhs: Var, rhs: Var): + """Greater than comparison. + + Args: + lhs: The first value. + rhs: The second value. + + Returns: + The result of the comparison. + """ + return f"({lhs} > {rhs})" + + +@comparison_operator +def greater_than_or_equal_operation(lhs: Var, rhs: Var): + """Greater than or equal comparison. + + Args: + lhs: The first value. + rhs: The second value. + + Returns: + The result of the comparison. + """ + return f"({lhs} >= {rhs})" + + +@comparison_operator +def less_than_operation(lhs: Var, rhs: Var): + """Less than comparison. + + Args: + lhs: The first value. + rhs: The second value. + + Returns: + The result of the comparison. + """ + return f"({lhs} < {rhs})" + + +@comparison_operator +def less_than_or_equal_operation(lhs: Var, rhs: Var): + """Less than or equal comparison. + + Args: + lhs: The first value. + rhs: The second value. + + Returns: + The result of the comparison. + """ + return f"({lhs} <= {rhs})" + + +@comparison_operator +def equal_operation(lhs: Var, rhs: Var): + """Equal comparison. + + Args: + lhs: The first value. + rhs: The second value. + + Returns: + The result of the comparison. + """ + return f"({lhs}?.valueOf?.() === {rhs}?.valueOf?.())" + + +@comparison_operator +def not_equal_operation(lhs: Var, rhs: Var): + """Not equal comparison. + + Args: + lhs: The first value. + rhs: The second value. + + Returns: + The result of the comparison. + """ + return f"({lhs}?.valueOf?.() !== {rhs}?.valueOf?.())" + + +@var_operation +def boolean_not_operation(value: BooleanVar): + """Boolean NOT the boolean. + + Args: + value: The boolean. + + Returns: + The boolean NOT operation. + """ + return var_operation_return(js_expression=f"!({value})", var_type=bool) + + +@dataclasses.dataclass( + eq=False, + frozen=True, + slots=True, +) +class LiteralNumberVar(LiteralVar, NumberVar[NUMBER_T]): + """Base class for immutable literal number vars.""" + + _var_value: float | int | decimal.Decimal = dataclasses.field(default=0) + + def json(self) -> str: + """Get the JSON representation of the var. + + Returns: + The JSON representation of the var. + + Raises: + PrimitiveUnserializableToJSONError: If the var is unserializable to JSON. + """ + if isinstance(self._var_value, decimal.Decimal): + return json.dumps(float(self._var_value)) + if math.isinf(self._var_value) or math.isnan(self._var_value): + msg = f"No valid JSON representation for {self}" + raise PrimitiveUnserializableToJSONError(msg) + return json.dumps(self._var_value) + + def __hash__(self) -> int: + """Calculate the hash value of the object. + + Returns: + int: The hash value of the object. + """ + return hash((type(self).__name__, self._var_value)) + + @classmethod + def _get_all_var_data_without_creating_var( + cls, value: float | int | decimal.Decimal + ) -> VarData | None: + """Get all the var data without creating the var. + + Args: + value: The value of the var. + + Returns: + The var data. + """ + return None + + @classmethod + def create( + cls, value: float | int | decimal.Decimal, _var_data: VarData | None = None + ): + """Create the number var. + + Args: + value: The value of the var. + _var_data: Additional hooks and imports associated with the Var. + + Returns: + The number var. + """ + if math.isinf(value): + js_expr = "Infinity" if value > 0 else "-Infinity" + elif math.isnan(value): + js_expr = "NaN" + else: + js_expr = str(value) + + return cls( + _js_expr=js_expr, + _var_type=type(value), + _var_data=_var_data, + _var_value=value, + ) + + +@dataclasses.dataclass( + eq=False, + frozen=True, + slots=True, +) +class LiteralBooleanVar(LiteralVar, BooleanVar): + """Base class for immutable literal boolean vars.""" + + _var_value: bool = dataclasses.field(default=False) + + def json(self) -> str: + """Get the JSON representation of the var. + + Returns: + The JSON representation of the var. + """ + return "true" if self._var_value else "false" + + def __hash__(self) -> int: + """Calculate the hash value of the object. + + Returns: + int: The hash value of the object. + """ + return hash((type(self).__name__, self._var_value)) + + @classmethod + def _get_all_var_data_without_creating_var(cls, value: bool) -> VarData | None: + """Get all the var data without creating the var. + + Args: + value: The value of the var. + + Returns: + The var data. + """ + return None + + @classmethod + def create(cls, value: bool, _var_data: VarData | None = None): + """Create the boolean var. + + Args: + value: The value of the var. + _var_data: Additional hooks and imports associated with the Var. + + Returns: + The boolean var. + """ + return cls( + _js_expr="true" if value else "false", + _var_type=bool, + _var_data=_var_data, + _var_value=value, + ) + + +number_types = NumberVar | int | float | decimal.Decimal +boolean_types = BooleanVar | bool + + +_IS_TRUE_IMPORT: ImportDict = { + f"$/{Dirs.STATE_PATH}": [ImportVar(tag="isTrue")], +} + +_IS_NOT_NULL_OR_UNDEFINED_IMPORT: ImportDict = { + f"$/{Dirs.STATE_PATH}": [ImportVar(tag="isNotNullOrUndefined")], +} + + +@var_operation +def boolify(value: Var): + """Convert the value to a boolean. + + Args: + value: The value. + + Returns: + The boolean value. + """ + return var_operation_return( + js_expression=f"isTrue({value})", + var_type=bool, + var_data=VarData(imports=_IS_TRUE_IMPORT), + ) + + +@var_operation +def is_not_none_operation(value: Var): + """Check if the value is not None. + + Args: + value: The value. + + Returns: + The boolean value. + """ + return var_operation_return( + js_expression=f"isNotNullOrUndefined({value})", + var_type=bool, + var_data=VarData(imports=_IS_NOT_NULL_OR_UNDEFINED_IMPORT), + ) + + +T = TypeVar("T") +U = TypeVar("U") + + +@var_operation +def ternary_operation( + condition: Var[bool], if_true: Var[T], if_false: Var[U] +) -> CustomVarOperationReturn[T | U]: + """Create a ternary operation. + + Args: + condition: The condition. + if_true: The value if the condition is true. + if_false: The value if the condition is false. + + Returns: + The ternary operation. + """ + type_value: type[T] | type[U] = unionize(if_true._var_type, if_false._var_type) + value: CustomVarOperationReturn[T | U] = var_operation_return( + js_expression=f"({condition} ? {if_true} : {if_false})", + var_type=type_value, + ) + return value + + +NUMBER_TYPES = (int, float, decimal.Decimal, NumberVar) diff --git a/packages/reflex-core/src/reflex_core/vars/object.py b/packages/reflex-core/src/reflex_core/vars/object.py new file mode 100644 index 00000000000..1e02cee562d --- /dev/null +++ b/packages/reflex-core/src/reflex_core/vars/object.py @@ -0,0 +1,647 @@ +"""Classes for immutable object vars.""" + +from __future__ import annotations + +import collections.abc +import dataclasses +import typing +from collections.abc import Mapping +from importlib.util import find_spec +from typing import ( + Any, + NoReturn, + TypeVar, + get_args, + get_type_hints, + is_typeddict, + overload, +) + +from rich.markup import escape + +from reflex_core.utils import types +from reflex_core.utils.exceptions import VarAttributeError +from reflex_core.utils.types import ( + GenericType, + get_attribute_access_type, + get_origin, + safe_issubclass, + unionize, +) + +from .base import ( + CachedVarOperation, + LiteralVar, + Var, + VarData, + cached_property_no_lock, + figure_out_type, + var_operation, + var_operation_return, +) +from .number import BooleanVar, NumberVar, raise_unsupported_operand_types +from .sequence import ArrayVar, LiteralArrayVar, StringVar + +OBJECT_TYPE = TypeVar("OBJECT_TYPE", covariant=True) + +KEY_TYPE = TypeVar("KEY_TYPE") +VALUE_TYPE = TypeVar("VALUE_TYPE") + +ARRAY_INNER_TYPE = TypeVar("ARRAY_INNER_TYPE") + +OTHER_KEY_TYPE = TypeVar("OTHER_KEY_TYPE") + + +def _determine_value_type(var_type: GenericType): + origin_var_type = get_origin(var_type) or var_type + + if origin_var_type in types.UnionTypes: + return unionize(*[ + _determine_value_type(arg) + for arg in get_args(var_type) + if arg is not type(None) + ]) + + if is_typeddict(origin_var_type) or dataclasses.is_dataclass(origin_var_type): + annotations = get_type_hints(origin_var_type) + return unionize(*annotations.values()) + + if origin_var_type in [dict, Mapping, collections.abc.Mapping]: + args = get_args(var_type) + return args[1] if args else Any + + return Any + + +PYTHON_TYPES = (Mapping,) +if find_spec("pydantic"): + import pydantic + + PYTHON_TYPES += (pydantic.BaseModel,) + + +class ObjectVar(Var[OBJECT_TYPE], python_types=PYTHON_TYPES): + """Base class for immutable object vars.""" + + def _key_type(self) -> type: + """Get the type of the keys of the object. + + Returns: + The type of the keys of the object. + """ + return str + + @overload + def _value_type( + self: ObjectVar[Mapping[Any, VALUE_TYPE]], + ) -> type[VALUE_TYPE]: ... + + @overload + def _value_type(self) -> GenericType: ... + + def _value_type(self) -> GenericType: + """Get the type of the values of the object. + + Returns: + The type of the values of the object. + """ + return _determine_value_type(self._var_type) + + def keys(self) -> ArrayVar[list[str]]: + """Get the keys of the object. + + Returns: + The keys of the object. + """ + return object_keys_operation(self) + + @overload + def values( + self: ObjectVar[Mapping[Any, VALUE_TYPE]], + ) -> ArrayVar[list[VALUE_TYPE]]: ... + + @overload + def values(self) -> ArrayVar: ... + + def values(self) -> ArrayVar: + """Get the values of the object. + + Returns: + The values of the object. + """ + return object_values_operation(self) + + @overload + def entries( + self: ObjectVar[Mapping[Any, VALUE_TYPE]], + ) -> ArrayVar[list[tuple[str, VALUE_TYPE]]]: ... + + @overload + def entries(self) -> ArrayVar: ... + + def entries(self) -> ArrayVar: + """Get the entries of the object. + + Returns: + The entries of the object. + """ + return object_entries_operation(self) + + items = entries + + def length(self) -> NumberVar[int]: + """Get the length of the object. + + Returns: + The length of the object. + """ + return self.keys().length() + + def merge(self, other: ObjectVar): + """Merge two objects. + + Args: + other: The other object to merge. + + Returns: + The merged object. + """ + return object_merge_operation(self, other) + + # NoReturn is used here to catch when key value is Any + @overload + def __getitem__( # pyright: ignore [reportOverlappingOverload] + self: ObjectVar[Mapping[Any, NoReturn]], + key: Var | Any, + ) -> Var: ... + + @overload + def __getitem__( + self: (ObjectVar[Mapping[Any, bool]]), + key: Var | Any, + ) -> BooleanVar: ... + + @overload + def __getitem__( + self: ( + ObjectVar[Mapping[Any, int]] + | ObjectVar[Mapping[Any, float]] + | ObjectVar[Mapping[Any, int | float]] + ), + key: Var | Any, + ) -> NumberVar: ... + + @overload + def __getitem__( + self: ObjectVar[Mapping[Any, str]], + key: Var | Any, + ) -> StringVar: ... + + @overload + def __getitem__( + self: ObjectVar[Mapping[Any, list[ARRAY_INNER_TYPE]]], + key: Var | Any, + ) -> ArrayVar[list[ARRAY_INNER_TYPE]]: ... + + @overload + def __getitem__( + self: ObjectVar[Mapping[Any, tuple[ARRAY_INNER_TYPE, ...]]], + key: Var | Any, + ) -> ArrayVar[tuple[ARRAY_INNER_TYPE, ...]]: ... + + @overload + def __getitem__( + self: ObjectVar[Mapping[Any, Mapping[OTHER_KEY_TYPE, VALUE_TYPE]]], + key: Var | Any, + ) -> ObjectVar[Mapping[OTHER_KEY_TYPE, VALUE_TYPE]]: ... + + @overload + def __getitem__( + self: ObjectVar[Mapping[Any, VALUE_TYPE]], + key: Var | Any, + ) -> Var[VALUE_TYPE]: ... + + def __getitem__(self, key: Var | Any) -> Var: + """Get an item from the object. + + Args: + key: The key to get from the object. + + 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() + + def get(self, key: Var | Any, default: Var | Any | None = None) -> Var: + """Get an item from the object. + + Args: + key: The key to get from the object. + default: The default value if the key is not found. + + Returns: + The item from the object. + """ + from reflex_components_core.core.cond import cond + + if default is None: + default = Var.create(None) + + value = self.__getitem__(key) # pyright: ignore[reportUnknownVariableType,reportAttributeAccessIssue,reportUnknownMemberType] + + return cond( # pyright: ignore[reportUnknownVariableType] + value, + value, + default, + ) + + # NoReturn is used here to catch when key value is Any + @overload + def __getattr__( # pyright: ignore [reportOverlappingOverload] + self: ObjectVar[Mapping[Any, NoReturn]], + name: str, + ) -> Var: ... + + @overload + def __getattr__( + self: ( + ObjectVar[Mapping[Any, int]] + | ObjectVar[Mapping[Any, float]] + | ObjectVar[Mapping[Any, int | float]] + ), + name: str, + ) -> NumberVar: ... + + @overload + def __getattr__( + self: ObjectVar[Mapping[Any, str]], + name: str, + ) -> StringVar: ... + + @overload + def __getattr__( + self: ObjectVar[Mapping[Any, list[ARRAY_INNER_TYPE]]], + name: str, + ) -> ArrayVar[list[ARRAY_INNER_TYPE]]: ... + + @overload + def __getattr__( + self: ObjectVar[Mapping[Any, tuple[ARRAY_INNER_TYPE, ...]]], + name: str, + ) -> ArrayVar[tuple[ARRAY_INNER_TYPE, ...]]: ... + + @overload + def __getattr__( + self: ObjectVar[Mapping[Any, Mapping[OTHER_KEY_TYPE, VALUE_TYPE]]], + name: str, + ) -> ObjectVar[Mapping[OTHER_KEY_TYPE, VALUE_TYPE]]: ... + + @overload + def __getattr__( + self: ObjectVar, + name: str, + ) -> ObjectItemOperation: ... + + def __getattr__(self, name: str) -> Var: + """Get an attribute of the var. + + Args: + name: The name of the attribute. + + Returns: + The attribute of the var. + + Raises: + VarAttributeError: The State var has no such attribute or may have been annotated wrongly. + """ + if name.startswith("__") and name.endswith("__"): + return getattr(super(type(self), self), name) + + var_type = self._var_type + + var_type = types.value_inside_optional(var_type) + + fixed_type = get_origin(var_type) or var_type + + if ( + is_typeddict(fixed_type) + or ( + isinstance(fixed_type, type) + and not safe_issubclass(fixed_type, Mapping) + ) + or (fixed_type in types.UnionTypes) + ): + attribute_type = get_attribute_access_type(var_type, name) + if attribute_type is None: + msg = ( + f"The State var `{self!s}` of type {escape(str(self._var_type))} has no attribute '{name}' or may have been annotated " + f"wrongly." + ) + raise VarAttributeError(msg) + return ObjectItemOperation.create(self, name, attribute_type).guess_type() + return ObjectItemOperation.create(self, name).guess_type() + + def contains(self, key: Var | Any) -> BooleanVar: + """Check if the object contains a key. + + Args: + key: The key to check. + + Returns: + The result of the check. + """ + return object_has_own_property_operation(self, key) + + +class RestProp(ObjectVar[dict[str, Any]]): + """A special object var representing forwarded rest props.""" + + +@dataclasses.dataclass( + eq=False, + frozen=True, + slots=True, +) +class LiteralObjectVar(CachedVarOperation, ObjectVar[OBJECT_TYPE], LiteralVar): + """Base class for immutable literal object vars.""" + + _var_value: Mapping[Var | Any, Var | Any] = dataclasses.field(default_factory=dict) + + def _key_type(self) -> type: + """Get the type of the keys of the object. + + Returns: + The type of the keys of the object. + """ + args_list = typing.get_args(self._var_type) + return args_list[0] if args_list else Any # pyright: ignore [reportReturnType] + + def _value_type(self) -> type: + """Get the type of the values of the object. + + Returns: + The type of the values of the object. + """ + args_list = typing.get_args(self._var_type) + return args_list[1] if args_list else Any # pyright: ignore [reportReturnType] + + @cached_property_no_lock + def _cached_var_name(self) -> str: + """The name of the var. + + Returns: + The name of the var. + """ + return ( + "({ " + + ", ".join([ + f"[{LiteralVar.create(key)!s}] : {LiteralVar.create(value)!s}" + for key, value in self._var_value.items() + ]) + + " })" + ) + + def json(self) -> str: + """Get the JSON representation of the object. + + Returns: + The JSON representation of the object. + + Raises: + TypeError: The keys and values of the object must be literal vars to get the JSON representation + """ + keys_and_values = [] + for key, value in self._var_value.items(): + key = LiteralVar.create(key) + value = LiteralVar.create(value) + if not isinstance(key, LiteralVar) or not isinstance(value, LiteralVar): + msg = "The keys and values of the object must be literal vars to get the JSON representation." + raise TypeError(msg) + keys_and_values.append(f"{key.json()}:{value.json()}") + return "{" + ", ".join(keys_and_values) + "}" + + def __hash__(self) -> int: + """Get the hash of the var. + + Returns: + The hash of the var. + """ + return hash((type(self).__name__, self._js_expr)) + + @classmethod + def _get_all_var_data_without_creating_var( + cls, + value: Mapping, + ) -> VarData | None: + """Get all the var data without creating a var. + + Args: + value: The value to get the var data from. + + Returns: + The var data. + """ + return VarData.merge( + LiteralArrayVar._get_all_var_data_without_creating_var(value), + LiteralArrayVar._get_all_var_data_without_creating_var(value.values()), + ) + + @cached_property_no_lock + def _cached_get_all_var_data(self) -> VarData | None: + """Get all the var data. + + Returns: + The var data. + """ + return VarData.merge( + LiteralArrayVar._get_all_var_data_without_creating_var(self._var_value), + LiteralArrayVar._get_all_var_data_without_creating_var( + self._var_value.values() + ), + self._var_data, + ) + + @classmethod + def create( + cls, + _var_value: Mapping, + _var_type: type[OBJECT_TYPE] | None = None, + _var_data: VarData | None = None, + ) -> LiteralObjectVar[OBJECT_TYPE]: + """Create the literal object var. + + Args: + _var_value: The value of the var. + _var_type: The type of the var. + _var_data: Additional hooks and imports associated with the Var. + + Returns: + The literal object var. + + Raises: + TypeError: If the value is not a mapping type or a dataclass. + """ + if not isinstance(_var_value, collections.abc.Mapping): + from reflex_core.utils.serializers import serialize + + serialized = serialize(_var_value, get_type=False) + if not isinstance(serialized, collections.abc.Mapping): + msg = f"Expected a mapping type or a dataclass, got {_var_value!r} of type {type(_var_value).__name__}." + raise TypeError(msg) + + return LiteralObjectVar( + _js_expr="", + _var_type=(type(_var_value) if _var_type is None else _var_type), + _var_data=_var_data, + _var_value=serialized, + ) + + return LiteralObjectVar( + _js_expr="", + _var_type=(figure_out_type(_var_value) if _var_type is None else _var_type), + _var_data=_var_data, + _var_value=_var_value, + ) + + +@var_operation +def object_keys_operation(value: ObjectVar): + """Get the keys of an object. + + Args: + value: The object to get the keys from. + + Returns: + The keys of the object. + """ + return var_operation_return( + js_expression=f"Object.keys({value} ?? {{}})", + var_type=list[str], + ) + + +@var_operation +def object_values_operation(value: ObjectVar): + """Get the values of an object. + + Args: + value: The object to get the values from. + + Returns: + The values of the object. + """ + return var_operation_return( + js_expression=f"Object.values({value} ?? {{}})", + var_type=list[value._value_type()], + ) + + +@var_operation +def object_entries_operation(value: ObjectVar): + """Get the entries of an object. + + Args: + value: The object to get the entries from. + + Returns: + The entries of the object. + """ + return var_operation_return( + js_expression=f"Object.entries({value} ?? {{}})", + var_type=list[tuple[str, value._value_type()]], + ) + + +@var_operation +def object_merge_operation(lhs: ObjectVar, rhs: ObjectVar): + """Merge two objects. + + Args: + lhs: The first object to merge. + rhs: The second object to merge. + + Returns: + The merged object. + """ + return var_operation_return( + js_expression=f"({{...{lhs}, ...{rhs}}})", + var_type=Mapping[ + lhs._key_type() | rhs._key_type(), + lhs._value_type() | rhs._value_type(), + ], + ) + + +@dataclasses.dataclass( + eq=False, + frozen=True, + slots=True, +) +class ObjectItemOperation(CachedVarOperation, Var): + """Operation to get an item from an object.""" + + _object: ObjectVar = dataclasses.field( + default_factory=lambda: LiteralObjectVar.create({}) + ) + _key: Var | Any = dataclasses.field(default_factory=lambda: LiteralVar.create(None)) + + @cached_property_no_lock + def _cached_var_name(self) -> str: + """The name of the operation. + + Returns: + The name of the operation. + """ + return f"{self._object!s}?.[{self._key!s}]" + + @classmethod + def create( + cls, + object: ObjectVar, + key: Var | Any, + _var_type: GenericType | None = None, + _var_data: VarData | None = None, + ) -> ObjectItemOperation: + """Create the object item operation. + + Args: + object: The object to get the item from. + key: The key to get from the object. + _var_type: The type of the item. + _var_data: Additional hooks and imports associated with the operation. + + Returns: + The object item operation. + """ + return cls( + _js_expr="", + _var_type=object._value_type() if _var_type is None else _var_type, + _var_data=_var_data, + _object=object, + _key=key if isinstance(key, Var) else LiteralVar.create(key), + ) + + +@var_operation +def object_has_own_property_operation(object: ObjectVar, key: Var): + """Check if an object has a key. + + Args: + object: The object to check. + key: The key to check. + + Returns: + The result of the check. + """ + return var_operation_return( + js_expression=f"{object}.hasOwnProperty({key})", + var_type=bool, + ) diff --git a/packages/reflex-core/src/reflex_core/vars/sequence.py b/packages/reflex-core/src/reflex_core/vars/sequence.py new file mode 100644 index 00000000000..8babfeab720 --- /dev/null +++ b/packages/reflex-core/src/reflex_core/vars/sequence.py @@ -0,0 +1,1841 @@ +"""Collection of string classes and utilities.""" + +from __future__ import annotations + +import collections.abc +import dataclasses +import decimal +import inspect +import json +import re +from collections.abc import Iterable, Mapping, Sequence +from typing import TYPE_CHECKING, Any, Literal, TypeVar, get_args, overload + +from typing_extensions import TypeVar as TypingExtensionsTypeVar + +from reflex import constants +from reflex_core.constants.base import REFLEX_VAR_OPENING_TAG +from reflex_core.utils import types +from reflex_core.utils.exceptions import VarTypeError +from reflex_core.utils.types import GenericType, get_origin + +from .base import ( + CachedVarOperation, + CustomVarOperationReturn, + LiteralVar, + Var, + VarData, + _global_vars, + cached_property_no_lock, + figure_out_type, + get_unique_variable_name, + unionize, + var_operation, + var_operation_return, +) +from .number import ( + BooleanVar, + LiteralNumberVar, + NumberVar, + raise_unsupported_operand_types, +) + +if TYPE_CHECKING: + from .base import DATACLASS_TYPE, SQLA_TYPE + from .function import FunctionVar + from .object import ObjectVar + +ARRAY_VAR_TYPE = TypeVar("ARRAY_VAR_TYPE", bound=Sequence, covariant=True) +OTHER_ARRAY_VAR_TYPE = TypeVar("OTHER_ARRAY_VAR_TYPE", bound=Sequence, covariant=True) +MAPPING_VAR_TYPE = TypeVar("MAPPING_VAR_TYPE", bound=Mapping, covariant=True) + +OTHER_TUPLE = TypeVar("OTHER_TUPLE") + +INNER_ARRAY_VAR = TypeVar("INNER_ARRAY_VAR") + + +KEY_TYPE = TypeVar("KEY_TYPE") +VALUE_TYPE = TypeVar("VALUE_TYPE") + + +class ArrayVar(Var[ARRAY_VAR_TYPE], python_types=(Sequence, set)): + """Base class for immutable array vars.""" + + def join(self, sep: StringVar | str = "") -> StringVar: + """Join the elements of the array. + + Args: + sep: The separator between elements. + + Returns: + The joined elements. + """ + if not isinstance(sep, (StringVar, str)): + raise_unsupported_operand_types("join", (type(self), type(sep))) + if ( + isinstance(self, LiteralArrayVar) + and ( + len( + args := [ + x + for x in self._var_value + if isinstance(x, (LiteralStringVar, str)) + ] + ) + == len(self._var_value) + ) + and isinstance(sep, (LiteralStringVar, str)) + ): + sep_str = sep._var_value if isinstance(sep, LiteralStringVar) else sep + return LiteralStringVar.create( + sep_str.join( + i._var_value if isinstance(i, LiteralStringVar) else i for i in args + ) + ) + return array_join_operation(self, sep) + + def reverse(self) -> ArrayVar[ARRAY_VAR_TYPE]: + """Reverse the array. + + Returns: + The reversed array. + """ + return array_reverse_operation(self) + + def __add__(self, other: ArrayVar[ARRAY_VAR_TYPE]) -> ArrayVar[ARRAY_VAR_TYPE]: + """Concatenate two arrays. + + Parameters: + other: The other array to concatenate. + + Returns: + ArrayConcatOperation: The concatenation of the two arrays. + """ + if not isinstance(other, ArrayVar): + raise_unsupported_operand_types("+", (type(self), type(other))) + + return array_concat_operation(self, other) + + @overload + def __getitem__(self, i: slice) -> ArrayVar[ARRAY_VAR_TYPE]: ... + + @overload + def __getitem__( + self: ( + ArrayVar[tuple[int, OTHER_TUPLE]] + | ArrayVar[tuple[float, OTHER_TUPLE]] + | ArrayVar[tuple[int | float, OTHER_TUPLE]] + ), + i: Literal[0, -2], + ) -> NumberVar: ... + + @overload + def __getitem__( + self: ArrayVar[tuple[Any, bool]], i: Literal[1, -1] + ) -> BooleanVar: ... + + @overload + def __getitem__( + self: ( + ArrayVar[tuple[Any, int]] + | ArrayVar[tuple[Any, float]] + | ArrayVar[tuple[Any, int | float]] + ), + i: Literal[1, -1], + ) -> NumberVar: ... + + @overload + def __getitem__( # pyright: ignore [reportOverlappingOverload] + self: ArrayVar[tuple[str, Any]], i: Literal[0, -2] + ) -> StringVar: ... + + @overload + def __getitem__( + self: ArrayVar[tuple[Any, str]], i: Literal[1, -1] + ) -> StringVar: ... + + @overload + def __getitem__( + self: ArrayVar[tuple[bool, Any]], i: Literal[0, -2] + ) -> BooleanVar: ... + + @overload + def __getitem__( + self: ArrayVar[Sequence[bool]], i: int | NumberVar + ) -> BooleanVar: ... + + @overload + def __getitem__( + self: ( + ArrayVar[Sequence[int]] + | ArrayVar[Sequence[float]] + | ArrayVar[Sequence[int | float]] + ), + i: int | NumberVar, + ) -> NumberVar: ... + + @overload + def __getitem__(self: ArrayVar[Sequence[str]], i: int | NumberVar) -> StringVar: ... + + @overload + def __getitem__( + self: ArrayVar[Sequence[OTHER_ARRAY_VAR_TYPE]], + i: int | NumberVar, + ) -> ArrayVar[OTHER_ARRAY_VAR_TYPE]: ... + + @overload + def __getitem__( + self: ArrayVar[Sequence[MAPPING_VAR_TYPE]], + i: int | NumberVar, + ) -> ObjectVar[MAPPING_VAR_TYPE]: ... + + @overload + def __getitem__( + self: ArrayVar[Sequence[SQLA_TYPE]], + i: int | NumberVar, + ) -> ObjectVar[SQLA_TYPE]: ... + + @overload + def __getitem__( + self: ArrayVar[Sequence[DATACLASS_TYPE]], + i: int | NumberVar, + ) -> ObjectVar[DATACLASS_TYPE]: ... + + @overload + def __getitem__(self, i: int | NumberVar) -> Var: ... + + def __getitem__(self, i: Any) -> ArrayVar[ARRAY_VAR_TYPE] | Var: + """Get a slice of the array. + + Args: + i: The slice. + + Returns: + The array slice operation. + """ + if isinstance(i, slice): + return ArraySliceOperation.create(self, i) + if not isinstance(i, (int, NumberVar)) or ( + isinstance(i, NumberVar) and i._is_strict_float() + ): + raise_unsupported_operand_types("[]", (type(self), type(i))) + return array_item_operation(self, i) + + def length(self) -> NumberVar[int]: + """Get the length of the array. + + Returns: + The length of the array. + """ + return array_length_operation(self) + + @overload + @classmethod + def range(cls, stop: int | NumberVar, /) -> ArrayVar[list[int]]: ... + + @overload + @classmethod + def range( + cls, + start: int | NumberVar, + end: int | NumberVar, + step: int | NumberVar = 1, + /, + ) -> ArrayVar[list[int]]: ... + + @overload + @classmethod + def range( + cls, + first_endpoint: int | NumberVar, + second_endpoint: int | NumberVar | None = None, + step: int | NumberVar | None = None, + ) -> ArrayVar[list[int]]: ... + + @classmethod + def range( + cls, + first_endpoint: int | NumberVar, + second_endpoint: int | NumberVar | None = None, + step: int | NumberVar | None = None, + ) -> ArrayVar[list[int]]: + """Create a range of numbers. + + Args: + first_endpoint: The end of the range if second_endpoint is not provided, otherwise the start of the range. + second_endpoint: The end of the range. + step: The step of the range. + + Returns: + The range of numbers. + """ + if any( + not isinstance(i, (int, NumberVar)) + for i in (first_endpoint, second_endpoint, step) + if i is not None + ): + raise_unsupported_operand_types( + "range", (type(first_endpoint), type(second_endpoint), type(step)) + ) + if second_endpoint is None: + start = 0 + end = first_endpoint + else: + start = first_endpoint + end = second_endpoint + + return array_range_operation(start, end, step or 1) + + @overload + def contains(self, other: Any) -> BooleanVar: ... + + @overload + def contains(self, other: Any, field: StringVar | str) -> BooleanVar: ... + + def contains(self, other: Any, field: Any = None) -> BooleanVar: + """Check if the array contains an element. + + Args: + other: The element to check for. + field: The field to check. + + Returns: + The array contains operation. + """ + if field is not None: + if not isinstance(field, (StringVar, str)): + raise_unsupported_operand_types("contains", (type(self), type(field))) + return array_contains_field_operation(self, other, field) + return array_contains_operation(self, other) + + def pluck(self, field: StringVar | str) -> ArrayVar: + """Pluck a field from the array. + + Args: + field: The field to pluck from the array. + + Returns: + The array pluck operation. + """ + return array_pluck_operation(self, field) + + def __mul__(self, other: NumberVar | int) -> ArrayVar[ARRAY_VAR_TYPE]: + """Multiply the sequence by a number or integer. + + Parameters: + other: The number or integer to multiply the sequence by. + + Returns: + ArrayVar[ARRAY_VAR_TYPE]: The result of multiplying the sequence by the given number or integer. + """ + if not isinstance(other, (NumberVar, int)) or ( + isinstance(other, NumberVar) and other._is_strict_float() + ): + raise_unsupported_operand_types("*", (type(self), type(other))) + + return repeat_array_operation(self, other) + + __rmul__ = __mul__ + + @overload + def __lt__(self, other: ArrayVar[ARRAY_VAR_TYPE]) -> BooleanVar: ... + + @overload + def __lt__(self, other: list | tuple) -> BooleanVar: ... + + def __lt__(self, other: Any): + """Check if the array is less than another array. + + Args: + other: The other array. + + Returns: + The array less than operation. + """ + if not isinstance(other, (ArrayVar, list, tuple)): + raise_unsupported_operand_types("<", (type(self), type(other))) + + return array_lt_operation(self, other) + + @overload + def __gt__(self, other: ArrayVar[ARRAY_VAR_TYPE]) -> BooleanVar: ... + + @overload + def __gt__(self, other: list | tuple) -> BooleanVar: ... + + def __gt__(self, other: Any): + """Check if the array is greater than another array. + + Args: + other: The other array. + + Returns: + The array greater than operation. + """ + if not isinstance(other, (ArrayVar, list, tuple)): + raise_unsupported_operand_types(">", (type(self), type(other))) + + return array_gt_operation(self, other) + + @overload + def __le__(self, other: ArrayVar[ARRAY_VAR_TYPE]) -> BooleanVar: ... + + @overload + def __le__(self, other: list | tuple) -> BooleanVar: ... + + def __le__(self, other: Any): + """Check if the array is less than or equal to another array. + + Args: + other: The other array. + + Returns: + The array less than or equal operation. + """ + if not isinstance(other, (ArrayVar, list, tuple)): + raise_unsupported_operand_types("<=", (type(self), type(other))) + + return array_le_operation(self, other) + + @overload + def __ge__(self, other: ArrayVar[ARRAY_VAR_TYPE]) -> BooleanVar: ... + + @overload + def __ge__(self, other: list | tuple) -> BooleanVar: ... + + def __ge__(self, other: Any): + """Check if the array is greater than or equal to another array. + + Args: + other: The other array. + + Returns: + The array greater than or equal operation. + """ + if not isinstance(other, (ArrayVar, list, tuple)): + raise_unsupported_operand_types(">=", (type(self), type(other))) + + return array_ge_operation(self, other) + + def foreach(self, fn: Any): + """Apply a function to each element of the array. + + Args: + fn: The function to apply. + + Returns: + The array after applying the function. + + Raises: + VarTypeError: If the function takes more than one argument. + """ + from .function import ArgsFunctionOperation + + if not callable(fn): + raise_unsupported_operand_types("foreach", (type(self), type(fn))) + # get the number of arguments of the function + num_args = len(inspect.signature(fn).parameters) + if num_args > 1: + msg = "The function passed to foreach should take at most one argument." + raise VarTypeError(msg) + + if num_args == 0: + return_value = fn() + function_var = ArgsFunctionOperation.create((), return_value) + else: + # generic number var + number_var = Var("").to(NumberVar, int) + + first_arg_type = self[number_var]._var_type + + arg_name = get_unique_variable_name() + + # get first argument type + first_arg = Var( + _js_expr=arg_name, + _var_type=first_arg_type, + ).guess_type() + + function_var = ArgsFunctionOperation.create( + (arg_name,), + Var.create(fn(first_arg)), + ) + + return map_array_operation(self, function_var) + + +@dataclasses.dataclass( + eq=False, + frozen=True, + slots=True, +) +class LiteralArrayVar(CachedVarOperation, LiteralVar, ArrayVar[ARRAY_VAR_TYPE]): + """Base class for immutable literal array vars.""" + + _var_value: Sequence[Var | Any] = dataclasses.field(default=()) + + @cached_property_no_lock + def _cached_var_name(self) -> str: + """The name of the var. + + Returns: + The name of the var. + """ + return ( + "[" + + ", ".join([ + str(LiteralVar.create(element)) for element in self._var_value + ]) + + "]" + ) + + @classmethod + def _get_all_var_data_without_creating_var(cls, value: Iterable) -> VarData | None: + """Get all the VarData associated with the Var without creating a Var. + + Args: + value: The value to get the VarData for. + + Returns: + The VarData associated with the Var. + """ + return VarData.merge(*[ + LiteralVar._get_all_var_data_without_creating_var_dispatch(element) + for element in value + ]) + + @cached_property_no_lock + def _cached_get_all_var_data(self) -> VarData | None: + """Get all the VarData associated with the Var. + + Returns: + The VarData associated with the Var. + """ + return VarData.merge( + *[ + LiteralVar._get_all_var_data_without_creating_var_dispatch(element) + for element in self._var_value + ], + self._var_data, + ) + + def __hash__(self) -> int: + """Get the hash of the var. + + Returns: + The hash of the var. + """ + return hash((self.__class__.__name__, self._js_expr)) + + def json(self) -> str: + """Get the JSON representation of the var. + + Returns: + The JSON representation of the var. + + Raises: + TypeError: If the array elements are not of type LiteralVar. + """ + elements = [] + for element in self._var_value: + element_var = LiteralVar.create(element) + if not isinstance(element_var, LiteralVar): + msg = f"Array elements must be of type LiteralVar, not {type(element_var)}" + raise TypeError(msg) + elements.append(element_var.json()) + + return "[" + ", ".join(elements) + "]" + + @classmethod + def create( + cls, + value: OTHER_ARRAY_VAR_TYPE, + _var_type: type[OTHER_ARRAY_VAR_TYPE] | None = None, + _var_data: VarData | None = None, + ) -> LiteralArrayVar[OTHER_ARRAY_VAR_TYPE]: + """Create a var from a string value. + + Args: + value: The value to create the var from. + _var_type: The type of the var. + _var_data: Additional hooks and imports associated with the Var. + + Returns: + The var. + """ + return LiteralArrayVar( + _js_expr="", + _var_type=figure_out_type(value) if _var_type is None else _var_type, + _var_data=_var_data, + _var_value=value, + ) + + +STRING_TYPE = TypingExtensionsTypeVar("STRING_TYPE", default=str) + + +class StringVar(Var[STRING_TYPE], python_types=str): + """Base class for immutable string vars.""" + + def __add__(self, other: StringVar | str) -> ConcatVarOperation: + """Concatenate two strings. + + Args: + other: The other string. + + Returns: + The string concatenation operation. + """ + if not isinstance(other, (StringVar, str)): + raise_unsupported_operand_types("+", (type(self), type(other))) + + return ConcatVarOperation.create(self, other) + + def __radd__(self, other: StringVar | str) -> ConcatVarOperation: + """Concatenate two strings. + + Args: + other: The other string. + + Returns: + The string concatenation operation. + """ + if not isinstance(other, (StringVar, str)): + raise_unsupported_operand_types("+", (type(other), type(self))) + + return ConcatVarOperation.create(other, self) + + def __mul__(self, other: NumberVar | int) -> StringVar: + """Multiply the sequence by a number or an integer. + + Args: + other: The number or integer to multiply the sequence by. + + Returns: + StringVar: The resulting sequence after multiplication. + """ + if not isinstance(other, (NumberVar, int)): + raise_unsupported_operand_types("*", (type(self), type(other))) + + return (self.split() * other).join() + + def __rmul__(self, other: NumberVar | int) -> StringVar: + """Multiply the sequence by a number or an integer. + + Args: + other: The number or integer to multiply the sequence by. + + Returns: + StringVar: The resulting sequence after multiplication. + """ + if not isinstance(other, (NumberVar, int)): + raise_unsupported_operand_types("*", (type(other), type(self))) + + return (self.split() * other).join() + + @overload + def __getitem__(self, i: slice) -> StringVar: ... + + @overload + def __getitem__(self, i: int | NumberVar) -> StringVar: ... + + def __getitem__(self, i: Any) -> StringVar: + """Get a slice of the string. + + Args: + i: The slice. + + Returns: + The string slice operation. + """ + if isinstance(i, slice): + return self.split()[i].join() + if not isinstance(i, (int, NumberVar)) or ( + isinstance(i, NumberVar) and i._is_strict_float() + ): + raise_unsupported_operand_types("[]", (type(self), type(i))) + return string_item_operation(self, i) + + def length(self) -> NumberVar: + """Get the length of the string. + + Returns: + The string length operation. + """ + return self.split().length() + + def lower(self) -> StringVar: + """Convert the string to lowercase. + + Returns: + The string lower operation. + """ + return string_lower_operation(self) + + def upper(self) -> StringVar: + """Convert the string to uppercase. + + Returns: + The string upper operation. + """ + return string_upper_operation(self) + + def title(self) -> StringVar: + """Convert the string to title case. + + Returns: + The string title operation. + """ + return string_title_operation(self) + + def capitalize(self) -> StringVar: + """Capitalize the string. + + Returns: + The string capitalize operation. + """ + return string_capitalize_operation(self) + + def strip(self) -> StringVar: + """Strip the string. + + Returns: + The string strip operation. + """ + return string_strip_operation(self) + + def reversed(self) -> StringVar: + """Reverse the string. + + Returns: + The string reverse operation. + """ + return self.split().reverse().join() + + def contains( + self, other: StringVar | str, field: StringVar | str | None = None + ) -> BooleanVar: + """Check if the string contains another string. + + Args: + other: The other string. + field: The field to check. + + Returns: + The string contains operation. + """ + if not isinstance(other, (StringVar, str)): + raise_unsupported_operand_types("contains", (type(self), type(other))) + if field is not None: + if not isinstance(field, (StringVar, str)): + raise_unsupported_operand_types("contains", (type(self), type(field))) + return string_contains_field_operation(self, other, field) + return string_contains_operation(self, other) + + def split(self, separator: StringVar | str = "") -> ArrayVar[list[str]]: + """Split the string. + + Args: + separator: The separator. + + Returns: + The string split operation. + """ + if not isinstance(separator, (StringVar, str)): + raise_unsupported_operand_types("split", (type(self), type(separator))) + return string_split_operation(self, separator) + + def startswith(self, prefix: StringVar | str) -> BooleanVar: + """Check if the string starts with a prefix. + + Args: + prefix: The prefix. + + Returns: + The string starts with operation. + """ + if not isinstance(prefix, (StringVar, str)): + raise_unsupported_operand_types("startswith", (type(self), type(prefix))) + return string_starts_with_operation(self, prefix) + + def endswith(self, suffix: StringVar | str) -> BooleanVar: + """Check if the string ends with a suffix. + + Args: + suffix: The suffix. + + Returns: + The string ends with operation. + """ + if not isinstance(suffix, (StringVar, str)): + raise_unsupported_operand_types("endswith", (type(self), type(suffix))) + return string_ends_with_operation(self, suffix) + + def __lt__(self, other: StringVar | str) -> BooleanVar: + """Check if the string is less than another string. + + Args: + other: The other string. + + Returns: + The string less than operation. + """ + if not isinstance(other, (StringVar, str)): + raise_unsupported_operand_types("<", (type(self), type(other))) + + return string_lt_operation(self, other) + + def __gt__(self, other: StringVar | str) -> BooleanVar: + """Check if the string is greater than another string. + + Args: + other: The other string. + + Returns: + The string greater than operation. + """ + if not isinstance(other, (StringVar, str)): + raise_unsupported_operand_types(">", (type(self), type(other))) + + return string_gt_operation(self, other) + + def __le__(self, other: StringVar | str) -> BooleanVar: + """Check if the string is less than or equal to another string. + + Args: + other: The other string. + + Returns: + The string less than or equal operation. + """ + if not isinstance(other, (StringVar, str)): + raise_unsupported_operand_types("<=", (type(self), type(other))) + + return string_le_operation(self, other) + + def __ge__(self, other: StringVar | str) -> BooleanVar: + """Check if the string is greater than or equal to another string. + + Args: + other: The other string. + + Returns: + The string greater than or equal operation. + """ + if not isinstance(other, (StringVar, str)): + raise_unsupported_operand_types(">=", (type(self), type(other))) + + 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): + """Check if a string is less than another string. + + Args: + lhs: The left-hand side string. + rhs: The right-hand side string. + + Returns: + The string less than operation. + """ + return var_operation_return(js_expression=f"{lhs} < {rhs}", var_type=bool) + + +@var_operation +def string_gt_operation(lhs: StringVar[Any] | str, rhs: StringVar[Any] | str): + """Check if a string is greater than another string. + + Args: + lhs: The left-hand side string. + rhs: The right-hand side string. + + Returns: + The string greater than operation. + """ + return var_operation_return(js_expression=f"{lhs} > {rhs}", var_type=bool) + + +@var_operation +def string_le_operation(lhs: StringVar[Any] | str, rhs: StringVar[Any] | str): + """Check if a string is less than or equal to another string. + + Args: + lhs: The left-hand side string. + rhs: The right-hand side string. + + Returns: + The string less than or equal operation. + """ + return var_operation_return(js_expression=f"{lhs} <= {rhs}", var_type=bool) + + +@var_operation +def string_ge_operation(lhs: StringVar[Any] | str, rhs: StringVar[Any] | str): + """Check if a string is greater than or equal to another string. + + Args: + lhs: The left-hand side string. + rhs: The right-hand side string. + + Returns: + The string greater than or equal operation. + """ + return var_operation_return(js_expression=f"{lhs} >= {rhs}", var_type=bool) + + +@var_operation +def string_lower_operation(string: StringVar[Any]): + """Convert a string to lowercase. + + Args: + string: The string to convert. + + Returns: + The lowercase string. + """ + return var_operation_return(js_expression=f"{string}.toLowerCase()", var_type=str) + + +@var_operation +def string_upper_operation(string: StringVar[Any]): + """Convert a string to uppercase. + + Args: + string: The string to convert. + + Returns: + The uppercase string. + """ + return var_operation_return(js_expression=f"{string}.toUpperCase()", var_type=str) + + +@var_operation +def string_title_operation(string: StringVar[Any]): + """Convert a string to title case. + + Args: + string: The string to convert. + + Returns: + The title case string. + """ + return var_operation_return( + js_expression=f"{string}.split(' ').map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()).join(' ')", + var_type=str, + ) + + +@var_operation +def string_capitalize_operation(string: StringVar[Any]): + """Capitalize a string. + + Args: + string: The string to capitalize. + + Returns: + The capitalized string. + """ + return var_operation_return( + js_expression=f"(((s) => s.charAt(0).toUpperCase() + s.slice(1).toLowerCase())({string}))", + var_type=str, + ) + + +@var_operation +def string_strip_operation(string: StringVar[Any]): + """Strip a string. + + Args: + string: The string to strip. + + Returns: + The stripped string. + """ + return var_operation_return(js_expression=f"{string}.trim()", var_type=str) + + +@var_operation +def string_contains_field_operation( + haystack: StringVar[Any], needle: StringVar[Any] | str, field: StringVar[Any] | str +): + """Check if a string contains another string. + + Args: + haystack: The haystack. + needle: The needle. + field: The field to check. + + Returns: + The string contains operation. + """ + return var_operation_return( + js_expression=f"{haystack}.some(obj => obj[{field}] === {needle})", + var_type=bool, + ) + + +@var_operation +def string_contains_operation(haystack: StringVar[Any], needle: StringVar[Any] | str): + """Check if a string contains another string. + + Args: + haystack: The haystack. + needle: The needle. + + Returns: + The string contains operation. + """ + return var_operation_return( + js_expression=f"{haystack}.includes({needle})", var_type=bool + ) + + +@var_operation +def string_starts_with_operation( + full_string: StringVar[Any], prefix: StringVar[Any] | str +): + """Check if a string starts with a prefix. + + Args: + full_string: The full string. + prefix: The prefix. + + Returns: + Whether the string starts with the prefix. + """ + return var_operation_return( + js_expression=f"{full_string}.startsWith({prefix})", var_type=bool + ) + + +@var_operation +def string_ends_with_operation( + full_string: StringVar[Any], suffix: StringVar[Any] | str +): + """Check if a string ends with a suffix. + + Args: + full_string: The full string. + suffix: The suffix. + + Returns: + Whether the string ends with the suffix. + """ + return var_operation_return( + js_expression=f"{full_string}.endsWith({suffix})", var_type=bool + ) + + +@var_operation +def string_item_operation(string: StringVar[Any], index: NumberVar | int): + """Get an item from a string. + + Args: + string: The string. + index: The index of the item. + + Returns: + The item from the string. + """ + return var_operation_return(js_expression=f"{string}?.at?.({index})", var_type=str) + + +@var_operation +def array_join_operation(array: ArrayVar, sep: StringVar[Any] | str = ""): + """Join the elements of an array. + + Args: + array: The array. + sep: The separator. + + Returns: + The joined elements. + """ + return var_operation_return(js_expression=f"{array}.join({sep})", var_type=str) + + +@var_operation +def string_replace_operation( + string: StringVar[Any], search_value: StringVar | str, new_value: StringVar | str +): + """Replace a string with a value. + + Args: + string: The string. + search_value: The string to search. + new_value: The value to be replaced with. + + Returns: + The string replace operation. + """ + return var_operation_return( + js_expression=f"{string}.replaceAll({search_value}, {new_value})", + var_type=str, + ) + + +@var_operation +def get_decimal_string_separator_operation(value: NumberVar, separator: StringVar): + """Get the decimal string separator. + + Args: + value: The number. + separator: The separator. + + Returns: + The decimal string separator. + """ + return var_operation_return( + js_expression=f"({value}.toLocaleString('en-US').replaceAll(',', {separator}))", + var_type=str, + ) + + +@var_operation +def get_decimal_string_operation( + value: NumberVar, decimals: NumberVar, separator: StringVar +): + """Get the decimal string of the number. + + Args: + value: The number. + decimals: The number of decimals. + separator: The separator. + + Returns: + The decimal string of the number. + """ + return var_operation_return( + js_expression=f"({value}.toLocaleString('en-US', ((decimals) => ({{minimumFractionDigits: decimals, maximumFractionDigits: decimals}}))({decimals})).replaceAll(',', {separator}))", + var_type=str, + ) + + +# Compile regex for finding reflex var tags. +_decode_var_pattern_re = ( + rf"{constants.REFLEX_VAR_OPENING_TAG}(.*?){constants.REFLEX_VAR_CLOSING_TAG}" +) +_decode_var_pattern = re.compile(_decode_var_pattern_re, flags=re.DOTALL) + + +@dataclasses.dataclass( + eq=False, + frozen=True, + slots=True, +) +class LiteralStringVar(LiteralVar, StringVar[str]): + """Base class for immutable literal string vars.""" + + _var_value: str = dataclasses.field(default="") + + @classmethod + def _get_all_var_data_without_creating_var(cls, value: str) -> VarData | None: + """Get all the VarData associated with the Var without creating a Var. + + Args: + value: The value to get the VarData for. + + Returns: + The VarData associated with the Var. + """ + if REFLEX_VAR_OPENING_TAG not in value: + return None + return cls.create(value)._get_all_var_data() + + @classmethod + def create( + cls, + value: str, + _var_type: GenericType | None = None, + _var_data: VarData | None = None, + ) -> StringVar: + """Create a var from a string value. + + Args: + value: The value to create the var from. + _var_type: The type of the var. + _var_data: Additional hooks and imports associated with the Var. + + Returns: + The var. + """ + # Determine var type in case the value is inherited from str. + _var_type = _var_type or type(value) or str + + if REFLEX_VAR_OPENING_TAG in value: + strings_and_vals: list[Var | str] = [] + offset = 0 + + # Find all tags + while m := _decode_var_pattern.search(value): + start, end = m.span() + + strings_and_vals.append(value[:start]) + + serialized_data = m.group(1) + + if serialized_data.isnumeric() or ( + serialized_data[0] == "-" and serialized_data[1:].isnumeric() + ): + # This is a global immutable var. + var = _global_vars[int(serialized_data)] + strings_and_vals.append(var) + value = value[(end + len(var._js_expr)) :] + + offset += end - start + + strings_and_vals.append(value) + + filtered_strings_and_vals = [ + s for s in strings_and_vals if isinstance(s, Var) or s + ] + if len(filtered_strings_and_vals) == 1: + only_string = filtered_strings_and_vals[0] + if isinstance(only_string, str): + return LiteralVar.create(only_string).to(StringVar, _var_type) + return only_string.to(StringVar, only_string._var_type) + + if len( + literal_strings := [ + s + for s in filtered_strings_and_vals + if isinstance(s, (str, LiteralStringVar)) + ] + ) == len(filtered_strings_and_vals): + return LiteralStringVar.create( + "".join( + s._var_value if isinstance(s, LiteralStringVar) else s + for s in literal_strings + ), + _var_type=_var_type, + _var_data=VarData.merge( + _var_data, + *( + s._get_all_var_data() + for s in filtered_strings_and_vals + if isinstance(s, Var) + ), + ), + ) + + concat_result = ConcatVarOperation.create( + *filtered_strings_and_vals, + _var_data=_var_data, + ) + + return ( + concat_result + if _var_type is str + else concat_result.to(StringVar, _var_type) + ) + + return LiteralStringVar( + _js_expr=json.dumps(value), + _var_type=_var_type, + _var_data=_var_data, + _var_value=value, + ) + + def __hash__(self) -> int: + """Get the hash of the var. + + Returns: + The hash of the var. + """ + return hash((type(self).__name__, self._var_value)) + + def json(self) -> str: + """Get the JSON representation of the var. + + Returns: + The JSON representation of the var. + """ + return json.dumps(self._var_value) + + +@dataclasses.dataclass( + eq=False, + frozen=True, + slots=True, +) +class ConcatVarOperation(CachedVarOperation, StringVar[str]): + """Representing a concatenation of literal string vars.""" + + _var_value: tuple[Var, ...] = dataclasses.field(default_factory=tuple) + + @cached_property_no_lock + def _cached_var_name(self) -> str: + """The name of the var. + + Returns: + The name of the var. + """ + list_of_strs: list[str | Var] = [] + last_string = "" + for var in self._var_value: + if isinstance(var, LiteralStringVar): + last_string += var._var_value + else: + if last_string: + list_of_strs.append(last_string) + last_string = "" + list_of_strs.append(var) + + if last_string: + list_of_strs.append(last_string) + + list_of_strs_filtered = [ + str(LiteralVar.create(s)) for s in list_of_strs if isinstance(s, Var) or s + ] + + if len(list_of_strs_filtered) == 1: + return list_of_strs_filtered[0] + + return "(" + "+".join(list_of_strs_filtered) + ")" + + @cached_property_no_lock + def _cached_get_all_var_data(self) -> VarData | None: + """Get all the VarData asVarDatae Var. + + Returns: + The VarData associated with the Var. + """ + return VarData.merge( + *[ + var._get_all_var_data() + for var in self._var_value + if isinstance(var, Var) + ], + self._var_data, + ) + + @classmethod + def create( + cls, + *value: Var | str, + _var_data: VarData | None = None, + ) -> ConcatVarOperation: + """Create a var from a string value. + + Args: + *value: The values to concatenate. + _var_data: Additional hooks and imports associated with the Var. + + Returns: + The var. + """ + return cls( + _js_expr="", + _var_type=str, + _var_data=_var_data, + _var_value=tuple(map(LiteralVar.create, value)), + ) + + +@var_operation +def string_split_operation(string: StringVar[Any], sep: StringVar | str = ""): + """Split a string. + + Args: + string: The string to split. + sep: The separator. + + Returns: + The split string. + """ + return var_operation_return( + js_expression=f"{string}.split({sep})", var_type=list[str] + ) + + +@dataclasses.dataclass( + eq=False, + frozen=True, + slots=True, +) +class ArraySliceOperation(CachedVarOperation, ArrayVar): + """Base class for immutable string vars that are the result of a string slice operation.""" + + _array: ArrayVar = dataclasses.field( + default_factory=lambda: LiteralArrayVar.create([]) + ) + _start: NumberVar | int = dataclasses.field(default_factory=lambda: 0) + _stop: NumberVar | int = dataclasses.field(default_factory=lambda: 0) + _step: NumberVar | int = dataclasses.field(default_factory=lambda: 1) + + @cached_property_no_lock + def _cached_var_name(self) -> str: + """The name of the var. + + Returns: + The name of the var. + + Raises: + ValueError: If the slice step is zero. + """ + start, end, step = self._start, self._stop, self._step + + normalized_start = ( + LiteralVar.create(start) if start is not None else Var(_js_expr="undefined") + ) + normalized_end = ( + LiteralVar.create(end) if end is not None else Var(_js_expr="undefined") + ) + if step is None: + return f"{self._array!s}.slice({normalized_start!s}, {normalized_end!s})" + if not isinstance(step, Var): + if step < 0: + actual_start = end + 1 if end is not None else 0 + actual_end = start + 1 if start is not None else self._array.length() + return str(self._array[actual_start:actual_end].reverse()[::-step]) + if step == 0: + msg = "slice step cannot be zero" + raise ValueError(msg) + return f"{self._array!s}.slice({normalized_start!s}, {normalized_end!s}).filter((_, i) => i % {step!s} === 0)" + + actual_start_reverse = end + 1 if end is not None else 0 + actual_end_reverse = start + 1 if start is not None else self._array.length() + + return f"{self.step!s} > 0 ? {self._array!s}.slice({normalized_start!s}, {normalized_end!s}).filter((_, i) => i % {step!s} === 0) : {self._array!s}.slice({actual_start_reverse!s}, {actual_end_reverse!s}).reverse().filter((_, i) => i % {-step!s} === 0)" + + @classmethod + def create( + cls, + array: ArrayVar, + slice: slice, + _var_data: VarData | None = None, + ) -> ArraySliceOperation: + """Create a var from a string value. + + Args: + array: The array. + slice: The slice. + _var_data: Additional hooks and imports associated with the Var. + + Returns: + The var. + """ + return cls( + _js_expr="", + _var_type=array._var_type, + _var_data=_var_data, + _array=array, + _start=slice.start, + _stop=slice.stop, + _step=slice.step, + ) + + +@var_operation +def array_pluck_operation( + array: ArrayVar[ARRAY_VAR_TYPE], + field: StringVar | str, +) -> CustomVarOperationReturn[ARRAY_VAR_TYPE]: + """Pluck a field from an array of objects. + + Args: + array: The array to pluck from. + field: The field to pluck from the objects in the array. + + Returns: + The reversed array. + """ + return var_operation_return( + js_expression=f"{array}.map(e=>e?.[{field}])", + var_type=array._var_type, + ) + + +@var_operation +def array_reverse_operation( + array: ArrayVar[ARRAY_VAR_TYPE], +) -> CustomVarOperationReturn[ARRAY_VAR_TYPE]: + """Reverse an array. + + Args: + array: The array to reverse. + + Returns: + The reversed array. + """ + return var_operation_return( + js_expression=f"{array}.slice().reverse()", + var_type=array._var_type, + ) + + +@var_operation +def array_lt_operation(lhs: ArrayVar | list | tuple, rhs: ArrayVar | list | tuple): + """Check if an array is less than another array. + + Args: + lhs: The left-hand side array. + rhs: The right-hand side array. + + Returns: + The array less than operation. + """ + return var_operation_return(js_expression=f"{lhs} < {rhs}", var_type=bool) + + +@var_operation +def array_gt_operation(lhs: ArrayVar | list | tuple, rhs: ArrayVar | list | tuple): + """Check if an array is greater than another array. + + Args: + lhs: The left-hand side array. + rhs: The right-hand side array. + + Returns: + The array greater than operation. + """ + return var_operation_return(js_expression=f"{lhs} > {rhs}", var_type=bool) + + +@var_operation +def array_le_operation(lhs: ArrayVar | list | tuple, rhs: ArrayVar | list | tuple): + """Check if an array is less than or equal to another array. + + Args: + lhs: The left-hand side array. + rhs: The right-hand side array. + + Returns: + The array less than or equal operation. + """ + return var_operation_return(js_expression=f"{lhs} <= {rhs}", var_type=bool) + + +@var_operation +def array_ge_operation(lhs: ArrayVar | list | tuple, rhs: ArrayVar | list | tuple): + """Check if an array is greater than or equal to another array. + + Args: + lhs: The left-hand side array. + rhs: The right-hand side array. + + Returns: + The array greater than or equal operation. + """ + return var_operation_return(js_expression=f"{lhs} >= {rhs}", var_type=bool) + + +@var_operation +def array_length_operation(array: ArrayVar): + """Get the length of an array. + + Args: + array: The array. + + Returns: + The length of the array. + """ + return var_operation_return( + js_expression=f"{array}.length", + var_type=int, + ) + + +def is_tuple_type(t: GenericType) -> bool: + """Check if a type is a tuple type. + + Args: + t: The type to check. + + Returns: + Whether the type is a tuple type. + """ + return get_origin(t) is tuple + + +def _determine_value_of_array_index( + var_type: GenericType, index: int | float | decimal.Decimal | None = None +): + """Determine the value of an array index. + + Args: + var_type: The type of the array. + index: The index of the array. + + Returns: + The value of the array index. + """ + origin_var_type = get_origin(var_type) or var_type + if origin_var_type in types.UnionTypes: + return unionize(*[ + _determine_value_of_array_index(t, index) + for t in get_args(var_type) + if t is not type(None) + ]) + if origin_var_type is range: + return int + if origin_var_type in [ + Sequence, + Iterable, + list, + set, + collections.abc.Sequence, + collections.abc.Iterable, + ]: + args = get_args(var_type) + return args[0] if args else Any + if origin_var_type is tuple: + args = get_args(var_type) + if len(args) == 2 and args[1] is ...: + return args[0] + return ( + args[int(index) % len(args)] + if args and index is not None + else (unionize(*args) if args else Any) + ) + return Any + + +@var_operation +def array_item_operation(array: ArrayVar, index: NumberVar | int): + """Get an item from an array. + + Args: + array: The array. + index: The index of the item. + + Returns: + The item from the array. + """ + element_type = _determine_value_of_array_index( + array._var_type, + ( + index + if isinstance(index, int) + else (index._var_value if isinstance(index, LiteralNumberVar) else None) + ), + ) + + return var_operation_return( + js_expression=f"{array!s}?.at?.({index!s})", + var_type=element_type, + ) + + +@var_operation +def array_range_operation( + start: NumberVar | int, stop: NumberVar | int, step: NumberVar | int +): + """Create a range of numbers. + + Args: + start: The start of the range. + stop: The end of the range. + step: The step of the range. + + Returns: + The range of numbers. + """ + return var_operation_return( + js_expression=f"Array.from({{ length: Math.ceil(({stop!s} - {start!s}) / {step!s}) }}, (_, i) => {start!s} + i * {step!s})", + var_type=list[int], + ) + + +@var_operation +def array_contains_field_operation( + haystack: ArrayVar, needle: Any | Var, field: StringVar | str +): + """Check if an array contains an element. + + Args: + haystack: The array to check. + needle: The element to check for. + field: The field to check. + + Returns: + The array contains operation. + """ + return var_operation_return( + js_expression=f"{haystack}.some(obj => obj[{field}] === {needle})", + var_type=bool, + ) + + +@var_operation +def array_contains_operation( + haystack: ArrayVar, needle: Any | Var +) -> CustomVarOperationReturn[bool]: + """Check if an array contains an element. + + Args: + haystack: The array to check. + needle: The element to check for. + + Returns: + The array contains operation. + """ + return var_operation_return( + js_expression=f"{haystack}.includes({needle})", + var_type=bool, + ) + + +@var_operation +def repeat_array_operation( + array: ArrayVar[ARRAY_VAR_TYPE], count: NumberVar | int +) -> CustomVarOperationReturn[ARRAY_VAR_TYPE]: + """Repeat an array a number of times. + + Args: + array: The array to repeat. + count: The number of times to repeat the array. + + Returns: + The repeated array. + """ + return var_operation_return( + js_expression=f"Array.from({{ length: {count} }}).flatMap(() => {array})", + var_type=array._var_type, + ) + + +@var_operation +def map_array_operation( + array: ArrayVar[ARRAY_VAR_TYPE], + function: FunctionVar, +) -> CustomVarOperationReturn[list[Any]]: + """Map a function over an array. + + Args: + array: The array. + function: The function to map. + + Returns: + The mapped array. + """ + return var_operation_return( + js_expression=f"{array}.map({function})", var_type=list[Any] + ) + + +@var_operation +def array_concat_operation( + lhs: ArrayVar[ARRAY_VAR_TYPE], rhs: ArrayVar[ARRAY_VAR_TYPE] +) -> CustomVarOperationReturn[ARRAY_VAR_TYPE]: + """Concatenate two arrays. + + Args: + lhs: The left-hand side array. + rhs: The right-hand side array. + + Returns: + The concatenated array. + """ + return var_operation_return( + js_expression=f"[...{lhs}, ...{rhs}]", + var_type=lhs._var_type | rhs._var_type, + ) + + +class RangeVar(ArrayVar[Sequence[int]], python_types=range): + """Base class for immutable range vars.""" + + +@dataclasses.dataclass( + eq=False, + frozen=True, + slots=True, +) +class LiteralRangeVar(CachedVarOperation, LiteralVar, RangeVar): + """Base class for immutable literal range vars.""" + + _var_value: range = dataclasses.field(default_factory=lambda: range(0)) + + @classmethod + def create( + cls, + value: range, + _var_type: type[range] | None = None, + _var_data: VarData | None = None, + ) -> RangeVar: + """Create a var from a string value. + + Args: + value: The value to create the var from. + _var_type: The type of the var. + _var_data: Additional hooks and imports associated with the Var. + + Returns: + The var. + """ + return cls( + _js_expr="", + _var_type=_var_type or range, + _var_data=_var_data, + _var_value=value, + ) + + def __hash__(self) -> int: + """Get the hash of the var. + + Returns: + The hash of the var. + """ + return hash(( + self.__class__.__name__, + self._var_value.start, + self._var_value.stop, + self._var_value.step, + )) + + @cached_property_no_lock + def _cached_var_name(self) -> str: + """The name of the var. + + Returns: + The name of the var. + """ + return f"Array.from({{ length: Math.ceil(({self._var_value.stop!s} - {self._var_value.start!s}) / {self._var_value.step!s}) }}, (_, i) => {self._var_value.start!s} + i * {self._var_value.step!s})" + + @cached_property_no_lock + def _cached_get_all_var_data(self) -> VarData | None: + """Get all the var data. + + Returns: + The var data. + """ + return self._var_data + + def json(self) -> str: + """Get the JSON representation of the var. + + Returns: + The JSON representation of the var. + """ + return json.dumps( + list(self._var_value), + ) diff --git a/pyproject.toml b/pyproject.toml index e666a6a9003..6f45b42a90e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -37,6 +37,7 @@ dependencies = [ "starlette >=0.47.0", "typing_extensions >=4.13.0", "wrapt >=1.17.0,<3.0", + "reflex-core", "reflex-components-code", "reflex-components-core", "reflex-components-dataeditor", @@ -331,6 +332,7 @@ required-version = ">=0.7.0" [tool.uv.sources] hatch-reflex-pyi.workspace = true +reflex-core.workspace = true reflex-components-code.workspace = true reflex-components-core.workspace = true reflex-components-dataeditor.workspace = true diff --git a/reflex/constants/base.py b/reflex/constants/base.py index c8a9a1dfde5..efacb9974a7 100644 --- a/reflex/constants/base.py +++ b/reflex/constants/base.py @@ -1,278 +1,3 @@ -"""Base file for constants that don't fit any other categories.""" +"""Re-export from reflex_core.""" -from __future__ import annotations - -import platform -from enum import Enum -from importlib import metadata -from pathlib import Path -from types import SimpleNamespace -from typing import Literal - -from platformdirs import PlatformDirs - -IS_WINDOWS = platform.system() == "Windows" -IS_MACOS = platform.system() == "Darwin" -IS_LINUX = platform.system() == "Linux" - - -class Dirs(SimpleNamespace): - """Various directories/paths used by Reflex.""" - - # The frontend directories in a project. - # The web folder where the frontend app is compiled to. - WEB = ".web" - # The directory where uploaded files are stored. - UPLOADED_FILES = "uploaded_files" - # The name of the assets directory. - APP_ASSETS = "assets" - # The name of the assets directory for external resources (a subfolder of APP_ASSETS). - EXTERNAL_APP_ASSETS = "external" - # The name of the utils file. - UTILS = "utils" - # The name of the state file. - STATE_PATH = UTILS + "/state" - # The name of the components file. - COMPONENTS_PATH = UTILS + "/components" - # The name of the contexts file. - CONTEXTS_PATH = UTILS + "/context" - # The name of the output directory. - BUILD_DIR = "build" - # The name of the static files directory. - STATIC = BUILD_DIR + "/client" - # The name of the public html directory served at "/" - PUBLIC = "public" - # The directory where styles are located. - STYLES = "styles" - # The name of the pages directory. - PAGES = "app" - # The name of the routes directory. - ROUTES = "routes" - # The name of the env json file. - ENV_JSON = "env.json" - # The name of the reflex json file. - REFLEX_JSON = "reflex.json" - # The name of the postcss config file. - POSTCSS_JS = "postcss.config.js" - # The name of the states directory. - STATES = ".states" - # Where compilation artifacts for the backend are stored. - BACKEND = "backend" - # JSON-encoded list of page routes that need to be evaluated on the backend. - STATEFUL_PAGES = "stateful_pages.json" - # Marker file indicating that upload component was used in the frontend. - UPLOAD_IS_USED = "upload_is_used" - - -def _reflex_version() -> str: - """Get the Reflex version. - - Returns: - The Reflex version. - """ - try: - return metadata.version("reflex") - except metadata.PackageNotFoundError: - return "unknown" - - -class Reflex(SimpleNamespace): - """Base constants concerning Reflex.""" - - # App names and versions. - # The name of the Reflex package. - MODULE_NAME = "reflex" - # The current version of Reflex. - VERSION = _reflex_version() - - # The reflex json file. - JSON = "reflex.json" - - # Files and directories used to init a new project. - # The directory to store reflex dependencies. - # on windows, we use C:/Users//AppData/Local/reflex. - # on macOS, we use ~/Library/Application Support/reflex. - # on linux, we use ~/.local/share/reflex. - # If user sets REFLEX_DIR envroment variable use that instead. - DIR = PlatformDirs(MODULE_NAME, False).user_data_path - - LOGS_DIR = DIR / "logs" - - # The root directory of the reflex library. - ROOT_DIR = Path(__file__).parents[2] - - RELEASES_URL = "https://api.github.com/repos/reflex-dev/templates/releases" - - # The reflex stylesheet language supported - STYLESHEETS_SUPPORTED = ["css", "sass", "scss"] - - -class ReflexHostingCLI(SimpleNamespace): - """Base constants concerning Reflex Hosting CLI.""" - - # The name of the Reflex Hosting CLI package. - MODULE_NAME = "reflex-hosting-cli" - - -class Templates(SimpleNamespace): - """Constants related to Templates.""" - - # The default template - DEFAULT = "blank" - - # The AI template - AI = "ai" - - # The option for the user to choose a remote template. - CHOOSE_TEMPLATES = "choose-templates" - - # The URL to find reflex templates. - REFLEX_TEMPLATES_URL = ( - "https://reflex.dev/docs/getting-started/open-source-templates/" - ) - - # The reflex.build frontend host - REFLEX_BUILD_FRONTEND = "https://build.reflex.dev" - - # The reflex.build frontend with referrer - REFLEX_BUILD_FRONTEND_WITH_REFERRER = ( - f"{REFLEX_BUILD_FRONTEND}/?utm_source=reflex_cli" - ) - - class Dirs(SimpleNamespace): - """Folders used by the template system of Reflex.""" - - # The template directory used during reflex init. - BASE = Reflex.ROOT_DIR / Reflex.MODULE_NAME / ".templates" - # The web subdirectory of the template directory. - WEB_TEMPLATE = BASE / "web" - # Where the code for the templates is stored. - CODE = "code" - - -class Javascript(SimpleNamespace): - """Constants related to Javascript.""" - - # The node modules directory. - NODE_MODULES = "node_modules" - - -class ReactRouter(Javascript): - """Constants related to React Router.""" - - # The react router config file - CONFIG_FILE = "react-router.config.js" - - # The associated Vite config file - VITE_CONFIG_FILE = "vite.config.js" - - # Regex to check for message displayed when frontend comes up - DEV_FRONTEND_LISTENING_REGEX = r"Local:[\s]+" - - # Regex to pattern the route path in the config file - # INFO Accepting connections at http://localhost:3000 - PROD_FRONTEND_LISTENING_REGEX = r"Accepting connections at[\s]+" - - FRONTEND_LISTENING_REGEX = ( - rf"(?:{DEV_FRONTEND_LISTENING_REGEX}|{PROD_FRONTEND_LISTENING_REGEX})(.*)" - ) - - SPA_FALLBACK = "__spa-fallback.html" - - -# Color mode variables -class ColorMode(SimpleNamespace): - """Constants related to ColorMode.""" - - NAME = "rawColorMode" - RESOLVED_NAME = "resolvedColorMode" - USE = "useColorMode" - TOGGLE = "toggleColorMode" - SET = "setColorMode" - - -LITERAL_ENV = Literal["dev", "prod"] - - -# Env modes -class Env(str, Enum): - """The environment modes.""" - - DEV = "dev" - PROD = "prod" - - -# Log levels -class LogLevel(str, Enum): - """The log levels.""" - - DEBUG = "debug" - DEFAULT = "default" - INFO = "info" - WARNING = "warning" - ERROR = "error" - CRITICAL = "critical" - - @classmethod - def from_string(cls, level: str | None) -> LogLevel | None: - """Convert a string to a log level. - - Args: - level: The log level as a string. - - Returns: - The log level. - """ - if not level: - return None - try: - return LogLevel[level.upper()] - except KeyError: - return None - - def __le__(self, other: LogLevel) -> bool: - """Compare log levels. - - Args: - other: The other log level. - - Returns: - True if the log level is less than or equal to the other log level. - """ - levels = list(LogLevel) - return levels.index(self) <= levels.index(other) - - def subprocess_level(self): - """Return the log level for the subprocess. - - Returns: - The log level for the subprocess - """ - return self if self != LogLevel.DEFAULT else LogLevel.WARNING - - -# Server socket configuration variables -POLLING_MAX_HTTP_BUFFER_SIZE = 1000 * 1000 - - -class Ping(SimpleNamespace): - """PING constants.""" - - # The 'ping' interval - INTERVAL = 25 - # The 'ping' timeout - TIMEOUT = 120 - - -# Keys in the client_side_storage dict -COOKIES = "cookies" -LOCAL_STORAGE = "local_storage" -SESSION_STORAGE = "session_storage" - -# Testing variables. -# Testing os env set by pytest when running a test case. -PYTEST_CURRENT_TEST = "PYTEST_CURRENT_TEST" -APP_HARNESS_FLAG = "APP_HARNESS_FLAG" - -REFLEX_VAR_OPENING_TAG = "" -REFLEX_VAR_CLOSING_TAG = "" +from reflex_core.constants.base import * # noqa: F401, F403 diff --git a/reflex/constants/colors.py b/reflex/constants/colors.py index a318eca990f..007eacd6dd2 100644 --- a/reflex/constants/colors.py +++ b/reflex/constants/colors.py @@ -1,99 +1,3 @@ -"""The colors used in Reflex are a wrapper around https://www.radix-ui.com/colors.""" +"""Re-export from reflex_core.""" -from __future__ import annotations - -from dataclasses import dataclass -from typing import TYPE_CHECKING, Literal, get_args - -if TYPE_CHECKING: - from reflex.vars import Var - -ColorType = Literal[ - "gray", - "mauve", - "slate", - "sage", - "olive", - "sand", - "tomato", - "red", - "ruby", - "crimson", - "pink", - "plum", - "purple", - "violet", - "iris", - "indigo", - "blue", - "cyan", - "teal", - "jade", - "green", - "grass", - "brown", - "orange", - "sky", - "mint", - "lime", - "yellow", - "amber", - "gold", - "bronze", - "accent", - "black", - "white", -] - -COLORS = frozenset(get_args(ColorType)) - -ShadeType = Literal[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12] -MIN_SHADE_VALUE = 1 -MAX_SHADE_VALUE = 12 - - -def format_color( - color: ColorType | Var[str], shade: ShadeType | Var[int], alpha: bool | Var[bool] -) -> str: - """Format a color as a CSS color string. - - Args: - color: The color to use. - shade: The shade of the color to use. - alpha: Whether to use the alpha variant of the color. - - Returns: - The formatted color. - """ - if isinstance(alpha, bool): - return f"var(--{color}-{'a' if alpha else ''}{shade})" - - from reflex_components_core.core import cond - - alpha_var = cond(alpha, "a", "") - return f"var(--{color}-{alpha_var}{shade})" - - -@dataclass -class Color: - """A color in the Reflex color palette.""" - - # The color palette to use - color: ColorType | Var[str] - - # The shade of the color to use - shade: ShadeType | Var[int] = 7 - - # Whether to use the alpha variant of the color - alpha: bool | Var[bool] = False - - def __format__(self, format_spec: str) -> str: - """Format the color as a CSS color string. - - Args: - format_spec: The format specifier to use. - - Returns: - The formatted color. - """ - return format_color(self.color, self.shade, self.alpha) +from reflex_core.constants.colors import * # noqa: F401, F403 diff --git a/reflex/constants/compiler.py b/reflex/constants/compiler.py index 0926e57f320..2168d30749f 100644 --- a/reflex/constants/compiler.py +++ b/reflex/constants/compiler.py @@ -1,210 +1,3 @@ -"""Compiler variables.""" +"""Re-export from reflex_core.""" -import dataclasses -import enum -from enum import Enum -from types import SimpleNamespace - -from reflex.constants import Dirs -from reflex.utils.imports import ImportVar - -# The prefix used to create setters for state vars. -SETTER_PREFIX = "set_" - -# The file used to specify no compilation. -NOCOMPILE_FILE = "nocompile" - - -class Ext(SimpleNamespace): - """Extension used in Reflex.""" - - # The extension for JS files. - JS = ".js" - # The extension for JSX files. - JSX = ".jsx" - # The extension for python files. - PY = ".py" - # The extension for css files. - CSS = ".css" - # The extension for zip files. - ZIP = ".zip" - # The extension for executable files on Windows. - EXE = ".exe" - # The extension for markdown files. - MD = ".md" - - -class CompileVars(SimpleNamespace): - """The variables used during compilation.""" - - # The expected variable name where the rx.App is stored. - APP = "app" - # The expected variable name where the API object is stored for deployment. - API = "api" - # The name of the router variable. - ROUTER = "router" - # The name of the socket variable. - SOCKET = "socket" - # The name of the variable to hold API results. - RESULT = "result" - # The name of the final variable. - FINAL = "final" - # The name of the process variable. - PROCESSING = "processing" - # The name of the state variable. - STATE = "state" - # The name of the events variable. - EVENTS = "events" - # The name of the initial hydrate event. - HYDRATE = "hydrate" - # The name of the is_hydrated variable. - IS_HYDRATED = "is_hydrated" - # The name of the function to add events to the queue. - ADD_EVENTS = "addEvents" - # The name of the function to apply event actions before invoking a target. - APPLY_EVENT_ACTIONS = "applyEventActions" - # The name of the var storing any connection error. - CONNECT_ERROR = "connectErrors" - # The name of the function for converting a dict to an event. - TO_EVENT = "ReflexEvent" - # The name of the internal on_load event. - ON_LOAD_INTERNAL = "reflex___state____on_load_internal_state.on_load_internal" - # The name of the internal event to update generic state vars. - UPDATE_VARS_INTERNAL = ( - "reflex___state____update_vars_internal_state.update_vars_internal" - ) - # The name of the frontend event exception state - FRONTEND_EXCEPTION_STATE = "reflex___state____frontend_event_exception_state" - # The full name of the frontend exception state - FRONTEND_EXCEPTION_STATE_FULL = ( - f"reflex___state____state.{FRONTEND_EXCEPTION_STATE}" - ) - - -class PageNames(SimpleNamespace): - """The name of basic pages deployed in the frontend.""" - - # The name of the index page. - INDEX_ROUTE = "index" - # The name of the app root page. - APP_ROOT = "root.jsx" - # The root stylesheet filename. - STYLESHEET_ROOT = "__reflex_global_styles" - # The name of the document root page. - DOCUMENT_ROOT = "_document.js" - # The name of the theme page. - THEME = "theme" - # The module containing components. - COMPONENTS = "components" - # The module containing shared stateful components - STATEFUL_COMPONENTS = "stateful_components" - - -class ComponentName(Enum): - """Component names.""" - - BACKEND = "Backend" - FRONTEND = "Frontend" - - def zip(self): - """Give the zip filename for the component. - - Returns: - The lower-case filename with zip extension. - """ - return self.value.lower() + Ext.ZIP - - -class CompileContext(str, Enum): - """The context in which the compiler is running.""" - - RUN = "run" - EXPORT = "export" - DEPLOY = "deploy" - UNDEFINED = "undefined" - - -class Imports(SimpleNamespace): - """Common sets of import vars.""" - - EVENTS = { - "react": [ImportVar(tag="useContext")], - f"$/{Dirs.CONTEXTS_PATH}": [ImportVar(tag="EventLoopContext")], - f"$/{Dirs.STATE_PATH}": [ - ImportVar(tag=CompileVars.TO_EVENT), - ImportVar(tag=CompileVars.APPLY_EVENT_ACTIONS), - ], - } - - -class Hooks(SimpleNamespace): - """Common sets of hook declarations.""" - - EVENTS = f"const [{CompileVars.ADD_EVENTS}, {CompileVars.CONNECT_ERROR}] = useContext(EventLoopContext);" - - class HookPosition(enum.Enum): - """The position of the hook in the component.""" - - INTERNAL = "internal" - PRE_TRIGGER = "pre_trigger" - POST_TRIGGER = "post_trigger" - - -class MemoizationDisposition(enum.Enum): - """The conditions under which a component should be memoized.""" - - # If the component uses state or events, it should be memoized. - STATEFUL = "stateful" - ALWAYS = "always" - NEVER = "never" - - -@dataclasses.dataclass(frozen=True) -class MemoizationMode: - """The mode for memoizing a Component.""" - - # The conditions under which the component should be memoized. - disposition: MemoizationDisposition = MemoizationDisposition.STATEFUL - - # Whether children of this component should be memoized first. - recursive: bool = True - - -DATA_UNDERSCORE = "data_" -DATA_DASH = "data-" -ARIA_UNDERSCORE = "aria_" -ARIA_DASH = "aria-" - -SPECIAL_ATTRS = ( - DATA_UNDERSCORE, - DATA_DASH, - ARIA_UNDERSCORE, - ARIA_DASH, -) - - -class SpecialAttributes(enum.Enum): - """Special attributes for components. - - These are placed in custom_attrs and rendered as-is rather than converting - to a style prop. - """ - - @classmethod - def is_special(cls, attr: str) -> bool: - """Check if the attribute is special. - - Args: - attr: the attribute to check - - Returns: - True if the attribute is special. - """ - return attr.startswith(SPECIAL_ATTRS) - - -class ResetStylesheet(SimpleNamespace): - """Constants for CSS reset stylesheet.""" - - # The filename of the CSS reset file. - FILENAME = "__reflex_style_reset.css" +from reflex_core.constants.compiler import * # noqa: F401, F403 diff --git a/reflex/constants/state.py b/reflex/constants/state.py index 3f6ebec2f17..02ccba72343 100644 --- a/reflex/constants/state.py +++ b/reflex/constants/state.py @@ -1,19 +1,3 @@ -"""State-related constants.""" +"""Re-export from reflex_core.""" -from enum import Enum - - -class StateManagerMode(str, Enum): - """State manager constants.""" - - DISK = "disk" - MEMORY = "memory" - REDIS = "redis" - - -# Used for things like console_log, etc. -FRONTEND_EVENT_STATE = "__reflex_internal_frontend_event_state" - -FIELD_MARKER = "_rx_state_" -MEMO_MARKER = "_rx_memo_" -CAMEL_CASE_MEMO_MARKER = "RxMemo" +from reflex_core.constants.state import * # noqa: F401, F403 diff --git a/reflex/utils/compat.py b/reflex/utils/compat.py index cb9f35485a3..1738102f45c 100644 --- a/reflex/utils/compat.py +++ b/reflex/utils/compat.py @@ -1,69 +1,3 @@ -"""Compatibility hacks and helpers.""" +"""Re-export from reflex_core.""" -import sys -from collections.abc import Mapping -from typing import TYPE_CHECKING, Any - -if TYPE_CHECKING: - from pydantic.fields import FieldInfo - - -async def windows_hot_reload_lifespan_hack(): - """[REF-3164] A hack to fix hot reload on Windows. - - Uvicorn has an issue stopping itself on Windows after detecting changes in - the filesystem. - - This workaround repeatedly prints and flushes null characters to stderr, - which seems to allow the uvicorn server to exit when the CTRL-C signal is - sent from the reloader process. - - Don't ask me why this works, I discovered it by accident - masenf. - """ - import asyncio - import sys - - try: - while True: - sys.stderr.write("\0") - sys.stderr.flush() - await asyncio.sleep(0.5) - except asyncio.CancelledError: - pass - - -def annotations_from_namespace(namespace: Mapping[str, Any]) -> dict[str, Any]: - """Get the annotations from a class namespace. - - Args: - namespace: The class namespace. - - Returns: - The (forward-ref) annotations from the class namespace. - """ - if sys.version_info >= (3, 14) and "__annotations__" not in namespace: - from annotationlib import ( - Format, - call_annotate_function, - get_annotate_from_class_namespace, - ) - - if annotate := get_annotate_from_class_namespace(namespace): - return call_annotate_function(annotate, format=Format.FORWARDREF) - return namespace.get("__annotations__", {}) - - -def sqlmodel_field_has_primary_key(field_info: "FieldInfo") -> bool: - """Determines if a field is a primary. - - Args: - field_info: a rx.model field - - Returns: - If field_info is a primary key (Bool) - """ - if getattr(field_info, "primary_key", None) is True: - return True - if getattr(field_info, "sa_column", None) is None: - return False - return bool(getattr(field_info.sa_column, "primary_key", None)) # pyright: ignore[reportAttributeAccessIssue] +from reflex_core.utils.compat import * # noqa: F401, F403 diff --git a/reflex/utils/console.py b/reflex/utils/console.py index 0fdaed5cc9e..ec5e07eea88 100644 --- a/reflex/utils/console.py +++ b/reflex/utils/console.py @@ -1,473 +1,3 @@ -"""Functions to communicate to the user via console.""" +"""Re-export from reflex_core.""" -from __future__ import annotations - -import contextlib -import datetime -import inspect -import os -import shutil -import sys -import time -from pathlib import Path -from types import FrameType - -from rich.console import Console -from rich.progress import MofNCompleteColumn, Progress, TaskID, TimeElapsedColumn -from rich.prompt import Prompt - -from reflex.constants import LogLevel -from reflex.constants.base import Reflex -from reflex.utils.decorator import once - -# Console for pretty printing. -_console = Console(highlight=False) -_console_stderr = Console(stderr=True, highlight=False) - -# The current log level. -_LOG_LEVEL = LogLevel.INFO - -# Deprecated features who's warning has been printed. -_EMITTED_DEPRECATION_WARNINGS = set() - -# Info messages which have been printed. -_EMITTED_INFO = set() - -# Warnings which have been printed. -_EMITTED_WARNINGS = set() - -# Errors which have been printed. -_EMITTED_ERRORS = set() - -# Success messages which have been printed. -_EMITTED_SUCCESS = set() - -# Debug messages which have been printed. -_EMITTED_DEBUG = set() - -# Logs which have been printed. -_EMITTED_LOGS = set() - -# Prints which have been printed. -_EMITTED_PRINTS = set() - - -def set_log_level(log_level: LogLevel | None): - """Set the log level. - - Args: - log_level: The log level to set. - - Raises: - TypeError: If the log level is a string. - """ - if log_level is None: - return - if not isinstance(log_level, LogLevel): - msg = f"log_level must be a LogLevel enum value, got {log_level} of type {type(log_level)} instead." - raise TypeError(msg) - global _LOG_LEVEL - if log_level != _LOG_LEVEL: - # Set the loglevel persistenly for subprocesses. - os.environ["REFLEX_LOGLEVEL"] = log_level.value - _LOG_LEVEL = log_level - - -def is_debug() -> bool: - """Check if the log level is debug. - - Returns: - True if the log level is debug. - """ - return _LOG_LEVEL <= LogLevel.DEBUG - - -def print(msg: str, *, dedupe: bool = False, **kwargs): - """Print a message. - - Args: - msg: The message to print. - dedupe: If True, suppress multiple console logs of print message. - kwargs: Keyword arguments to pass to the print function. - """ - if dedupe: - if msg in _EMITTED_PRINTS: - return - _EMITTED_PRINTS.add(msg) - _console.print(msg, **kwargs) - - -def _print_stderr(msg: str, *, dedupe: bool = False, **kwargs): - """Print a message to stderr. - - Args: - msg: The message to print. - dedupe: If True, suppress multiple console logs of print message. - kwargs: Keyword arguments to pass to the print function. - """ - if dedupe: - if msg in _EMITTED_PRINTS: - return - _EMITTED_PRINTS.add(msg) - _console_stderr.print(msg, **kwargs) - - -@once -def log_file_console(): - """Create a console that logs to a file. - - Returns: - A Console object that logs to a file. - """ - from reflex.environment import environment - - if not (env_log_file := environment.REFLEX_LOG_FILE.get()): - subseconds = int((time.time() % 1) * 1000) - timestamp = time.strftime("%Y-%m-%d_%H-%M-%S") + f"_{subseconds:03d}" - log_file = Reflex.DIR / "logs" / (timestamp + ".log") - log_file.parent.mkdir(parents=True, exist_ok=True) - else: - log_file = env_log_file - if log_file.exists(): - log_file.unlink() - log_file.touch() - return Console(file=log_file.open("a", encoding="utf-8")) - - -@once -def should_use_log_file_console() -> bool: - """Check if the log file console should be used. - - Returns: - True if the log file console should be used, False otherwise. - """ - from reflex.environment import environment - - return environment.REFLEX_ENABLE_FULL_LOGGING.get() - - -def print_to_log_file(msg: str, *, dedupe: bool = False, **kwargs): - """Print a message to the log file. - - Args: - msg: The message to print. - dedupe: If True, suppress multiple console logs of print message. - kwargs: Keyword arguments to pass to the print function. - """ - log_file_console().print(f"[{datetime.datetime.now()}] {msg}", **kwargs) - - -def debug(msg: str, *, dedupe: bool = False, **kwargs): - """Print a debug message. - - Args: - msg: The debug message. - dedupe: If True, suppress multiple console logs of debug message. - kwargs: Keyword arguments to pass to the print function. - """ - if is_debug(): - msg_ = f"[purple]Debug: {msg}[/purple]" - if dedupe: - if msg_ in _EMITTED_DEBUG: - return - _EMITTED_DEBUG.add(msg_) - if progress := kwargs.pop("progress", None): - progress.console.print(msg_, **kwargs) - else: - print(msg_, **kwargs) - if should_use_log_file_console() and kwargs.pop("progress", None) is None: - print_to_log_file(f"[purple]Debug: {msg}[/purple]", **kwargs) - - -def info(msg: str, *, dedupe: bool = False, **kwargs): - """Print an info message. - - Args: - msg: The info message. - dedupe: If True, suppress multiple console logs of info message. - kwargs: Keyword arguments to pass to the print function. - """ - if _LOG_LEVEL <= LogLevel.INFO: - if dedupe: - if msg in _EMITTED_INFO: - return - _EMITTED_INFO.add(msg) - print(f"[cyan]Info: {msg}[/cyan]", **kwargs) - if should_use_log_file_console(): - print_to_log_file(f"[cyan]Info: {msg}[/cyan]", **kwargs) - - -def success(msg: str, *, dedupe: bool = False, **kwargs): - """Print a success message. - - Args: - msg: The success message. - dedupe: If True, suppress multiple console logs of success message. - kwargs: Keyword arguments to pass to the print function. - """ - if _LOG_LEVEL <= LogLevel.INFO: - if dedupe: - if msg in _EMITTED_SUCCESS: - return - _EMITTED_SUCCESS.add(msg) - print(f"[green]Success: {msg}[/green]", **kwargs) - if should_use_log_file_console(): - print_to_log_file(f"[green]Success: {msg}[/green]", **kwargs) - - -def log(msg: str, *, dedupe: bool = False, **kwargs): - """Takes a string and logs it to the console. - - Args: - msg: The message to log. - dedupe: If True, suppress multiple console logs of log message. - kwargs: Keyword arguments to pass to the print function. - """ - if _LOG_LEVEL <= LogLevel.INFO: - if dedupe: - if msg in _EMITTED_LOGS: - return - _EMITTED_LOGS.add(msg) - _console.log(msg, **kwargs) - if should_use_log_file_console(): - print_to_log_file(msg, **kwargs) - - -def rule(title: str, **kwargs): - """Prints a horizontal rule with a title. - - Args: - title: The title of the rule. - kwargs: Keyword arguments to pass to the print function. - """ - _console.rule(title, **kwargs) - - -def warn(msg: str, *, dedupe: bool = False, **kwargs): - """Print a warning message. - - Args: - msg: The warning message. - dedupe: If True, suppress multiple console logs of warning message. - kwargs: Keyword arguments to pass to the print function. - """ - if _LOG_LEVEL <= LogLevel.WARNING: - if dedupe: - if msg in _EMITTED_WARNINGS: - return - _EMITTED_WARNINGS.add(msg) - print(f"[orange1]Warning: {msg}[/orange1]", **kwargs) - if should_use_log_file_console(): - print_to_log_file(f"[orange1]Warning: {msg}[/orange1]", **kwargs) - - -@once -def _exclude_paths_from_frame_info() -> list[Path]: - import importlib.util - - import click - import granian - import socketio - import typing_extensions - - import reflex as rx - - # Exclude utility modules that should never be the source of deprecated reflex usage. - exclude_modules = [click, rx, typing_extensions, socketio, granian] - modules_paths = [file for m in exclude_modules if (file := m.__file__)] + [ - spec.origin - for m in [*sys.builtin_module_names, *sys.stdlib_module_names] - if (spec := importlib.util.find_spec(m)) and spec.origin - ] - exclude_roots = [ - p.parent.resolve() if (p := Path(file)).name == "__init__.py" else p.resolve() - for file in modules_paths - ] - # Specifically exclude the reflex cli module. - if reflex_bin := shutil.which(b"reflex"): - exclude_roots.append(Path(reflex_bin.decode())) - - return exclude_roots - - -def _get_first_non_framework_frame() -> FrameType | None: - exclude_roots = _exclude_paths_from_frame_info() - - frame = inspect.currentframe() - while frame := frame and frame.f_back: - frame_path = Path(inspect.getfile(frame)).resolve() - if not any(frame_path.is_relative_to(root) for root in exclude_roots): - break - return frame - - -def deprecate( - *, - feature_name: str, - reason: str, - deprecation_version: str, - removal_version: str, - dedupe: bool = True, - **kwargs, -): - """Print a deprecation warning. - - Args: - feature_name: The feature to deprecate. - reason: The reason for deprecation. - deprecation_version: The version the feature was deprecated - removal_version: The version the deprecated feature will be removed - dedupe: If True, suppress multiple console logs of deprecation message. - kwargs: Keyword arguments to pass to the print function. - """ - dedupe_key = feature_name - loc = "" - - # See if we can find where the deprecation exists in "user code" - origin_frame = _get_first_non_framework_frame() - if origin_frame is not None: - filename = Path(origin_frame.f_code.co_filename) - if filename.is_relative_to(Path.cwd()): - filename = filename.relative_to(Path.cwd()) - loc = f" ({filename}:{origin_frame.f_lineno})" - dedupe_key = f"{dedupe_key} {loc}" - - if dedupe_key not in _EMITTED_DEPRECATION_WARNINGS: - msg = ( - f"{feature_name} has been deprecated in version {deprecation_version}. {reason.rstrip('.').lstrip('. ')}. It will be completely " - f"removed in {removal_version}.{loc}" - ) - if _LOG_LEVEL <= LogLevel.WARNING: - print(f"[yellow]DeprecationWarning: {msg}[/yellow]", **kwargs) - if should_use_log_file_console(): - print_to_log_file(f"[yellow]DeprecationWarning: {msg}[/yellow]", **kwargs) - if dedupe: - _EMITTED_DEPRECATION_WARNINGS.add(dedupe_key) - - -def error(msg: str, *, dedupe: bool = False, **kwargs): - """Print an error message. - - Args: - msg: The error message. - dedupe: If True, suppress multiple console logs of error message. - kwargs: Keyword arguments to pass to the print function. - """ - if _LOG_LEVEL <= LogLevel.ERROR: - if dedupe: - if msg in _EMITTED_ERRORS: - return - _EMITTED_ERRORS.add(msg) - _print_stderr(f"[red]{msg}[/red]", **kwargs) - if should_use_log_file_console(): - print_to_log_file(f"[red]{msg}[/red]", **kwargs) - - -def ask( - question: str, - choices: list[str] | None = None, - default: str | None = None, - show_choices: bool = True, -) -> str | None: - """Takes a prompt question and optionally a list of choices - and returns the user input. - - Args: - question: The question to ask the user. - choices: A list of choices to select from. - default: The default option selected. - show_choices: Whether to show the choices. - - Returns: - A string with the user input. - """ - return Prompt.ask( - question, choices=choices, default=default, show_choices=show_choices - ) - - -def progress(): - """Create a new progress bar. - - Returns: - A new progress bar. - """ - return Progress( - *Progress.get_default_columns()[:-1], - MofNCompleteColumn(), - TimeElapsedColumn(), - ) - - -def status(*args, **kwargs): - """Create a status with a spinner. - - Args: - *args: Args to pass to the status. - **kwargs: Kwargs to pass to the status. - - Returns: - 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]") - - -class PoorProgress: - """A poor man's progress bar.""" - - def __init__(self): - """Initialize the progress bar.""" - super().__init__() - self.tasks = {} - self.progress = 0 - self.total = 0 - - def add_task(self, task: str, total: int): - """Add a task to the progress bar. - - Args: - task: The task name. - total: The total number of steps for the task. - - Returns: - The task ID. - """ - self.total += total - task_id = TaskID(len(self.tasks)) - self.tasks[task_id] = {"total": total, "current": 0} - return task_id - - def advance(self, task: TaskID, advance: int = 1): - """Advance the progress of a task. - - Args: - task: The task ID. - advance: The number of steps to advance. - """ - if task in self.tasks: - self.tasks[task]["current"] += advance - self.progress += advance - _console.print(f"Progress: {self.progress}/{self.total}") - - def start(self): - """Start the progress bar.""" - - def stop(self): - """Stop the progress bar.""" +from reflex_core.utils.console import * # noqa: F401, F403 diff --git a/reflex/utils/decorator.py b/reflex/utils/decorator.py index bdec72807c4..aaec02ebf02 100644 --- a/reflex/utils/decorator.py +++ b/reflex/utils/decorator.py @@ -1,148 +1,3 @@ -"""Decorator utilities.""" +"""Re-export from reflex_core.""" -import functools -from collections.abc import Callable -from pathlib import Path -from typing import ParamSpec, TypeVar, cast - -T = TypeVar("T") - - -def once(f: Callable[[], T]) -> Callable[[], T]: - """A decorator that calls the function once and caches the result. - - Args: - f: The function to call. - - Returns: - A function that calls the function once and caches the result. - """ - unset = object() - value: object | T = unset - - @functools.wraps(f) - def wrapper() -> T: - nonlocal value - value = f() if value is unset else value - return value # pyright: ignore[reportReturnType] - - return wrapper - - -def once_unless_none(f: Callable[[], T | None]) -> Callable[[], T | None]: - """A decorator that calls the function once and caches the result unless it is None. - - Args: - f: The function to call. - - Returns: - A function that calls the function once and caches the result unless it is None. - """ - value: T | None = None - - @functools.wraps(f) - def wrapper() -> T | None: - nonlocal value - value = f() if value is None else value - return value - - return wrapper - - -P = ParamSpec("P") - - -def debug(f: Callable[P, T]) -> Callable[P, T]: - """A decorator that prints the function name, arguments, and result. - - Args: - f: The function to call. - - Returns: - A function that prints the function name, arguments, and result. - """ - - @functools.wraps(f) - def wrapper(*args: P.args, **kwargs: P.kwargs) -> T: - result = f(*args, **kwargs) - print( # noqa: T201 - f"Calling {f.__name__} with args: {args} and kwargs: {kwargs}, result: {result}" - ) - return result - - return wrapper - - -def _write_cached_procedure_file(payload: str, cache_file: Path, value: object): - import pickle - - cache_file.parent.mkdir(parents=True, exist_ok=True) - cache_file.write_bytes(pickle.dumps((payload, value))) - - -def _read_cached_procedure_file(cache_file: Path) -> tuple[str | None, object]: - import pickle - - if cache_file.exists(): - with cache_file.open("rb") as f: - return pickle.loads(f.read()) - - return None, None - - -P = ParamSpec("P") -Picklable = TypeVar("Picklable") - - -def cached_procedure( - cache_file_path: Callable[[], Path], - payload_fn: Callable[P, str], -) -> Callable[[Callable[P, Picklable]], Callable[P, Picklable]]: - """Decorator to cache the result of a function based on its arguments. - - Args: - cache_file_path: Function that computes the cache file path. - payload_fn: Function that computes cache payload from function args. - - Returns: - The decorated function. - """ - - def _inner_decorator(func: Callable[P, Picklable]) -> Callable[P, Picklable]: - def _inner(*args: P.args, **kwargs: P.kwargs) -> Picklable: - cache_file = cache_file_path() - - payload, value = _read_cached_procedure_file(cache_file) - new_payload = payload_fn(*args, **kwargs) - - if payload != new_payload: - new_value = func(*args, **kwargs) - _write_cached_procedure_file(new_payload, cache_file, new_value) - return new_value - - from reflex.utils import console - - console.debug( - f"Using cached value for {func.__name__} with payload: {new_payload}" - ) - return cast("Picklable", value) - - return _inner - - return _inner_decorator - - -def cache_result_in_disk( - cache_file_path: Callable[[], Path], -) -> Callable[[Callable[[], Picklable]], Callable[[], Picklable]]: - """Decorator to cache the result of a function on disk. - - Args: - cache_file_path: Function that computes the cache file path. - - Returns: - The decorated function. - """ - return cached_procedure( - cache_file_path=cache_file_path, payload_fn=lambda: "constant" - ) +from reflex_core.utils.decorator import * # noqa: F401, F403 diff --git a/reflex/utils/exceptions.py b/reflex/utils/exceptions.py index 4c4e2ad35c5..f2a63d36021 100644 --- a/reflex/utils/exceptions.py +++ b/reflex/utils/exceptions.py @@ -1,286 +1,3 @@ -"""Custom Exceptions.""" +"""Re-export from reflex_core.""" -from __future__ import annotations - -from typing import TYPE_CHECKING, Any - -if TYPE_CHECKING: - from reflex.vars import Var - - -class ReflexError(Exception): - """Base exception for all Reflex exceptions.""" - - -class ConfigError(ReflexError): - """Custom exception for config related errors.""" - - -class InvalidStateManagerModeError(ReflexError, ValueError): - """Raised when an invalid state manager mode is provided.""" - - -class ReflexRuntimeError(ReflexError, RuntimeError): - """Custom RuntimeError for Reflex.""" - - -class UploadTypeError(ReflexError, TypeError): - """Custom TypeError for upload related errors.""" - - -class EnvVarValueError(ReflexError, ValueError): - """Custom ValueError raised when unable to convert env var to expected type.""" - - -class ComponentTypeError(ReflexError, TypeError): - """Custom TypeError for component related errors.""" - - -class ChildrenTypeError(ComponentTypeError): - """Raised when the children prop of a component is not a valid type.""" - - def __init__(self, component: str, child: Any): - """Initialize the exception. - - Args: - component: The name of the component. - child: The child that caused the error. - """ - super().__init__( - f"Component {component} received child {child} of type {type(child)}. " - "Accepted types are other components, state vars, or primitive Python types (dict excluded)." - ) - - -class EventHandlerTypeError(ReflexError, TypeError): - """Custom TypeError for event handler related errors.""" - - -class EventHandlerValueError(ReflexError, ValueError): - """Custom ValueError for event handler related errors.""" - - -class StateValueError(ReflexError, ValueError): - """Custom ValueError for state related errors.""" - - -class VarNameError(ReflexError, NameError): - """Custom NameError for when a state var has been shadowed by a substate var.""" - - -class VarTypeError(ReflexError, TypeError): - """Custom TypeError for var related errors.""" - - -class VarValueError(ReflexError, ValueError): - """Custom ValueError for var related errors.""" - - -class VarAttributeError(ReflexError, AttributeError): - """Custom AttributeError for var related errors.""" - - -class UntypedVarError(ReflexError, TypeError): - """Custom TypeError for untyped var errors.""" - - def __init__(self, var: Var, action: str, doc_link: str = ""): - """Create an UntypedVarError from a var. - - Args: - var: The var. - action: The action that caused the error. - doc_link: The link to the documentation. - """ - var_data = var._get_all_var_data() - is_state_var = ( - var_data - and var_data.state - and var_data.field_name - and var_data.state + "." + var_data.field_name == str(var) - ) - super().__init__( - f"Cannot {action} on untyped var '{var!s}' of type '{var._var_type!s}'." - + ( - " Please add a type annotation to the var in the state class." - if is_state_var - else " You can call the var's .to(desired_type) method to convert it to the desired type." - ) - + (f" See {doc_link}" if doc_link else "") - ) - - -class UntypedComputedVarError(ReflexError, TypeError): - """Custom TypeError for untyped computed var errors.""" - - def __init__(self, var_name: str): - """Initialize the UntypedComputedVarError. - - Args: - var_name: The name of the computed var. - """ - super().__init__(f"Computed var '{var_name}' must have a type annotation.") - - -class ComputedVarSignatureError(ReflexError, TypeError): - """Custom TypeError for computed var signature errors.""" - - def __init__(self, var_name: str, signature: str): - """Initialize the ComputedVarSignatureError. - - Args: - var_name: The name of the var. - signature: The invalid signature. - """ - super().__init__(f"Computed var `{var_name}{signature}` cannot take arguments.") - - -class MissingAnnotationError(ReflexError, TypeError): - """Custom TypeError for missing annotations.""" - - def __init__(self, var_name: str): - """Initialize the MissingAnnotationError. - - Args: - var_name: The name of the var. - """ - super().__init__(f"Var '{var_name}' must have a type annotation.") - - -class UploadValueError(ReflexError, ValueError): - """Custom ValueError for upload related errors.""" - - -class PageValueError(ReflexError, ValueError): - """Custom ValueError for page related errors.""" - - -class RouteValueError(ReflexError, ValueError): - """Custom ValueError for route related errors.""" - - -class VarOperationTypeError(ReflexError, TypeError): - """Custom TypeError for when unsupported operations are performed on vars.""" - - -class VarDependencyError(ReflexError, ValueError): - """Custom ValueError for when a var depends on a non-existent var.""" - - -class InvalidStylePropError(ReflexError, TypeError): - """Custom Type Error when style props have invalid values.""" - - -class ImmutableStateError(ReflexError): - """Raised when a background task attempts to modify state outside of context.""" - - -class LockExpiredError(ReflexError): - """Raised when the state lock expires while an event is being processed.""" - - -class MatchTypeError(ReflexError, TypeError): - """Raised when the return types of match cases are different.""" - - -class EventHandlerArgTypeMismatchError(ReflexError, TypeError): - """Raised when the annotations of args accepted by an EventHandler differs from the spec of the event trigger.""" - - -class EventFnArgMismatchError(ReflexError, TypeError): - """Raised when the number of args required by an event handler is more than provided by the event trigger.""" - - -class DynamicRouteArgShadowsStateVarError(ReflexError, NameError): - """Raised when a dynamic route arg shadows a state var.""" - - -class ComputedVarShadowsStateVarError(ReflexError, NameError): - """Raised when a computed var shadows a state var.""" - - -class ComputedVarShadowsBaseVarsError(ReflexError, NameError): - """Raised when a computed var shadows a base var.""" - - -class EventHandlerShadowsBuiltInStateMethodError(ReflexError, NameError): - """Raised when an event handler shadows a built-in state method.""" - - -class GeneratedCodeHasNoFunctionDefsError(ReflexError): - """Raised when refactored code generated with flexgen has no functions defined.""" - - -class PrimitiveUnserializableToJSONError(ReflexError, ValueError): - """Raised when a primitive type is unserializable to JSON. Usually with NaN and Infinity.""" - - -class InvalidLifespanTaskTypeError(ReflexError, TypeError): - """Raised when an invalid task type is registered as a lifespan task.""" - - -class DynamicComponentMissingLibraryError(ReflexError, ValueError): - """Raised when a dynamic component is missing a library.""" - - -class SetUndefinedStateVarError(ReflexError, AttributeError): - """Raised when setting the value of a var without first declaring it.""" - - -class StateSchemaMismatchError(ReflexError, TypeError): - """Raised when the serialized schema of a state class does not match the current schema.""" - - -class EnvironmentVarValueError(ReflexError, ValueError): - """Raised when an environment variable is set to an invalid value.""" - - -class DynamicComponentInvalidSignatureError(ReflexError, TypeError): - """Raised when a dynamic component has an invalid signature.""" - - -class InvalidPropValueError(ReflexError): - """Raised when a prop value is invalid.""" - - -class StateTooLargeError(ReflexError): - """Raised when the state is too large to be serialized.""" - - -class StateSerializationError(ReflexError): - """Raised when the state cannot be serialized.""" - - -class StateMismatchError(ReflexError, ValueError): - """Raised when the state retrieved does not match the expected state.""" - - -class SystemPackageMissingError(ReflexError): - """Raised when a system package is missing.""" - - def __init__(self, package: str): - """Initialize the SystemPackageMissingError. - - Args: - package: The missing package. - """ - from reflex.constants import IS_MACOS - - extra = ( - f" You can do so by running 'brew install {package}'." if IS_MACOS else "" - ) - super().__init__( - f"System package '{package}' is missing." - f" Please install it through your system package manager.{extra}" - ) - - -class EventDeserializationError(ReflexError, ValueError): - """Raised when an event cannot be deserialized.""" - - -class InvalidLockWarningThresholdError(ReflexError): - """Raised when an invalid lock warning threshold is provided.""" - - -class UnretrievableVarValueError(ReflexError): - """Raised when the value of a var is not retrievable.""" +from reflex_core.utils.exceptions import * # noqa: F401, F403 diff --git a/reflex/utils/format.py b/reflex/utils/format.py index 4f847487649..84a1d6f8039 100644 --- a/reflex/utils/format.py +++ b/reflex/utils/format.py @@ -1,774 +1,3 @@ -"""Formatting operations.""" +"""Re-export from reflex_core.""" -from __future__ import annotations - -import inspect -import json -import os -import re -from typing import TYPE_CHECKING, Any - -from reflex import constants -from reflex.constants.state import FRONTEND_EVENT_STATE -from reflex.utils import exceptions - -if TYPE_CHECKING: - from reflex.components.component import ComponentStyle - from reflex.event import ArgsSpec, EventChain, EventHandler, EventSpec, EventType - -WRAP_MAP = { - "{": "}", - "(": ")", - "[": "]", - "<": ">", - '"': '"', - "'": "'", - "`": "`", -} - - -def length_of_largest_common_substring(str1: str, str2: str) -> int: - """Find the length of the largest common substring between two strings. - - Args: - str1: The first string. - str2: The second string. - - Returns: - The length of the largest common substring. - """ - if not str1 or not str2: - return 0 - - # Create a matrix of size (len(str1) + 1) x (len(str2) + 1) - dp = [[0] * (len(str2) + 1) for _ in range(len(str1) + 1)] - - # Variables to keep track of maximum length and ending position - max_length = 0 - - # Fill the dp matrix - for i in range(1, len(str1) + 1): - for j in range(1, len(str2) + 1): - if str1[i - 1] == str2[j - 1]: - dp[i][j] = dp[i - 1][j - 1] + 1 - if dp[i][j] > max_length: - max_length = dp[i][j] - - return max_length - - -def get_close_char(open: str, close: str | None = None) -> str: - """Check if the given character is a valid brace. - - Args: - open: The open character. - close: The close character if provided. - - Returns: - The close character. - - Raises: - ValueError: If the open character is not a valid brace. - """ - if close is not None: - return close - if open not in WRAP_MAP: - msg = f"Invalid wrap open: {open}, must be one of {WRAP_MAP.keys()}" - raise ValueError(msg) - return WRAP_MAP[open] - - -def is_wrapped(text: str, open: str, close: str | None = None) -> bool: - """Check if the given text is wrapped in the given open and close characters. - - "(a) + (b)" --> False - "((abc))" --> True - "(abc)" --> True - - Args: - text: The text to check. - open: The open character. - close: The close character. - - Returns: - Whether the text is wrapped. - """ - close = get_close_char(open, close) - if not (text.startswith(open) and text.endswith(close)): - return False - - depth = 0 - for ch in text[:-1]: - if ch == open: - depth += 1 - if ch == close: - depth -= 1 - if depth == 0: # it shouldn't close before the end - return False - return True - - -def wrap( - text: str, - open: str, - close: str | None = None, - check_first: bool = True, - num: int = 1, -) -> str: - """Wrap the given text in the given open and close characters. - - Args: - text: The text to wrap. - open: The open character. - close: The close character. - check_first: Whether to check if the text is already wrapped. - num: The number of times to wrap the text. - - Returns: - The wrapped text. - """ - close = get_close_char(open, close) - - # If desired, check if the text is already wrapped in braces. - if check_first and is_wrapped(text=text, open=open, close=close): - return text - - # Wrap the text in braces. - return f"{open * num}{text}{close * num}" - - -def indent(text: str, indent_level: int = 2) -> str: - """Indent the given text by the given indent level. - - Args: - text: The text to indent. - indent_level: The indent level. - - Returns: - The indented text. - """ - lines = text.splitlines() - if len(lines) < 2: - return text - return os.linesep.join(f"{' ' * indent_level}{line}" for line in lines) + os.linesep - - -def to_snake_case(text: str) -> str: - """Convert a string to snake case. - - The words in the text are converted to lowercase and - separated by underscores. - - Args: - text: The string to convert. - - Returns: - The snake case string. - """ - s1 = re.sub(r"(.)([A-Z][a-z]+)", r"\1_\2", text) - return re.sub(r"([a-z0-9])([A-Z])", r"\1_\2", s1).lower().replace("-", "_") - - -def to_camel_case(text: str, treat_hyphens_as_underscores: bool = True) -> str: - """Convert a string to camel case. - - The first word in the text is converted to lowercase and - the rest of the words are converted to title case, removing underscores. - - Args: - text: The string to convert. - treat_hyphens_as_underscores: Whether to allow hyphens in the string. - - Returns: - The camel case string. - """ - if treat_hyphens_as_underscores: - text = text.replace("-", "_") - words = text.split("_") - # Capitalize the first letter of each word except the first one - if len(words) == 1: - return words[0] - return words[0] + "".join([w.capitalize() for w in words[1:]]) - - -def to_title_case(text: str, sep: str = "") -> str: - """Convert a string from snake case to title case. - - Args: - text: The string to convert. - sep: The separator to use to join the words. - - Returns: - The title case string. - """ - return sep.join(word.title() for word in text.split("_")) - - -def to_kebab_case(text: str) -> str: - """Convert a string to kebab case. - - The words in the text are converted to lowercase and - separated by hyphens. - - Args: - text: The string to convert. - - Returns: - The title case string. - """ - return to_snake_case(text).replace("_", "-") - - -def make_default_page_title(app_name: str, route: str) -> str: - """Make a default page title from a route. - - Args: - app_name: The name of the app owning the page. - route: The route to make the title from. - - Returns: - The default page title. - """ - route_parts = [ - part - for part in route.split("/") - if part and not (part.startswith("[") and part.endswith("]")) - ] - - title = constants.DefaultPage.TITLE.format( - app_name, route_parts[-1] if route_parts else constants.PageNames.INDEX_ROUTE - ) - return to_title_case(title) - - -def _escape_js_string(string: str) -> str: - """Escape the string for use as a JS string literal. - - Args: - string: The string to escape. - - Returns: - The escaped string. - """ - - # TODO: we may need to re-visit this logic after new Var API is implemented. - def escape_outside_segments(segment: str): - """Escape backticks in segments outside of `${}`. - - Args: - segment: The part of the string to escape. - - Returns: - The escaped or unescaped segment. - """ - if segment.startswith("${") and segment.endswith("}"): - # Return the `${}` segment unchanged - return segment - # Escape backticks in the segment - return segment.replace(r"\`", "`").replace("`", r"\`") - - # Split the string into parts, keeping the `${}` segments - parts = re.split(r"(\$\{.*?\})", string) - escaped_parts = [escape_outside_segments(part) for part in parts] - return "".join(escaped_parts) - - -def _wrap_js_string(string: str) -> str: - """Wrap string so it looks like {`string`}. - - Args: - string: The string to wrap. - - Returns: - The wrapped string. - """ - string = wrap(string, "`") - return wrap(string, "{") - - -def format_string(string: str) -> str: - """Format the given string as a JS string literal.. - - Args: - string: The string to format. - - Returns: - The formatted string. - """ - return _wrap_js_string(_escape_js_string(string)) - - -def format_var(var: Var) -> str: - """Format the given Var as a javascript value. - - Args: - var: The Var to format. - - Returns: - The formatted Var. - """ - return str(var) - - -def format_route(route: str) -> str: - """Format the given route. - - Args: - route: The route to format. - - Returns: - The formatted route. - """ - route = route.strip("/") - - # If the route is empty, return the index route. - if route == "": - return constants.PageNames.INDEX_ROUTE - - return route - - -def format_match( - cond: str | Var, - match_cases: list[tuple[list[Var], Var]], - default: Var, -) -> str: - """Format a match expression whose return type is a Var. - - Args: - cond: The condition. - match_cases: The list of cases to match. - default: The default case. - - Returns: - The formatted match expression - - """ - switch_code = f"(() => {{ switch (JSON.stringify({cond})) {{" - - for case in match_cases: - conditions, return_value = case - - case_conditions = " ".join([ - f"case JSON.stringify({condition!s}):" for condition in conditions - ]) - case_code = f"{case_conditions} return ({return_value!s}); break;" - switch_code += case_code - - switch_code += f"default: return ({default!s}); break;" - switch_code += "};})()" - - return switch_code - - -def format_prop( - prop: Var | EventChain | ComponentStyle | str, -) -> int | float | str: - """Format a prop. - - Args: - prop: The prop to format. - - Returns: - The formatted prop to display within a tag. - - Raises: - exceptions.InvalidStylePropError: If the style prop value is not a valid type. - TypeError: If the prop is not valid. - ValueError: If the prop is not a string. - """ - # import here to avoid circular import. - from reflex.event import EventChain - from reflex.utils import serializers - from reflex.vars import Var - - try: - # Handle var props. - if isinstance(prop, Var): - return str(prop) - - # Handle event props. - if isinstance(prop, EventChain): - return str(Var.create(prop)) - - # Handle other types. - if isinstance(prop, str): - if is_wrapped(prop, "{"): - return prop - return json_dumps(prop) - - # For dictionaries, convert any properties to strings. - if isinstance(prop, dict): - prop = serializers.serialize_dict(prop) # pyright: ignore [reportAttributeAccessIssue] - - else: - # Dump the prop as JSON. - prop = json_dumps(prop) - except exceptions.InvalidStylePropError: - raise - except TypeError as e: - msg = f"Could not format prop: {prop} of type {type(prop)}" - raise TypeError(msg) from e - - # Wrap the variable in braces. - if not isinstance(prop, str): - msg = f"Invalid prop: {prop}. Expected a string." - raise ValueError(msg) - return wrap(prop, "{", check_first=False) - - -def format_props(*single_props, **key_value_props) -> list[str]: - """Format the tag's props. - - Args: - single_props: Props that are not key-value pairs. - key_value_props: Props that are key-value pairs. - - Returns: - The formatted props list. - """ - # Format all the props. - from reflex.vars import LiteralStringVar, LiteralVar, Var - - return [ - (str(LiteralStringVar.create(name)) if "-" in name else name) - + ":" - + str(format_prop(prop if isinstance(prop, Var) else LiteralVar.create(prop))) - for name, prop in sorted(key_value_props.items()) - if prop is not None - ] + [(f"...{LiteralVar.create(prop)!s}") for prop in single_props] - - -def get_event_handler_parts(handler: EventHandler) -> tuple[str, str]: - """Get the state and function name of an event handler. - - Args: - handler: The event handler to get the parts of. - - Returns: - The state and function name. - """ - # Get the class that defines the event handler. - parts = handler.fn.__qualname__.split(".") - - # Get the state full name - state_full_name = handler.state_full_name - - # If there's no enclosing class, just return the function name. - if not state_full_name: - return ("", parts[-1]) - - # Get the function name - name = parts[-1] - - from reflex.state import State - - if state_full_name == FRONTEND_EVENT_STATE and name not in State.__dict__: - return ("", to_snake_case(handler.fn.__qualname__)) - - return (state_full_name, name) - - -def format_event_handler(handler: EventHandler) -> str: - """Format an event handler. - - Args: - handler: The event handler to format. - - Returns: - The formatted function. - """ - state, name = get_event_handler_parts(handler) - if state == "": - return name - return f"{state}.{name}" - - -def format_event(event_spec: EventSpec) -> str: - """Format an event. - - Args: - event_spec: The event to format. - - Returns: - The compiled event. - """ - args = ",".join([ - ":".join(( - name._js_expr, - ( - wrap( - json.dumps(val._js_expr).strip('"').replace("`", "\\`"), - "`", - ) - if val._var_is_string - else str(val) - ), - )) - for name, val in event_spec.args - ]) - event_args = [ - wrap(format_event_handler(event_spec.handler), '"'), - ] - event_args.append(wrap(args, "{")) - - if event_spec.client_handler_name: - event_args.append(wrap(event_spec.client_handler_name, '"')) - return f"ReflexEvent({', '.join(event_args)})" - - -if TYPE_CHECKING: - from reflex.vars import Var - - -def format_queue_events( - events: EventType[Any] | None = None, - args_spec: ArgsSpec | None = None, -) -> Var[EventChain]: - """Format a list of event handler / event spec as a javascript callback. - - The resulting code can be passed to interfaces that expect a callback - function and when triggered it will directly call queueEvents. - - It is intended to be executed in the rx.call_script context, where some - existing API needs a callback to trigger a backend event handler. - - Args: - events: The events to queue. - args_spec: The argument spec for the callback. - - Returns: - The compiled javascript callback to queue the given events on the frontend. - - Raises: - ValueError: If a lambda function is given which returns a Var. - """ - from reflex.event import ( - EventChain, - EventHandler, - EventSpec, - call_event_fn, - call_event_handler, - ) - from reflex.vars import FunctionVar, Var - - if not events: - return Var("(() => null)").to(FunctionVar, EventChain) - - # If no spec is provided, the function will take no arguments. - def _default_args_spec(): - return [] - - # Construct the arguments that the function accepts. - sig = inspect.signature(args_spec or _default_args_spec) - if sig.parameters: - arg_def = ",".join(f"_{p}" for p in sig.parameters) - arg_def = f"({arg_def})" - else: - arg_def = "()" - - payloads = [] - if not isinstance(events, list): - events = [events] - - # Process each event/spec/lambda (similar to Component._create_event_chain). - for spec in events: - specs: list[EventSpec] = [] - if isinstance(spec, (EventHandler, EventSpec)): - specs = [call_event_handler(spec, args_spec or _default_args_spec)] - elif isinstance(spec, type(lambda: None)): - specs = call_event_fn(spec, args_spec or _default_args_spec) # pyright: ignore [reportAssignmentType, reportArgumentType] - if isinstance(specs, Var): - msg = f"Invalid event spec: {specs}. Expected a list of EventSpecs." - raise ValueError(msg) - payloads.extend(format_event(s) for s in specs) - - # Return the final code snippet, expecting queueEvents, processEvent, and socket to be in scope. - # Typically this snippet will _only_ run from within an rx.call_script eval context. - return Var( - f"{arg_def} => {{queueEvents([{','.join(payloads)}], {constants.CompileVars.SOCKET}, false, navigate, params);" - f"processEvent({constants.CompileVars.SOCKET}, navigate, params);}}", - ).to(FunctionVar, EventChain) - - -def format_query_params(router_data: dict[str, Any]) -> dict[str, str]: - """Convert back query params name to python-friendly case. - - Args: - router_data: the router_data dict containing the query params - - Returns: - The reformatted query params - """ - params = router_data[constants.RouteVar.QUERY] - return {k.replace("-", "_"): v for k, v in params.items()} - - -def format_state_name(state_name: str) -> str: - """Format a state name, replacing dots with double underscore. - - This allows individual substates to be accessed independently as javascript vars - without using dot notation. - - Args: - state_name: The state name to format. - - Returns: - The formatted state name. - """ - return state_name.replace(".", "__") - - -def format_ref(ref: str) -> str: - """Format a ref. - - Args: - ref: The ref to format. - - Returns: - The formatted ref. - """ - # Replace all non-word characters with underscores. - clean_ref = re.sub(r"[^\w]+", "_", ref) - return f"ref_{clean_ref}" - - -def format_library_name(library_fullname: str | dict[str, Any]) -> str: - """Format the name of a library. - - Args: - library_fullname: The library reference, either as a string or a dictionary with a 'name' key. - - Returns: - The name without the @version if it was part of the name - - Raises: - KeyError: If library_fullname is a dictionary without a 'name' key. - TypeError: If library_fullname or its 'name' value is not a string. - """ - # If input is a dictionary, extract the 'name' key - if isinstance(library_fullname, dict): - if "name" not in library_fullname: - msg = "Dictionary input must contain a 'name' key" - raise KeyError(msg) - library_fullname = library_fullname["name"] - - # Process the library name as a string - if not isinstance(library_fullname, str): - msg = "Library name must be a string" - raise TypeError(msg) - - if library_fullname.startswith("https://"): - return library_fullname - - lib, at, version = library_fullname.rpartition("@") - if not lib: - lib = at + version - - return lib - - -def json_dumps(obj: Any, **kwargs) -> str: - """Takes an object and returns a jsonified string. - - Args: - obj: The object to be serialized. - kwargs: Additional keyword arguments to pass to json.dumps. - - Returns: - A string - """ - from reflex.utils import serializers - - kwargs.setdefault("ensure_ascii", False) - kwargs.setdefault("default", serializers.serialize) - - return json.dumps(obj, **kwargs) - - -def collect_form_dict_names(form_dict: dict[str, Any]) -> dict[str, Any]: - """Collapse keys with consecutive suffixes into a single list value. - - Separators dash and underscore are removed, unless this would overwrite an existing key. - - Args: - form_dict: The dict to collapse. - - Returns: - The collapsed dict. - """ - ending_digit_regex = re.compile(r"^(.*?)[_-]?(\d+)$") - collapsed = {} - for k in sorted(form_dict): - m = ending_digit_regex.match(k) - if m: - collapsed.setdefault(m.group(1), []).append(form_dict[k]) - # collapsing never overwrites valid data from the form_dict - collapsed.update(form_dict) - return collapsed - - -def format_array_ref(refs: str, idx: Var | None) -> str: - """Format a ref accessed by array. - - Args: - refs : The ref array to access. - idx : The index of the ref in the array. - - Returns: - The formatted ref. - """ - clean_ref = re.sub(r"[^\w]+", "_", refs) - if idx is not None: - return f"refs_{clean_ref}[{idx!s}]" - return f"refs_{clean_ref}" - - -def format_data_editor_column(col: str | dict): - """Format a given column into the proper format. - - Args: - col: The column. - - Returns: - The formatted column. - - Raises: - ValueError: invalid type provided for column. - """ - from reflex.vars import Var - - if isinstance(col, str): - return {"title": col, "id": col.lower(), "type": "str"} - - if isinstance(col, (dict,)): - if "id" not in col: - col["id"] = col["title"].lower() - if "type" not in col: - col["type"] = "str" - if "overlayIcon" not in col: - col["overlayIcon"] = None - return col - - if isinstance(col, Var): - return col - - msg = f"unexpected type ({(type(col).__name__)}: {col}) for column header in data_editor" - raise ValueError(msg) - - -def format_data_editor_cell(cell: Any): - """Format a given data into a renderable cell for data_editor. - - Args: - cell: The data to format. - - Returns: - The formatted cell. - """ - from reflex.vars.base import Var - - return { - "kind": Var(_js_expr="GridCellKind.Text"), - "data": cell, - } +from reflex_core.utils.format import * # noqa: F401, F403 diff --git a/reflex/utils/imports.py b/reflex/utils/imports.py index e4ec1935739..8965fae2249 100644 --- a/reflex/utils/imports.py +++ b/reflex/utils/imports.py @@ -1,150 +1,3 @@ -"""Import operations.""" +"""Re-export from reflex_core.""" -from __future__ import annotations - -import dataclasses -from collections import defaultdict -from collections.abc import Mapping, Sequence - - -def merge_parsed_imports( - *imports: ImmutableParsedImportDict, -) -> ParsedImportDict: - """Merge multiple parsed import dicts together. - - Args: - *imports: The list of import dicts to merge. - - Returns: - The merged import dicts. - """ - all_imports: defaultdict[str, list[ImportVar]] = defaultdict(list) - for import_dict in imports: - for lib, fields in import_dict.items(): - all_imports[lib].extend(fields) - return all_imports - - -def merge_imports( - *imports: ImportDict | ParsedImportDict | ParsedImportTuple, -) -> ParsedImportDict: - """Merge multiple import dicts together. - - Args: - *imports: The list of import dicts to merge. - - Returns: - The merged import dicts. - """ - all_imports: defaultdict[str, list[ImportVar]] = defaultdict(list) - for import_dict in imports: - for lib, fields in ( - import_dict if isinstance(import_dict, tuple) else import_dict.items() - ): - # If the lib is an absolute path, we need to prefix it with a $ - lib = ( - "$" + lib - if lib.startswith(("/utils/", "/components/", "/styles/", "/public/")) - else lib - ) - if isinstance(fields, (list, tuple, set)): - all_imports[lib].extend( - ImportVar(field) if isinstance(field, str) else field - for field in fields - ) - else: - all_imports[lib].append( - ImportVar(fields) if isinstance(fields, str) else fields - ) - return all_imports - - -def parse_imports( - imports: ImmutableImportDict | ImmutableParsedImportDict, -) -> ParsedImportDict: - """Parse the import dict into a standard format. - - Args: - imports: The import dict to parse. - - Returns: - The parsed import dict. - """ - return { - package: [maybe_tags] - if isinstance(maybe_tags, ImportVar) - else [ImportVar(tag=maybe_tags)] - if isinstance(maybe_tags, str) - else [ImportVar(tag=tag) if isinstance(tag, str) else tag for tag in maybe_tags] - for package, maybe_tags in imports.items() - } - - -def collapse_imports( - imports: ParsedImportDict | ParsedImportTuple, -) -> ParsedImportDict: - """Remove all duplicate ImportVar within an ImportDict. - - Args: - imports: The import dict to collapse. - - Returns: - The collapsed import dict. - """ - return { - lib: ( - list(set(import_vars)) - if isinstance(import_vars, list) - else list(import_vars) - ) - for lib, import_vars in ( - imports if isinstance(imports, tuple) else imports.items() - ) - } - - -@dataclasses.dataclass(frozen=True) -class ImportVar: - """An import var.""" - - # The name of the import tag. - tag: str | None - - # whether the import is default or named. - is_default: bool | None = False - - # The tag alias. - alias: str | None = None - - # Whether this import need to install the associated lib - install: bool | None = True - - # whether this import should be rendered or not - render: bool | None = True - - # The path of the package to import from. - package_path: str = "/" - - @property - def name(self) -> str: - """The name of the import. - - Returns: - The name(tag name with alias) of tag. - """ - if self.alias: - return ( - self.alias - if self.is_default and self.tag != "*" - else (self.tag + " as " + self.alias if self.tag else self.alias) - ) - return self.tag or "" - - -ImportTypes = str | ImportVar | list[str | ImportVar] | list[ImportVar] -ImmutableImportTypes = str | ImportVar | Sequence[str | ImportVar] -ImportDict = dict[str, ImportTypes] -ImmutableImportDict = Mapping[str, ImmutableImportTypes] -ParsedImportDict = dict[str, list[ImportVar]] -ImmutableParsedImportDict = Mapping[str, Sequence[ImportVar]] -ParsedImportTuple = tuple[tuple[str, tuple[ImportVar, ...]], ...] +from reflex_core.utils.imports import * # noqa: F401, F403 diff --git a/reflex/utils/pyi_generator.py b/reflex/utils/pyi_generator.py index 5db65f9ee52..c67d448628f 100644 --- a/reflex/utils/pyi_generator.py +++ b/reflex/utils/pyi_generator.py @@ -1,1652 +1,3 @@ -"""The pyi generator module.""" +"""Re-export from reflex_core.""" -from __future__ import annotations - -import ast -import contextlib -import importlib -import inspect -import json -import logging -import multiprocessing -import re -import subprocess -import sys -import typing -from collections.abc import Callable, Iterable, Mapping, Sequence -from concurrent.futures import ProcessPoolExecutor -from functools import cache -from hashlib import md5 -from inspect import getfullargspec -from itertools import chain -from pathlib import Path -from types import MappingProxyType, ModuleType, SimpleNamespace, UnionType -from typing import Any, get_args, get_origin - -from reflex.components.component import Component -from reflex.vars.base import Var - - -def _is_union(cls: Any) -> bool: - origin = getattr(cls, "__origin__", None) - if origin is typing.Union: - return True - return origin is None and isinstance(cls, UnionType) - - -def _is_optional(cls: Any) -> bool: - return ( - cls is None - or cls is type(None) - or (_is_union(cls) and type(None) in get_args(cls)) - ) - - -def _is_literal(cls: Any) -> bool: - return getattr(cls, "__origin__", None) is typing.Literal - - -def _safe_issubclass(cls: Any, cls_check: Any | tuple[Any, ...]) -> bool: - try: - return issubclass(cls, cls_check) - except TypeError: - return False - - -logger = logging.getLogger("pyi_generator") - -PWD = Path.cwd() - -PYI_HASHES = "pyi_hashes.json" - -EXCLUDED_FILES = [ - "app.py", - "component.py", - "bare.py", - "foreach.py", - "cond.py", - "match.py", - "multiselect.py", - "literals.py", -] - -# These props exist on the base component, but should not be exposed in create methods. -EXCLUDED_PROPS = [ - "alias", - "children", - "event_triggers", - "library", - "lib_dependencies", - "tag", - "is_default", - "special_props", - "_is_tag_in_global_scope", - "_invalid_children", - "_memoization_mode", - "_rename_props", - "_valid_children", - "_valid_parents", - "State", -] - -OVERWRITE_TYPES = { - "style": "Sequence[Mapping[str, Any]] | Mapping[str, Any] | Var[Mapping[str, Any]] | Breakpoints | None", -} - -DEFAULT_TYPING_IMPORTS = { - "Any", - "Callable", - "Dict", - # "List", - "Sequence", - "Mapping", - "Literal", - "Optional", - "Union", - "Annotated", -} - -# TODO: fix import ordering and unused imports with ruff later -DEFAULT_IMPORTS = { - "typing": sorted(DEFAULT_TYPING_IMPORTS), - "reflex_components_core.core.breakpoints": ["Breakpoints"], - "reflex.event": [ - "EventChain", - "EventHandler", - "EventSpec", - "EventType", - "KeyInputInfo", - "PointerEventInfo", - ], - "reflex.style": ["Style"], - "reflex.vars.base": ["Var"], -} - - -def _walk_files(path: str | Path): - """Walk all files in a path. - This can be replaced with Path.walk() in python3.12. - - Args: - path: The path to walk. - - Yields: - The next file in the path. - """ - for p in Path(path).iterdir(): - if p.is_dir(): - yield from _walk_files(p) - continue - yield p.resolve() - - -def _relative_to_pwd(path: Path) -> Path: - """Get the relative path of a path to the current working directory. - - Args: - path: The path to get the relative path for. - - Returns: - The relative path. - """ - if path.is_absolute(): - return path.relative_to(PWD) - return path - - -def _get_type_hint( - value: Any, type_hint_globals: dict, is_optional: bool = True -) -> str: - """Resolve the type hint for value. - - Args: - value: The type annotation as a str or actual types/aliases. - type_hint_globals: The globals to use to resolving a type hint str. - is_optional: Whether the type hint should be wrapped in Optional. - - Returns: - The resolved type hint as a str. - - Raises: - TypeError: If the value name is not visible in the type hint globals. - """ - res = "" - args = get_args(value) - - if value is type(None) or value is None: - return "None" - - if _is_union(value): - if type(None) in value.__args__: - res_args = [ - _get_type_hint(arg, type_hint_globals, _is_optional(arg)) - for arg in value.__args__ - if arg is not type(None) - ] - res_args.sort() - if len(res_args) == 1: - return f"{res_args[0]} | None" - res = f"{' | '.join(res_args)}" - return f"{res} | None" - - res_args = [ - _get_type_hint(arg, type_hint_globals, _is_optional(arg)) - for arg in value.__args__ - ] - res_args.sort() - return f"{' | '.join(res_args)}" - - if args: - inner_container_type_args = ( - sorted(repr(arg) for arg in args) - if _is_literal(value) - else [ - _get_type_hint(arg, type_hint_globals, is_optional=False) - for arg in args - if arg is not type(None) - ] - ) - - if ( - value.__module__ not in ["builtins", "__builtins__"] - and value.__name__ not in type_hint_globals - ): - msg = ( - f"{value.__module__ + '.' + value.__name__} is not a default import, " - "add it to DEFAULT_IMPORTS in pyi_generator.py" - ) - raise TypeError(msg) - - res = f"{value.__name__}[{', '.join(inner_container_type_args)}]" - - if value.__name__ == "Var": - args = list( - chain.from_iterable([ - get_args(arg) if _is_union(arg) else [arg] for arg in args - ]) - ) - - # For Var types, Union with the inner args so they can be passed directly. - types = [res] + [ - _get_type_hint(arg, type_hint_globals, is_optional=False) - for arg in args - if arg is not type(None) - ] - if len(types) > 1: - res = " | ".join(sorted(types)) - - elif isinstance(value, str): - ev = eval(value, type_hint_globals) - if _is_optional(ev): - return _get_type_hint(ev, type_hint_globals, is_optional=False) - - if _is_union(ev): - res = [ - _get_type_hint(arg, type_hint_globals, _is_optional(arg)) - for arg in ev.__args__ - ] - return f"{' | '.join(res)}" - res = ( - _get_type_hint(ev, type_hint_globals, is_optional=False) - if ev.__name__ == "Var" - else value - ) - elif isinstance(value, list): - res = [ - _get_type_hint(arg, type_hint_globals, _is_optional(arg)) for arg in value - ] - return f"[{', '.join(res)}]" - else: - res = value.__name__ - if is_optional and not res.startswith("Optional") and not res.endswith("| None"): - res = f"{res} | None" - return res - - -@cache -def _get_source(obj: Any) -> str: - """Get and cache the source for a Python object. - - Args: - obj: The object whose source should be retrieved. - - Returns: - The source code for the object. - """ - return inspect.getsource(obj) - - -@cache -def _get_class_prop_comments(clz: type[Component]) -> Mapping[str, tuple[str, ...]]: - """Parse and cache prop comments for a component class. - - Args: - clz: The class to extract prop comments from. - - Returns: - An immutable mapping of prop name to comment lines. - """ - props_comments: dict[str, tuple[str, ...]] = {} - comments = [] - for line in _get_source(clz).splitlines(): - reached_functions = re.search(r"def ", line) - if reached_functions: - # We've reached the functions, so stop. - break - - if line == "": - # We hit a blank line, so clear comments to avoid commented out prop appearing in next prop docs. - comments.clear() - continue - - # Get comments for prop - if line.strip().startswith("#"): - # Remove noqa from the comments. - line = line.partition(" # noqa")[0] - comments.append(line) - continue - - # Check if this line has a prop. - match = re.search(r"\w+:", line) - if match is None: - # This line doesn't have a var, so continue. - continue - - # Get the prop. - prop = match.group(0).strip(":") - if comments: - props_comments[prop] = tuple( - comment.strip().strip("#") for comment in comments - ) - comments.clear() - - return MappingProxyType(props_comments) - - -@cache -def _get_full_argspec(func: Callable) -> inspect.FullArgSpec: - """Get and cache the full argspec for a callable. - - Args: - func: The callable to inspect. - - Returns: - The full argument specification. - """ - return getfullargspec(func) - - -@cache -def _get_signature_return_annotation(func: Callable) -> Any: - """Get and cache a callable's return annotation. - - Args: - func: The callable to inspect. - - Returns: - The callable's return annotation. - """ - return inspect.signature(func).return_annotation - - -@cache -def _get_module_star_imports(module_name: str) -> Mapping[str, Any]: - """Resolve names imported by `from module import *`. - - Args: - module_name: The module to inspect. - - Returns: - An immutable mapping of imported names to values. - """ - module = importlib.import_module(module_name) - exported_names = getattr(module, "__all__", None) - if exported_names is not None: - return MappingProxyType({ - name: getattr(module, name) for name in exported_names - }) - return MappingProxyType({ - name: value for name, value in vars(module).items() if not name.startswith("_") - }) - - -@cache -def _get_module_selected_imports( - module_name: str, imported_names: tuple[str, ...] -) -> Mapping[str, Any]: - """Resolve a set of imported names from a module. - - Args: - module_name: The module to import from. - imported_names: The names to resolve. - - Returns: - An immutable mapping of imported names to values. - """ - module = importlib.import_module(module_name) - return MappingProxyType({name: getattr(module, name) for name in imported_names}) - - -@cache -def _get_class_annotation_globals(target_class: type) -> Mapping[str, Any]: - """Get globals needed to resolve class annotations. - - Args: - target_class: The class whose annotation globals should be resolved. - - Returns: - An immutable mapping of globals for the class MRO. - """ - available_vars: dict[str, Any] = {} - for module_name in {cls.__module__ for cls in target_class.__mro__}: - available_vars.update(sys.modules[module_name].__dict__) - return MappingProxyType(available_vars) - - -@cache -def _get_class_event_triggers(target_class: type) -> frozenset[str]: - """Get and cache event trigger names for a class. - - Args: - target_class: The class to inspect. - - Returns: - The event trigger names defined on the class. - """ - return frozenset(target_class.get_event_triggers()) - - -def _generate_imports( - typing_imports: Iterable[str], -) -> list[ast.ImportFrom | ast.Import]: - """Generate the import statements for the stub file. - - Args: - typing_imports: The typing imports to include. - - Returns: - The list of import statements. - """ - return [ - *[ - ast.ImportFrom(module=name, names=[ast.alias(name=val) for val in values]) # pyright: ignore [reportCallIssue] - for name, values in DEFAULT_IMPORTS.items() - ], - ast.Import([ast.alias("reflex")]), - ] - - -def _generate_docstrings(clzs: list[type[Component]], props: list[str]) -> str: - """Generate the docstrings for the create method. - - Args: - clzs: The classes to generate docstrings for. - props: The props to generate docstrings for. - - Returns: - The docstring for the create method. - """ - props_comments = {} - for clz in clzs: - for prop, comment_lines in _get_class_prop_comments(clz).items(): - if prop in props: - props_comments[prop] = list(comment_lines) - clz = clzs[0] - new_docstring = [] - for line in (clz.create.__doc__ or "").splitlines(): - if "**" in line: - indent = line.split("**")[0] - new_docstring.extend([ - f"{indent}{n}:{' '.join(c)}" for n, c in props_comments.items() - ]) - new_docstring.append(line) - return "\n".join(new_docstring) - - -def _extract_func_kwargs_as_ast_nodes( - func: Callable, - type_hint_globals: dict[str, Any], -) -> list[tuple[ast.arg, ast.Constant | None]]: - """Get the kwargs already defined on the function. - - Args: - func: The function to extract kwargs from. - type_hint_globals: The globals to use to resolving a type hint str. - - Returns: - The list of kwargs as ast arg nodes. - """ - spec = _get_full_argspec(func) - kwargs = [] - - for kwarg in spec.kwonlyargs: - arg = ast.arg(arg=kwarg) - if kwarg in spec.annotations: - arg.annotation = ast.Name( - id=_get_type_hint(spec.annotations[kwarg], type_hint_globals) - ) - default = None - if spec.kwonlydefaults is not None and kwarg in spec.kwonlydefaults: - default = ast.Constant(value=spec.kwonlydefaults[kwarg]) - kwargs.append((arg, default)) - return kwargs - - -def _extract_class_props_as_ast_nodes( - func: Callable, - clzs: list[type], - type_hint_globals: dict[str, Any], - extract_real_default: bool = False, -) -> list[tuple[ast.arg, ast.Constant | None]]: - """Get the props defined on the class and all parents. - - Args: - func: The function that kwargs will be added to. - clzs: The classes to extract props from. - type_hint_globals: The globals to use to resolving a type hint str. - extract_real_default: Whether to extract the real default value from the - pydantic field definition. - - Returns: - The list of props as ast arg nodes - """ - spec = _get_full_argspec(func) - func_kwonlyargs = set(spec.kwonlyargs) - all_props: set[str] = set() - kwargs = [] - for target_class in clzs: - event_triggers = _get_class_event_triggers(target_class) - # Import from the target class to ensure type hints are resolvable. - type_hint_globals.update(_get_module_star_imports(target_class.__module__)) - annotation_globals = { - **type_hint_globals, - **_get_class_annotation_globals(target_class), - } - for name, value in target_class.__annotations__.items(): - if ( - name in func_kwonlyargs - or name in EXCLUDED_PROPS - or name in all_props - or name in event_triggers - or (isinstance(value, str) and "ClassVar" in value) - ): - continue - all_props.add(name) - - default = None - if extract_real_default: - # TODO: This is not currently working since the default is not type compatible - # with the annotation in some cases. - with contextlib.suppress(AttributeError, KeyError): - # Try to get default from pydantic field definition. - default = target_class.__fields__[name].default - if isinstance(default, Var): - default = default._decode() - - kwargs.append(( - ast.arg( - arg=name, - annotation=ast.Name( - id=OVERWRITE_TYPES.get( - name, - _get_type_hint( - value, - annotation_globals, - ), - ) - ), - ), - ast.Constant(value=default), # pyright: ignore [reportArgumentType] - )) - return kwargs - - -def _get_visible_type_name( - typ: Any, type_hint_globals: Mapping[str, Any] | None -) -> str | None: - """Get a visible identifier for a type in the current module. - - Args: - typ: The type annotation to resolve. - type_hint_globals: The globals visible in the current module. - - Returns: - The visible identifier if one exists, otherwise None. - """ - if type_hint_globals is None: - return None - - type_name = getattr(typ, "__name__", None) - if ( - type_name is not None - and type_name in type_hint_globals - and type_hint_globals[type_name] is typ - ): - return type_name - - for name, value in type_hint_globals.items(): - if name.isidentifier() and value is typ: - return name - - return None - - -def type_to_ast( - typ: Any, - cls: type, - type_hint_globals: Mapping[str, Any] | None = None, -) -> ast.expr: - """Converts any type annotation into its AST representation. - Handles nested generic types, unions, etc. - - Args: - typ: The type annotation to convert. - cls: The class where the type annotation is used. - type_hint_globals: The globals visible where the annotation is used. - - Returns: - The AST representation of the type annotation. - """ - if typ is type(None) or typ is None: - return ast.Name(id="None") - - origin = get_origin(typ) - if origin is typing.Literal: - return ast.Subscript( - value=ast.Name(id="Literal"), - slice=ast.Tuple( - elts=[ast.Constant(value=val) for val in get_args(typ)], ctx=ast.Load() - ), - ctx=ast.Load(), - ) - if origin is UnionType: - origin = typing.Union - - # Handle plain types (int, str, custom classes, etc.) - if origin is None: - if hasattr(typ, "__name__"): - if typ.__module__.startswith("reflex."): - typ_parts = typ.__module__.split(".") - cls_parts = cls.__module__.split(".") - - zipped = list(zip(typ_parts, cls_parts, strict=False)) - - if all(a == b for a, b in zipped) and len(typ_parts) == len(cls_parts): - return ast.Name(id=typ.__name__) - if visible_name := _get_visible_type_name(typ, type_hint_globals): - return ast.Name(id=visible_name) - if ( - typ.__module__ in DEFAULT_IMPORTS - and typ.__name__ in DEFAULT_IMPORTS[typ.__module__] - ): - return ast.Name(id=typ.__name__) - return ast.Name(id=typ.__module__ + "." + typ.__name__) - return ast.Name(id=typ.__name__) - if hasattr(typ, "_name"): - return ast.Name(id=typ._name) - return ast.Name(id=str(typ)) - - # Get the base type name (List, Dict, Optional, etc.) - base_name = getattr(origin, "_name", origin.__name__) - - # Get type arguments - args = get_args(typ) - - # Handle empty type arguments - if not args: - return ast.Name(id=base_name) - - # Convert all type arguments recursively - arg_nodes = [type_to_ast(arg, cls, type_hint_globals) for arg in args] - - # Special case for single-argument types (like list[T] or Optional[T]) - if len(arg_nodes) == 1: - slice_value = arg_nodes[0] - else: - slice_value = ast.Tuple(elts=arg_nodes, ctx=ast.Load()) - - return ast.Subscript( - value=ast.Name(id=base_name), - slice=slice_value, - ctx=ast.Load(), - ) - - -@cache -def _get_parent_imports(func: Callable) -> Mapping[str, tuple[str, ...]]: - """Get parent imports needed to resolve forwarded type hints. - - Args: - func: The callable whose annotations are being analyzed. - - Returns: - An immutable mapping of module names to imported symbol names. - """ - imports_: dict[str, set[str]] = {"reflex.vars": {"Var"}} - module_dir = set(dir(importlib.import_module(func.__module__))) - for type_hint in inspect.get_annotations(func).values(): - try: - match = re.match(r"\w+\[([\w\d]+)\]", type_hint) - except TypeError: - continue - if match: - type_hint = match.group(1) - if type_hint in module_dir: - imports_.setdefault(func.__module__, set()).add(type_hint) - return MappingProxyType({ - module_name: tuple(sorted(imported_names)) - for module_name, imported_names in imports_.items() - }) - - -def _generate_component_create_functiondef( - clz: type[Component], - type_hint_globals: dict[str, Any], - lineno: int, - decorator_list: Sequence[ast.expr] = (ast.Name(id="classmethod"),), -) -> ast.FunctionDef: - """Generate the create function definition for a Component. - - Args: - clz: The Component class to generate the create functiondef for. - type_hint_globals: The globals to use to resolving a type hint str. - lineno: The line number to use for the ast nodes. - decorator_list: The list of decorators to apply to the create functiondef. - - Returns: - The create functiondef node for the ast. - - Raises: - TypeError: If clz is not a subclass of Component. - """ - if not issubclass(clz, Component): - msg = f"clz must be a subclass of Component, not {clz!r}" - raise TypeError(msg) - - # add the imports needed by get_type_hint later - type_hint_globals.update({ - name: getattr(typing, name) for name in DEFAULT_TYPING_IMPORTS - }) - - if clz.__module__ != clz.create.__module__: - imports_ = _get_parent_imports(clz.create) - for name, values in imports_.items(): - type_hint_globals.update(_get_module_selected_imports(name, values)) - - kwargs = _extract_func_kwargs_as_ast_nodes(clz.create, type_hint_globals) - - # kwargs associated with props defined in the class and its parents - all_classes = [c for c in clz.__mro__ if issubclass(c, Component)] - prop_kwargs = _extract_class_props_as_ast_nodes( - clz.create, all_classes, type_hint_globals - ) - all_props = [arg[0].arg for arg in prop_kwargs] - kwargs.extend(prop_kwargs) - - def figure_out_return_type(annotation: Any): - if isinstance(annotation, type) and issubclass(annotation, inspect._empty): - return ast.Name(id="EventType[Any]") - - if not isinstance(annotation, str) and get_origin(annotation) is tuple: - arguments = get_args(annotation) - - arguments_without_var = [ - get_args(argument)[0] if get_origin(argument) == Var else argument - for argument in arguments - ] - - # Convert each argument type to its AST representation - type_args = [ - type_to_ast(arg, cls=clz, type_hint_globals=type_hint_globals) - for arg in arguments_without_var - ] - - # Get all prefixes of the type arguments - all_count_args_type = [ - ast.Name( - f"EventType[{', '.join([ast.unparse(arg) for arg in type_args[:i]])}]" - ) - if i > 0 - else ast.Name("EventType[()]") - for i in range(len(type_args) + 1) - ] - - # Create EventType using the joined string - return ast.Name(id=f"{' | '.join(map(ast.unparse, all_count_args_type))}") - - if isinstance(annotation, str) and annotation.lower().startswith("tuple["): - inside_of_tuple = ( - annotation - .removeprefix("tuple[") - .removeprefix("Tuple[") - .removesuffix("]") - ) - - if inside_of_tuple == "()": - return ast.Name(id="EventType[()]") - - arguments = [""] - - bracket_count = 0 - - for char in inside_of_tuple: - if char == "[": - bracket_count += 1 - elif char == "]": - bracket_count -= 1 - - if char == "," and bracket_count == 0: - arguments.append("") - else: - arguments[-1] += char - - arguments = [argument.strip() for argument in arguments] - - arguments_without_var = [ - argument.removeprefix("Var[").removesuffix("]") - if argument.startswith("Var[") - else argument - for argument in arguments - ] - - all_count_args_type = [ - ast.Name(f"EventType[{', '.join(arguments_without_var[:i])}]") - if i > 0 - else ast.Name("EventType[()]") - for i in range(len(arguments) + 1) - ] - - return ast.Name(id=f"{' | '.join(map(ast.unparse, all_count_args_type))}") - return ast.Name(id="EventType[Any]") - - event_triggers = clz.get_event_triggers() - - # event handler kwargs - kwargs.extend( - ( - ast.arg( - arg=trigger, - annotation=ast.Subscript( - ast.Name("Optional"), - ast.Name( - id=ast.unparse( - figure_out_return_type( - _get_signature_return_annotation(event_specs) - ) - if not isinstance( - event_specs := event_triggers[trigger], Sequence - ) - else ast.Subscript( - ast.Name("Union"), - ast.Tuple([ - figure_out_return_type( - _get_signature_return_annotation(event_spec) - ) - for event_spec in event_specs - ]), - ) - ) - ), - ), - ), - ast.Constant(value=None), - ) - for trigger in sorted(event_triggers) - ) - - logger.debug(f"Generated {clz.__name__}.create method with {len(kwargs)} kwargs") - create_args = ast.arguments( - args=[ast.arg(arg="cls")], - posonlyargs=[], - vararg=ast.arg(arg="children"), - kwonlyargs=[arg[0] for arg in kwargs], - kw_defaults=[arg[1] for arg in kwargs], - kwarg=ast.arg(arg="props"), - defaults=[], - ) - - return ast.FunctionDef( # pyright: ignore [reportCallIssue] - name="create", - args=create_args, - body=[ - ast.Expr( - value=ast.Constant( - value=_generate_docstrings( - all_classes, [*all_props, *event_triggers] - ) - ), - ), - ast.Expr( - value=ast.Constant(value=Ellipsis), - ), - ], - decorator_list=list(decorator_list), - lineno=lineno, - returns=ast.Constant(value=clz.__name__), - ) - - -def _generate_staticmethod_call_functiondef( - node: ast.ClassDef, - clz: type[Component] | type[SimpleNamespace], - type_hint_globals: dict[str, Any], -) -> ast.FunctionDef | None: - fullspec = _get_full_argspec(clz.__call__) - - call_args = ast.arguments( - args=[ - ast.arg( - name, - annotation=ast.Name( - id=_get_type_hint( - anno := fullspec.annotations[name], - type_hint_globals, - is_optional=_is_optional(anno), - ) - ), - ) - for name in fullspec.args - ], - posonlyargs=[], - kwonlyargs=[], - kw_defaults=[], - kwarg=ast.arg(arg="props"), - defaults=( - [ast.Constant(value=default) for default in fullspec.defaults] - if fullspec.defaults - else [] - ), - ) - return ast.FunctionDef( # pyright: ignore [reportCallIssue] - name="__call__", - args=call_args, - body=[ - ast.Expr(value=ast.Constant(value=clz.__call__.__doc__)), - ast.Expr( - value=ast.Constant(...), - ), - ], - decorator_list=[ast.Name(id="staticmethod")], - lineno=node.lineno, - returns=ast.Constant( - value=_get_type_hint( - typing.get_type_hints(clz.__call__).get("return", None), - type_hint_globals, - is_optional=False, - ) - ), - ) - - -def _generate_namespace_call_functiondef( - node: ast.ClassDef, - clz_name: str, - classes: dict[str, type[Component] | type[SimpleNamespace]], - type_hint_globals: dict[str, Any], -) -> ast.FunctionDef | None: - """Generate the __call__ function definition for a SimpleNamespace. - - Args: - node: The existing __call__ classdef parent node from the ast - clz_name: The name of the SimpleNamespace class to generate the __call__ functiondef for. - classes: Map name to actual class definition. - type_hint_globals: The globals to use to resolving a type hint str. - - Returns: - The create functiondef node for the ast. - """ - # add the imports needed by get_type_hint later - type_hint_globals.update({ - name: getattr(typing, name) for name in DEFAULT_TYPING_IMPORTS - }) - - clz = classes[clz_name] - - if not hasattr(clz.__call__, "__self__"): - return _generate_staticmethod_call_functiondef(node, clz, type_hint_globals) - - # Determine which class is wrapped by the namespace __call__ method - component_clz = clz.__call__.__self__ - - if clz.__call__.__func__.__name__ != "create": # pyright: ignore [reportFunctionMemberAccess] - return None - - if not issubclass(component_clz, Component): - return None - - definition = _generate_component_create_functiondef( - clz=component_clz, - type_hint_globals=type_hint_globals, - lineno=node.lineno, - decorator_list=[], - ) - definition.name = "__call__" - - # Turn the definition into a staticmethod - del definition.args.args[0] # remove `cls` arg - definition.decorator_list = [ast.Name(id="staticmethod")] - - return definition - - -class StubGenerator(ast.NodeTransformer): - """A node transformer that will generate the stubs for a given module.""" - - def __init__( - self, - module: ModuleType, - classes: dict[str, type[Component | SimpleNamespace]], - ): - """Initialize the stub generator. - - Args: - module: The actual module object module to generate stubs for. - classes: The actual Component class objects to generate stubs for. - """ - super().__init__() - # Dict mapping class name to actual class object. - self.classes = classes - # Track the last class node that was visited. - self.current_class = None - # These imports will be included in the AST of stub files. - self.typing_imports = DEFAULT_TYPING_IMPORTS.copy() - # Whether those typing imports have been inserted yet. - self.inserted_imports = False - # This dict is used when evaluating type hints. - self.type_hint_globals = module.__dict__.copy() - - @staticmethod - def _remove_docstring( - node: ast.Module | ast.ClassDef | ast.FunctionDef, - ) -> ast.Module | ast.ClassDef | ast.FunctionDef: - """Removes any docstring in place. - - Args: - node: The node to remove the docstring from. - - Returns: - The modified node. - """ - if ( - node.body - and isinstance(node.body[0], ast.Expr) - and isinstance(node.body[0].value, ast.Constant) - ): - node.body.pop(0) - return node - - def _current_class_is_component(self) -> type[Component] | None: - """Check if the current class is a Component. - - Returns: - Whether the current class is a Component. - """ - if ( - self.current_class is not None - and self.current_class in self.classes - and issubclass((clz := self.classes[self.current_class]), Component) - ): - return clz - return None - - def visit_Module(self, node: ast.Module) -> ast.Module: - """Visit a Module node and remove docstring from body. - - Args: - node: The Module node to visit. - - Returns: - The modified Module node. - """ - self.generic_visit(node) - return self._remove_docstring(node) # pyright: ignore [reportReturnType] - - def visit_Import( - self, node: ast.Import | ast.ImportFrom - ) -> ast.Import | ast.ImportFrom | list[ast.Import | ast.ImportFrom]: - """Collect import statements from the module. - - If this is the first import statement, insert the typing imports before it. - - Args: - node: The import node to visit. - - Returns: - The modified import node(s). - """ - if not self.inserted_imports: - self.inserted_imports = True - default_imports = _generate_imports(self.typing_imports) - return [*default_imports, node] - return node - - def visit_ImportFrom( - self, node: ast.ImportFrom - ) -> ast.Import | ast.ImportFrom | list[ast.Import | ast.ImportFrom] | None: - """Visit an ImportFrom node. - - Remove any `from __future__ import *` statements, and hand off to visit_Import. - - Args: - node: The ImportFrom node to visit. - - Returns: - The modified ImportFrom node. - """ - if node.module == "__future__": - return None # ignore __future__ imports: https://docs.astral.sh/ruff/rules/future-annotations-in-stub/ - return self.visit_Import(node) - - def visit_ClassDef(self, node: ast.ClassDef) -> ast.ClassDef: - """Visit a ClassDef node. - - Remove all assignments in the class body, and add a create functiondef - if one does not exist. - - Args: - node: The ClassDef node to visit. - - Returns: - The modified ClassDef node. - """ - self.current_class = node.name - self._remove_docstring(node) - - # Define `__call__` as a real function so the docstring appears in the stub. - call_definition = None - for child in node.body[:]: - found_call = False - if ( - isinstance(child, ast.AnnAssign) - and isinstance(child.target, ast.Name) - and child.target.id.startswith("_") - ): - node.body.remove(child) - if isinstance(child, ast.Assign): - for target in child.targets[:]: - if isinstance(target, ast.Name) and target.id == "__call__": - child.targets.remove(target) - found_call = True - if not found_call: - continue - if not child.targets[:]: - node.body.remove(child) - call_definition = _generate_namespace_call_functiondef( - node, - self.current_class, - self.classes, - type_hint_globals=self.type_hint_globals, - ) - break - - self.generic_visit(node) # Visit child nodes. - - if ( - not any( - isinstance(child, ast.FunctionDef) and child.name == "create" - for child in node.body - ) - and (clz := self._current_class_is_component()) is not None - ): - # Add a new .create FunctionDef since one does not exist. - node.body.append( - _generate_component_create_functiondef( - clz=clz, - type_hint_globals=self.type_hint_globals, - lineno=node.lineno, - ) - ) - if call_definition is not None: - node.body.append(call_definition) - if not node.body: - # We should never return an empty body. - node.body.append(ast.Expr(value=ast.Constant(value=Ellipsis))) - self.current_class = None - return node - - def visit_FunctionDef(self, node: ast.FunctionDef) -> Any: - """Visit a FunctionDef node. - - Special handling for `.create` functions to add type hints for all props - defined on the component class. - - Remove all private functions and blank out the function body of the - remaining public functions. - - Args: - node: The FunctionDef node to visit. - - Returns: - The modified FunctionDef node (or None). - """ - if ( - node.name == "create" - and self.current_class in self.classes - and issubclass((clz := self.classes[self.current_class]), Component) - ): - node = _generate_component_create_functiondef( - clz=clz, - type_hint_globals=self.type_hint_globals, - lineno=node.lineno, - decorator_list=node.decorator_list, - ) - else: - if node.name.startswith("_") and node.name != "__call__": - return None # remove private methods - - if node.body[-1] != ast.Expr(value=ast.Constant(value=Ellipsis)): - # Blank out the function body for public functions. - node.body = [ast.Expr(value=ast.Constant(value=Ellipsis))] - return node - - def visit_Assign(self, node: ast.Assign) -> ast.Assign | None: - """Remove non-annotated assignment statements. - - Args: - node: The Assign node to visit. - - Returns: - The modified Assign node (or None). - """ - # Special case for assignments to `typing.Any` as fallback. - if ( - node.value is not None - and isinstance(node.value, ast.Name) - and node.value.id == "Any" - ): - return node - - if self._current_class_is_component(): - # Remove annotated assignments in Component classes (props) - return None - - # remove dunder method assignments for lazy_loader.attach - for target in node.targets: - if isinstance(target, ast.Tuple): - for name in target.elts: - if isinstance(name, ast.Name) and name.id.startswith("_"): - return None - - return node - - def visit_AnnAssign(self, node: ast.AnnAssign) -> ast.AnnAssign | None: - """Visit an AnnAssign node (Annotated assignment). - - Remove private target and remove the assignment value in the stub. - - Args: - node: The AnnAssign node to visit. - - Returns: - The modified AnnAssign node (or None). - """ - # skip ClassVars - if ( - isinstance(node.annotation, ast.Subscript) - and isinstance(node.annotation.value, ast.Name) - and node.annotation.value.id == "ClassVar" - ): - return node - if isinstance(node.target, ast.Name) and node.target.id.startswith("_"): - return None - if self._current_class_is_component(): - # Remove annotated assignments in Component classes (props) - return None - # Blank out assignments in type stubs. - node.value = None - return node - - -class InitStubGenerator(StubGenerator): - """A node transformer that will generate the stubs for a given init file.""" - - def visit_Import( - self, node: ast.Import | ast.ImportFrom - ) -> ast.Import | ast.ImportFrom | list[ast.Import | ast.ImportFrom]: - """Collect import statements from the init module. - - Args: - node: The import node to visit. - - Returns: - The modified import node(s). - """ - return [node] - - -def _path_to_module_name(path: Path) -> str: - """Convert a file path to a dotted module name. - - Args: - path: The file path to convert. - - Returns: - The dotted module name. - """ - return _relative_to_pwd(path).with_suffix("").as_posix().replace("/", ".") - - -def _write_pyi_file(module_path: Path, source: str) -> str: - relpath = str(_relative_to_pwd(module_path)).replace("\\", "/") - pyi_content = ( - "\n".join([ - f'"""Stub file for {relpath}"""', - "# ------------------- DO NOT EDIT ----------------------", - "# This file was generated by `reflex/utils/pyi_generator.py`!", - "# ------------------------------------------------------", - "", - ]) - + source - ) - - pyi_path = module_path.with_suffix(".pyi") - pyi_path.write_text(pyi_content) - logger.info(f"Wrote {relpath}") - return md5(pyi_content.encode()).hexdigest() - - -# Mapping from component subpackage name to its target Python package. -_COMPONENT_SUBPACKAGE_TARGETS: dict[str, str] = { - # reflex-components (base package) - "base": "reflex_components_core.base", - "core": "reflex_components_core.core", - "datadisplay": "reflex_components_core.datadisplay", - "el": "reflex_components_core.el", - "gridjs": "reflex_components_gridjs", - "lucide": "reflex_components_lucide", - "moment": "reflex_components_moment", - # Deep overrides (datadisplay split) - "datadisplay.code": "reflex_components_code.code", - "datadisplay.shiki_code_block": "reflex_components_code.shiki_code_block", - "datadisplay.dataeditor": "reflex_components_dataeditor.dataeditor", - # Standalone packages - "markdown": "reflex_components_markdown", - "plotly": "reflex_components_plotly", - "radix": "reflex_components_radix", - "react_player": "reflex_components_react_player", - "react_router": "reflex_components_react_router", - "recharts": "reflex_components_recharts", - "sonner": "reflex_components_sonner", -} - - -def _rewrite_component_import(module: str) -> str: - """Rewrite a lazy-loader module path to the correct absolute package import. - - Args: - module: The module path from ``_SUBMOD_ATTRS`` (e.g. ``"components.radix.themes.base"``). - - Returns: - An absolute import path (``"reflex_components_radix.themes.base"``) for moved - components, or a relative path (``".components.component"``) for everything else. - """ - if module == "components": - # "components": ["el", "radix", ...] — these are re-exported submodules. - # Can't map to a single package, but the pyi generator handles each attr individually. - return "reflex_components_core" - if module.startswith("components."): - rest = module[len("components.") :] - # Try progressively deeper matches (e.g. "datadisplay.code" before "datadisplay"). - parts = rest.split(".") - for depth in range(min(len(parts), 2), 0, -1): - key = ".".join(parts[:depth]) - target = _COMPONENT_SUBPACKAGE_TARGETS.get(key) - if target is not None: - remainder = ".".join(parts[depth:]) - return f"{target}.{remainder}" if remainder else target - return f".{module}" - - -def _get_init_lazy_imports(mod: tuple | ModuleType, new_tree: ast.AST): - # retrieve the _SUBMODULES and _SUBMOD_ATTRS from an init file if present. - sub_mods: set[str] | None = getattr(mod, "_SUBMODULES", None) - sub_mod_attrs: dict[str, list[str | tuple[str, str]]] | None = getattr( - mod, "_SUBMOD_ATTRS", None - ) - extra_mappings: dict[str, str] | None = getattr(mod, "_EXTRA_MAPPINGS", None) - - if not sub_mods and not sub_mod_attrs and not extra_mappings: - return None - sub_mods_imports = [] - sub_mod_attrs_imports = [] - extra_mappings_imports = [] - - if sub_mods: - sub_mods_imports = [f"from . import {mod}" for mod in sorted(sub_mods)] - sub_mods_imports.append("") - - if sub_mod_attrs: - flattened_sub_mod_attrs = { - imported: module - for module, attrs in sub_mod_attrs.items() - for imported in attrs - } - # construct the import statement and handle special cases for aliases - for imported, module in flattened_sub_mod_attrs.items(): - # For "components": ["el", "radix", ...], resolve each attr to its package. - if ( - module == "components" - and isinstance(imported, str) - and imported in _COMPONENT_SUBPACKAGE_TARGETS - ): - target = _COMPONENT_SUBPACKAGE_TARGETS[imported] - sub_mod_attrs_imports.append(f"import {target} as {imported}") - continue - - rewritten = _rewrite_component_import(module) - if isinstance(imported, tuple): - suffix = ( - (imported[0] + " as " + imported[1]) - if imported[0] != imported[1] - else imported[0] - ) - else: - suffix = imported - sub_mod_attrs_imports.append(f"from {rewritten} import {suffix}") - sub_mod_attrs_imports.append("") - - if extra_mappings: - for alias, import_path in extra_mappings.items(): - module_name, import_name = import_path.rsplit(".", 1) - extra_mappings_imports.append( - f"from {module_name} import {import_name} as {alias}" - ) - - text = ( - "\n" - + "\n".join([ - *sub_mods_imports, - *sub_mod_attrs_imports, - *extra_mappings_imports, - ]) - + "\n" - ) - text += ast.unparse(new_tree) + "\n\n" - text += f"__all__ = {getattr(mod, '__all__', [])!r}\n" - return text - - -def _scan_file(module_path: Path) -> tuple[str, str] | None: - """Process a single Python file and generate its .pyi stub. - - Args: - module_path: Path to the Python source file. - - Returns: - Tuple of (pyi_path, content_hash) or None if no stub needed. - """ - module_import = _path_to_module_name(module_path) - module = importlib.import_module(module_import) - logger.debug(f"Read {module_path}") - class_names = { - name: obj - for name, obj in vars(module).items() - if isinstance(obj, type) - and (_safe_issubclass(obj, Component) or _safe_issubclass(obj, SimpleNamespace)) - and obj != Component - and inspect.getmodule(obj) == module - } - is_init_file = _relative_to_pwd(module_path).name == "__init__.py" - if not class_names and not is_init_file: - return None - - if is_init_file: - new_tree = InitStubGenerator(module, class_names).visit( - ast.parse(_get_source(module)) - ) - init_imports = _get_init_lazy_imports(module, new_tree) - if not init_imports: - return None - content_hash = _write_pyi_file(module_path, init_imports) - else: - new_tree = StubGenerator(module, class_names).visit( - ast.parse(_get_source(module)) - ) - content_hash = _write_pyi_file(module_path, ast.unparse(new_tree)) - return str(module_path.with_suffix(".pyi").resolve()), content_hash - - -class PyiGenerator: - """A .pyi file generator that will scan all defined Component in Reflex and - generate the appropriate stub. - """ - - modules: list = [] - root: str = "" - current_module: Any = {} - written_files: list[tuple[str, str]] = [] - - def _scan_files(self, files: list[Path]): - max_workers = min(multiprocessing.cpu_count() or 1, len(files), 8) - use_parallel = ( - max_workers > 1 and "fork" in multiprocessing.get_all_start_methods() - ) - - if not use_parallel: - # Serial fallback: _scan_file handles its own imports. - for file in files: - result = _scan_file(file) - if result is not None: - self.written_files.append(result) - return - - # Pre-import all modules sequentially to populate sys.modules - # so forked workers inherit the cache and skip redundant imports. - importable_files: list[Path] = [] - for file in files: - module_import = _path_to_module_name(file) - try: - importlib.import_module(module_import) - importable_files.append(file) - except Exception: - logger.exception(f"Failed to import {module_import}") - - # Generate stubs in parallel using forked worker processes. - ctx = multiprocessing.get_context("fork") - with ProcessPoolExecutor(max_workers=max_workers, mp_context=ctx) as executor: - self.written_files.extend( - r for r in executor.map(_scan_file, importable_files) if r is not None - ) - - def scan_all( - self, - targets: list, - changed_files: list[Path] | None = None, - use_json: bool = False, - ): - """Scan all targets for class inheriting Component and generate the .pyi files. - - Args: - targets: the list of file/folders to scan. - changed_files (optional): the list of changed files since the last run. - use_json: whether to use json to store the hashes. - """ - file_targets = [] - for target in targets: - target_path = Path(target) - if ( - target_path.is_file() - and target_path.suffix == ".py" - and target_path.name not in EXCLUDED_FILES - ): - file_targets.append(target_path) - continue - if not target_path.is_dir(): - continue - for file_path in _walk_files(target_path): - relative = _relative_to_pwd(file_path) - if relative.name in EXCLUDED_FILES or file_path.suffix != ".py": - continue - if ( - changed_files is not None - and _relative_to_pwd(file_path) not in changed_files - ): - continue - file_targets.append(file_path) - - # check if pyi changed but not the source - if changed_files is not None: - for changed_file in changed_files: - if changed_file.suffix != ".pyi": - continue - py_file_path = changed_file.with_suffix(".py") - if not py_file_path.exists() and changed_file.exists(): - changed_file.unlink() - if py_file_path in file_targets: - continue - subprocess.run(["git", "checkout", changed_file]) - - self._scan_files(file_targets) - - file_paths, hashes = ( - [f[0] for f in self.written_files], - [f[1] for f in self.written_files], - ) - - # Fix generated pyi files with ruff. - if file_paths: - subprocess.run(["ruff", "check", "--fix", *file_paths]) - subprocess.run(["ruff", "format", *file_paths]) - - if use_json: - if file_paths and changed_files is None: - file_paths = list(map(Path, file_paths)) - top_dir = file_paths[0].parent - for file_path in file_paths: - file_parent = file_path.parent - while len(file_parent.parts) > len(top_dir.parts): - file_parent = file_parent.parent - while len(top_dir.parts) > len(file_parent.parts): - top_dir = top_dir.parent - while not file_parent.samefile(top_dir): - file_parent = file_parent.parent - top_dir = top_dir.parent - - while ( - not top_dir.samefile(top_dir.parent) - and not (top_dir / PYI_HASHES).exists() - ): - top_dir = top_dir.parent - - pyi_hashes_file = top_dir / PYI_HASHES - - if pyi_hashes_file.exists(): - pyi_hashes_file.write_text( - json.dumps( - dict( - zip( - [ - f.relative_to(pyi_hashes_file.parent).as_posix() - for f in file_paths - ], - hashes, - strict=True, - ) - ), - indent=2, - sort_keys=True, - ) - + "\n", - ) - elif file_paths: - file_paths = list(map(Path, file_paths)) - pyi_hashes_parent = file_paths[0].parent - while ( - not pyi_hashes_parent.samefile(pyi_hashes_parent.parent) - and not (pyi_hashes_parent / PYI_HASHES).exists() - ): - pyi_hashes_parent = pyi_hashes_parent.parent - - pyi_hashes_file = pyi_hashes_parent / PYI_HASHES - if pyi_hashes_file.exists(): - pyi_hashes = json.loads(pyi_hashes_file.read_text()) - for file_path, hashed_content in zip( - file_paths, hashes, strict=False - ): - formatted_path = file_path.relative_to( - pyi_hashes_parent - ).as_posix() - pyi_hashes[formatted_path] = hashed_content - - pyi_hashes_file.write_text( - json.dumps(pyi_hashes, indent=2, sort_keys=True) + "\n" - ) - - -if __name__ == "__main__": - import argparse - - parser = argparse.ArgumentParser(description="Generate .pyi stub files") - parser.add_argument( - "targets", - nargs="*", - default=["reflex/components", "reflex/experimental", "reflex/__init__.py"], - help="Target directories/files to process", - ) - args = parser.parse_args() - - logging.basicConfig(level=logging.INFO) - logging.getLogger("blib2to3.pgen2.driver").setLevel(logging.INFO) - - gen = PyiGenerator() - gen.scan_all(args.targets, None, use_json=True) +from reflex_core.utils.pyi_generator import * # noqa: F401, F403 diff --git a/reflex/utils/serializers.py b/reflex/utils/serializers.py index 25aca8a5dab..e4c0cfd5a6c 100644 --- a/reflex/utils/serializers.py +++ b/reflex/utils/serializers.py @@ -1,498 +1,3 @@ -"""Serializers used to convert Var types to JSON strings.""" +"""Re-export from reflex_core.""" -from __future__ import annotations - -import contextlib -import dataclasses -import decimal -import functools -import inspect -import json -import warnings -from collections.abc import Callable, Mapping, Sequence -from datetime import date, datetime, time, timedelta -from enum import Enum -from importlib.util import find_spec -from pathlib import Path -from typing import Any, Literal, TypeVar, get_type_hints, overload -from uuid import UUID - -from reflex.constants.colors import Color -from reflex.utils import console, types - -# Mapping from type to a serializer. -# The serializer should convert the type to a JSON object. -SerializedType = str | bool | int | float | list | dict | None - - -Serializer = Callable[[Any], SerializedType] - - -SERIALIZERS: dict[type, Serializer] = {} -SERIALIZER_TYPES: dict[type, type] = {} - -SERIALIZED_FUNCTION = TypeVar("SERIALIZED_FUNCTION", bound=Serializer) - - -@overload -def serializer( - fn: None = None, - to: type[SerializedType] | None = None, - overwrite: bool | None = None, -) -> Callable[[SERIALIZED_FUNCTION], SERIALIZED_FUNCTION]: ... - - -@overload -def serializer( - fn: SERIALIZED_FUNCTION, - to: type[SerializedType] | None = None, - overwrite: bool | None = None, -) -> SERIALIZED_FUNCTION: ... - - -def serializer( - fn: SERIALIZED_FUNCTION | None = None, - to: Any = None, - overwrite: bool | None = None, -) -> SERIALIZED_FUNCTION | Callable[[SERIALIZED_FUNCTION], SERIALIZED_FUNCTION]: - """Decorator to add a serializer for a given type. - - Args: - fn: The function to decorate. - to: The type returned by the serializer. If this is `str`, then any Var created from this type will be treated as a string. - overwrite: Whether to overwrite the existing serializer. - - Returns: - The decorated function. - """ - - def wrapper(fn: SERIALIZED_FUNCTION) -> SERIALIZED_FUNCTION: - # Check the type hints to get the type of the argument. - type_hints = get_type_hints(fn) - args = [arg for arg in type_hints if arg != "return"] - - # Make sure the function takes a single argument. - if len(args) != 1: - msg = "Serializer must take a single argument." - raise ValueError(msg) - - # Get the type of the argument. - type_ = type_hints[args[0]] - - # Make sure the type is not already registered. - registered_fn = SERIALIZERS.get(type_) - if registered_fn is not None and registered_fn != fn and overwrite is not True: - message = f"Overwriting serializer for type {type_} from {registered_fn.__module__}:{registered_fn.__qualname__} to {fn.__module__}:{fn.__qualname__}." - if overwrite is False: - raise ValueError(message) - caller_frame = next( - filter( - lambda frame: frame.filename != __file__, - inspect.getouterframes(inspect.currentframe()), - ), - None, - ) - file_info = ( - f"(at {caller_frame.filename}:{caller_frame.lineno})" - if caller_frame - else "" - ) - console.warn( - f"{message} Call rx.serializer with `overwrite=True` if this is intentional. {file_info}" - ) - - to_type = to or type_hints.get("return") - - # Apply type transformation if requested - if to_type: - SERIALIZER_TYPES[type_] = to_type - get_serializer_type.cache_clear() - - # Register the serializer. - SERIALIZERS[type_] = fn - get_serializer.cache_clear() - - # Return the function. - return fn - - if fn is not None: - return wrapper(fn) - return wrapper - - -@overload -def serialize( - value: Any, get_type: Literal[True] -) -> tuple[SerializedType | None, types.GenericType | None]: ... - - -@overload -def serialize(value: Any, get_type: Literal[False]) -> SerializedType | None: ... - - -@overload -def serialize(value: Any) -> SerializedType | None: ... - - -def serialize( - value: Any, get_type: bool = False -) -> SerializedType | tuple[SerializedType | None, types.GenericType | None] | None: - """Serialize the value to a JSON string. - - Args: - value: The value to serialize. - get_type: Whether to return the type of the serialized value. - - Returns: - The serialized value, or None if a serializer is not found. - """ - # Get the serializer for the type. - serializer = get_serializer(type(value)) - - # If there is no serializer, return None. - if serializer is None: - if dataclasses.is_dataclass(value) and not isinstance(value, type): - return {k.name: getattr(value, k.name) for k in dataclasses.fields(value)} - - if get_type: - return None, None - return None - - # Serialize the value. - serialized = serializer(value) - - # Return the serialized value and the type. - if get_type: - return serialized, get_serializer_type(type(value)) - return serialized - - -@functools.lru_cache -def get_serializer(type_: type) -> Serializer | None: - """Get the serializer for the type. - - Args: - type_: The type to get the serializer for. - - Returns: - The serializer for the type, or None if there is no serializer. - """ - # First, check if the type is registered. - serializer = SERIALIZERS.get(type_) - if serializer is not None: - return serializer - - # If the type is not registered, check if it is a subclass of a registered type. - for registered_type, serializer in reversed(SERIALIZERS.items()): - if issubclass(type_, registered_type): - return serializer - - # If there is no serializer, return None. - return None - - -@functools.lru_cache -def get_serializer_type(type_: type) -> type | None: - """Get the converted type for the type after serializing. - - Args: - type_: The type to get the serializer type for. - - Returns: - The serialized type for the type, or None if there is no type conversion registered. - """ - # First, check if the type is registered. - serializer = SERIALIZER_TYPES.get(type_) - if serializer is not None: - return serializer - - # If the type is not registered, check if it is a subclass of a registered type. - for registered_type, serializer in reversed(SERIALIZER_TYPES.items()): - if issubclass(type_, registered_type): - return serializer - - # If there is no serializer, return None. - return None - - -def has_serializer(type_: type, into_type: type | None = None) -> bool: - """Check if there is a serializer for the type. - - Args: - type_: The type to check. - into_type: The type to serialize into. - - Returns: - Whether there is a serializer for the type. - """ - serializer_for_type = get_serializer(type_) - return serializer_for_type is not None and ( - into_type is None or get_serializer_type(type_) == into_type - ) - - -def can_serialize(type_: type, into_type: type | None = None) -> bool: - """Check if there is a serializer for the type. - - Args: - type_: The type to check. - into_type: The type to serialize into. - - Returns: - Whether there is a serializer for the type. - """ - return ( - isinstance(type_, type) - and dataclasses.is_dataclass(type_) - and (into_type is None or into_type is dict) - ) or has_serializer(type_, into_type) - - -@serializer(to=str) -def serialize_type(value: type) -> str: - """Serialize a python type. - - Args: - value: the type to serialize. - - Returns: - The serialized type. - """ - return value.__name__ - - -if find_spec("pydantic"): - from pydantic import BaseModel - - @serializer(to=dict) - def serialize_base_model(model: BaseModel) -> dict: - """Serialize a pydantic v2 BaseModel instance. - - Args: - model: The BaseModel to serialize. - - Returns: - The serialized BaseModel. - """ - return model.model_dump() - - -@serializer -def serialize_set(value: set) -> list: - """Serialize a set to a JSON serializable list. - - Args: - value: The set to serialize. - - Returns: - The serialized list. - """ - return list(value) - - -@serializer -def serialize_sequence(value: Sequence) -> list: - """Serialize a sequence to a JSON serializable list. - - Args: - value: The sequence to serialize. - - Returns: - The serialized list. - """ - return list(value) - - -@serializer(to=dict) -def serialize_mapping(value: Mapping) -> dict: - """Serialize a mapping type to a dictionary. - - Args: - value: The mapping instance to serialize. - - Returns: - A new dictionary containing the same key-value pairs as the input mapping. - """ - return {**value} - - -@serializer(to=str) -def serialize_datetime(dt: date | datetime | time | timedelta) -> str: - """Serialize a datetime to a JSON string. - - Args: - dt: The datetime to serialize. - - Returns: - The serialized datetime. - """ - return str(dt) - - -@serializer(to=str) -def serialize_path(path: Path) -> str: - """Serialize a pathlib.Path to a JSON string. - - Args: - path: The path to serialize. - - Returns: - The serialized path. - """ - return str(path.as_posix()) - - -@serializer -def serialize_enum(en: Enum) -> str: - """Serialize a enum to a JSON string. - - Args: - en: The enum to serialize. - - Returns: - The serialized enum. - """ - return en.value - - -@serializer(to=str) -def serialize_uuid(uuid: UUID) -> str: - """Serialize a UUID to a JSON string. - - Args: - uuid: The UUID to serialize. - - Returns: - The serialized UUID. - """ - return str(uuid) - - -@serializer(to=float) -def serialize_decimal(value: decimal.Decimal) -> float: - """Serialize a Decimal to a float. - - Args: - value: The Decimal to serialize. - - Returns: - The serialized Decimal as a float. - """ - return float(value) - - -@serializer(to=str) -def serialize_color(color: Color) -> str: - """Serialize a color. - - Args: - color: The color to serialize. - - Returns: - The serialized color. - """ - return color.__format__("") - - -with contextlib.suppress(ImportError): - from pandas import DataFrame - - def format_dataframe_values(df: DataFrame) -> list[list[Any]]: - """Format dataframe values to a list of lists. - - Args: - df: The dataframe to format. - - Returns: - The dataframe as a list of lists. - """ - return [ - [str(d) if isinstance(d, (list, tuple)) else d for d in data] - for data in list(df.to_numpy().tolist()) - ] - - @serializer - def serialize_dataframe(df: DataFrame) -> dict: - """Serialize a pandas dataframe. - - Args: - df: The dataframe to serialize. - - Returns: - The serialized dataframe. - """ - return { - "columns": df.columns.tolist(), - "data": format_dataframe_values(df), - } - - -with contextlib.suppress(ImportError): - from plotly.graph_objects import Figure, layout - from plotly.io import to_json - - @serializer - def serialize_figure(figure: Figure) -> dict: - """Serialize a plotly figure. - - Args: - figure: The figure to serialize. - - Returns: - The serialized figure. - """ - return json.loads(str(to_json(figure))) - - @serializer - def serialize_template(template: layout.Template) -> dict: - """Serialize a plotly template. - - Args: - template: The template to serialize. - - Returns: - The serialized template. - """ - return { - "data": json.loads(str(to_json(template.data))), - "layout": json.loads(str(to_json(template.layout))), - } - - -with contextlib.suppress(ImportError): - import base64 - import io - - from PIL.Image import MIME - from PIL.Image import Image as Img - - @serializer - def serialize_image(image: Img) -> str: - """Serialize a plotly figure. - - Args: - image: The image to serialize. - - Returns: - The serialized image. - """ - buff = io.BytesIO() - image_format = getattr(image, "format", None) or "PNG" - image.save(buff, format=image_format) - image_bytes = buff.getvalue() - base64_image = base64.b64encode(image_bytes).decode("utf-8") - try: - # Newer method to get the mime type, but does not always work. - mime_type = image.get_format_mimetype() # pyright: ignore [reportAttributeAccessIssue] - except AttributeError: - try: - # Fallback method - mime_type = MIME[image_format] - except KeyError: - # Unknown mime_type: warn and return image/png and hope the browser can sort it out. - warnings.warn( # noqa: B028 - f"Unknown mime type for {image} {image_format}. Defaulting to image/png" - ) - mime_type = "image/png" - - return f"data:{mime_type};base64,{base64_image}" +from reflex_core.utils.serializers import * # noqa: F401, F403 diff --git a/reflex/utils/types.py b/reflex/utils/types.py index 2c82d6929fb..dc3ee548ab5 100644 --- a/reflex/utils/types.py +++ b/reflex/utils/types.py @@ -1,1197 +1,3 @@ -"""Contains custom types and methods to check types.""" +"""Re-export from reflex_core.""" -from __future__ import annotations - -import dataclasses -import sys -import types -from collections.abc import Callable, Iterable, Mapping, Sequence -from enum import Enum -from functools import cached_property, lru_cache -from importlib.util import find_spec -from types import GenericAlias -from typing import ( # noqa: UP035 - TYPE_CHECKING, - Any, - Awaitable, - ClassVar, - Dict, - ForwardRef, - List, - Literal, - MutableMapping, - NoReturn, - Protocol, - Tuple, - TypeVar, - Union, - _eval_type, # pyright: ignore [reportAttributeAccessIssue] - _GenericAlias, # pyright: ignore [reportAttributeAccessIssue] - _SpecialGenericAlias, # pyright: ignore [reportAttributeAccessIssue] - get_args, - is_typeddict, -) -from typing import get_origin as get_origin_og -from typing import get_type_hints as get_type_hints_og - -from typing_extensions import Self as Self -from typing_extensions import override as override - -from reflex import constants -from reflex.utils import console - -# Potential GenericAlias types for isinstance checks. -GenericAliasTypes = (_GenericAlias, GenericAlias, _SpecialGenericAlias) - -# Potential Union types for isinstance checks. -UnionTypes = (Union, types.UnionType) - -# Union of generic types. -GenericType = type | _GenericAlias - -# Valid state var types. -PrimitiveTypes = (int, float, bool, str, list, dict, set, tuple) -StateVarTypes = (*PrimitiveTypes, type(None)) - -if TYPE_CHECKING: - from reflex.state import BaseState - from reflex.vars.base import Var - -VAR1 = TypeVar("VAR1", bound="Var") -VAR2 = TypeVar("VAR2", bound="Var") -VAR3 = TypeVar("VAR3", bound="Var") -VAR4 = TypeVar("VAR4", bound="Var") -VAR5 = TypeVar("VAR5", bound="Var") -VAR6 = TypeVar("VAR6", bound="Var") -VAR7 = TypeVar("VAR7", bound="Var") - - -class _ArgsSpec0(Protocol): - def __call__(self) -> Sequence[Var]: ... - - -class _ArgsSpec1(Protocol): - def __call__(self, var1: VAR1, /) -> Sequence[Var]: ... # pyright: ignore [reportInvalidTypeVarUse] - - -class _ArgsSpec2(Protocol): - def __call__(self, var1: VAR1, var2: VAR2, /) -> Sequence[Var]: ... # pyright: ignore [reportInvalidTypeVarUse] - - -class _ArgsSpec3(Protocol): - def __call__(self, var1: VAR1, var2: VAR2, var3: VAR3, /) -> Sequence[Var]: ... # pyright: ignore [reportInvalidTypeVarUse] - - -class _ArgsSpec4(Protocol): - def __call__( - self, - var1: VAR1, # pyright: ignore [reportInvalidTypeVarUse] - var2: VAR2, # pyright: ignore [reportInvalidTypeVarUse] - var3: VAR3, # pyright: ignore [reportInvalidTypeVarUse] - var4: VAR4, # pyright: ignore [reportInvalidTypeVarUse] - /, - ) -> Sequence[Var]: ... - - -class _ArgsSpec5(Protocol): - def __call__( - self, - var1: VAR1, # pyright: ignore [reportInvalidTypeVarUse] - var2: VAR2, # pyright: ignore [reportInvalidTypeVarUse] - var3: VAR3, # pyright: ignore [reportInvalidTypeVarUse] - var4: VAR4, # pyright: ignore [reportInvalidTypeVarUse] - var5: VAR5, # pyright: ignore [reportInvalidTypeVarUse] - /, - ) -> Sequence[Var]: ... - - -class _ArgsSpec6(Protocol): - def __call__( - self, - var1: VAR1, # pyright: ignore [reportInvalidTypeVarUse] - var2: VAR2, # pyright: ignore [reportInvalidTypeVarUse] - var3: VAR3, # pyright: ignore [reportInvalidTypeVarUse] - var4: VAR4, # pyright: ignore [reportInvalidTypeVarUse] - var5: VAR5, # pyright: ignore [reportInvalidTypeVarUse] - var6: VAR6, # pyright: ignore [reportInvalidTypeVarUse] - /, - ) -> Sequence[Var]: ... - - -class _ArgsSpec7(Protocol): - def __call__( - self, - var1: VAR1, # pyright: ignore [reportInvalidTypeVarUse] - var2: VAR2, # pyright: ignore [reportInvalidTypeVarUse] - var3: VAR3, # pyright: ignore [reportInvalidTypeVarUse] - var4: VAR4, # pyright: ignore [reportInvalidTypeVarUse] - var5: VAR5, # pyright: ignore [reportInvalidTypeVarUse] - var6: VAR6, # pyright: ignore [reportInvalidTypeVarUse] - var7: VAR7, # pyright: ignore [reportInvalidTypeVarUse] - /, - ) -> Sequence[Var]: ... - - -ArgsSpec = ( - _ArgsSpec0 - | _ArgsSpec1 - | _ArgsSpec2 - | _ArgsSpec3 - | _ArgsSpec4 - | _ArgsSpec5 - | _ArgsSpec6 - | _ArgsSpec7 -) - -Scope = MutableMapping[str, Any] -Message = MutableMapping[str, Any] - -Receive = Callable[[], Awaitable[Message]] -Send = Callable[[Message], Awaitable[None]] - -ASGIApp = Callable[[Scope, Receive, Send], Awaitable[None]] - -PrimitiveToAnnotation = { - list: List, # noqa: UP006 - tuple: Tuple, # noqa: UP006 - dict: Dict, # noqa: UP006 -} - -RESERVED_BACKEND_VAR_NAMES = {"_abc_impl", "_backend_vars", "_was_touched", "_mixin"} - - -class Unset: - """A class to represent an unset value. - - This is used to differentiate between a value that is not set and a value that is set to None. - """ - - def __repr__(self) -> str: - """Return the string representation of the class. - - Returns: - The string representation of the class. - """ - return "Unset" - - def __bool__(self) -> bool: - """Return False when the class is used in a boolean context. - - Returns: - False - """ - return False - - -@lru_cache -def _get_origin_cached(tp: Any): - return get_origin_og(tp) - - -def get_origin(tp: Any): - """Get the origin of a class. - - Args: - tp: The class to get the origin of. - - Returns: - The origin of the class. - """ - return ( - origin - if (origin := getattr(tp, "__origin__", None)) is not None - else _get_origin_cached(tp) - ) - - -@lru_cache -def is_generic_alias(cls: GenericType) -> bool: - """Check whether the class is a generic alias. - - Args: - cls: The class to check. - - Returns: - Whether the class is a generic alias. - """ - return isinstance(cls, GenericAliasTypes) - - -@lru_cache -def get_type_hints(obj: Any) -> dict[str, Any]: - """Get the type hints of a class. - - Args: - obj: The class to get the type hints of. - - Returns: - The type hints of the class. - """ - return get_type_hints_og(obj) - - -def _unionize(args: list[GenericType]) -> GenericType: - if not args: - return Any # pyright: ignore [reportReturnType] - if len(args) == 1: - return args[0] - return Union[tuple(args)] # noqa: UP007 - - -def unionize(*args: GenericType) -> type: - """Unionize the types. - - Args: - args: The types to unionize. - - Returns: - The unionized types. - """ - return _unionize([arg for arg in args if arg is not NoReturn]) - - -def is_none(cls: GenericType) -> bool: - """Check if a class is None. - - Args: - cls: The class to check. - - Returns: - Whether the class is None. - """ - return cls is type(None) or cls is None - - -def is_union(cls: GenericType) -> bool: - """Check if a class is a Union. - - Args: - cls: The class to check. - - Returns: - Whether the class is a Union. - """ - origin = getattr(cls, "__origin__", None) - if origin is Union: - return True - return origin is None and isinstance(cls, types.UnionType) - - -def is_literal(cls: GenericType) -> bool: - """Check if a class is a Literal. - - Args: - cls: The class to check. - - Returns: - Whether the class is a literal. - """ - return getattr(cls, "__origin__", None) is Literal - - -@lru_cache -def has_args(cls: type) -> bool: - """Check if the class has generic parameters. - - Args: - cls: The class to check. - - Returns: - Whether the class has generic - """ - if get_args(cls): - return True - - # Check if the class inherits from a generic class (using __orig_bases__) - if hasattr(cls, "__orig_bases__"): - for base in cls.__orig_bases__: - if get_args(base): - return True - - return False - - -def is_optional(cls: GenericType) -> bool: - """Check if a class is an Optional. - - Args: - cls: The class to check. - - Returns: - Whether the class is an Optional. - """ - return ( - cls is None - or cls is type(None) - or (is_union(cls) and type(None) in get_args(cls)) - ) - - -def is_classvar(a_type: Any) -> bool: - """Check if a type is a ClassVar. - - Args: - a_type: The type to check. - - Returns: - Whether the type is a ClassVar. - """ - return ( - a_type is ClassVar - or (type(a_type) is _GenericAlias and a_type.__origin__ is ClassVar) - or ( - type(a_type) is ForwardRef and a_type.__forward_arg__.startswith("ClassVar") - ) - ) - - -def value_inside_optional(cls: GenericType) -> GenericType: - """Get the value inside an Optional type or the original type. - - Args: - cls: The class to check. - - Returns: - The value inside the Optional type or the original type. - """ - if is_union(cls) and len(args := get_args(cls)) >= 2 and type(None) in args: - if len(args) == 2: - return args[0] if args[1] is type(None) else args[1] - return unionize(*[arg for arg in args if arg is not type(None)]) - return cls - - -def get_field_type(cls: GenericType, field_name: str) -> GenericType | None: - """Get the type of a field in a class. - - Args: - cls: The class to check. - field_name: The name of the field to check. - - Returns: - The type of the field, if it exists, else None. - """ - if (fields := getattr(cls, "_fields", None)) is not None and field_name in fields: - return fields[field_name].annotated_type - if ( - hasattr(cls, "__fields__") - and field_name in cls.__fields__ - and hasattr(cls.__fields__[field_name], "annotation") - and not isinstance(cls.__fields__[field_name].annotation, (str, ForwardRef)) - ): - return cls.__fields__[field_name].annotation - type_hints = get_type_hints(cls) - return type_hints.get(field_name, None) - - -PROPERTY_CLASSES = (property,) -if find_spec("sqlalchemy") and find_spec("sqlalchemy.ext"): - from sqlalchemy.ext.hybrid import hybrid_property - - PROPERTY_CLASSES += (hybrid_property,) - - -def get_property_hint(attr: Any | None) -> GenericType | None: - """Check if an attribute is a property and return its type hint. - - Args: - attr: The descriptor to check. - - Returns: - The type hint of the property, if it is a property, else None. - """ - if not isinstance(attr, PROPERTY_CLASSES): - return None - hints = get_type_hints(attr.fget) - return hints.get("return", None) - - -def get_attribute_access_type(cls: GenericType, name: str) -> GenericType | None: - """Check if an attribute can be accessed on the cls and return its type. - - Supports pydantic models, unions, and annotated attributes on rx.Model. - - Args: - cls: The class to check. - name: The name of the attribute to check. - - Returns: - The type of the attribute, if accessible, or None - """ - try: - attr = getattr(cls, name, None) - except NotImplementedError: - attr = None - - if hint := get_property_hint(attr): - return hint - - if hasattr(cls, "__fields__") and name in cls.__fields__: - # pydantic models - return get_field_type(cls, name) - if find_spec("sqlalchemy") and find_spec("sqlalchemy.orm"): - import sqlalchemy - from sqlalchemy.ext.associationproxy import AssociationProxyInstance - from sqlalchemy.orm import ( - DeclarativeBase, - Mapped, - QueryableAttribute, - Relationship, - ) - - from reflex.model import Model - - if find_spec("sqlmodel"): - from sqlmodel import SQLModel - - sqlmodel_types = (Model, SQLModel) - else: - sqlmodel_types = (Model,) - - if isinstance(cls, type) and issubclass(cls, DeclarativeBase): - insp = sqlalchemy.inspect(cls) - if name in insp.columns: - # check for list types - column = insp.columns[name] - column_type = column.type - try: - type_ = insp.columns[name].type.python_type - except NotImplementedError: - type_ = None - if type_ is not None: - if hasattr(column_type, "item_type"): - try: - item_type = column_type.item_type.python_type # pyright: ignore [reportAttributeAccessIssue] - except NotImplementedError: - item_type = None - if item_type is not None: - if type_ in PrimitiveToAnnotation: - type_ = PrimitiveToAnnotation[type_] - type_ = type_[item_type] # pyright: ignore [reportIndexIssue] - if hasattr(column, "nullable") and column.nullable: - type_ = type_ | None - return type_ - if name in insp.all_orm_descriptors: - descriptor = insp.all_orm_descriptors[name] - if hint := get_property_hint(descriptor): - return hint - if isinstance(descriptor, QueryableAttribute): - prop = descriptor.property - if isinstance(prop, Relationship): - type_ = prop.mapper.class_ - # TODO: check for nullable? - return list[type_] if prop.uselist else type_ | None - if isinstance(attr, AssociationProxyInstance): - return list[ - get_attribute_access_type( - attr.target_class, - attr.remote_attr.key, # pyright: ignore [reportAttributeAccessIssue] - ) - ] - elif ( - isinstance(cls, type) - and not is_generic_alias(cls) - and issubclass(cls, sqlmodel_types) - ): - # Check in the annotations directly (for sqlmodel.Relationship) - hints = get_type_hints(cls) # pyright: ignore [reportArgumentType] - if name in hints: - type_ = hints[name] - type_origin = get_origin(type_) - if isinstance(type_origin, type) and issubclass(type_origin, Mapped): - return get_args(type_)[0] # SQLAlchemy v2 - return type_ - if is_union(cls): - # Check in each arg of the annotation. - return unionize( - *(get_attribute_access_type(arg, name) for arg in get_args(cls)) - ) - if isinstance(cls, type): - # Bare class - exceptions = NameError - try: - hints = get_type_hints(cls) # pyright: ignore [reportArgumentType] - if name in hints: - return hints[name] - except exceptions as e: - console.warn(f"Failed to resolve ForwardRefs for {cls}.{name} due to {e}") - return None # Attribute is not accessible. - - -@lru_cache -def get_base_class(cls: GenericType) -> type: - """Get the base class of a class. - - Args: - cls: The class. - - Returns: - The base class of the class. - - Raises: - TypeError: If a literal has multiple types. - """ - if is_literal(cls): - # only literals of the same type are supported. - arg_type = type(get_args(cls)[0]) - if not all(type(arg) is arg_type for arg in get_args(cls)): - msg = "only literals of the same type are supported" - raise TypeError(msg) - return type(get_args(cls)[0]) - - if is_union(cls): - return tuple(get_base_class(arg) for arg in get_args(cls)) # pyright: ignore [reportReturnType] - - return get_base_class(cls.__origin__) if is_generic_alias(cls) else cls - - -def does_obj_satisfy_typed_dict( - obj: Any, - cls: GenericType, - *, - nested: int = 0, - treat_var_as_type: bool = True, - treat_mutable_obj_as_immutable: bool = False, -) -> bool: - """Check if an object satisfies a typed dict. - - Args: - obj: The object to check. - cls: The typed dict to check against. - nested: How many levels deep to check. - treat_var_as_type: Whether to treat Var as the type it represents, i.e. _var_type. - treat_mutable_obj_as_immutable: Whether to treat mutable objects as immutable. Useful if a component declares a mutable object as a prop, but the value is not expected to change. - - Returns: - Whether the object satisfies the typed dict. - """ - if not isinstance(obj, Mapping): - return False - - key_names_to_values = get_type_hints(cls) - required_keys: frozenset[str] = getattr(cls, "__required_keys__", frozenset()) - is_closed = getattr(cls, "__closed__", False) - extra_items_type = getattr(cls, "__extra_items__", Any) - - for key, value in obj.items(): - if is_closed and key not in key_names_to_values: - return False - if nested: - if key in key_names_to_values: - expected_type = key_names_to_values[key] - if not _isinstance( - value, - expected_type, - nested=nested - 1, - treat_var_as_type=treat_var_as_type, - treat_mutable_obj_as_immutable=treat_mutable_obj_as_immutable, - ): - return False - else: - if not _isinstance( - value, - extra_items_type, - nested=nested - 1, - treat_var_as_type=treat_var_as_type, - treat_mutable_obj_as_immutable=treat_mutable_obj_as_immutable, - ): - return False - - # required keys are all present - return required_keys.issubset(frozenset(obj)) - - -def _isinstance( - obj: Any, - cls: GenericType, - *, - nested: int = 0, - treat_var_as_type: bool = True, - treat_mutable_obj_as_immutable: bool = False, -) -> bool: - """Check if an object is an instance of a class. - - Args: - obj: The object to check. - cls: The class to check against. - nested: How many levels deep to check. - treat_var_as_type: Whether to treat Var as the type it represents, i.e. _var_type. - treat_mutable_obj_as_immutable: Whether to treat mutable objects as immutable. Useful if a component declares a mutable object as a prop, but the value is not expected to change. - - Returns: - Whether the object is an instance of the class. - """ - 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 treat_var_as_type and _isinstance( - obj._var_value, cls, nested=nested, treat_var_as_type=True - ) - if isinstance(obj, Var): - return treat_var_as_type and typehint_issubclass( - obj._var_type, - cls, - treat_mutable_superclasss_as_immutable=treat_mutable_obj_as_immutable, - treat_literals_as_union_of_types=True, - treat_any_as_subtype_of_everything=True, - ) - - if cls is None or cls is type(None): - return obj is None - - if cls is not None and is_union(cls): - return any( - _isinstance(obj, arg, nested=nested, treat_var_as_type=treat_var_as_type) - for arg in get_args(cls) - ) - - if is_literal(cls): - return obj in get_args(cls) - - origin = get_origin(cls) - - if origin is None: - # cls is a typed dict - if is_typeddict(cls): - if nested: - return does_obj_satisfy_typed_dict( - obj, - cls, - nested=nested - 1, - treat_var_as_type=treat_var_as_type, - treat_mutable_obj_as_immutable=treat_mutable_obj_as_immutable, - ) - return isinstance(obj, dict) - - # cls is a float - if cls is float: - return isinstance(obj, (float, int)) - - # cls is a simple class - return isinstance(obj, cls) - - args = get_args(cls) - - if not args: - if treat_mutable_obj_as_immutable: - if origin is dict: - origin = Mapping - elif origin is list or origin is set: - origin = Sequence - # cls is a simple generic class - return isinstance(obj, origin) - - if origin is Var and args: - # cls is a Var - return _isinstance( - obj, - args[0], - nested=nested, - treat_var_as_type=treat_var_as_type, - treat_mutable_obj_as_immutable=treat_mutable_obj_as_immutable, - ) - - if nested > 0 and args: - if origin is list: - expected_class = Sequence if treat_mutable_obj_as_immutable else list - return isinstance(obj, expected_class) and all( - _isinstance( - item, - args[0], - nested=nested - 1, - treat_var_as_type=treat_var_as_type, - ) - for item in obj - ) - if origin is tuple: - if args[-1] is Ellipsis: - return isinstance(obj, tuple) and all( - _isinstance( - item, - args[0], - nested=nested - 1, - treat_var_as_type=treat_var_as_type, - ) - for item in obj - ) - return ( - isinstance(obj, tuple) - and len(obj) == len(args) - and all( - _isinstance( - item, - arg, - nested=nested - 1, - treat_var_as_type=treat_var_as_type, - ) - for item, arg in zip(obj, args, strict=True) - ) - ) - if safe_issubclass(origin, Mapping): - expected_class = ( - dict - if origin is dict and not treat_mutable_obj_as_immutable - else Mapping - ) - return isinstance(obj, expected_class) and all( - _isinstance( - key, args[0], nested=nested - 1, treat_var_as_type=treat_var_as_type - ) - and _isinstance( - value, - args[1], - nested=nested - 1, - treat_var_as_type=treat_var_as_type, - ) - for key, value in obj.items() - ) - if origin is set: - expected_class = Sequence if treat_mutable_obj_as_immutable else set - return isinstance(obj, expected_class) and all( - _isinstance( - item, - args[0], - nested=nested - 1, - treat_var_as_type=treat_var_as_type, - ) - for item in obj - ) - - if args: - from reflex.vars import Field - - if origin is Field: - return _isinstance( - obj, args[0], nested=nested, treat_var_as_type=treat_var_as_type - ) - - return isinstance(obj, get_base_class(cls)) - - -def is_dataframe(value: type) -> bool: - """Check if the given value is a dataframe. - - Args: - value: The value to check. - - Returns: - Whether the value is a dataframe. - """ - if is_generic_alias(value) or value == Any: - return False - return value.__name__ == "DataFrame" - - -def is_valid_var_type(type_: type) -> bool: - """Check if the given type is a valid prop type. - - Args: - type_: The type to check. - - Returns: - Whether the type is a valid prop type. - """ - from reflex.utils import serializers - - if is_union(type_): - return all(is_valid_var_type(arg) for arg in get_args(type_)) - - if is_literal(type_): - types = {type(value) for value in get_args(type_)} - return all(is_valid_var_type(type_) for type_ in types) - - type_ = origin if (origin := get_origin(type_)) is not None else type_ - - return ( - issubclass(type_, StateVarTypes) - or serializers.has_serializer(type_) - or dataclasses.is_dataclass(type_) - ) - - -def is_backend_base_variable(name: str, cls: type[BaseState]) -> bool: - """Check if this variable name correspond to a backend variable. - - Args: - name: The name of the variable to check - cls: The class of the variable to check (must be a BaseState subclass) - - Returns: - bool: The result of the check - """ - if name in RESERVED_BACKEND_VAR_NAMES: - return False - - if not name.startswith("_"): - return False - - if name.startswith("__"): - return False - - if name.startswith(f"_{cls.__name__}__"): - return False - - hints = cls._get_type_hints() - if name in hints: - hint = get_origin(hints[name]) - if hint == ClassVar: - return False - - if name in cls.inherited_backend_vars: - return False - - from reflex.vars.base import is_computed_var - - if name in cls.__dict__: - value = cls.__dict__[name] - if type(value) is classmethod: - return False - if callable(value): - return False - - if isinstance( - value, - ( - types.FunctionType, - property, - cached_property, - ), - ) or is_computed_var(value): - return False - - return True - - -def check_type_in_allowed_types(value_type: type, allowed_types: Iterable) -> bool: - """Check that a value type is found in a list of allowed types. - - Args: - value_type: Type of value. - allowed_types: Iterable of allowed types. - - Returns: - If the type is found in the allowed types. - """ - return get_base_class(value_type) in allowed_types - - -def check_prop_in_allowed_types(prop: Any, allowed_types: Iterable) -> bool: - """Check that a prop value is in a list of allowed types. - Does the check in a way that works regardless if it's a raw value or a state Var. - - Args: - prop: The prop to check. - allowed_types: The list of allowed types. - - Returns: - If the prop type match one of the allowed_types. - """ - from reflex.vars import Var - - type_ = prop._var_type if isinstance(prop, Var) else type(prop) - return type_ in allowed_types - - -def is_encoded_fstring(value: Any) -> bool: - """Check if a value is an encoded Var f-string. - - Args: - value: The value string to check. - - Returns: - Whether the value is an f-string - """ - return isinstance(value, str) and constants.REFLEX_VAR_OPENING_TAG in value - - -def validate_literal(key: str, value: Any, expected_type: type, comp_name: str): - """Check that a value is a valid literal. - - Args: - key: The prop name. - value: The prop value to validate. - expected_type: The expected type(literal type). - comp_name: Name of the component. - - Raises: - ValueError: When the value is not a valid literal. - """ - from reflex.vars import Var - - if ( - is_literal(expected_type) - and not isinstance(value, Var) # validating vars is not supported yet. - and not is_encoded_fstring(value) # f-strings are not supported. - and value not in expected_type.__args__ - ): - allowed_values = expected_type.__args__ - if value not in allowed_values: - allowed_value_str = ",".join([ - str(v) if not isinstance(v, str) else f"'{v}'" for v in allowed_values - ]) - value_str = f"'{value}'" if isinstance(value, str) else value - msg = f"prop value for {key!s} of the `{comp_name}` component should be one of the following: {allowed_value_str}. Got {value_str} instead" - raise ValueError(msg) - - -def safe_issubclass(cls: Any, cls_check: Any | tuple[Any, ...]): - """Check if a class is a subclass of another class. Returns False if internal error occurs. - - Args: - cls: The class to check. - cls_check: The class to check against. - - Returns: - Whether the class is a subclass of the other class. - """ - try: - return issubclass(cls, cls_check) - except TypeError: - return False - - -def typehint_issubclass( - possible_subclass: Any, - possible_superclass: Any, - *, - treat_mutable_superclasss_as_immutable: bool = False, - treat_literals_as_union_of_types: bool = True, - treat_any_as_subtype_of_everything: bool = False, -) -> bool: - """Check if a type hint is a subclass of another type hint. - - Args: - possible_subclass: The type hint to check. - possible_superclass: The type hint to check against. - treat_mutable_superclasss_as_immutable: Whether to treat target classes as immutable. - treat_literals_as_union_of_types: Whether to treat literals as a union of their types. - treat_any_as_subtype_of_everything: Whether to treat Any as a subtype of everything. This is the default behavior in Python. - - Returns: - Whether the type hint is a subclass of the other type hint. - """ - if possible_subclass is possible_superclass or possible_superclass is Any: - return True - if possible_subclass is Any: - return treat_any_as_subtype_of_everything - if possible_subclass is NoReturn: - return True - - provided_type_origin = get_origin(possible_subclass) - accepted_type_origin = get_origin(possible_superclass) - - if provided_type_origin is None and accepted_type_origin is None: - # In this case, we are dealing with a non-generic type, so we can use issubclass - return issubclass(possible_subclass, possible_superclass) - - if treat_literals_as_union_of_types and is_literal(possible_superclass): - args = get_args(possible_superclass) - return any( - typehint_issubclass( - possible_subclass, - type(arg), - treat_mutable_superclasss_as_immutable=treat_mutable_superclasss_as_immutable, - treat_literals_as_union_of_types=treat_literals_as_union_of_types, - treat_any_as_subtype_of_everything=treat_any_as_subtype_of_everything, - ) - for arg in args - ) - - if is_literal(possible_subclass): - args = get_args(possible_subclass) - return all( - _isinstance( - arg, - possible_superclass, - treat_mutable_obj_as_immutable=treat_mutable_superclasss_as_immutable, - nested=2, - ) - for arg in args - ) - - provided_type_origin = ( - Union if provided_type_origin is types.UnionType else provided_type_origin - ) - accepted_type_origin = ( - Union if accepted_type_origin is types.UnionType else accepted_type_origin - ) - - # Get type arguments (e.g., [float, int] for dict[float, int]) - provided_args = get_args(possible_subclass) - accepted_args = get_args(possible_superclass) - - if accepted_type_origin is Union: - if provided_type_origin is not Union: - return any( - typehint_issubclass( - possible_subclass, - accepted_arg, - treat_mutable_superclasss_as_immutable=treat_mutable_superclasss_as_immutable, - treat_literals_as_union_of_types=treat_literals_as_union_of_types, - treat_any_as_subtype_of_everything=treat_any_as_subtype_of_everything, - ) - for accepted_arg in accepted_args - ) - return all( - any( - typehint_issubclass( - provided_arg, - accepted_arg, - treat_mutable_superclasss_as_immutable=treat_mutable_superclasss_as_immutable, - treat_literals_as_union_of_types=treat_literals_as_union_of_types, - treat_any_as_subtype_of_everything=treat_any_as_subtype_of_everything, - ) - for accepted_arg in accepted_args - ) - for provided_arg in provided_args - ) - if provided_type_origin is Union: - return all( - typehint_issubclass( - provided_arg, - possible_superclass, - treat_mutable_superclasss_as_immutable=treat_mutable_superclasss_as_immutable, - treat_literals_as_union_of_types=treat_literals_as_union_of_types, - treat_any_as_subtype_of_everything=treat_any_as_subtype_of_everything, - ) - for provided_arg in provided_args - ) - - provided_type_origin = provided_type_origin or possible_subclass - accepted_type_origin = accepted_type_origin or possible_superclass - - if treat_mutable_superclasss_as_immutable: - if accepted_type_origin is dict: - accepted_type_origin = Mapping - elif accepted_type_origin is list or accepted_type_origin is set: - accepted_type_origin = Sequence - - # Check if the origin of both types is the same (e.g., list for list[int]) - if not safe_issubclass( - provided_type_origin or possible_subclass, - accepted_type_origin or possible_superclass, - ): - return False - - # Ensure all specific types are compatible with accepted types - # Note this is not necessarily correct, as it doesn't check against contravariance and covariance - # It also ignores when the length of the arguments is different - return all( - typehint_issubclass( - provided_arg, - accepted_arg, - treat_mutable_superclasss_as_immutable=treat_mutable_superclasss_as_immutable, - treat_literals_as_union_of_types=treat_literals_as_union_of_types, - treat_any_as_subtype_of_everything=treat_any_as_subtype_of_everything, - ) - for provided_arg, accepted_arg in zip( - provided_args, accepted_args, strict=False - ) - if accepted_arg is not Any - ) - - -def resolve_annotations( - raw_annotations: Mapping[str, type[Any]], module_name: str | None -) -> dict[str, type[Any]]: - """Partially taken from typing.get_type_hints. - - Resolve string or ForwardRef annotations into type objects if possible. - - Args: - raw_annotations: The raw annotations to resolve. - module_name: The name of the module. - - Returns: - The resolved annotations. - """ - module = sys.modules.get(module_name, None) if module_name is not None else None - - base_globals: dict[str, Any] | None = ( - module.__dict__ if module is not None else None - ) - - annotations = {} - for name, value in raw_annotations.items(): - if isinstance(value, str): - if sys.version_info == (3, 10, 0): - value = ForwardRef(value, is_argument=False) - else: - value = ForwardRef(value, is_argument=False, is_class=True) - try: - if sys.version_info >= (3, 13): - value = _eval_type(value, base_globals, None, type_params=()) - else: - value = _eval_type(value, base_globals, None) - except NameError: - # this is ok, it can be fixed with update_forward_refs - pass - annotations[name] = value - return annotations - - -TYPES_THAT_HAS_DEFAULT_VALUE = (int, float, tuple, list, set, dict, str) - - -def get_default_value_for_type(t: GenericType) -> Any: - """Get the default value of the var. - - Args: - t: The type of the var. - - Returns: - The default value of the var, if it has one, else None. - - Raises: - ImportError: If the var is a dataframe and pandas is not installed. - """ - if is_optional(t): - return None - - origin = get_origin(t) if is_generic_alias(t) else t - if origin is Literal: - args = get_args(t) - return args[0] if args else None - if safe_issubclass(origin, TYPES_THAT_HAS_DEFAULT_VALUE): - return origin() - if safe_issubclass(origin, Mapping): - return {} - if is_dataframe(origin): - try: - import pandas as pd - - return pd.DataFrame() - except ImportError as e: - msg = "Please install pandas to use dataframes in your app." - raise ImportError(msg) from e - return None - - -IMMUTABLE_TYPES = ( - int, - float, - bool, - str, - bytes, - frozenset, - tuple, - type(None), - Enum, -) - - -def is_immutable(i: Any) -> bool: - """Check if a value is immutable. - - Args: - i: The value to check. - - Returns: - Whether the value is immutable. - """ - return isinstance(i, IMMUTABLE_TYPES) +from reflex_core.utils.types import * # noqa: F401, F403 diff --git a/reflex/vars/__init__.py b/reflex/vars/__init__.py index c1989e5733d..04e3fd83f76 100644 --- a/reflex/vars/__init__.py +++ b/reflex/vars/__init__.py @@ -1,59 +1,4 @@ """Immutable-Based Var System.""" -from .base import ( - BaseStateMeta, - EvenMoreBasicBaseState, - Field, - LiteralVar, - Var, - VarData, - field, - get_unique_variable_name, - get_uuid_string_var, - var_operation, - var_operation_return, -) -from .color import ColorVar, LiteralColorVar -from .datetime import DateTimeVar -from .function import FunctionStringVar, FunctionVar, VarOperationCall -from .number import BooleanVar, LiteralBooleanVar, LiteralNumberVar, NumberVar -from .object import LiteralObjectVar, ObjectVar, RestProp -from .sequence import ( - ArrayVar, - ConcatVarOperation, - LiteralArrayVar, - LiteralStringVar, - StringVar, -) - -__all__ = [ - "ArrayVar", - "BaseStateMeta", - "BooleanVar", - "ColorVar", - "ConcatVarOperation", - "DateTimeVar", - "EvenMoreBasicBaseState", - "Field", - "FunctionStringVar", - "FunctionVar", - "LiteralArrayVar", - "LiteralBooleanVar", - "LiteralColorVar", - "LiteralNumberVar", - "LiteralObjectVar", - "LiteralStringVar", - "LiteralVar", - "NumberVar", - "ObjectVar", - "RestProp", - "StringVar", - "Var", - "VarData", - "VarOperationCall", - "field", - "get_unique_variable_name", - "get_uuid_string_var", - "var_operation", - "var_operation_return", -] +from reflex_core.vars import * # noqa: F401, F403 +from reflex_core.vars import __all__ diff --git a/reflex/vars/base.py b/reflex/vars/base.py index 0c86f4b93d4..68b81bf9358 100644 --- a/reflex/vars/base.py +++ b/reflex/vars/base.py @@ -1,3692 +1,3 @@ -"""Collection of base classes.""" +"""Re-export from reflex_core.""" -from __future__ import annotations - -import contextlib -import copy -import dataclasses -import datetime -import functools -import inspect -import json -import re -import string -import uuid -import warnings -from abc import ABCMeta -from collections.abc import Callable, Coroutine, Iterable, Mapping, Sequence -from dataclasses import _MISSING_TYPE, MISSING -from decimal import Decimal -from types import CodeType, FunctionType -from typing import ( - TYPE_CHECKING, - Annotated, - Any, - ClassVar, - Generic, - Literal, - NoReturn, - ParamSpec, - Protocol, - TypeGuard, - TypeVar, - cast, - get_args, - get_type_hints, - overload, -) - -from rich.markup import escape -from typing_extensions import dataclass_transform, override - -from reflex import constants -from reflex.constants.compiler import Hooks -from reflex.constants.state import FIELD_MARKER -from reflex.utils import console, exceptions, imports, serializers, types -from reflex.utils.compat import annotations_from_namespace -from reflex.utils.decorator import once -from reflex.utils.exceptions import ( - ComputedVarSignatureError, - UntypedComputedVarError, - VarAttributeError, - VarDependencyError, - VarTypeError, -) -from reflex.utils.format import format_state_name -from reflex.utils.imports import ( - ImmutableImportDict, - ImmutableParsedImportDict, - ImportDict, - ImportVar, - ParsedImportTuple, - parse_imports, -) -from reflex.utils.types import ( - GenericType, - Self, - _isinstance, - get_origin, - has_args, - safe_issubclass, - unionize, -) - -if TYPE_CHECKING: - from reflex.components.component import BaseComponent - from reflex.constants.colors import Color - from reflex.state import BaseState - - from .color import LiteralColorVar - 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) -OTHER_VAR_TYPE = TypeVar("OTHER_VAR_TYPE") -STRING_T = TypeVar("STRING_T", bound=str) -SEQUENCE_TYPE = TypeVar("SEQUENCE_TYPE", bound=Sequence) - -warnings.filterwarnings("ignore", message="fields may not start with an underscore") - -_PYDANTIC_VALIDATE_VALUES = "__pydantic_validate_values__" - - -def _pydantic_validator(*args, **kwargs): - return None - - -@dataclasses.dataclass( - eq=False, - frozen=True, -) -class VarSubclassEntry: - """Entry for a Var subclass.""" - - var_subclass: type[Var] - to_var_subclass: type[ToOperation] - python_types: tuple[GenericType, ...] - - -_var_subclasses: list[VarSubclassEntry] = [] -_var_literal_subclasses: list[tuple[type[LiteralVar], VarSubclassEntry]] = [] - - -@dataclasses.dataclass( - eq=True, - frozen=True, -) -class VarData: - """Metadata associated with a x.""" - - # The name of the enclosing state. - state: str = dataclasses.field(default="") - - # The name of the field in the state. - field_name: str = dataclasses.field(default="") - - # Imports needed to render this var - imports: ParsedImportTuple = dataclasses.field(default_factory=tuple) - - # Hooks that need to be present in the component to render this var - hooks: tuple[str, ...] = dataclasses.field(default_factory=tuple) - - # Dependencies of the var - deps: tuple[Var, ...] = dataclasses.field(default_factory=tuple) - - # Position of the hook in the component - position: Hooks.HookPosition | None = None - - # Components that are part of this var - components: tuple[BaseComponent, ...] = dataclasses.field(default_factory=tuple) - - def __init__( - self, - state: str = "", - field_name: str = "", - imports: ImmutableImportDict | ImmutableParsedImportDict | None = None, - hooks: Mapping[str, VarData | None] | Sequence[str] | str | None = None, - deps: list[Var] | None = None, - position: Hooks.HookPosition | None = None, - components: Iterable[BaseComponent] | None = None, - ): - """Initialize the var data. - - Args: - state: The name of the enclosing state. - field_name: The name of the field in the state. - imports: Imports needed to render this var. - hooks: Hooks that need to be present in the component to render this var. - deps: Dependencies of the var for useCallback. - position: Position of the hook in the component. - components: Components that are part of this var. - """ - if isinstance(hooks, str): - hooks = [hooks] - if not isinstance(hooks, dict): - hooks = dict.fromkeys(hooks or []) - immutable_imports: ParsedImportTuple = tuple( - (k, tuple(v)) for k, v in parse_imports(imports or {}).items() - ) - object.__setattr__(self, "state", state) - object.__setattr__(self, "field_name", field_name) - object.__setattr__(self, "imports", immutable_imports) - object.__setattr__(self, "hooks", tuple(hooks or {})) - object.__setattr__(self, "deps", tuple(deps or [])) - object.__setattr__(self, "position", position or None) - object.__setattr__(self, "components", tuple(components or [])) - - if hooks and any(hooks.values()): - # Merge our dependencies first, so they can be referenced. - merged_var_data = VarData.merge(*hooks.values(), self) - if merged_var_data is not None: - object.__setattr__(self, "state", merged_var_data.state) - object.__setattr__(self, "field_name", merged_var_data.field_name) - object.__setattr__(self, "imports", merged_var_data.imports) - object.__setattr__(self, "hooks", merged_var_data.hooks) - object.__setattr__(self, "deps", merged_var_data.deps) - object.__setattr__(self, "position", merged_var_data.position) - object.__setattr__(self, "components", merged_var_data.components) - - def old_school_imports(self) -> ImportDict: - """Return the imports as a mutable dict. - - Returns: - The imports as a mutable dict. - """ - return {k: list(v) for k, v in self.imports} - - def merge(*all: VarData | None) -> VarData | None: - """Merge multiple var data objects. - - Args: - *all: The var data objects to merge. - - Returns: - The merged var data object. - - Raises: - ReflexError: If trying to merge VarData with different positions. - - # noqa: DAR102 *all - """ - all_var_datas = list(filter(None, all)) - - if not all_var_datas: - return None - - if len(all_var_datas) == 1: - return all_var_datas[0] - - # Get the first non-empty field name or default to empty string. - field_name = next( - (var_data.field_name for var_data in all_var_datas if var_data.field_name), - "", - ) - - # Get the first non-empty state or default to empty string. - state = next( - (var_data.state for var_data in all_var_datas if var_data.state), "" - ) - - hooks: dict[str, VarData | None] = { - hook: None for var_data in all_var_datas for hook in var_data.hooks - } - - imports_ = imports.merge_imports( - *(var_data.imports for var_data in all_var_datas) - ) - - deps = [dep for var_data in all_var_datas for dep in var_data.deps] - - positions = list( - dict.fromkeys( - var_data.position - for var_data in all_var_datas - if var_data.position is not None - ) - ) - if positions: - if len(positions) > 1: - msg = f"Cannot merge var data with different positions: {positions}" - raise exceptions.ReflexError(msg) - position = positions[0] - else: - position = None - - components = tuple( - component for var_data in all_var_datas for component in var_data.components - ) - - return VarData( - state=state, - field_name=field_name, - imports=imports_, - hooks=hooks, - deps=deps, - position=position, - components=components, - ) - - def __bool__(self) -> bool: - """Check if the var data is non-empty. - - Returns: - True if any field is set to a non-default value. - """ - return bool( - self.state - or self.imports - or self.hooks - or self.field_name - or self.deps - or self.position - or self.components - ) - - @classmethod - def from_state(cls, state: type[BaseState] | str, field_name: str = "") -> VarData: - """Set the state of the var. - - Args: - state: The state to set or the full name of the state. - field_name: The name of the field in the state. Optional. - - Returns: - The var with the set state. - """ - from reflex.utils import format - - state_name = state if isinstance(state, str) else state.get_full_name() - return VarData( - state=state_name, - field_name=field_name, - hooks={ - "const {0} = useContext(StateContexts.{0})".format( - format.format_state_name(state_name) - ): None - }, - imports={ - f"$/{constants.Dirs.CONTEXTS_PATH}": [ImportVar(tag="StateContexts")], - "react": [ImportVar(tag="useContext")], - }, - ) - - -def _decode_var_immutable(value: str) -> tuple[VarData | None, str]: - """Decode the state name from a formatted var. - - Args: - value: The value to extract the state name from. - - Returns: - The extracted state name and the value without the state name. - """ - var_datas = [] - if isinstance(value, str): - # fast path if there is no encoded VarData - if constants.REFLEX_VAR_OPENING_TAG not in value: - return None, value - - offset = 0 - - # Find all tags. - while m := _decode_var_pattern.search(value): - start, end = m.span() - value = value[:start] + value[end:] - - serialized_data = m.group(1) - - if serialized_data.isnumeric() or ( - serialized_data[0] == "-" and serialized_data[1:].isnumeric() - ): - # This is a global immutable var. - var = _global_vars[int(serialized_data)] - var_data = var._get_all_var_data() - - if var_data is not None: - var_datas.append(var_data) - offset += end - start - - return VarData.merge(*var_datas) if var_datas else None, value - - -def can_use_in_object_var(cls: GenericType) -> bool: - """Check if the class can be used in an ObjectVar. - - Args: - cls: The class to check. - - Returns: - Whether the class can be used in an ObjectVar. - """ - if types.is_union(cls): - return all(can_use_in_object_var(t) for t in types.get_args(cls)) - return ( - isinstance(cls, type) - and not safe_issubclass(cls, Var) - and serializers.can_serialize(cls, dict) - ) - - -class MetaclassVar(type): - """Metaclass for the Var class.""" - - def __setattr__(cls, name: str, value: Any): - """Set an attribute on the class. - - Args: - name: The name of the attribute. - value: The value of the attribute. - """ - super().__setattr__( - name, value if name != _PYDANTIC_VALIDATE_VALUES else _pydantic_validator - ) - - -@dataclasses.dataclass( - eq=False, - frozen=True, -) -class Var(Generic[VAR_TYPE], metaclass=MetaclassVar): - """Base class for immutable vars.""" - - # The name of the var. - _js_expr: str = dataclasses.field() - - # The type of the var. - _var_type: types.GenericType = dataclasses.field(default=Any) - - # Extra metadata associated with the Var - _var_data: VarData | None = dataclasses.field(default=None) - - def __str__(self) -> str: - """String representation of the var. Guaranteed to be a valid Javascript expression. - - Returns: - The name of the var. - """ - return self._js_expr - - @property - def _var_is_local(self) -> bool: - """Whether this is a local javascript variable. - - Returns: - False - """ - return False - - @property - def _var_is_string(self) -> bool: - """Whether the var is a string literal. - - Returns: - False - """ - return False - - def __init_subclass__( - cls, - python_types: tuple[GenericType, ...] | GenericType = types.Unset(), - default_type: GenericType = types.Unset(), - **kwargs, - ): - """Initialize the subclass. - - Args: - python_types: The python types that the var represents. - default_type: The default type of the var. Defaults to the first python type. - **kwargs: Additional keyword arguments. - """ - super().__init_subclass__(**kwargs) - - if python_types or default_type: - python_types = ( - (python_types if isinstance(python_types, tuple) else (python_types,)) - if python_types - else () - ) - - default_type = default_type or (python_types[0] if python_types else Any) - - @dataclasses.dataclass( - eq=False, - frozen=True, - slots=True, - ) - class ToVarOperation(ToOperation, cls): - """Base class of converting a var to another var type.""" - - _original: Var = dataclasses.field( - default=Var(_js_expr="null", _var_type=None), - ) - - _default_var_type: ClassVar[GenericType] = default_type - - new_to_var_operation_name = f"{cls.__name__.removesuffix('Var')}CastedVar" - ToVarOperation.__qualname__ = ( - ToVarOperation.__qualname__.removesuffix(ToVarOperation.__name__) - + new_to_var_operation_name - ) - ToVarOperation.__name__ = new_to_var_operation_name - - _var_subclasses.append(VarSubclassEntry(cls, ToVarOperation, python_types)) - - def __post_init__(self): - """Post-initialize the var. - - Raises: - TypeError: If _js_expr is not a string. - """ - if not isinstance(self._js_expr, str): - msg = f"Expected _js_expr to be a string, got value {self._js_expr!r} of type {type(self._js_expr).__name__}" - raise TypeError(msg) - - if self._var_data is not None and not isinstance(self._var_data, VarData): - msg = f"Expected _var_data to be a VarData, got value {self._var_data!r} of type {type(self._var_data).__name__}" - raise TypeError(msg) - - # Decode any inline Var markup and apply it to the instance - var_data_, js_expr_ = _decode_var_immutable(self._js_expr) - - if var_data_ or js_expr_ != self._js_expr: - self.__init__( - _js_expr=js_expr_, - _var_type=self._var_type, - _var_data=VarData.merge(self._var_data, var_data_), - ) - - def __hash__(self) -> int: - """Define a hash function for the var. - - Returns: - The hash of the var. - """ - return hash((self._js_expr, self._var_type, self._var_data)) - - def _get_all_var_data(self) -> VarData | None: - """Get all VarData associated with the Var. - - Returns: - The VarData of the components and all of its children. - """ - return self._var_data - - def __deepcopy__(self, memo: dict[int, Any]) -> Self: - """Deepcopy the var. - - Args: - memo: The memo dictionary to use for the deepcopy. - - Returns: - A deepcopy of the var. - """ - return self - - def equals(self, other: Var) -> bool: - """Check if two vars are equal. - - Args: - other: The other var to compare. - - Returns: - Whether the vars are equal. - """ - return ( - self._js_expr == other._js_expr - and self._var_type == other._var_type - and self._get_all_var_data() == other._get_all_var_data() - ) - - @overload - def _replace( - self, - _var_type: type[OTHER_VAR_TYPE], - merge_var_data: VarData | None = None, - **kwargs: Any, - ) -> Var[OTHER_VAR_TYPE]: ... - - @overload - def _replace( - self, - _var_type: GenericType | None = None, - merge_var_data: VarData | None = None, - **kwargs: Any, - ) -> Self: ... - - def _replace( - self, - _var_type: GenericType | None = None, - merge_var_data: VarData | None = None, - **kwargs: Any, - ) -> Self | Var: - """Make a copy of this Var with updated fields. - - Args: - _var_type: The new type of the Var. - merge_var_data: VarData to merge into the existing VarData. - **kwargs: Var fields to update. - - Returns: - A new Var with the updated fields overwriting the corresponding fields in this Var. - - Raises: - TypeError: If _var_is_local, _var_is_string, or _var_full_name_needs_state_prefix is not None. - """ - if kwargs.get("_var_is_local", False) is not False: - msg = "The _var_is_local argument is not supported for Var." - raise TypeError(msg) - - if kwargs.get("_var_is_string", False) is not False: - msg = "The _var_is_string argument is not supported for Var." - raise TypeError(msg) - - if kwargs.get("_var_full_name_needs_state_prefix", False) is not False: - msg = "The _var_full_name_needs_state_prefix argument is not supported for Var." - raise TypeError(msg) - value_with_replaced = dataclasses.replace( - self, - _var_type=_var_type or self._var_type, - _var_data=VarData.merge( - kwargs.get("_var_data", self._var_data), merge_var_data - ), - **kwargs, - ) - - if (js_expr := kwargs.get("_js_expr")) is not None: - object.__setattr__(value_with_replaced, "_js_expr", js_expr) - - 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, - ) -> LiteralBooleanVar: ... - - @overload - @classmethod - def create( - cls, - value: int, - _var_data: VarData | None = None, - ) -> LiteralNumberVar[int]: ... - - @overload - @classmethod - def create( - cls, - value: float, - _var_data: VarData | None = None, - ) -> LiteralNumberVar[float]: ... - - @overload - @classmethod - def create( - cls, - value: Decimal, - _var_data: VarData | None = None, - ) -> LiteralNumberVar[Decimal]: ... - - @overload - @classmethod - def create( # pyright: ignore [reportOverlappingOverload] - cls, - value: Color, - _var_data: VarData | None = None, - ) -> LiteralColorVar: ... - - @overload - @classmethod - def create( # pyright: ignore [reportOverlappingOverload] - cls, - value: str, - _var_data: VarData | None = None, - ) -> LiteralStringVar: ... - - @overload - @classmethod - def create( # pyright: ignore [reportOverlappingOverload] - cls, - value: STRING_T, - _var_data: VarData | None = None, - ) -> StringVar[STRING_T]: ... - - @overload - @classmethod - def create( # pyright: ignore[reportOverlappingOverload] - cls, - value: None, - _var_data: VarData | None = None, - ) -> LiteralNoneVar: ... - - @overload - @classmethod - def create( - cls, - value: MAPPING_TYPE, - _var_data: VarData | None = None, - ) -> LiteralObjectVar[MAPPING_TYPE]: ... - - @overload - @classmethod - def create( - cls, - value: SEQUENCE_TYPE, - _var_data: VarData | None = None, - ) -> LiteralArrayVar[SEQUENCE_TYPE]: ... - - @overload - @classmethod - def create( - cls, - value: OTHER_VAR_TYPE, - _var_data: VarData | None = None, - ) -> Var[OTHER_VAR_TYPE]: ... - - @classmethod - def create( - cls, - value: OTHER_VAR_TYPE, - _var_data: VarData | None = None, - ) -> Var[OTHER_VAR_TYPE]: - """Create a var from a value. - - Args: - value: The value to create the var from. - _var_data: Additional hooks and imports associated with the Var. - - Returns: - The var. - """ - # If the value is already a var, do nothing. - if isinstance(value, Var): - return value - - return LiteralVar.create(value, _var_data=_var_data) - - def __format__(self, format_spec: str) -> str: - """Format the var into a Javascript equivalent to an f-string. - - Args: - format_spec: The format specifier (Ignored for now). - - Returns: - The formatted var. - """ - hashed_var = hash(self) - - _global_vars[hashed_var] = self - - # Encode the _var_data into the formatted output for tracking purposes. - return f"{constants.REFLEX_VAR_OPENING_TAG}{hashed_var}{constants.REFLEX_VAR_CLOSING_TAG}{self._js_expr}" - - @overload - def to(self, output: type[str]) -> StringVar: ... # pyright: ignore[reportOverlappingOverload] - - @overload - def to(self, output: type[bool]) -> BooleanVar: ... - - @overload - def to(self, output: type[int]) -> NumberVar[int]: ... - - @overload - def to(self, output: type[float]) -> NumberVar[float]: ... - - @overload - def to(self, output: type[Decimal]) -> NumberVar[Decimal]: ... - - @overload - def to( - self, - output: type[SEQUENCE_TYPE], - ) -> ArrayVar[SEQUENCE_TYPE]: ... - - @overload - def to( - self, - output: type[MAPPING_TYPE], - ) -> ObjectVar[MAPPING_TYPE]: ... - - @overload - def to( - self, output: type[ObjectVar], var_type: type[VAR_INSIDE] - ) -> ObjectVar[VAR_INSIDE]: ... - - @overload - def to( - self, output: type[ObjectVar], var_type: None = None - ) -> ObjectVar[VAR_TYPE]: ... - - @overload - def to(self, output: VAR_SUBCLASS, var_type: None = None) -> VAR_SUBCLASS: ... - - @overload - def to( - self, - output: type[OUTPUT] | types.GenericType, - var_type: types.GenericType | None = None, - ) -> OUTPUT: ... - - def to( - self, - output: type[OUTPUT] | types.GenericType, - var_type: types.GenericType | None = None, - ) -> Var: - """Convert the var to a different type. - - Args: - output: The output type. - var_type: The type of the var. - - Returns: - The converted var. - """ - from .object import ObjectVar - - fixed_output_type = get_origin(output) or output - - # If the first argument is a python type, we map it to the corresponding Var type. - for var_subclass in _var_subclasses[::-1]: - if fixed_output_type in var_subclass.python_types or safe_issubclass( - fixed_output_type, var_subclass.python_types - ): - return self.to(var_subclass.var_subclass, output) - - if fixed_output_type is None: - return get_to_operation(NoneVar).create(self) # pyright: ignore [reportReturnType] - - # Handle fixed_output_type being Base or a dataclass. - if can_use_in_object_var(output): - return self.to(ObjectVar, output) - - if isinstance(output, type): - for var_subclass in _var_subclasses[::-1]: - if safe_issubclass(output, var_subclass.var_subclass): - current_var_type = self._var_type - if current_var_type is Any: - new_var_type = var_type - else: - new_var_type = var_type or current_var_type - return var_subclass.to_var_subclass.create( # pyright: ignore [reportReturnType] - value=self, _var_type=new_var_type - ) - - # If we can't determine the first argument, we just replace the _var_type. - if not safe_issubclass(output, Var) or var_type is None: - return dataclasses.replace( - self, - _var_type=output, - ) - - # We couldn't determine the output type to be any other Var type, so we replace the _var_type. - if var_type is not None: - return dataclasses.replace( - self, - _var_type=var_type, - ) - - return self - - @overload - def guess_type(self: Var[NoReturn]) -> Var[Any]: ... # pyright: ignore [reportOverlappingOverload] - - @overload - def guess_type(self: Var[str]) -> StringVar: ... - - @overload - def guess_type(self: Var[bool]) -> BooleanVar: ... - - @overload - def guess_type(self: Var[int] | Var[float] | Var[int | float]) -> NumberVar: ... - - @overload - def guess_type(self) -> Self: ... - - def guess_type(self) -> Var: - """Guesses the type of the variable based on its `_var_type` attribute. - - Returns: - Var: The guessed type of the variable. - - Raises: - TypeError: If the type is not supported for guessing. - """ - from .object import ObjectVar - - var_type = self._var_type - if var_type is None: - return self.to(None) - if var_type is NoReturn: - return self.to(Any) - - var_type = types.value_inside_optional(var_type) - - if var_type is Any: - return self - - fixed_type = get_origin(var_type) or var_type - - if fixed_type in types.UnionTypes: - inner_types = get_args(var_type) - non_optional_inner_types = [ - types.value_inside_optional(inner_type) for inner_type in inner_types - ] - fixed_inner_types = [ - get_origin(inner_type) or inner_type - for inner_type in non_optional_inner_types - ] - - for var_subclass in _var_subclasses[::-1]: - if all( - safe_issubclass(t, var_subclass.python_types) - for t in fixed_inner_types - ): - return self.to(var_subclass.var_subclass, self._var_type) - - if can_use_in_object_var(var_type): - return self.to(ObjectVar, self._var_type) - - return self - - if fixed_type is Literal: - args = get_args(var_type) - fixed_type = unionize(*(type(arg) for arg in args)) - - if not isinstance(fixed_type, type): - msg = f"Unsupported type {var_type} for guess_type." - raise TypeError(msg) - - if fixed_type is None: - return self.to(None) - - for var_subclass in _var_subclasses[::-1]: - if safe_issubclass(fixed_type, var_subclass.python_types): - return self.to(var_subclass.var_subclass, self._var_type) - - if can_use_in_object_var(fixed_type): - return self.to(ObjectVar, self._var_type) - - return self - - @staticmethod - def _get_setter_name_for_name( - name: str, - ) -> str: - """Get the name of the var's generated setter function. - - Args: - name: The name of the var. - - Returns: - The name of the setter function. - """ - return constants.SETTER_PREFIX + name - - def _get_setter(self, name: str) -> Callable[[BaseState, Any], None]: - """Get the var's setter function. - - Args: - name: The name of the var. - - Returns: - A function that that creates a setter for the var. - """ - setter_name = Var._get_setter_name_for_name(name) - - def setter(state: Any, value: Any): - """Get the setter for the var. - - Args: - state: The state within which we add the setter function. - value: The value to set. - """ - if self._var_type in [int, float]: - try: - value = self._var_type(value) - setattr(state, name, value) - except ValueError: - console.debug( - f"{type(state).__name__}.{self._js_expr}: Failed conversion of {value!s} to '{self._var_type.__name__}'. Value not set.", - ) - else: - setattr(state, name, value) - - setter.__annotations__["value"] = self._var_type - - setter.__qualname__ = setter_name - - return setter - - def _var_set_state(self, state: type[BaseState] | str) -> Self: - """Set the state of the var. - - Args: - state: The state to set. - - Returns: - The var with the state set. - """ - formatted_state_name = ( - state - if isinstance(state, str) - else format_state_name(state.get_full_name()) - ) - - return StateOperation.create( # pyright: ignore [reportReturnType] - formatted_state_name, - self, - _var_data=VarData.merge( - VarData.from_state(state, self._js_expr), self._var_data - ), - ).guess_type() - - def __eq__(self, other: Var | Any) -> BooleanVar: - """Check if the current variable is equal to the given variable. - - Args: - other (Var | Any): The variable to compare with. - - Returns: - BooleanVar: A BooleanVar object representing the result of the equality check. - """ - from .number import equal_operation - - return equal_operation(self, other) - - def __ne__(self, other: Var | Any) -> BooleanVar: - """Check if the current object is not equal to the given object. - - Parameters: - other (Var | Any): The object to compare with. - - Returns: - BooleanVar: A BooleanVar object representing the result of the comparison. - """ - from .number import equal_operation - - return ~equal_operation(self, other) - - def bool(self) -> BooleanVar: - """Convert the var to a boolean. - - Returns: - The boolean var. - """ - from .number import boolify - - return boolify(self) - - def is_none(self) -> BooleanVar: - """Check if the var is None. - - Returns: - A BooleanVar object representing the result of the check. - """ - from .number import is_not_none_operation - - return ~is_not_none_operation(self) - - def is_not_none(self) -> BooleanVar: - """Check if the var is not None. - - Returns: - A BooleanVar object representing the result of the check. - """ - from .number import is_not_none_operation - - return is_not_none_operation(self) - - def __and__( - self, other: Var[OTHER_VAR_TYPE] | Any - ) -> Var[VAR_TYPE | OTHER_VAR_TYPE]: - """Perform a logical AND operation on the current instance and another variable. - - Args: - other: The variable to perform the logical AND operation with. - - Returns: - A `BooleanVar` object representing the result of the logical AND operation. - """ - return and_operation(self, other) - - def __rand__( - self, other: Var[OTHER_VAR_TYPE] | Any - ) -> Var[VAR_TYPE | OTHER_VAR_TYPE]: - """Perform a logical AND operation on the current instance and another variable. - - Args: - other: The variable to perform the logical AND operation with. - - Returns: - A `BooleanVar` object representing the result of the logical AND operation. - """ - return and_operation(other, self) - - def __or__( - self, other: Var[OTHER_VAR_TYPE] | Any - ) -> Var[VAR_TYPE | OTHER_VAR_TYPE]: - """Perform a logical OR operation on the current instance and another variable. - - Args: - other: The variable to perform the logical OR operation with. - - Returns: - A `BooleanVar` object representing the result of the logical OR operation. - """ - return or_operation(self, other) - - def __ror__( - self, other: Var[OTHER_VAR_TYPE] | Any - ) -> Var[VAR_TYPE | OTHER_VAR_TYPE]: - """Perform a logical OR operation on the current instance and another variable. - - Args: - other: The variable to perform the logical OR operation with. - - Returns: - A `BooleanVar` object representing the result of the logical OR operation. - """ - return or_operation(other, self) - - def __invert__(self) -> BooleanVar: - """Perform a logical NOT operation on the current instance. - - Returns: - A `BooleanVar` object representing the result of the logical NOT operation. - """ - return ~self.bool() - - def to_string(self, use_json: bool = True) -> StringVar: - """Convert the var to a string. - - Args: - use_json: Whether to use JSON stringify. If False, uses Object.prototype.toString. - - Returns: - The string var. - """ - from .function import JSON_STRINGIFY, PROTOTYPE_TO_STRING - from .sequence import StringVar - - return ( - JSON_STRINGIFY.call(self).to(StringVar) - if use_json - else PROTOTYPE_TO_STRING.call(self).to(StringVar) - ) - - def _as_ref(self) -> Var: - """Get a reference to the var. - - Returns: - The reference to the var. - """ - return Var( - _js_expr=f"refs[{Var.create(str(self))}]", - _var_data=VarData( - imports={ - f"$/{constants.Dirs.STATE_PATH}": [imports.ImportVar(tag="refs")] - } - ), - ).to(str) - - def js_type(self) -> StringVar: - """Returns the javascript type of the object. - - This method uses the `typeof` function from the `FunctionStringVar` class - to determine the type of the object. - - Returns: - StringVar: A string variable representing the type of the object. - """ - from .function import FunctionStringVar - from .sequence import StringVar - - type_of = FunctionStringVar("typeof") - return type_of.call(self).to(StringVar) - - def _without_data(self): - """Create a copy of the var without the data. - - Returns: - The var without the data. - """ - return dataclasses.replace(self, _var_data=None) - - def _decode(self) -> Any: - """Decode Var as a python value. - - Note that Var with state set cannot be decoded python-side and will be - returned as full_name. - - Returns: - The decoded value or the Var name. - """ - if isinstance(self, LiteralVar): - return self._var_value - try: - return json.loads(str(self)) - except ValueError: - return str(self) - - @property - def _var_state(self) -> str: - """Compat method for getting the state. - - Returns: - The state name associated with the var. - """ - var_data = self._get_all_var_data() - return var_data.state if var_data else "" - - @overload - @classmethod - def range(cls, stop: int | NumberVar, /) -> ArrayVar[Sequence[int]]: ... - - @overload - @classmethod - def range( - cls, - start: int | NumberVar, - end: int | NumberVar, - step: int | NumberVar = 1, - /, - ) -> ArrayVar[Sequence[int]]: ... - - @classmethod - def range( - cls, - first_endpoint: int | NumberVar, - second_endpoint: int | NumberVar | None = None, - step: int | NumberVar | None = None, - ) -> ArrayVar[Sequence[int]]: - """Create a range of numbers. - - Args: - first_endpoint: The end of the range if second_endpoint is not provided, otherwise the start of the range. - second_endpoint: The end of the range. - step: The step of the range. - - Returns: - The range of numbers. - """ - from .sequence import ArrayVar - - return ArrayVar.range(first_endpoint, second_endpoint, step) - - if not TYPE_CHECKING: - - def __getitem__(self, key: Any) -> Var: - """Get the item from the var. - - Args: - key: The key to get. - - Raises: - UntypedVarError: If the var type is Any. - TypeError: If the var type is Any. - - # noqa: DAR101 self - """ - if self._var_type is Any: - raise exceptions.UntypedVarError( - self, - f"access the item '{key}'", - ) - msg = f"Var of type {self._var_type} does not support item access." - raise TypeError(msg) - - def __getattr__(self, name: str): - """Get an attribute of the var. - - Args: - name: The name of the attribute. - - Raises: - VarAttributeError: If the attribute does not exist. - UntypedVarError: If the var type is Any. - TypeError: If the var type is Any. - - # noqa: DAR101 self - """ - if name.startswith("_"): - msg = f"Attribute {name} not found." - raise VarAttributeError(msg) - - if name == "contains": - msg = f"Var of type {self._var_type} does not support contains check." - raise TypeError(msg) - if name == "reverse": - msg = "Cannot reverse non-list var." - raise TypeError(msg) - - if self._var_type is Any: - raise exceptions.UntypedVarError( - self, - f"access the attribute '{name}'", - ) - - msg = f"The State var {escape(self._js_expr)} of type {escape(str(self._var_type))} has no attribute '{name}' or may have been annotated wrongly." - raise VarAttributeError(msg) - - def __bool__(self) -> bool: - """Raise exception if using Var in a boolean context. - - Raises: - VarTypeError: when attempting to bool-ify the Var. - - # noqa: DAR101 self - """ - msg = ( - f"Cannot convert Var {str(self)!r} to bool for use with `if`, `and`, `or`, and `not`. " - "Instead use `rx.cond` and bitwise operators `&` (and), `|` (or), `~` (invert)." - ) - raise VarTypeError(msg) - - def __iter__(self) -> Any: - """Raise exception if using Var in an iterable context. - - Raises: - VarTypeError: when attempting to iterate over the Var. - - # noqa: DAR101 self - """ - msg = f"Cannot iterate over Var {str(self)!r}. Instead use `rx.foreach`." - raise VarTypeError(msg) - - def __contains__(self, _: Any) -> Var: - """Override the 'in' operator to alert the user that it is not supported. - - Raises: - VarTypeError: the operation is not supported - - # noqa: DAR101 self - """ - msg = ( - "'in' operator not supported for Var types, use Var.contains() instead." - ) - raise VarTypeError(msg) - - -OUTPUT = TypeVar("OUTPUT", bound=Var) - -VAR_SUBCLASS = TypeVar("VAR_SUBCLASS", bound=Var) -VAR_INSIDE = TypeVar("VAR_INSIDE") - - -class ToOperation: - """A var operation that converts a var to another type.""" - - def __getattr__(self, name: str) -> Any: - """Get an attribute of the var. - - Args: - name: The name of the attribute. - - Returns: - The attribute of the var. - """ - from .object import ObjectVar - - if isinstance(self, ObjectVar) and name != "_js_expr": - return ObjectVar.__getattr__(self, name) - return getattr(self._original, name) - - def __post_init__(self): - """Post initialization.""" - object.__delattr__(self, "_js_expr") - - def __hash__(self) -> int: - """Calculate the hash value of the object. - - Returns: - int: The hash value of the object. - """ - return hash(self._original) - - def _get_all_var_data(self) -> VarData | None: - """Get all the var data. - - Returns: - The var data. - """ - return VarData.merge( - self._original._get_all_var_data(), - self._var_data, - ) - - @classmethod - def create( - cls, - value: Var, - _var_type: GenericType | None = None, - _var_data: VarData | None = None, - ): - """Create a ToOperation. - - Args: - value: The value of the var. - _var_type: The type of the Var. - _var_data: Additional hooks and imports associated with the Var. - - Returns: - The ToOperation. - """ - return cls( - _js_expr="", # pyright: ignore [reportCallIssue] - _var_data=_var_data, # pyright: ignore [reportCallIssue] - _var_type=_var_type or cls._default_var_type, # pyright: ignore [reportCallIssue, reportAttributeAccessIssue] - _original=value, # pyright: ignore [reportCallIssue] - ) - - -class LiteralVar(Var): - """Base class for immutable literal vars.""" - - def __init_subclass__(cls, **kwargs): - """Initialize the subclass. - - Args: - **kwargs: Additional keyword arguments. - - Raises: - TypeError: If the LiteralVar subclass does not have a corresponding Var subclass. - """ - super().__init_subclass__(**kwargs) - - bases = cls.__bases__ - - bases_normalized = [ - base if isinstance(base, type) else get_origin(base) for base in bases - ] - - possible_bases = [ - base - for base in bases_normalized - if safe_issubclass(base, Var) and base != LiteralVar - ] - - if not possible_bases: - msg = f"LiteralVar subclass {cls} must have a base class that is a subclass of Var and not LiteralVar." - raise TypeError(msg) - - var_subclasses = [ - var_subclass - for var_subclass in _var_subclasses - if var_subclass.var_subclass in possible_bases - ] - - if not var_subclasses: - msg = f"LiteralVar {cls} must have a base class annotated with `python_types`." - raise TypeError(msg) - - if len(var_subclasses) != 1: - msg = f"LiteralVar {cls} must have exactly one base class annotated with `python_types`." - raise TypeError(msg) - - var_subclass = var_subclasses[0] - - # Remove the old subclass, happens because __init_subclass__ is called twice - # for each subclass. This is because of __slots__ in dataclasses. - for var_literal_subclass in list(_var_literal_subclasses): - if var_literal_subclass[1] is var_subclass: - _var_literal_subclasses.remove(var_literal_subclass) - - _var_literal_subclasses.append((cls, var_subclass)) - - @classmethod - def _create_literal_var( - cls, - value: Any, - _var_data: VarData | None = None, - ) -> Var: - """Create a var from a value. - - Args: - value: The value to create the var from. - _var_data: Additional hooks and imports associated with the Var. - - Returns: - The var. - - Raises: - TypeError: If the value is not a supported type for LiteralVar. - """ - from .object import LiteralObjectVar - from .sequence import ArrayVar, LiteralStringVar - - if isinstance(value, Var): - if _var_data is None: - return value - return value._replace(merge_var_data=_var_data) - - for literal_subclass, var_subclass in _var_literal_subclasses[::-1]: - if isinstance(value, var_subclass.python_types): - return literal_subclass.create(value, _var_data=_var_data) - - if ( - (as_var_method := getattr(value, "_as_var", None)) is not None - and callable(as_var_method) - and isinstance((resulting_var := as_var_method()), Var) - ): - return resulting_var - - from reflex.event import EventHandler - from reflex.utils.format import get_event_handler_parts - - if isinstance(value, EventHandler): - return Var(_js_expr=".".join(filter(None, get_event_handler_parts(value)))) - - serialized_value = serializers.serialize(value) - if serialized_value is not None: - if isinstance(serialized_value, Mapping): - return LiteralObjectVar.create( - serialized_value, - _var_type=type(value), - _var_data=_var_data, - ) - if isinstance(serialized_value, str): - return LiteralStringVar.create( - serialized_value, _var_type=type(value), _var_data=_var_data - ) - return LiteralVar.create(serialized_value, _var_data=_var_data) - - if dataclasses.is_dataclass(value) and not isinstance(value, type): - return LiteralObjectVar.create( - { - k.name: (None if callable(v := getattr(value, k.name)) else v) - for k in dataclasses.fields(value) - }, - _var_type=type(value), - _var_data=_var_data, - ) - - if isinstance(value, range): - return ArrayVar.range(value.start, value.stop, value.step) - - msg = f"Unsupported type {type(value)} for LiteralVar. Tried to create a LiteralVar from {value}." - raise TypeError(msg) - - if not TYPE_CHECKING: - create = _create_literal_var - - def __post_init__(self): - """Post-initialize the var.""" - - @classmethod - def _get_all_var_data_without_creating_var( - cls, - value: Any, - ) -> VarData | None: - return cls.create(value)._get_all_var_data() - - @classmethod - def _get_all_var_data_without_creating_var_dispatch( - cls, - value: Any, - ) -> VarData | None: - """Get all the var data without creating a var. - - Args: - value: The value to get the var data from. - - Returns: - The var data or None. - - Raises: - TypeError: If the value is not a supported type for LiteralVar. - """ - from .object import LiteralObjectVar - from .sequence import LiteralStringVar - - if isinstance(value, Var): - return value._get_all_var_data() - - for literal_subclass, var_subclass in _var_literal_subclasses[::-1]: - if isinstance(value, var_subclass.python_types): - return literal_subclass._get_all_var_data_without_creating_var(value) - - if ( - (as_var_method := getattr(value, "_as_var", None)) is not None - and callable(as_var_method) - and isinstance((resulting_var := as_var_method()), Var) - ): - return resulting_var._get_all_var_data() - - from reflex.event import EventHandler - from reflex.utils.format import get_event_handler_parts - - if isinstance(value, EventHandler): - return Var( - _js_expr=".".join(filter(None, get_event_handler_parts(value))) - )._get_all_var_data() - - serialized_value = serializers.serialize(value) - if serialized_value is not None: - if isinstance(serialized_value, Mapping): - return LiteralObjectVar._get_all_var_data_without_creating_var( - serialized_value - ) - if isinstance(serialized_value, str): - return LiteralStringVar._get_all_var_data_without_creating_var( - serialized_value - ) - return LiteralVar._get_all_var_data_without_creating_var_dispatch( - serialized_value - ) - - if dataclasses.is_dataclass(value) and not isinstance(value, type): - return LiteralObjectVar._get_all_var_data_without_creating_var({ - k.name: (None if callable(v := getattr(value, k.name)) else v) - for k in dataclasses.fields(value) - }) - - if isinstance(value, range): - return None - - msg = f"Unsupported type {type(value)} for LiteralVar. Tried to create a LiteralVar from {value}." - raise TypeError(msg) - - @property - def _var_value(self) -> Any: - msg = "LiteralVar subclasses must implement the _var_value property." - raise NotImplementedError(msg) - - def json(self) -> str: - """Serialize the var to a JSON string. - - Raises: - NotImplementedError: If the method is not implemented. - """ - msg = "LiteralVar subclasses must implement the json method." - raise NotImplementedError(msg) - - -@serializers.serializer -def serialize_literal(value: LiteralVar): - """Serialize a Literal type. - - Args: - value: The Literal to serialize. - - Returns: - The serialized Literal. - """ - return value._var_value - - -def get_python_literal(value: LiteralVar | Any) -> Any | None: - """Get the Python literal value. - - Args: - value: The value to get the Python literal value of. - - Returns: - The Python literal value. - """ - if isinstance(value, LiteralVar): - return value._var_value - if isinstance(value, Var): - return None - return value - - -P = ParamSpec("P") -T = TypeVar("T") - - -# NoReturn is used to match CustomVarOperationReturn with no type hint. -@overload -def var_operation( # pyright: ignore [reportOverlappingOverload] - func: Callable[P, CustomVarOperationReturn[NoReturn]], -) -> Callable[P, Var]: ... - - -@overload -def var_operation( - func: Callable[P, CustomVarOperationReturn[None]], -) -> Callable[P, NoneVar]: ... - - -@overload -def var_operation( # pyright: ignore [reportOverlappingOverload] - func: Callable[P, CustomVarOperationReturn[bool]] - | Callable[P, CustomVarOperationReturn[bool | None]], -) -> Callable[P, BooleanVar]: ... - - -NUMBER_T = TypeVar("NUMBER_T", int, float, int | float) - - -@overload -def var_operation( - func: Callable[P, CustomVarOperationReturn[NUMBER_T]] - | Callable[P, CustomVarOperationReturn[NUMBER_T | None]], -) -> Callable[P, NumberVar[NUMBER_T]]: ... - - -@overload -def var_operation( - func: Callable[P, CustomVarOperationReturn[str]] - | Callable[P, CustomVarOperationReturn[str | None]], -) -> Callable[P, StringVar]: ... - - -LIST_T = TypeVar("LIST_T", bound=Sequence) - - -@overload -def var_operation( - func: Callable[P, CustomVarOperationReturn[LIST_T]] - | Callable[P, CustomVarOperationReturn[LIST_T | None]], -) -> Callable[P, ArrayVar[LIST_T]]: ... - - -OBJECT_TYPE = TypeVar("OBJECT_TYPE", bound=Mapping) - - -@overload -def var_operation( - func: Callable[P, CustomVarOperationReturn[OBJECT_TYPE]] - | Callable[P, CustomVarOperationReturn[OBJECT_TYPE | None]], -) -> Callable[P, ObjectVar[OBJECT_TYPE]]: ... - - -@overload -def var_operation( - func: Callable[P, CustomVarOperationReturn[T]] - | Callable[P, CustomVarOperationReturn[T | None]], -) -> Callable[P, Var[T]]: ... - - -def var_operation( # pyright: ignore [reportInconsistentOverload] - func: Callable[P, CustomVarOperationReturn[T]], -) -> Callable[P, Var[T]]: - """Decorator for creating a var operation. - - Example: - ```python - @var_operation - def add(a: NumberVar, b: NumberVar): - return custom_var_operation(f"{a} + {b}") - ``` - - Args: - func: The function to decorate. - - Returns: - The decorated function. - """ - func_args = list(inspect.signature(func).parameters) - - @functools.wraps(func) - def wrapper(*args: P.args, **kwargs: P.kwargs) -> Var[T]: - args_vars = { - func_args[i]: (LiteralVar.create(arg) if not isinstance(arg, Var) else arg) - for i, arg in enumerate(args) - } - kwargs_vars = { - key: LiteralVar.create(value) if not isinstance(value, Var) else value - for key, value in kwargs.items() - } - - return CustomVarOperation.create( - name=func.__name__, - args=tuple(list(args_vars.items()) + list(kwargs_vars.items())), - return_var=func(*args_vars.values(), **kwargs_vars), # pyright: ignore [reportCallIssue, reportReturnType] - ).guess_type() - - return wrapper - - -def figure_out_type(value: Any) -> types.GenericType: - """Figure out the type of the value. - - Args: - value: The value to figure out the type of. - - Returns: - The type of the value. - """ - if isinstance(value, (list, set, tuple, Mapping, Var)): - if isinstance(value, Var): - return value._var_type - if has_args(value_type := type(value)): - return value_type - if isinstance(value, list): - if not value: - return Sequence[NoReturn] - return Sequence[unionize(*{figure_out_type(v) for v in value[:100]})] - if isinstance(value, set): - return set[unionize(*{figure_out_type(v) for v in value})] - if isinstance(value, tuple): - if not value: - return tuple[NoReturn, ...] - if len(value) <= 5: - return tuple[tuple(figure_out_type(v) for v in value)] - return tuple[unionize(*{figure_out_type(v) for v in value[:100]}), ...] - if isinstance(value, Mapping): - if not value: - return Mapping[NoReturn, NoReturn] - return Mapping[ - unionize(*{figure_out_type(k) for k in list(value.keys())[:100]}), - unionize(*{figure_out_type(v) for v in list(value.values())[:100]}), - ] - return type(value) - - -GLOBAL_CACHE = {} - - -class cached_property: # noqa: N801 - """A cached property that caches the result of the function.""" - - def __init__(self, func: Callable): - """Initialize the cached_property. - - Args: - func: The function to cache. - """ - self._func = func - self._attrname = None - - def __set_name__(self, owner: Any, name: str): - """Set the name of the cached property. - - Args: - owner: The owner of the cached property. - name: The name of the cached property. - - Raises: - TypeError: If the cached property is assigned to two different names. - """ - if self._attrname is None: - self._attrname = name - - original_del = getattr(owner, "__del__", None) - - def delete_property(this: Any): - """Delete the cached property. - - Args: - this: The object to delete the cached property from. - """ - cached_field_name = "_reflex_cache_" + name - try: - unique_id = object.__getattribute__(this, cached_field_name) - except AttributeError: - if original_del is not None: - original_del(this) - return - GLOBAL_CACHE.pop(unique_id, None) - - if original_del is not None: - original_del(this) - - owner.__del__ = delete_property - - elif name != self._attrname: - msg = ( - "Cannot assign the same cached_property to two different names " - f"({self._attrname!r} and {name!r})." - ) - raise TypeError(msg) - - def __get__(self, instance: Any, owner: type | None = None): - """Get the cached property. - - Args: - instance: The instance to get the cached property from. - owner: The owner of the cached property. - - Returns: - The cached property. - - Raises: - TypeError: If the class does not have __set_name__. - """ - if self._attrname is None: - msg = "Cannot use cached_property on a class without __set_name__." - raise TypeError(msg) - cached_field_name = "_reflex_cache_" + self._attrname - try: - unique_id = object.__getattribute__(instance, cached_field_name) - except AttributeError: - unique_id = uuid.uuid4().int - object.__setattr__(instance, cached_field_name, unique_id) - if unique_id not in GLOBAL_CACHE: - GLOBAL_CACHE[unique_id] = self._func(instance) - return GLOBAL_CACHE[unique_id] - - -cached_property_no_lock = cached_property - - -class VarProtocol(Protocol): - """A protocol for Var.""" - - __dataclass_fields__: ClassVar[dict[str, dataclasses.Field[Any]]] - - @property - def _js_expr(self) -> str: ... - - @property - def _var_type(self) -> types.GenericType: ... - - @property - def _var_data(self) -> VarData: ... - - -class CachedVarOperation: - """Base class for cached var operations to lower boilerplate code.""" - - def __post_init__(self): - """Post-initialize the CachedVarOperation.""" - object.__delattr__(self, "_js_expr") - - def __getattr__(self, name: str) -> Any: - """Get an attribute of the var. - - Args: - name: The name of the attribute. - - Returns: - The attribute. - """ - if name == "_js_expr": - return self._cached_var_name - - parent_classes = inspect.getmro(type(self)) - - next_class = parent_classes[parent_classes.index(CachedVarOperation) + 1] - - return next_class.__getattr__(self, name) - - def _get_all_var_data(self) -> VarData | None: - """Get all VarData associated with the Var. - - Returns: - The VarData of the components and all of its children. - """ - return self._cached_get_all_var_data - - @cached_property_no_lock - def _cached_get_all_var_data(self: VarProtocol) -> VarData | None: - """Get the cached VarData. - - Returns: - The cached VarData. - """ - return VarData.merge( - *( - value._get_all_var_data() if isinstance(value, Var) else None - for value in ( - getattr(self, field.name) for field in dataclasses.fields(self) - ) - ), - self._var_data, - ) - - def __hash__(self: DataclassInstance) -> int: - """Calculate the hash of the object. - - Returns: - The hash of the object. - """ - return hash(( - type(self).__name__, - *[ - getattr(self, field.name) - for field in dataclasses.fields(self) - if field.name not in ["_js_expr", "_var_data", "_var_type"] - ], - )) - - -def and_operation( - a: Var[VAR_TYPE] | Any, b: Var[OTHER_VAR_TYPE] | Any -) -> Var[VAR_TYPE | OTHER_VAR_TYPE]: - """Perform a logical AND operation on two variables. - - Args: - a: The first variable. - b: The second variable. - - Returns: - The result of the logical AND operation. - """ - return _and_operation(a, b) - - -@var_operation -def _and_operation(a: Var, b: Var): - """Perform a logical AND operation on two variables. - - Args: - a: The first variable. - b: The second variable. - - Returns: - The result of the logical AND operation. - """ - return var_operation_return( - js_expression=f"({a} && {b})", - var_type=unionize(a._var_type, b._var_type), - ) - - -def or_operation( - a: Var[VAR_TYPE] | Any, b: Var[OTHER_VAR_TYPE] | Any -) -> Var[VAR_TYPE | OTHER_VAR_TYPE]: - """Perform a logical OR operation on two variables. - - Args: - a: The first variable. - b: The second variable. - - Returns: - The result of the logical OR operation. - """ - return _or_operation(a, b) - - -@var_operation -def _or_operation(a: Var, b: Var): - """Perform a logical OR operation on two variables. - - Args: - a: The first variable. - b: The second variable. - - Returns: - The result of the logical OR operation. - """ - return var_operation_return( - js_expression=f"({a} || {b})", - var_type=unionize(a._var_type, b._var_type), - ) - - -RETURN_TYPE = TypeVar("RETURN_TYPE") - -DICT_KEY = TypeVar("DICT_KEY") -DICT_VAL = TypeVar("DICT_VAL") - -LIST_INSIDE = TypeVar("LIST_INSIDE") - - -class FakeComputedVarBaseClass(property): - """A fake base class for ComputedVar to avoid inheriting from property.""" - - __pydantic_run_validation__ = False - - -def is_computed_var(obj: Any) -> TypeGuard[ComputedVar]: - """Check if the object is a ComputedVar. - - Args: - obj: The object to check. - - Returns: - Whether the object is a ComputedVar. - """ - return isinstance(obj, FakeComputedVarBaseClass) - - -@dataclasses.dataclass( - eq=False, - frozen=True, - slots=True, -) -class ComputedVar(Var[RETURN_TYPE]): - """A field with computed getters.""" - - # Whether to track dependencies and cache computed values - _cache: bool = dataclasses.field(default=False) - - # Whether the computed var is a backend var - _backend: bool = dataclasses.field(default=False) - - # The initial value of the computed var - _initial_value: RETURN_TYPE | types.Unset = dataclasses.field(default=types.Unset()) - - # Explicit var dependencies to track - _static_deps: dict[str | None, set[str]] = dataclasses.field(default_factory=dict) - - # Whether var dependencies should be auto-determined - _auto_deps: bool = dataclasses.field(default=True) - - # Interval at which the computed var should be updated - _update_interval: datetime.timedelta | None = dataclasses.field(default=None) - - _fget: Callable[[BaseState], RETURN_TYPE] = dataclasses.field( - default_factory=lambda: lambda _: None - ) # pyright: ignore [reportAssignmentType] - - _name: str = dataclasses.field(default="") - - def __init__( - self, - fget: Callable[[BASE_STATE], RETURN_TYPE], - initial_value: RETURN_TYPE | types.Unset = types.Unset(), - cache: bool = True, - deps: list[str | Var] | None = None, - auto_deps: bool = True, - interval: int | datetime.timedelta | None = None, - backend: bool | None = None, - **kwargs, - ): - """Initialize a ComputedVar. - - Args: - fget: The getter function. - initial_value: The initial value of the computed var. - cache: Whether to cache the computed value. - deps: Explicit var dependencies to track. - auto_deps: Whether var dependencies should be auto-determined. - interval: Interval at which the computed var should be updated. - backend: Whether the computed var is a backend var. - **kwargs: additional attributes to set on the instance - - Raises: - TypeError: If the computed var dependencies are not Var instances or var names. - UntypedComputedVarError: If the computed var is untyped. - """ - hint = kwargs.pop("return_type", None) or get_type_hints(fget).get( - "return", Any - ) - - if hint is Any: - raise UntypedComputedVarError(var_name=fget.__name__) - is_using_fget_name = "_js_expr" not in kwargs - js_expr = kwargs.pop("_js_expr", fget.__name__ + FIELD_MARKER) - kwargs.setdefault("_var_type", hint) - - Var.__init__( - self, - _js_expr=js_expr, - _var_type=kwargs.pop("_var_type"), - _var_data=kwargs.pop( - "_var_data", - VarData(field_name=fget.__name__) if is_using_fget_name else None, - ), - ) - - if kwargs: - msg = f"Unexpected keyword arguments: {tuple(kwargs)}" - raise TypeError(msg) - - if backend is None: - backend = fget.__name__.startswith("_") - - object.__setattr__(self, "_backend", backend) - object.__setattr__(self, "_initial_value", initial_value) - object.__setattr__(self, "_cache", cache) - object.__setattr__(self, "_name", fget.__name__) - - if isinstance(interval, int): - interval = datetime.timedelta(seconds=interval) - - object.__setattr__(self, "_update_interval", interval) - - object.__setattr__( - self, - "_static_deps", - self._calculate_static_deps(deps), - ) - object.__setattr__(self, "_auto_deps", auto_deps) - - object.__setattr__(self, "_fget", fget) - - def _calculate_static_deps( - self, - deps: list[str | Var] | dict[str | None, set[str]] | None = None, - ) -> dict[str | None, set[str]]: - """Calculate the static dependencies of the computed var from user input or existing dependencies. - - Args: - deps: The user input dependencies or existing dependencies. - - Returns: - The static dependencies. - """ - if isinstance(deps, dict): - # Assume a dict is coming from _replace, so no special processing. - return deps - static_deps = {} - if deps is not None: - for dep in deps: - static_deps = self._add_static_dep(dep, static_deps) - return static_deps - - def _add_static_dep( - self, dep: str | Var, deps: dict[str | None, set[str]] | None = None - ) -> dict[str | None, set[str]]: - """Add a static dependency to the computed var or existing dependency set. - - Args: - dep: The dependency to add. - deps: The existing dependency set. - - Returns: - The updated dependency set. - - Raises: - TypeError: If the computed var dependencies are not Var instances or var names. - """ - if deps is None: - deps = self._static_deps - if isinstance(dep, Var): - state_name = ( - all_var_data.state - if (all_var_data := dep._get_all_var_data()) and all_var_data.state - else None - ) - if all_var_data is not None: - var_name = all_var_data.field_name - else: - var_name = dep._js_expr - deps.setdefault(state_name, set()).add(var_name) - elif isinstance(dep, str) and dep != "": - deps.setdefault(None, set()).add(dep) - else: - msg = "ComputedVar dependencies must be Var instances or var names (non-empty strings)." - raise TypeError(msg) - return deps - - @override - def _replace( - self, - merge_var_data: VarData | None = None, - **kwargs: Any, - ) -> Self: - """Replace the attributes of the ComputedVar. - - Args: - merge_var_data: VarData to merge into the existing VarData. - **kwargs: Var fields to update. - - Returns: - The new ComputedVar instance. - - Raises: - TypeError: If kwargs contains keys that are not allowed. - """ - if "deps" in kwargs: - kwargs["deps"] = self._calculate_static_deps(kwargs["deps"]) - field_values = { - "fget": kwargs.pop("fget", self._fget), - "initial_value": kwargs.pop("initial_value", self._initial_value), - "cache": kwargs.pop("cache", self._cache), - "deps": kwargs.pop("deps", copy.copy(self._static_deps)), - "auto_deps": kwargs.pop("auto_deps", self._auto_deps), - "interval": kwargs.pop("interval", self._update_interval), - "backend": kwargs.pop("backend", self._backend), - "_js_expr": kwargs.pop("_js_expr", self._js_expr), - "_var_type": kwargs.pop("_var_type", self._var_type), - "_var_data": kwargs.pop( - "_var_data", VarData.merge(self._var_data, merge_var_data) - ), - "return_type": kwargs.pop("return_type", self._var_type), - } - - if kwargs: - unexpected_kwargs = ", ".join(kwargs.keys()) - msg = f"Unexpected keyword arguments: {unexpected_kwargs}" - raise TypeError(msg) - - return type(self)(**field_values) - - @property - def _cache_attr(self) -> str: - """Get the attribute used to cache the value on the instance. - - Returns: - An attribute name. - """ - return f"__cached_{self._js_expr}" - - @property - def _last_updated_attr(self) -> str: - """Get the attribute used to store the last updated timestamp. - - Returns: - An attribute name. - """ - return f"__last_updated_{self._js_expr}" - - def needs_update(self, instance: BaseState) -> bool: - """Check if the computed var needs to be updated. - - Args: - instance: The state instance that the computed var is attached to. - - Returns: - True if the computed var needs to be updated, False otherwise. - """ - if self._update_interval is None: - return False - last_updated = getattr(instance, self._last_updated_attr, None) - if last_updated is None: - return True - return datetime.datetime.now() - last_updated > self._update_interval - - @overload - def __get__( - self: ComputedVar[bool], - instance: None, - owner: type, - ) -> BooleanVar: ... - - @overload - def __get__( - self: ComputedVar[int] | ComputedVar[float], - instance: None, - owner: type, - ) -> NumberVar: ... - - @overload - def __get__( - self: ComputedVar[str], - instance: None, - owner: type, - ) -> StringVar: ... - - @overload - def __get__( - self: ComputedVar[MAPPING_TYPE], - instance: None, - owner: type, - ) -> ObjectVar[MAPPING_TYPE]: ... - - @overload - def __get__( - self: ComputedVar[list[LIST_INSIDE]], - instance: None, - owner: type, - ) -> ArrayVar[list[LIST_INSIDE]]: ... - - @overload - def __get__( - self: ComputedVar[tuple[LIST_INSIDE, ...]], - instance: None, - owner: type, - ) -> ArrayVar[tuple[LIST_INSIDE, ...]]: ... - - @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]: ... - - @overload - def __get__(self, instance: BaseState, owner: type) -> RETURN_TYPE: ... - - def __get__(self, instance: BaseState | None, owner: type): - """Get the ComputedVar value. - - If the value is already cached on the instance, return the cached value. - - Args: - instance: the instance of the class accessing this computed var. - owner: the class that this descriptor is attached to. - - Returns: - The value of the var for the given instance. - """ - if instance is None: - state_where_defined = owner - while self._name in state_where_defined.inherited_vars: - state_where_defined = state_where_defined.get_parent_state() - - field_name = ( - format_state_name(state_where_defined.get_full_name()) - + "." - + self._js_expr - ) - - return dispatch( - field_name, - var_data=VarData.from_state(state_where_defined, self._name), - result_var_type=self._var_type, - existing_var=self, - ) - - if not self._cache: - value = self.fget(instance) - else: - # handle caching - if not hasattr(instance, self._cache_attr) or self.needs_update(instance): - # Set cache attr on state instance. - setattr(instance, self._cache_attr, self.fget(instance)) - # Ensure the computed var gets serialized to redis. - instance._was_touched = True - # Set the last updated timestamp on the state instance. - setattr(instance, self._last_updated_attr, datetime.datetime.now()) - value = getattr(instance, self._cache_attr) - - self._check_deprecated_return_type(instance, value) - - return value - - def _check_deprecated_return_type(self, instance: BaseState, value: Any) -> None: - if not _isinstance(value, self._var_type, nested=1, treat_var_as_type=False): - console.error( - f"Computed var '{type(instance).__name__}.{self._name}' must return" - f" a value of type '{escape(str(self._var_type))}', got '{value!s}' of type {type(value)}." - ) - - def _deps( - self, - objclass: type[BaseState], - obj: FunctionType | CodeType | None = None, - ) -> dict[str, set[str]]: - """Determine var dependencies of this ComputedVar. - - Save references to attributes accessed on "self" or other fetched states. - - Recursively called when the function makes a method call on "self" or - define comprehensions or nested functions that may reference "self". - - Args: - objclass: the class obj this ComputedVar is attached to. - obj: the object to disassemble (defaults to the fget function). - - Returns: - A dictionary mapping state names to the set of variable names - accessed by the given obj. - """ - from .dep_tracking import DependencyTracker - - d = {} - if self._static_deps: - d.update(self._static_deps) - # None is a placeholder for the current state class. - if None in d: - d[objclass.get_full_name()] = d.pop(None) - - if not self._auto_deps: - return d - - if obj is None: - fget = self._fget - if fget is not None: - obj = cast(FunctionType, fget) - else: - return d - - try: - return DependencyTracker( - func=obj, state_cls=objclass, dependencies=d - ).dependencies - except Exception as e: - console.warn( - "Failed to automatically determine dependencies for computed var " - f"{objclass.__name__}.{self._name}: {e}. " - "Set auto_deps=False and provide accurate deps=['var1', 'var2'] to suppress this warning." - ) - return d - - def mark_dirty(self, instance: BaseState) -> None: - """Mark this ComputedVar as dirty. - - Args: - instance: the state instance that needs to recompute the value. - """ - with contextlib.suppress(AttributeError): - delattr(instance, self._cache_attr) - - def add_dependency(self, objclass: type[BaseState], dep: Var): - """Explicitly add a dependency to the ComputedVar. - - After adding the dependency, when the `dep` changes, this computed var - will be marked dirty. - - Args: - objclass: The class obj this ComputedVar is attached to. - dep: The dependency to add. - - Raises: - VarDependencyError: If the dependency is not a Var instance with a - state and field name - """ - if all_var_data := dep._get_all_var_data(): - state_name = all_var_data.state - if state_name: - var_name = all_var_data.field_name - if var_name: - self._static_deps.setdefault(state_name, set()).add(var_name) - target_state_class = objclass.get_root_state().get_class_substate( - state_name - ) - target_state_class._var_dependencies.setdefault( - var_name, set() - ).add(( - objclass.get_full_name(), - self._name, - )) - target_state_class._potentially_dirty_states.add( - objclass.get_full_name() - ) - return - msg = ( - "ComputedVar dependencies must be Var instances with a state and " - f"field name, got {dep!r}." - ) - raise VarDependencyError(msg) - - def _determine_var_type(self) -> type: - """Get the type of the var. - - Returns: - The type of the var. - """ - hints = get_type_hints(self._fget) - if "return" in hints: - return hints["return"] - return Any # pyright: ignore [reportReturnType] - - @property - def __class__(self) -> type: - """Get the class of the var. - - Returns: - The class of the var. - """ - return FakeComputedVarBaseClass - - @property - def fget(self) -> Callable[[BaseState], RETURN_TYPE]: - """Get the getter function. - - Returns: - The getter function. - """ - return self._fget - - -class DynamicRouteVar(ComputedVar[str | list[str]]): - """A ComputedVar that represents a dynamic route.""" - - -async def _default_async_computed_var(_self: BaseState) -> Any: # noqa: RUF029 - return None - - -@dataclasses.dataclass( - eq=False, - frozen=True, - init=False, - slots=True, -) -class AsyncComputedVar(ComputedVar[RETURN_TYPE]): - """A computed var that wraps a coroutinefunction.""" - - _fget: Callable[[BaseState], Coroutine[None, None, RETURN_TYPE]] = ( - dataclasses.field(default=_default_async_computed_var) - ) - - @overload - def __get__( - self: AsyncComputedVar[bool], - instance: None, - owner: type, - ) -> BooleanVar: ... - - @overload - def __get__( - self: AsyncComputedVar[int] | ComputedVar[float], - instance: None, - owner: type, - ) -> NumberVar: ... - - @overload - def __get__( - self: AsyncComputedVar[str], - instance: None, - owner: type, - ) -> StringVar: ... - - @overload - def __get__( - self: AsyncComputedVar[MAPPING_TYPE], - instance: None, - owner: type, - ) -> ObjectVar[MAPPING_TYPE]: ... - - @overload - def __get__( - self: AsyncComputedVar[list[LIST_INSIDE]], - instance: None, - owner: type, - ) -> ArrayVar[list[LIST_INSIDE]]: ... - - @overload - def __get__( - self: AsyncComputedVar[tuple[LIST_INSIDE, ...]], - instance: None, - owner: type, - ) -> ArrayVar[tuple[LIST_INSIDE, ...]]: ... - - @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]: ... - - @overload - def __get__( - self, instance: BaseState, owner: type - ) -> Coroutine[None, None, RETURN_TYPE]: ... - - def __get__( - self, instance: BaseState | None, owner - ) -> Var | Coroutine[None, None, RETURN_TYPE]: - """Get the ComputedVar value. - - If the value is already cached on the instance, return the cached value. - - Args: - instance: the instance of the class accessing this computed var. - owner: the class that this descriptor is attached to. - - Returns: - The value of the var for the given instance. - """ - if instance is None: - return super(AsyncComputedVar, self).__get__(instance, owner) - - if not self._cache: - - async def _awaitable_result(instance: BaseState = instance) -> RETURN_TYPE: - value = await self.fget(instance) - self._check_deprecated_return_type(instance, value) - return value - - return _awaitable_result() - - # handle caching - async def _awaitable_result(instance: BaseState = instance) -> RETURN_TYPE: - if not hasattr(instance, self._cache_attr) or self.needs_update(instance): - # Set cache attr on state instance. - setattr(instance, self._cache_attr, await self.fget(instance)) - # Ensure the computed var gets serialized to redis. - instance._was_touched = True - # Set the last updated timestamp on the state instance. - setattr(instance, self._last_updated_attr, datetime.datetime.now()) - value = getattr(instance, self._cache_attr) - self._check_deprecated_return_type(instance, value) - return value - - return _awaitable_result() - - @property - def fget(self) -> Callable[[BaseState], Coroutine[None, None, RETURN_TYPE]]: - """Get the getter function. - - Returns: - The getter function. - """ - return self._fget - - -if TYPE_CHECKING: - BASE_STATE = TypeVar("BASE_STATE", bound=BaseState) - - -class _ComputedVarDecorator(Protocol): - """A protocol for the ComputedVar decorator.""" - - @overload - def __call__( - self, - fget: Callable[[BASE_STATE], Coroutine[Any, Any, RETURN_TYPE]], - ) -> AsyncComputedVar[RETURN_TYPE]: ... - - @overload - def __call__( - self, - fget: Callable[[BASE_STATE], RETURN_TYPE], - ) -> ComputedVar[RETURN_TYPE]: ... - - def __call__( - self, - fget: Callable[[BASE_STATE], Any], - ) -> ComputedVar[Any]: ... - - -@overload -def computed_var( - fget: None = None, - initial_value: Any | types.Unset = types.Unset(), - cache: bool = True, - deps: list[str | Var] | None = None, - auto_deps: bool = True, - interval: datetime.timedelta | int | None = None, - backend: bool | None = None, - **kwargs, -) -> _ComputedVarDecorator: ... - - -@overload -def computed_var( - fget: Callable[[BASE_STATE], Coroutine[Any, Any, RETURN_TYPE]], - initial_value: RETURN_TYPE | types.Unset = types.Unset(), - cache: bool = True, - deps: list[str | Var] | None = None, - auto_deps: bool = True, - interval: datetime.timedelta | int | None = None, - backend: bool | None = None, - **kwargs, -) -> AsyncComputedVar[RETURN_TYPE]: ... - - -@overload -def computed_var( - fget: Callable[[BASE_STATE], RETURN_TYPE], - initial_value: RETURN_TYPE | types.Unset = types.Unset(), - cache: bool = True, - deps: list[str | Var] | None = None, - auto_deps: bool = True, - interval: datetime.timedelta | int | None = None, - backend: bool | None = None, - **kwargs, -) -> ComputedVar[RETURN_TYPE]: ... - - -def computed_var( - fget: Callable[[BASE_STATE], Any] | None = None, - initial_value: Any | types.Unset = types.Unset(), - cache: bool = True, - deps: list[str | Var] | None = None, - auto_deps: bool = True, - interval: datetime.timedelta | int | None = None, - backend: bool | None = None, - **kwargs, -) -> ComputedVar | Callable[[Callable[[BASE_STATE], Any]], ComputedVar]: - """A ComputedVar decorator with or without kwargs. - - Args: - fget: The getter function. - initial_value: The initial value of the computed var. - cache: Whether to cache the computed value. - deps: Explicit var dependencies to track. - auto_deps: Whether var dependencies should be auto-determined. - interval: Interval at which the computed var should be updated. - backend: Whether the computed var is a backend var. - **kwargs: additional attributes to set on the instance - - Returns: - A ComputedVar instance. - - Raises: - ValueError: If caching is disabled and an update interval is set. - VarDependencyError: If user supplies dependencies without caching. - ComputedVarSignatureError: If the getter function has more than one argument. - """ - if cache is False and interval is not None: - msg = "Cannot set update interval without caching." - raise ValueError(msg) - - if cache is False and (deps is not None or auto_deps is False): - msg = "Cannot track dependencies without caching." - raise VarDependencyError(msg) - - if fget is not None: - sign = inspect.signature(fget) - if len(sign.parameters) != 1: - raise ComputedVarSignatureError(fget.__name__, signature=str(sign)) - - if inspect.iscoroutinefunction(fget): - computed_var_cls = AsyncComputedVar - else: - computed_var_cls = ComputedVar - return computed_var_cls( - fget, - initial_value=initial_value, - cache=cache, - deps=deps, - auto_deps=auto_deps, - interval=interval, - backend=backend, - **kwargs, - ) - - def wrapper(fget: Callable[[BASE_STATE], Any]) -> ComputedVar: - if inspect.iscoroutinefunction(fget): - computed_var_cls = AsyncComputedVar - else: - computed_var_cls = ComputedVar - return computed_var_cls( - fget, - initial_value=initial_value, - cache=cache, - deps=deps, - auto_deps=auto_deps, - interval=interval, - backend=backend, - **kwargs, - ) - - return wrapper - - -RETURN = TypeVar("RETURN") - - -class CustomVarOperationReturn(Var[RETURN]): - """Base class for custom var operations.""" - - @classmethod - def create( - cls, - js_expression: str, - _var_type: type[RETURN] | None = None, - _var_data: VarData | None = None, - ) -> CustomVarOperationReturn[RETURN]: - """Create a CustomVarOperation. - - Args: - js_expression: The JavaScript expression to evaluate. - _var_type: The type of the var. - _var_data: Additional hooks and imports associated with the Var. - - Returns: - The CustomVarOperation. - """ - return CustomVarOperationReturn( - _js_expr=js_expression, - _var_type=_var_type or Any, - _var_data=_var_data, - ) - - -def var_operation_return( - js_expression: str, - var_type: type[RETURN] | GenericType | None = None, - var_data: VarData | None = None, -) -> CustomVarOperationReturn[RETURN]: - """Shortcut for creating a CustomVarOperationReturn. - - Args: - js_expression: The JavaScript expression to evaluate. - var_type: The type of the var. - var_data: Additional hooks and imports associated with the Var. - - Returns: - The CustomVarOperationReturn. - """ - return CustomVarOperationReturn.create( - js_expression, - var_type, - var_data, - ) - - -@dataclasses.dataclass( - eq=False, - frozen=True, - slots=True, -) -class CustomVarOperation(CachedVarOperation, Var[T]): - """Base class for custom var operations.""" - - _name: str = dataclasses.field(default="") - - _args: tuple[tuple[str, Var], ...] = dataclasses.field(default_factory=tuple) - - _return: CustomVarOperationReturn[T] = dataclasses.field( - default_factory=lambda: CustomVarOperationReturn.create("") - ) - - @cached_property_no_lock - def _cached_var_name(self) -> str: - """Get the cached var name. - - Returns: - The cached var name. - """ - return str(self._return) - - @cached_property_no_lock - def _cached_get_all_var_data(self) -> VarData | None: - """Get the cached VarData. - - Returns: - The cached VarData. - """ - return VarData.merge( - *(arg[1]._get_all_var_data() for arg in self._args), - self._return._get_all_var_data(), - self._var_data, - ) - - @classmethod - def create( - cls, - name: str, - args: tuple[tuple[str, Var], ...], - return_var: CustomVarOperationReturn[T], - _var_data: VarData | None = None, - ) -> CustomVarOperation[T]: - """Create a CustomVarOperation. - - Args: - name: The name of the operation. - args: The arguments to the operation. - return_var: The return var. - _var_data: Additional hooks and imports associated with the Var. - - Returns: - The CustomVarOperation. - """ - return CustomVarOperation( - _js_expr="", - _var_type=return_var._var_type, - _var_data=_var_data, - _name=name, - _args=args, - _return=return_var, - ) - - -class NoneVar(Var[None], python_types=type(None)): - """A var representing None.""" - - -@dataclasses.dataclass( - eq=False, - frozen=True, - slots=True, -) -class LiteralNoneVar(LiteralVar, NoneVar): - """A var representing None.""" - - _var_value: None = None - - def json(self) -> str: - """Serialize the var to a JSON string. - - Returns: - The JSON string. - """ - return "null" - - @classmethod - def _get_all_var_data_without_creating_var(cls, value: None) -> VarData | None: - return None - - @classmethod - def create( - cls, - value: None = None, - _var_data: VarData | None = None, - ) -> LiteralNoneVar: - """Create a var from a value. - - Args: - value: The value of the var. Must be None. Existed for compatibility with LiteralVar. - _var_data: Additional hooks and imports associated with the Var. - - Returns: - The var. - """ - return LiteralNoneVar( - _js_expr="null", - _var_type=None, - _var_data=_var_data, - ) - - -def get_to_operation(var_subclass: type[Var]) -> type[ToOperation]: - """Get the ToOperation class for a given Var subclass. - - Args: - var_subclass: The Var subclass. - - Returns: - The ToOperation class. - - Raises: - ValueError: If the ToOperation class cannot be found. - """ - possible_classes = [ - saved_var_subclass.to_var_subclass - for saved_var_subclass in _var_subclasses - if saved_var_subclass.var_subclass is var_subclass - ] - if not possible_classes: - msg = f"Could not find ToOperation for {var_subclass}." - raise ValueError(msg) - return possible_classes[0] - - -@dataclasses.dataclass( - eq=False, - frozen=True, - slots=True, -) -class StateOperation(CachedVarOperation, Var): - """A var operation that accesses a field on an object.""" - - _state_name: str = dataclasses.field(default="") - _field: Var = dataclasses.field(default_factory=lambda: LiteralNoneVar.create()) - - @cached_property_no_lock - def _cached_var_name(self) -> str: - """Get the cached var name. - - Returns: - The cached var name. - """ - return f"{self._state_name!s}.{self._field!s}" - - def __getattr__(self, name: str) -> Any: - """Get an attribute of the var. - - Args: - name: The name of the attribute. - - Returns: - The attribute. - """ - if name == "_js_expr": - return self._cached_var_name - - return getattr(self._field, name) - - @classmethod - def create( - cls, - state_name: str, - field: Var, - _var_data: VarData | None = None, - ) -> StateOperation: - """Create a DotOperation. - - Args: - state_name: The name of the state. - field: The field of the state. - _var_data: Additional hooks and imports associated with the Var. - - Returns: - The DotOperation. - """ - return StateOperation( - _js_expr="", - _var_type=field._var_type, - _var_data=_var_data, - _state_name=state_name, - _field=field, - ) - - -def get_uuid_string_var() -> Var: - """Return a Var that generates a single memoized UUID via .web/utils/state.js. - - useMemo with an empty dependency array ensures that the generated UUID is - consistent across re-renders of the component. - - Returns: - A Var that generates a UUID at runtime. - """ - from reflex.utils.imports import ImportVar - from reflex.vars import Var - - unique_uuid_var = get_unique_variable_name() - unique_uuid_var_data = VarData( - imports={ - f"$/{constants.Dirs.STATE_PATH}": ImportVar(tag="generateUUID"), - "react": "useMemo", - }, - hooks={f"const {unique_uuid_var} = useMemo(generateUUID, [])": None}, - ) - - return Var( - _js_expr=unique_uuid_var, - _var_type=str, - _var_data=unique_uuid_var_data, - ) - - -# Set of unique variable names. -USED_VARIABLES = set() - - -@once -def _rng(): - import random - - return random.Random(42) - - -def get_unique_variable_name() -> str: - """Get a unique variable name. - - Returns: - The unique variable name. - """ - name = "".join([_rng().choice(string.ascii_lowercase) for _ in range(8)]) - if name not in USED_VARIABLES: - USED_VARIABLES.add(name) - return name - return get_unique_variable_name() - - -# Compile regex for finding reflex var tags. -_decode_var_pattern_re = ( - rf"{constants.REFLEX_VAR_OPENING_TAG}(.*?){constants.REFLEX_VAR_CLOSING_TAG}" -) -_decode_var_pattern = re.compile(_decode_var_pattern_re, flags=re.DOTALL) - -# Defined global immutable vars. -_global_vars: dict[int, Var] = {} - - -dispatchers: dict[GenericType, Callable[[Var], Var]] = {} - - -def transform(fn: Callable[[Var], Var]) -> Callable[[Var], Var]: - """Register a function to transform a Var. - - Args: - fn: The function to register. - - Returns: - The decorator. - - Raises: - TypeError: If the return type of the function is not a Var. - TypeError: If the Var return type does not have a generic type. - ValueError: If a function for the generic type is already registered. - """ - types = get_type_hints(fn) - return_type = types["return"] - - origin = get_origin(return_type) - - if origin is not Var: - msg = f"Expected return type of {fn.__name__} to be a Var, got {origin}." - raise TypeError(msg) - - generic_args = get_args(return_type) - - if not generic_args: - msg = f"Expected Var return type of {fn.__name__} to have a generic type." - raise TypeError(msg) - - generic_type = get_origin(generic_args[0]) or generic_args[0] - - if generic_type in dispatchers: - msg = f"Function for {generic_type} already registered." - raise ValueError(msg) - - dispatchers[generic_type] = fn - - return fn - - -def dispatch( - field_name: str, - var_data: VarData, - result_var_type: GenericType, - existing_var: Var | None = None, -) -> Var: - """Dispatch a Var to the appropriate transformation function. - - Args: - field_name: The name of the field. - var_data: The VarData associated with the Var. - result_var_type: The type of the Var. - existing_var: The existing Var to transform. Optional. - - Returns: - The transformed Var. - - Raises: - TypeError: If the return type of the function is not a Var. - TypeError: If the Var return type does not have a generic type. - TypeError: If the first argument of the function is not a Var. - TypeError: If the first argument of the function does not have a generic type - """ - result_origin_var_type = get_origin(result_var_type) or result_var_type - - if result_origin_var_type in dispatchers: - fn = dispatchers[result_origin_var_type] - fn_types = get_type_hints(fn) - fn_first_arg_type = fn_types.get( - next(iter(inspect.signature(fn).parameters.values())).name, Any - ) - - fn_return = fn_types.get("return", Any) - - fn_return_origin = get_origin(fn_return) or fn_return - - if fn_return_origin is not Var: - msg = f"Expected return type of {fn.__name__} to be a Var, got {fn_return}." - raise TypeError(msg) - - fn_return_generic_args = get_args(fn_return) - - if not fn_return_generic_args: - msg = f"Expected generic type of {fn_return} to be a type." - raise TypeError(msg) - - arg_origin = get_origin(fn_first_arg_type) or fn_first_arg_type - - if arg_origin is not Var: - msg = f"Expected first argument of {fn.__name__} to be a Var, got {fn_first_arg_type}." - raise TypeError(msg) - - arg_generic_args = get_args(fn_first_arg_type) - - if not arg_generic_args: - msg = f"Expected generic type of {fn_first_arg_type} to be a type." - raise TypeError(msg) - - fn_return_type = fn_return_generic_args[0] - - var = ( - Var( - field_name, - _var_data=var_data, - _var_type=fn_return_type, - ).guess_type() - if existing_var is None - else existing_var._replace( - _var_type=fn_return_type, - _var_data=var_data, - _js_expr=field_name, - ).guess_type() - ) - - return fn(var) - - if existing_var is not None: - return existing_var._replace( - _js_expr=field_name, - _var_data=var_data, - _var_type=result_var_type, - ).guess_type() - return Var( - field_name, - _var_data=var_data, - _var_type=result_var_type, - ).guess_type() - - -if TYPE_CHECKING: - from _typeshed import DataclassInstance - from sqlalchemy.orm import DeclarativeBase - - SQLA_TYPE = TypeVar("SQLA_TYPE", bound=DeclarativeBase | None) - DATACLASS_TYPE = TypeVar("DATACLASS_TYPE", bound=DataclassInstance | None) - MAPPING_TYPE = TypeVar("MAPPING_TYPE", bound=Mapping | None) - V = TypeVar("V") - - -FIELD_TYPE = TypeVar("FIELD_TYPE") - - -class Field(Generic[FIELD_TYPE]): - """A field for a state.""" - - if TYPE_CHECKING: - type_: GenericType - default: FIELD_TYPE | _MISSING_TYPE - default_factory: Callable[[], FIELD_TYPE] | None - - def __init__( - self, - default: FIELD_TYPE | _MISSING_TYPE = MISSING, - default_factory: Callable[[], FIELD_TYPE] | None = None, - is_var: bool = True, - annotated_type: GenericType # pyright: ignore [reportRedeclaration] - | _MISSING_TYPE = MISSING, - ) -> None: - """Initialize the field. - - Args: - default: The default value for the field. - default_factory: The default factory for the field. - is_var: Whether the field is a Var. - annotated_type: The annotated type for the field. - """ - self.default = default - self.default_factory = default_factory - self.is_var = is_var - if annotated_type is not MISSING: - type_origin = get_origin(annotated_type) or annotated_type - if type_origin is Field and ( - args := getattr(annotated_type, "__args__", None) - ): - annotated_type: GenericType = args[0] - type_origin = get_origin(annotated_type) or annotated_type - - if self.default is MISSING and self.default_factory is None: - default_value = types.get_default_value_for_type(annotated_type) - if default_value is None and not types.is_optional(annotated_type): - annotated_type = annotated_type | None - if types.is_immutable(default_value): - self.default = default_value - else: - self.default_factory = functools.partial( - copy.deepcopy, default_value - ) - self.outer_type_ = self.annotated_type = annotated_type - - if type_origin is Annotated: - type_origin = annotated_type.__origin__ # pyright: ignore [reportAttributeAccessIssue] - - self.type_ = self.type_origin = type_origin - else: - self.outer_type_ = self.annotated_type = self.type_ = self.type_origin = Any - - def default_value(self) -> FIELD_TYPE: - """Get the default value for the field. - - Returns: - The default value for the field. - - Raises: - ValueError: If no default value or factory is provided. - """ - if self.default is not MISSING: - return self.default - if self.default_factory is not None: - return self.default_factory() - msg = "No default value or factory provided." - raise ValueError(msg) - - def __repr__(self) -> str: - """Represent the field in a readable format. - - Returns: - The string representation of the field. - """ - annotated_type_str = ( - f", annotated_type={self.annotated_type!r}" - if self.annotated_type is not MISSING - else "" - ) - if self.default is not MISSING: - return f"Field(default={self.default!r}, is_var={self.is_var}{annotated_type_str})" - return f"Field(default_factory={self.default_factory!r}, is_var={self.is_var}{annotated_type_str})" - - if TYPE_CHECKING: - - def __set__(self, instance: Any, value: FIELD_TYPE): - """Set the Var. - - Args: - instance: The instance of the class setting the Var. - value: The value to set the Var to. - - # noqa: DAR101 self - """ - - @overload - def __get__(self: Field[None], instance: None, owner: Any) -> NoneVar: ... - - @overload - def __get__( - self: Field[bool] | Field[bool | None], instance: None, owner: Any - ) -> BooleanVar: ... - - @overload - def __get__( - self: Field[int] | Field[int | None], - instance: None, - owner: Any, - ) -> NumberVar[int]: ... - - @overload - def __get__( - self: Field[float] - | Field[int | float] - | Field[float | None] - | Field[int | float | None], - instance: None, - owner: Any, - ) -> NumberVar: ... - - @overload - def __get__( - self: Field[str] | Field[str | None], instance: None, owner: Any - ) -> StringVar: ... - - @overload - def __get__( - self: Field[list[V]] - | Field[set[V]] - | Field[list[V] | None] - | Field[set[V] | None], - instance: None, - owner: Any, - ) -> ArrayVar[Sequence[V]]: ... - - @overload - def __get__( - self: Field[SEQUENCE_TYPE] | Field[SEQUENCE_TYPE | None], - instance: None, - owner: Any, - ) -> ArrayVar[SEQUENCE_TYPE]: ... - - @overload - def __get__( - self: Field[MAPPING_TYPE] | Field[MAPPING_TYPE | None], - instance: None, - owner: Any, - ) -> ObjectVar[MAPPING_TYPE]: ... - - @overload - def __get__( - self: Field[SQLA_TYPE] | Field[SQLA_TYPE | None], instance: None, owner: Any - ) -> ObjectVar[SQLA_TYPE]: ... - - if TYPE_CHECKING: - - @overload - def __get__( - self: Field[DATACLASS_TYPE] | Field[DATACLASS_TYPE | None], - instance: None, - owner: Any, - ) -> ObjectVar[DATACLASS_TYPE]: ... - - @overload - def __get__(self, instance: None, owner: Any) -> Var[FIELD_TYPE]: ... - - @overload - def __get__(self, instance: Any, owner: Any) -> FIELD_TYPE: ... - - def __get__(self, instance: Any, owner: Any): # pyright: ignore [reportInconsistentOverload] - """Get the Var. - - Args: - instance: The instance of the class accessing the Var. - owner: The class that the Var is attached to. - """ - - -@overload -def field( - default: FIELD_TYPE | _MISSING_TYPE = MISSING, - *, - is_var: Literal[False], - default_factory: Callable[[], FIELD_TYPE] | None = None, -) -> FIELD_TYPE: ... - - -@overload -def field( - default: FIELD_TYPE | _MISSING_TYPE = MISSING, - *, - default_factory: Callable[[], FIELD_TYPE] | None = None, - is_var: Literal[True] = True, -) -> Field[FIELD_TYPE]: ... - - -def field( - default: FIELD_TYPE | _MISSING_TYPE = MISSING, - *, - default_factory: Callable[[], FIELD_TYPE] | None = None, - is_var: bool = True, -) -> Field[FIELD_TYPE] | FIELD_TYPE: - """Create a field for a state. - - Args: - default: The default value for the field. - default_factory: The default factory for the field. - is_var: Whether the field is a Var. - - Returns: - The field for the state. - - Raises: - ValueError: If both default and default_factory are specified. - """ - if default is not MISSING and default_factory is not None: - msg = "cannot specify both default and default_factory" - raise ValueError(msg) - if default is not MISSING and not types.is_immutable(default): - console.warn( - "Mutable default values are not recommended. " - "Use default_factory instead to avoid unexpected behavior." - ) - return Field( - default_factory=functools.partial(copy.deepcopy, default), - is_var=is_var, - ) - return Field( - default=default, - default_factory=default_factory, - is_var=is_var, - ) - - -@dataclass_transform(kw_only_default=True, field_specifiers=(field,)) -class BaseStateMeta(ABCMeta): - """Meta class for BaseState.""" - - if TYPE_CHECKING: - __inherited_fields__: Mapping[str, Field] - __own_fields__: dict[str, Field] - __fields__: dict[str, Field] - - # Whether this state class is a mixin and should not be instantiated. - _mixin: bool = False - - def __new__( - cls, - name: str, - bases: tuple[type, ...], - namespace: dict[str, Any], - mixin: bool = False, - ) -> type: - """Create a new class. - - Args: - name: The name of the class. - bases: The bases of the class. - namespace: The namespace of the class. - mixin: Whether the class is a mixin and should not be instantiated. - - Returns: - The new class. - """ - state_bases = [ - base for base in bases if issubclass(base, EvenMoreBasicBaseState) - ] - mixin = mixin or ( - bool(state_bases) and all(base._mixin for base in state_bases) - ) - # Add the field to the class - inherited_fields: dict[str, Field] = {} - own_fields: dict[str, Field] = {} - resolved_annotations = types.resolve_annotations( - annotations_from_namespace(namespace), namespace["__module__"] - ) - - for base in bases[::-1]: - if hasattr(base, "__inherited_fields__"): - inherited_fields.update(base.__inherited_fields__) - for base in bases[::-1]: - if hasattr(base, "__own_fields__"): - inherited_fields.update(base.__own_fields__) - - for key, value in [ - (key, value) - for key, value in namespace.items() - if key not in resolved_annotations - ]: - if isinstance(value, Field): - if value.annotated_type is not Any: - new_value = value - elif value.default is not MISSING: - new_value = Field( - default=value.default, - is_var=value.is_var, - annotated_type=figure_out_type(value.default), - ) - else: - new_value = Field( - default_factory=value.default_factory, - is_var=value.is_var, - annotated_type=Any, - ) - elif ( - not key.startswith("__") - and not callable(value) - and not isinstance(value, (staticmethod, classmethod, property, Var)) - ): - if types.is_immutable(value): - new_value = Field( - default=value, - annotated_type=figure_out_type(value), - ) - else: - new_value = Field( - default_factory=functools.partial(copy.deepcopy, value), - annotated_type=figure_out_type(value), - ) - else: - continue - - own_fields[key] = new_value - - for key, annotation in resolved_annotations.items(): - value = namespace.get(key, MISSING) - - if types.is_classvar(annotation): - # If the annotation is a classvar, skip it. - continue - - if value is MISSING: - value = Field( - annotated_type=annotation, - ) - elif not isinstance(value, Field): - if types.is_immutable(value): - value = Field( - default=value, - annotated_type=annotation, - ) - else: - value = Field( - default_factory=functools.partial(copy.deepcopy, value), - annotated_type=annotation, - ) - else: - value = Field( - default=value.default, - default_factory=value.default_factory, - is_var=value.is_var, - annotated_type=annotation, - ) - - own_fields[key] = value - - namespace["__own_fields__"] = own_fields - namespace["__inherited_fields__"] = inherited_fields - namespace["__fields__"] = inherited_fields | own_fields - namespace["_mixin"] = mixin - return super().__new__(cls, name, bases, namespace) - - -class EvenMoreBasicBaseState(metaclass=BaseStateMeta): - """A simplified base state class that provides basic functionality.""" - - def __init__( - self, - **kwargs, - ): - """Initialize the state with the given kwargs. - - Args: - **kwargs: The kwargs to pass to the state. - """ - super().__init__() - for key, value in kwargs.items(): - object.__setattr__(self, key, value) - for name, value in type(self).get_fields().items(): - if name not in kwargs: - default_value = value.default_value() - object.__setattr__(self, name, default_value) - - def set(self, **kwargs): - """Mutate the state by setting the given kwargs. Returns the state. - - Args: - **kwargs: The kwargs to set. - - Returns: - The state with the fields set to the given kwargs. - """ - for key, value in kwargs.items(): - setattr(self, key, value) - return self - - @classmethod - def get_fields(cls) -> Mapping[str, Field]: - """Get the fields of the component. - - Returns: - The fields of the component. - """ - return cls.__fields__ - - @classmethod - def add_field(cls, name: str, var: Var, default_value: Any): - """Add a field to the class after class definition. - - Used by State.add_var() to correctly handle the new variable. - - Args: - name: The name of the field to add. - var: The variable to add a field for. - default_value: The default value of the field. - """ - if types.is_immutable(default_value): - new_field = Field( - default=default_value, - annotated_type=var._var_type, - ) - else: - new_field = Field( - default_factory=functools.partial(copy.deepcopy, default_value), - annotated_type=var._var_type, - ) - cls.__fields__[name] = new_field +from reflex_core.vars.base import * # noqa: F401, F403 diff --git a/reflex/vars/color.py b/reflex/vars/color.py index e3c3f7d5c1b..37494d9b021 100644 --- a/reflex/vars/color.py +++ b/reflex/vars/color.py @@ -1,166 +1,3 @@ -"""Vars for colors.""" +"""Re-export from reflex_core.""" -import dataclasses - -from reflex.constants.colors import Color -from reflex.vars.base import ( - CachedVarOperation, - LiteralVar, - Var, - VarData, - cached_property_no_lock, - get_python_literal, -) -from reflex.vars.number import ternary_operation -from reflex.vars.sequence import ConcatVarOperation, LiteralStringVar, StringVar - - -class ColorVar(StringVar[Color], python_types=Color): - """Base class for immutable color vars.""" - - -@dataclasses.dataclass( - eq=False, - frozen=True, - slots=True, -) -class LiteralColorVar(CachedVarOperation, LiteralVar, ColorVar): - """Base class for immutable literal color vars.""" - - _var_value: Color = dataclasses.field(default_factory=lambda: Color(color="black")) - - @classmethod - def _get_all_var_data_without_creating_var( - cls, - value: Color, - ) -> VarData | None: - return VarData.merge( - LiteralStringVar._get_all_var_data_without_creating_var(value.color) - if isinstance(value.color, str) - else value.color._get_all_var_data(), - value.alpha._get_all_var_data() - if not isinstance(value.alpha, bool) - else None, - value.shade._get_all_var_data() - if not isinstance(value.shade, int) - else None, - ) - - @classmethod - def create( - cls, - value: Color, - _var_type: type[Color] | None = None, - _var_data: VarData | None = None, - ) -> ColorVar: - """Create a var from a string value. - - Args: - value: The value to create the var from. - _var_type: The type of the var. - _var_data: Additional hooks and imports associated with the Var. - - Returns: - The var. - """ - return cls( - _js_expr="", - _var_type=_var_type or Color, - _var_data=_var_data, - _var_value=value, - ) - - def __hash__(self) -> int: - """Get the hash of the var. - - Returns: - The hash of the var. - """ - return hash(( - self.__class__.__name__, - self._var_value.color, - self._var_value.alpha, - self._var_value.shade, - )) - - @cached_property_no_lock - def _cached_var_name(self) -> str: - """The name of the var. - - Returns: - The name of the var. - """ - alpha = self._var_value.alpha - alpha = ( - ternary_operation( - alpha, - LiteralStringVar.create("a"), - LiteralStringVar.create(""), - ) - if isinstance(alpha, Var) - else LiteralStringVar.create("a" if alpha else "") - ) - - shade = self._var_value.shade - shade = ( - shade.to_string(use_json=False) - if isinstance(shade, Var) - else LiteralStringVar.create(str(shade)) - ) - return str( - ConcatVarOperation.create( - LiteralStringVar.create("var(--"), - self._var_value.color, - LiteralStringVar.create("-"), - alpha, - shade, - LiteralStringVar.create(")"), - ) - ) - - @cached_property_no_lock - def _cached_get_all_var_data(self) -> VarData | None: - """Get all the var data. - - Returns: - The var data. - """ - return VarData.merge( - LiteralStringVar._get_all_var_data_without_creating_var( - self._var_value.color - ) - if isinstance(self._var_value.color, str) - else self._var_value.color._get_all_var_data(), - self._var_value.alpha._get_all_var_data() - if not isinstance(self._var_value.alpha, bool) - else None, - self._var_value.shade._get_all_var_data() - if not isinstance(self._var_value.shade, int) - else None, - self._var_data, - ) - - def json(self) -> str: - """Get the JSON representation of the var. - - Returns: - The JSON representation of the var. - - Raises: - TypeError: If the color is not a valid color. - """ - color, alpha, shade = map( - get_python_literal, - (self._var_value.color, self._var_value.alpha, self._var_value.shade), - ) - if color is None or alpha is None or shade is None: - msg = "Cannot serialize color that contains non-literal vars." - raise TypeError(msg) - if ( - not isinstance(color, str) - or not isinstance(alpha, bool) - or not isinstance(shade, int) - ): - msg = "Color is not a valid color." - raise TypeError(msg) - return f"var(--{color}-{'a' if alpha else ''}{shade})" +from reflex_core.vars.color import * # noqa: F401, F403 diff --git a/reflex/vars/datetime.py b/reflex/vars/datetime.py index 89a787e3e06..58c176f8e7c 100644 --- a/reflex/vars/datetime.py +++ b/reflex/vars/datetime.py @@ -1,202 +1,3 @@ -"""Immutable datetime and date vars.""" +"""Re-export from reflex_core.""" -from __future__ import annotations - -import dataclasses -from datetime import date, datetime -from typing import Any, TypeVar - -from reflex.utils.exceptions import VarTypeError -from reflex.vars.number import BooleanVar - -from .base import ( - CustomVarOperationReturn, - LiteralVar, - Var, - VarData, - var_operation, - var_operation_return, -) - -DATETIME_T = TypeVar("DATETIME_T", datetime, date) - -datetime_types = datetime | date - - -def raise_var_type_error(): - """Raise a VarTypeError. - - Raises: - VarTypeError: Cannot compare a datetime object with a non-datetime object. - """ - msg = "Cannot compare a datetime object with a non-datetime object." - raise VarTypeError(msg) - - -class DateTimeVar(Var[DATETIME_T], python_types=(datetime, date)): - """A variable that holds a datetime or date object.""" - - def __lt__(self, other: datetime_types | DateTimeVar) -> BooleanVar: - """Less than comparison. - - Args: - other: The other datetime to compare. - - Returns: - The result of the comparison. - """ - if not isinstance(other, DATETIME_TYPES): - raise_var_type_error() - return date_lt_operation(self, other) - - def __le__(self, other: datetime_types | DateTimeVar) -> BooleanVar: - """Less than or equal comparison. - - Args: - other: The other datetime to compare. - - Returns: - The result of the comparison. - """ - if not isinstance(other, DATETIME_TYPES): - raise_var_type_error() - return date_le_operation(self, other) - - def __gt__(self, other: datetime_types | DateTimeVar) -> BooleanVar: - """Greater than comparison. - - Args: - other: The other datetime to compare. - - Returns: - The result of the comparison. - """ - if not isinstance(other, DATETIME_TYPES): - raise_var_type_error() - return date_gt_operation(self, other) - - def __ge__(self, other: datetime_types | DateTimeVar) -> BooleanVar: - """Greater than or equal comparison. - - Args: - other: The other datetime to compare. - - Returns: - The result of the comparison. - """ - if not isinstance(other, DATETIME_TYPES): - raise_var_type_error() - return date_ge_operation(self, other) - - -@var_operation -def date_gt_operation(lhs: DateTimeVar | Any, rhs: DateTimeVar | Any): - """Greater than comparison. - - Args: - lhs: The left-hand side of the operation. - rhs: The right-hand side of the operation. - - Returns: - The result of the operation. - """ - return date_compare_operation(rhs, lhs, strict=True) - - -@var_operation -def date_lt_operation(lhs: DateTimeVar | Any, rhs: DateTimeVar | Any): - """Less than comparison. - - Args: - lhs: The left-hand side of the operation. - rhs: The right-hand side of the operation. - - Returns: - The result of the operation. - """ - return date_compare_operation(lhs, rhs, strict=True) - - -@var_operation -def date_le_operation(lhs: DateTimeVar | Any, rhs: DateTimeVar | Any): - """Less than or equal comparison. - - Args: - lhs: The left-hand side of the operation. - rhs: The right-hand side of the operation. - - Returns: - The result of the operation. - """ - return date_compare_operation(lhs, rhs) - - -@var_operation -def date_ge_operation(lhs: DateTimeVar | Any, rhs: DateTimeVar | Any): - """Greater than or equal comparison. - - Args: - lhs: The left-hand side of the operation. - rhs: The right-hand side of the operation. - - Returns: - The result of the operation. - """ - return date_compare_operation(rhs, lhs) - - -def date_compare_operation( - lhs: DateTimeVar[DATETIME_T] | Any, - rhs: DateTimeVar[DATETIME_T] | Any, - strict: bool = False, -) -> CustomVarOperationReturn[bool]: - """Check if the value is less than the other value. - - Args: - lhs: The left-hand side of the operation. - rhs: The right-hand side of the operation. - strict: Whether to use strict comparison. - - Returns: - The result of the operation. - """ - return var_operation_return( - f"({lhs} {'<' if strict else '<='} {rhs})", - bool, - ) - - -@dataclasses.dataclass( - eq=False, - frozen=True, - slots=True, -) -class LiteralDatetimeVar(LiteralVar, DateTimeVar): - """Base class for immutable datetime and date vars.""" - - _var_value: date = dataclasses.field(default=datetime.now()) - - @classmethod - def _get_all_var_data_without_creating_var(cls, value: date) -> VarData | None: - return None - - @classmethod - def create(cls, value: date, _var_data: VarData | None = None): - """Create a new instance of the class. - - Args: - value: The value to set. - - Returns: - LiteralDatetimeVar: The new instance of the class. - """ - js_expr = f'"{value!s}"' - return cls( - _js_expr=js_expr, - _var_type=type(value), - _var_value=value, - _var_data=_var_data, - ) - - -DATETIME_TYPES = (datetime, date, DateTimeVar) +from reflex_core.vars.datetime import * # noqa: F401, F403 diff --git a/reflex/vars/dep_tracking.py b/reflex/vars/dep_tracking.py index 2315eb9f814..c0b91022d02 100644 --- a/reflex/vars/dep_tracking.py +++ b/reflex/vars/dep_tracking.py @@ -1,485 +1,3 @@ -"""Collection of base classes.""" +"""Re-export from reflex_core.""" -from __future__ import annotations - -import contextlib -import dataclasses -import dis -import enum -import importlib -import inspect -import sys -from types import CellType, CodeType, FunctionType, ModuleType -from typing import TYPE_CHECKING, Any, ClassVar, cast - -from reflex.utils.exceptions import VarValueError - -if TYPE_CHECKING: - from reflex.state import BaseState - - from .base import Var - - -CellEmpty = object() - - -def get_cell_value(cell: CellType) -> Any: - """Get the value of a cell object. - - Args: - cell: The cell object to get the value from. (func.__closure__ objects) - - Returns: - The value from the cell or CellEmpty if a ValueError is raised. - """ - try: - return cell.cell_contents - except ValueError: - return CellEmpty - - -class ScanStatus(enum.Enum): - """State of the dis instruction scanning loop.""" - - SCANNING = enum.auto() - GETTING_ATTR = enum.auto() - GETTING_STATE = enum.auto() - GETTING_STATE_POST_AWAIT = enum.auto() - GETTING_VAR = enum.auto() - GETTING_IMPORT = enum.auto() - - -class UntrackedLocalVarError(VarValueError): - """Raised when a local variable is referenced, but it is not tracked in the current scope.""" - - -def assert_base_state( - local_value: Any, - local_name: str | None = None, -) -> type[BaseState]: - """Assert that a local variable is a BaseState subclass. - - Args: - local_value: The value of the local variable to check. - local_name: The name of the local variable to check. - - Returns: - The local variable value if it is a BaseState subclass. - - Raises: - VarValueError: If the object is not a BaseState subclass. - """ - from reflex.state import BaseState - - if not isinstance(local_value, type) or not issubclass(local_value, BaseState): - msg = f"Cannot determine dependencies in fetched state {local_name!r}: {local_value!r} is not a BaseState." - raise VarValueError(msg) - return local_value - - -@dataclasses.dataclass -class DependencyTracker: - """State machine for identifying state attributes that are accessed by a function.""" - - func: FunctionType | CodeType = dataclasses.field() - state_cls: type[BaseState] = dataclasses.field() - - dependencies: dict[str, set[str]] = dataclasses.field(default_factory=dict) - - scan_status: ScanStatus = dataclasses.field(default=ScanStatus.SCANNING) - top_of_stack: str | None = dataclasses.field(default=None) - - tracked_locals: dict[str, type[BaseState] | ModuleType] = dataclasses.field( - default_factory=dict - ) - - _getting_state_class: type[BaseState] | ModuleType | None = dataclasses.field( - default=None - ) - _get_var_value_positions: dis.Positions | None = dataclasses.field(default=None) - _last_import_name: str | None = dataclasses.field(default=None) - - INVALID_NAMES: ClassVar[list[str]] = ["parent_state", "substates", "get_substate"] - - def __post_init__(self): - """After initializing, populate the dependencies dict.""" - with contextlib.suppress(AttributeError): - # unbox functools.partial - self.func = cast(FunctionType, self.func.func) # pyright: ignore[reportAttributeAccessIssue] - with contextlib.suppress(AttributeError): - # unbox EventHandler - self.func = cast(FunctionType, self.func.fn) # pyright: ignore[reportAttributeAccessIssue,reportFunctionMemberAccess] - - if isinstance(self.func, FunctionType): - with contextlib.suppress(AttributeError, IndexError): - # the first argument to the function is the name of "self" arg - self.tracked_locals[self.func.__code__.co_varnames[0]] = self.state_cls - - self._populate_dependencies() - - def _merge_deps(self, tracker: DependencyTracker) -> None: - """Merge dependencies from another DependencyTracker. - - Args: - tracker: The DependencyTracker to merge dependencies from. - """ - for state_name, dep_name in tracker.dependencies.items(): - self.dependencies.setdefault(state_name, set()).update(dep_name) - - def get_tracked_local(self, local_name: str) -> type[BaseState] | ModuleType: - """Get the value of a local name tracked in the current function scope. - - Args: - local_name: The name of the local variable to fetch. - - Returns: - The value of local name tracked in the current scope (a referenced - BaseState subclass or imported module). - - Raises: - UntrackedLocalVarError: If the local variable is not being tracked. - """ - try: - local_value = self.tracked_locals[local_name] - except KeyError as ke: - msg = f"{local_name!r} is not tracked in the current scope." - raise UntrackedLocalVarError(msg) from ke - return local_value - - def load_attr_or_method(self, instruction: dis.Instruction) -> None: - """Handle loading an attribute or method from the object on top of the stack. - - This method directly tracks attributes and recursively merges - dependencies from analyzing the dependencies of any methods called. - - Args: - instruction: The dis instruction to process. - - Raises: - VarValueError: if the attribute is an disallowed name or attribute - does not reference a BaseState. - """ - from .base import ComputedVar - - if instruction.argval in self.INVALID_NAMES: - msg = f"Cached var {self!s} cannot access arbitrary state via `{instruction.argval}`." - raise VarValueError(msg) - if instruction.argval == "get_state": - # Special case: arbitrary state access requested. - self.scan_status = ScanStatus.GETTING_STATE - return - if instruction.argval == "get_var_value": - # Special case: arbitrary var access requested. - if sys.version_info >= (3, 11): - self._get_var_value_positions = instruction.positions - self.scan_status = ScanStatus.GETTING_VAR - return - - # Reset status back to SCANNING after attribute is accessed. - self.scan_status = ScanStatus.SCANNING - if not self.top_of_stack: - return - target_obj = self.get_tracked_local(self.top_of_stack) - try: - target_state = assert_base_state(target_obj, local_name=self.top_of_stack) - except VarValueError: - # If the target state is not a BaseState, we cannot track dependencies on it. - return - try: - ref_obj = getattr(target_state, instruction.argval) - except AttributeError: - # Not found on this state class, maybe it is a dynamic attribute that will be picked up later. - ref_obj = None - - if isinstance(ref_obj, property) and not isinstance(ref_obj, ComputedVar): - # recurse into property fget functions - ref_obj = ref_obj.fget - if callable(ref_obj): - # recurse into callable attributes - self._merge_deps( - type(self)(func=cast(FunctionType, ref_obj), state_cls=target_state) - ) - elif ( - instruction.argval in target_state.backend_vars - or instruction.argval in target_state.vars - ): - # var access - self.dependencies.setdefault(target_state.get_full_name(), set()).add( - instruction.argval - ) - - def _get_globals(self) -> dict[str, Any]: - """Get the globals of the function. - - Returns: - The var names and values in the globals of the function. - """ - if isinstance(self.func, CodeType): - return {} - return self.func.__globals__ # pyright: ignore[reportAttributeAccessIssue] - - def _get_closure(self) -> dict[str, Any]: - """Get the closure of the function, with unbound values omitted. - - Returns: - The var names and values in the closure of the function. - """ - if isinstance(self.func, CodeType): - return {} - return { - var_name: get_cell_value(cell) - for var_name, cell in zip( - self.func.__code__.co_freevars, # pyright: ignore[reportAttributeAccessIssue] - self.func.__closure__ or (), - strict=False, - ) - if get_cell_value(cell) is not CellEmpty - } - - def handle_getting_state(self, instruction: dis.Instruction) -> None: - """Handle bytecode analysis when `get_state` was called in the function. - - If the wrapped function is getting an arbitrary state and saving it to a - local variable, this method associates the local variable name with the - state class in self.tracked_locals. - - When an attribute/method is accessed on a tracked local, it will be - associated with this state. - - Args: - instruction: The dis instruction to process. - - Raises: - VarValueError: if the state class cannot be determined from the instruction. - """ - if isinstance(self.func, CodeType): - msg = "Dependency detection cannot identify get_state class from a code object." - raise VarValueError(msg) - if instruction.opname in ("LOAD_FAST", "LOAD_FAST_BORROW"): - self._getting_state_class = self.get_tracked_local( - local_name=instruction.argval, - ) - elif instruction.opname == "LOAD_GLOBAL": - # Special case: referencing state class from global scope. - try: - self._getting_state_class = self._get_globals()[instruction.argval] - except (ValueError, KeyError) as ve: - msg = f"Cached var {self!s} cannot access arbitrary state `{instruction.argval}`, not found in globals." - raise VarValueError(msg) from ve - elif instruction.opname == "LOAD_DEREF": - # Special case: referencing state class from closure. - try: - self._getting_state_class = self._get_closure()[instruction.argval] - except (ValueError, KeyError) as ve: - msg = f"Cached var {self!s} cannot access arbitrary state `{instruction.argval}`, is it defined yet?" - raise VarValueError(msg) from ve - elif instruction.opname in ("LOAD_ATTR", "LOAD_METHOD"): - self._getting_state_class = getattr( - self._getting_state_class, - instruction.argval, - ) - elif instruction.opname == "GET_AWAITABLE": - # Now inside the `await` machinery, subsequent instructions - # operate on the result of the `get_state` call. - self.scan_status = ScanStatus.GETTING_STATE_POST_AWAIT - if self._getting_state_class is not None: - self.top_of_stack = "_" - self.tracked_locals[self.top_of_stack] = self._getting_state_class - self._getting_state_class = None - - def handle_getting_state_post_await(self, instruction: dis.Instruction) -> None: - """Handle bytecode analysis after `get_state` was called in the function. - - This function is called _after_ awaiting self.get_state to capture the - local variable holding the state instance or directly record access to - attributes accessed on the result of get_state. - - Args: - instruction: The dis instruction to process. - - Raises: - VarValueError: if the state class cannot be determined from the instruction. - """ - if instruction.opname == "STORE_FAST" and self.top_of_stack: - # Storing the result of get_state in a local variable. - self.tracked_locals[instruction.argval] = self.tracked_locals.pop( - self.top_of_stack - ) - self.top_of_stack = None - self.scan_status = ScanStatus.SCANNING - elif instruction.opname in ("LOAD_ATTR", "LOAD_METHOD"): - # Attribute access on an inline `get_state`, not assigned to a variable. - self.load_attr_or_method(instruction) - - def _eval_var(self, positions: dis.Positions) -> Var: - """Evaluate instructions from the wrapped function to get the Var object. - - Args: - positions: The disassembly positions of the get_var_value call. - - Returns: - The Var object. - - Raises: - VarValueError: if the source code for the var cannot be determined. - """ - # Get the original source code and eval it to get the Var. - module = inspect.getmodule(self.func) - if module is None or self._get_var_value_positions is None: - msg = f"Cannot determine the source code for the var in {self.func!r}." - raise VarValueError(msg) - start_line = self._get_var_value_positions.end_lineno - start_column = self._get_var_value_positions.end_col_offset - end_line = positions.end_lineno - end_column = positions.end_col_offset - if ( - start_line is None - or start_column is None - or end_line is None - or end_column is None - ): - msg = f"Cannot determine the source code for the var in {self.func!r}." - raise VarValueError(msg) - source = inspect.getsource(module).splitlines(True)[start_line - 1 : end_line] - # Create a python source string snippet. - if len(source) > 1: - snipped_source = "".join([ - *source[0][start_column:], - *source[1:-1], - *source[-1][:end_column], - ]) - else: - snipped_source = source[0][start_column:end_column] - # Evaluate the string in the context of the function's globals, closure and tracked local scope. - return eval( - f"({snipped_source})", - self._get_globals(), - {**self._get_closure(), **self.tracked_locals}, - ) - - def handle_getting_var(self, instruction: dis.Instruction) -> None: - """Handle bytecode analysis when `get_var_value` was called in the function. - - This only really works if the expression passed to `get_var_value` is - evaluable in the function's global scope or closure, so getting the var - value from a var saved in a local variable or in the class instance is - not possible. - - Args: - instruction: The dis instruction to process. - - Raises: - VarValueError: if the source code for the var cannot be determined. - """ - if instruction.opname == "CALL": - if instruction.positions is None: - msg = f"Cannot determine the source code for the var in {self.func!r}." - raise VarValueError(msg) - the_var = self._eval_var(instruction.positions) - the_var_data = the_var._get_all_var_data() - if the_var_data is None: - msg = f"Cannot determine the source code for the var in {self.func!r}." - raise VarValueError(msg) - self.dependencies.setdefault(the_var_data.state, set()).add( - the_var_data.field_name - ) - self.scan_status = ScanStatus.SCANNING - - def _populate_dependencies(self) -> None: - """Update self.dependencies based on the disassembly of self.func. - - Save references to attributes accessed on "self" or other fetched states. - - Recursively called when the function makes a method call on "self" or - define comprehensions or nested functions that may reference "self". - """ - for instruction in dis.get_instructions(self.func): - if self.scan_status == ScanStatus.GETTING_STATE: - self.handle_getting_state(instruction) - elif self.scan_status == ScanStatus.GETTING_STATE_POST_AWAIT: - self.handle_getting_state_post_await(instruction) - elif self.scan_status == ScanStatus.GETTING_VAR: - self.handle_getting_var(instruction) - elif ( - instruction.opname - in ( - "LOAD_FAST", - "LOAD_DEREF", - "LOAD_FAST_BORROW", - "LOAD_FAST_CHECK", - "LOAD_FAST_AND_CLEAR", - ) - and instruction.argval in self.tracked_locals - ): - # bytecode loaded the class instance to the top of stack, next load instruction - # is referencing an attribute on self - self.top_of_stack = instruction.argval - self.scan_status = ScanStatus.GETTING_ATTR - elif ( - instruction.opname - in ( - "LOAD_FAST_LOAD_FAST", - "LOAD_FAST_BORROW_LOAD_FAST_BORROW", - "STORE_FAST_LOAD_FAST", - ) - and instruction.argval[-1] in self.tracked_locals - ): - # Double LOAD_FAST family instructions load multiple values onto the stack, - # the last value in the argval list is the top of the stack. - self.top_of_stack = instruction.argval[-1] - self.scan_status = ScanStatus.GETTING_ATTR - elif self.scan_status == ScanStatus.GETTING_ATTR and instruction.opname in ( - "LOAD_ATTR", - "LOAD_METHOD", - ): - self.load_attr_or_method(instruction) - self.top_of_stack = None - elif instruction.opname == "LOAD_CONST" and isinstance( - instruction.argval, CodeType - ): - # recurse into nested functions / comprehensions, which can reference - # instance attributes from the outer scope - self._merge_deps( - type(self)( - func=instruction.argval, - state_cls=self.state_cls, - tracked_locals=self.tracked_locals, - ) - ) - elif instruction.opname == "IMPORT_NAME" and instruction.argval is not None: - self.scan_status = ScanStatus.GETTING_IMPORT - self._last_import_name = instruction.argval - importlib.import_module(instruction.argval) - top_module_name = instruction.argval.split(".")[0] - self.tracked_locals[instruction.argval] = sys.modules[top_module_name] - self.top_of_stack = instruction.argval - elif instruction.opname == "IMPORT_FROM": - if not self._last_import_name: - msg = f"Cannot find package associated with import {instruction.argval} in {self.func!r}." - raise VarValueError(msg) - if instruction.argval in self._last_import_name.split("."): - # `import ... as ...` case: - # import from interim package, update tracked_locals for the last imported name. - self.tracked_locals[self._last_import_name] = getattr( - self.tracked_locals[self._last_import_name], instruction.argval - ) - continue - # Importing a name from a package/module. - if self._last_import_name is not None and self.top_of_stack: - # The full import name does NOT end up in scope for a `from ... import`. - self.tracked_locals.pop(self._last_import_name) - self.tracked_locals[instruction.argval] = getattr( - importlib.import_module(self._last_import_name), - instruction.argval, - ) - # If we see a STORE_FAST, we can assign the top of stack to an aliased name. - self.top_of_stack = instruction.argval - elif ( - self.scan_status == ScanStatus.GETTING_IMPORT - and instruction.opname == "STORE_FAST" - and self.top_of_stack is not None - ): - self.tracked_locals[instruction.argval] = self.tracked_locals.pop( - self.top_of_stack - ) - self.top_of_stack = None +from reflex_core.vars.dep_tracking import * # noqa: F401, F403 diff --git a/reflex/vars/function.py b/reflex/vars/function.py index 709b9b03b05..547e61efbd6 100644 --- a/reflex/vars/function.py +++ b/reflex/vars/function.py @@ -1,555 +1,3 @@ -"""Immutable function vars.""" +"""Re-export from reflex_core.""" -from __future__ import annotations - -import dataclasses -from collections.abc import Callable, Sequence -from typing import Any, Concatenate, Generic, ParamSpec, Protocol, TypeVar, overload - -from reflex.utils import format -from reflex.utils.types import GenericType - -from .base import CachedVarOperation, LiteralVar, Var, VarData, cached_property_no_lock - -P = ParamSpec("P") -V1 = TypeVar("V1") -V2 = TypeVar("V2") -V3 = TypeVar("V3") -V4 = TypeVar("V4") -V5 = TypeVar("V5") -V6 = TypeVar("V6") -R = TypeVar("R") - - -class ReflexCallable(Protocol[P, R]): - """Protocol for a callable.""" - - __call__: Callable[P, R] - - -CALLABLE_TYPE = TypeVar("CALLABLE_TYPE", bound=ReflexCallable, covariant=True) -OTHER_CALLABLE_TYPE = TypeVar( - "OTHER_CALLABLE_TYPE", bound=ReflexCallable, covariant=True -) - - -def _is_js_identifier_start(char: str) -> bool: - """Check whether a character can start a JavaScript identifier. - - Returns: - True if the character is valid as the first character of a JS identifier. - """ - return char == "$" or char == "_" or char.isalpha() - - -def _is_js_identifier_char(char: str) -> bool: - """Check whether a character can continue a JavaScript identifier. - - Returns: - True if the character is valid within a JS identifier. - """ - return _is_js_identifier_start(char) or char.isdigit() - - -def _starts_with_arrow_function(expr: str) -> bool: - """Check whether an expression starts with an inline arrow function. - - Returns: - True if the expression begins with an arrow function. - """ - if "=>" not in expr: - return False - - expr = expr.lstrip() - if not expr: - return False - - if expr.startswith("async"): - async_remainder = expr[len("async") :] - if async_remainder[:1].isspace(): - expr = async_remainder.lstrip() - - if not expr: - return False - - if _is_js_identifier_start(expr[0]): - end_index = 1 - while end_index < len(expr) and _is_js_identifier_char(expr[end_index]): - end_index += 1 - return expr[end_index:].lstrip().startswith("=>") - - if not expr.startswith("("): - return False - - depth = 0 - string_delimiter: str | None = None - escaped = False - - for index, char in enumerate(expr): - if string_delimiter is not None: - if escaped: - escaped = False - elif char == "\\": - escaped = True - elif char == string_delimiter: - string_delimiter = None - continue - - if char in {"'", '"', "`"}: - string_delimiter = char - continue - - if char == "(": - depth += 1 - continue - - if char == ")": - depth -= 1 - if depth == 0: - return expr[index + 1 :].lstrip().startswith("=>") - - return False - - -class FunctionVar(Var[CALLABLE_TYPE], default_type=ReflexCallable[Any, Any]): - """Base class for immutable function vars.""" - - @overload - def partial(self) -> FunctionVar[CALLABLE_TYPE]: ... - - @overload - def partial( - self: FunctionVar[ReflexCallable[Concatenate[V1, P], R]], - arg1: V1 | Var[V1], - ) -> FunctionVar[ReflexCallable[P, R]]: ... - - @overload - def partial( - self: FunctionVar[ReflexCallable[Concatenate[V1, V2, P], R]], - arg1: V1 | Var[V1], - arg2: V2 | Var[V2], - ) -> FunctionVar[ReflexCallable[P, R]]: ... - - @overload - def partial( - self: FunctionVar[ReflexCallable[Concatenate[V1, V2, V3, P], R]], - arg1: V1 | Var[V1], - arg2: V2 | Var[V2], - arg3: V3 | Var[V3], - ) -> FunctionVar[ReflexCallable[P, R]]: ... - - @overload - def partial( - self: FunctionVar[ReflexCallable[Concatenate[V1, V2, V3, V4, P], R]], - arg1: V1 | Var[V1], - arg2: V2 | Var[V2], - arg3: V3 | Var[V3], - arg4: V4 | Var[V4], - ) -> FunctionVar[ReflexCallable[P, R]]: ... - - @overload - def partial( - self: FunctionVar[ReflexCallable[Concatenate[V1, V2, V3, V4, V5, P], R]], - arg1: V1 | Var[V1], - arg2: V2 | Var[V2], - arg3: V3 | Var[V3], - arg4: V4 | Var[V4], - arg5: V5 | Var[V5], - ) -> FunctionVar[ReflexCallable[P, R]]: ... - - @overload - def partial( - self: FunctionVar[ReflexCallable[Concatenate[V1, V2, V3, V4, V5, V6, P], R]], - arg1: V1 | Var[V1], - arg2: V2 | Var[V2], - arg3: V3 | Var[V3], - arg4: V4 | Var[V4], - arg5: V5 | Var[V5], - arg6: V6 | Var[V6], - ) -> FunctionVar[ReflexCallable[P, R]]: ... - - @overload - def partial( - self: FunctionVar[ReflexCallable[P, R]], *args: Var | Any - ) -> FunctionVar[ReflexCallable[P, R]]: ... - - @overload - def partial(self, *args: Var | Any) -> FunctionVar: ... - - def partial(self, *args: Var | Any) -> FunctionVar: # pyright: ignore [reportInconsistentOverload] - """Partially apply the function with the given arguments. - - Args: - *args: The arguments to partially apply the function with. - - Returns: - The partially applied function. - """ - if not args: - return self - return ArgsFunctionOperation.create( - ("...args",), - VarOperationCall.create(self, *args, Var(_js_expr="...args")), - ) - - @overload - def call( - self: FunctionVar[ReflexCallable[[V1], R]], arg1: V1 | Var[V1] - ) -> VarOperationCall[[V1], R]: ... - - @overload - def call( - self: FunctionVar[ReflexCallable[[V1, V2], R]], - arg1: V1 | Var[V1], - arg2: V2 | Var[V2], - ) -> VarOperationCall[[V1, V2], R]: ... - - @overload - def call( - self: FunctionVar[ReflexCallable[[V1, V2, V3], R]], - arg1: V1 | Var[V1], - arg2: V2 | Var[V2], - arg3: V3 | Var[V3], - ) -> VarOperationCall[[V1, V2, V3], R]: ... - - @overload - def call( - self: FunctionVar[ReflexCallable[[V1, V2, V3, V4], R]], - arg1: V1 | Var[V1], - arg2: V2 | Var[V2], - arg3: V3 | Var[V3], - arg4: V4 | Var[V4], - ) -> VarOperationCall[[V1, V2, V3, V4], R]: ... - - @overload - def call( - self: FunctionVar[ReflexCallable[[V1, V2, V3, V4, V5], R]], - arg1: V1 | Var[V1], - arg2: V2 | Var[V2], - arg3: V3 | Var[V3], - arg4: V4 | Var[V4], - arg5: V5 | Var[V5], - ) -> VarOperationCall[[V1, V2, V3, V4, V5], R]: ... - - @overload - def call( - self: FunctionVar[ReflexCallable[[V1, V2, V3, V4, V5, V6], R]], - arg1: V1 | Var[V1], - arg2: V2 | Var[V2], - arg3: V3 | Var[V3], - arg4: V4 | Var[V4], - arg5: V5 | Var[V5], - arg6: V6 | Var[V6], - ) -> VarOperationCall[[V1, V2, V3, V4, V5, V6], R]: ... - - @overload - def call( - self: FunctionVar[ReflexCallable[P, R]], *args: Var | Any - ) -> VarOperationCall[P, R]: ... - - @overload - def call(self, *args: Var | Any) -> Var: ... - - def call(self, *args: Var | Any) -> Var: # pyright: ignore [reportInconsistentOverload] - """Call the function with the given arguments. - - Args: - *args: The arguments to call the function with. - - Returns: - The function call operation. - """ - return VarOperationCall.create(self, *args).guess_type() - - __call__ = call - - -class BuilderFunctionVar( - FunctionVar[CALLABLE_TYPE], default_type=ReflexCallable[Any, Any] -): - """Base class for immutable function vars with the builder pattern.""" - - __call__ = FunctionVar.partial - - -class FunctionStringVar(FunctionVar[CALLABLE_TYPE]): - """Base class for immutable function vars from a string.""" - - @classmethod - def create( - cls, - func: str, - _var_type: type[OTHER_CALLABLE_TYPE] = ReflexCallable[Any, Any], - _var_data: VarData | None = None, - ) -> FunctionStringVar[OTHER_CALLABLE_TYPE]: - """Create a new function var from a string. - - Args: - func: The function to call. - _var_type: The type of the Var. - _var_data: Additional hooks and imports associated with the Var. - - Returns: - The function var. - """ - return FunctionStringVar( - _js_expr=func, - _var_type=_var_type, - _var_data=_var_data, - ) - - -@dataclasses.dataclass( - eq=False, - frozen=True, - slots=True, -) -class VarOperationCall(Generic[P, R], CachedVarOperation, Var[R]): - """Base class for immutable vars that are the result of a function call.""" - - _func: FunctionVar[ReflexCallable[P, R]] | None = dataclasses.field(default=None) - _args: tuple[Var | Any, ...] = dataclasses.field(default_factory=tuple) - - @cached_property_no_lock - def _cached_var_name(self) -> str: - """The name of the var. - - Returns: - The name of the var. - """ - func_expr = str(self._func) - if _starts_with_arrow_function(func_expr) and not format.is_wrapped( - func_expr, "(" - ): - func_expr = format.wrap(func_expr, "(") - - return f"({func_expr}({', '.join([str(LiteralVar.create(arg)) for arg in self._args])}))" - - @cached_property_no_lock - def _cached_get_all_var_data(self) -> VarData | None: - """Get all the var data associated with the var. - - Returns: - All the var data associated with the var. - """ - return VarData.merge( - self._func._get_all_var_data() if self._func is not None else None, - *[LiteralVar.create(arg)._get_all_var_data() for arg in self._args], - self._var_data, - ) - - @classmethod - def create( - cls, - func: FunctionVar[ReflexCallable[P, R]], - *args: Var | Any, - _var_type: GenericType = Any, - _var_data: VarData | None = None, - ) -> VarOperationCall: - """Create a new function call var. - - Args: - func: The function to call. - *args: The arguments to call the function with. - _var_type: The type of the Var. - _var_data: Additional hooks and imports associated with the Var. - - Returns: - The function call var. - """ - function_return_type = ( - func._var_type.__args__[1] - if getattr(func._var_type, "__args__", None) - else Any - ) - var_type = _var_type if _var_type is not Any else function_return_type - return cls( - _js_expr="", - _var_type=var_type, - _var_data=_var_data, - _func=func, - _args=args, - ) - - -@dataclasses.dataclass(frozen=True) -class DestructuredArg: - """Class for destructured arguments.""" - - fields: tuple[str, ...] = () - rest: str | None = None - - def to_javascript(self) -> str: - """Convert the destructured argument to JavaScript. - - Returns: - The destructured argument in JavaScript. - """ - return format.wrap( - ", ".join(self.fields) + (f", ...{self.rest}" if self.rest else ""), - "{", - "}", - ) - - -@dataclasses.dataclass( - frozen=True, -) -class FunctionArgs: - """Class for function arguments.""" - - args: tuple[str | DestructuredArg, ...] = () - rest: str | None = None - - -def format_args_function_operation( - args: FunctionArgs, return_expr: Var | Any, explicit_return: bool -) -> str: - """Format an args function operation. - - Args: - args: The function arguments. - return_expr: The return expression. - explicit_return: Whether to use explicit return syntax. - - Returns: - The formatted args function operation. - """ - arg_names_str = ", ".join([ - arg if isinstance(arg, str) else arg.to_javascript() for arg in args.args - ]) + (f", ...{args.rest}" if args.rest else "") - - return_expr_str = str(LiteralVar.create(return_expr)) - - # Wrap return expression in curly braces if explicit return syntax is used. - return_expr_str_wrapped = ( - format.wrap(return_expr_str, "{", "}") if explicit_return else return_expr_str - ) - - return f"(({arg_names_str}) => {return_expr_str_wrapped})" - - -@dataclasses.dataclass( - eq=False, - frozen=True, - slots=True, -) -class ArgsFunctionOperation(CachedVarOperation, FunctionVar): - """Base class for immutable function defined via arguments and return expression.""" - - _args: FunctionArgs = dataclasses.field(default_factory=FunctionArgs) - _return_expr: Var | Any = dataclasses.field(default=None) - _explicit_return: bool = dataclasses.field(default=False) - - @cached_property_no_lock - def _cached_var_name(self) -> str: - """The name of the var. - - Returns: - The name of the var. - """ - return format_args_function_operation( - self._args, self._return_expr, self._explicit_return - ) - - @classmethod - def create( - cls, - args_names: Sequence[str | DestructuredArg], - return_expr: Var | Any, - rest: str | None = None, - explicit_return: bool = False, - _var_type: GenericType = Callable, - _var_data: VarData | None = None, - ): - """Create a new function var. - - Args: - args_names: The names of the arguments. - return_expr: The return expression of the function. - rest: The name of the rest argument. - explicit_return: Whether to use explicit return syntax. - _var_type: The type of the Var. - _var_data: Additional hooks and imports associated with the Var. - - Returns: - The function var. - """ - return_expr = Var.create(return_expr) - return cls( - _js_expr="", - _var_type=_var_type, - _var_data=_var_data, - _args=FunctionArgs(args=tuple(args_names), rest=rest), - _return_expr=return_expr, - _explicit_return=explicit_return, - ) - - -@dataclasses.dataclass( - eq=False, - frozen=True, - slots=True, -) -class ArgsFunctionOperationBuilder(CachedVarOperation, BuilderFunctionVar): - """Base class for immutable function defined via arguments and return expression with the builder pattern.""" - - _args: FunctionArgs = dataclasses.field(default_factory=FunctionArgs) - _return_expr: Var | Any = dataclasses.field(default=None) - _explicit_return: bool = dataclasses.field(default=False) - - @cached_property_no_lock - def _cached_var_name(self) -> str: - """The name of the var. - - Returns: - The name of the var. - """ - return format_args_function_operation( - self._args, self._return_expr, self._explicit_return - ) - - @classmethod - def create( - cls, - args_names: Sequence[str | DestructuredArg], - return_expr: Var | Any, - rest: str | None = None, - explicit_return: bool = False, - _var_type: GenericType = Callable, - _var_data: VarData | None = None, - ): - """Create a new function var. - - Args: - args_names: The names of the arguments. - return_expr: The return expression of the function. - rest: The name of the rest argument. - explicit_return: Whether to use explicit return syntax. - _var_type: The type of the Var. - _var_data: Additional hooks and imports associated with the Var. - - Returns: - The function var. - """ - return_expr = Var.create(return_expr) - return cls( - _js_expr="", - _var_type=_var_type, - _var_data=_var_data, - _args=FunctionArgs(args=tuple(args_names), rest=rest), - _return_expr=return_expr, - _explicit_return=explicit_return, - ) - - -JSON_STRINGIFY = FunctionStringVar.create( - "JSON.stringify", _var_type=ReflexCallable[[Any], str] -) -ARRAY_ISARRAY = FunctionStringVar.create( - "Array.isArray", _var_type=ReflexCallable[[Any], bool] -) -PROTOTYPE_TO_STRING = FunctionStringVar.create( - "((__to_string) => __to_string.toString())", - _var_type=ReflexCallable[[Any], str], -) +from reflex_core.vars.function import * # noqa: F401, F403 diff --git a/reflex/vars/number.py b/reflex/vars/number.py index 4f639c799d0..555916bce88 100644 --- a/reflex/vars/number.py +++ b/reflex/vars/number.py @@ -1,1148 +1,3 @@ -"""Immutable number vars.""" +"""Re-export from reflex_core.""" -from __future__ import annotations - -import dataclasses -import decimal -import json -import math -from collections.abc import Callable -from typing import TYPE_CHECKING, Any, NoReturn, TypeVar, overload - -from typing_extensions import TypeVar as TypeVarExt - -from reflex.constants.base import Dirs -from reflex.utils.exceptions import ( - PrimitiveUnserializableToJSONError, - VarTypeError, - VarValueError, -) -from reflex.utils.imports import ImportDict, ImportVar -from reflex.utils.types import safe_issubclass - -from .base import ( - CustomVarOperationReturn, - LiteralVar, - Var, - VarData, - unionize, - var_operation, - var_operation_return, -) - -NUMBER_T = TypeVarExt( - "NUMBER_T", - bound=(int | float | decimal.Decimal), - default=(int | float | decimal.Decimal), - covariant=True, -) - -if TYPE_CHECKING: - from .sequence import ArrayVar - - -def raise_unsupported_operand_types( - operator: str, operands_types: tuple[type, ...] -) -> NoReturn: - """Raise an unsupported operand types error. - - Args: - operator: The operator. - operands_types: The types of the operands. - - Raises: - VarTypeError: The operand types are unsupported. - """ - msg = f"Unsupported Operand type(s) for {operator}: {', '.join(t.__name__ for t in operands_types)}" - raise VarTypeError(msg) - - -class NumberVar(Var[NUMBER_T], python_types=(int, float, decimal.Decimal)): - """Base class for immutable number vars.""" - - def __add__(self, other: number_types) -> NumberVar: - """Add two numbers. - - Args: - other: The other number. - - Returns: - The number addition operation. - """ - if not isinstance(other, NUMBER_TYPES): - raise_unsupported_operand_types("+", (type(self), type(other))) - return number_add_operation(self, +other) - - def __radd__(self, other: number_types) -> NumberVar: - """Add two numbers. - - Args: - other: The other number. - - Returns: - The number addition operation. - """ - if not isinstance(other, NUMBER_TYPES): - raise_unsupported_operand_types("+", (type(other), type(self))) - return number_add_operation(+other, self) - - def __sub__(self, other: number_types) -> NumberVar: - """Subtract two numbers. - - Args: - other: The other number. - - Returns: - The number subtraction operation. - """ - if not isinstance(other, NUMBER_TYPES): - raise_unsupported_operand_types("-", (type(self), type(other))) - - return number_subtract_operation(self, +other) - - def __rsub__(self, other: number_types) -> NumberVar: - """Subtract two numbers. - - Args: - other: The other number. - - Returns: - The number subtraction operation. - """ - if not isinstance(other, NUMBER_TYPES): - raise_unsupported_operand_types("-", (type(other), type(self))) - - return number_subtract_operation(+other, self) - - def __abs__(self): - """Get the absolute value of the number. - - Returns: - The number absolute operation. - """ - return number_abs_operation(self) - - @overload - def __mul__(self, other: number_types | boolean_types) -> NumberVar: ... - - @overload - def __mul__(self, other: list | tuple | set | ArrayVar) -> ArrayVar: ... - - def __mul__(self, other: Any): - """Multiply two numbers. - - Args: - other: The other number. - - Returns: - The number multiplication operation. - """ - from .sequence import ArrayVar, LiteralArrayVar - - if isinstance(other, (list, tuple, ArrayVar)): - if isinstance(other, ArrayVar): - return other * self - return LiteralArrayVar.create(other) * self - - if not isinstance(other, NUMBER_TYPES): - raise_unsupported_operand_types("*", (type(self), type(other))) - - return number_multiply_operation(self, +other) - - @overload - def __rmul__(self, other: number_types | boolean_types) -> NumberVar: ... - - @overload - def __rmul__(self, other: list | tuple | set | ArrayVar) -> ArrayVar: ... - - def __rmul__(self, other: Any): - """Multiply two numbers. - - Args: - other: The other number. - - Returns: - The number multiplication operation. - """ - from .sequence import ArrayVar, LiteralArrayVar - - if isinstance(other, (list, tuple, ArrayVar)): - if isinstance(other, ArrayVar): - return other * self - return LiteralArrayVar.create(other) * self - - if not isinstance(other, NUMBER_TYPES): - raise_unsupported_operand_types("*", (type(other), type(self))) - - return number_multiply_operation(+other, self) - - def __truediv__(self, other: number_types) -> NumberVar: - """Divide two numbers. - - Args: - other: The other number. - - Returns: - The number true division operation. - """ - if not isinstance(other, NUMBER_TYPES): - raise_unsupported_operand_types("/", (type(self), type(other))) - - return number_true_division_operation(self, +other) - - def __rtruediv__(self, other: number_types) -> NumberVar: - """Divide two numbers. - - Args: - other: The other number. - - Returns: - The number true division operation. - """ - if not isinstance(other, NUMBER_TYPES): - raise_unsupported_operand_types("/", (type(other), type(self))) - - return number_true_division_operation(+other, self) - - def __floordiv__(self, other: number_types) -> NumberVar: - """Floor divide two numbers. - - Args: - other: The other number. - - Returns: - The number floor division operation. - """ - if not isinstance(other, NUMBER_TYPES): - raise_unsupported_operand_types("//", (type(self), type(other))) - - return number_floor_division_operation(self, +other) - - def __rfloordiv__(self, other: number_types) -> NumberVar: - """Floor divide two numbers. - - Args: - other: The other number. - - Returns: - The number floor division operation. - """ - if not isinstance(other, NUMBER_TYPES): - raise_unsupported_operand_types("//", (type(other), type(self))) - - return number_floor_division_operation(+other, self) - - def __mod__(self, other: number_types) -> NumberVar: - """Modulo two numbers. - - Args: - other: The other number. - - Returns: - The number modulo operation. - """ - if not isinstance(other, NUMBER_TYPES): - raise_unsupported_operand_types("%", (type(self), type(other))) - - return number_modulo_operation(self, +other) - - def __rmod__(self, other: number_types) -> NumberVar: - """Modulo two numbers. - - Args: - other: The other number. - - Returns: - The number modulo operation. - """ - if not isinstance(other, NUMBER_TYPES): - raise_unsupported_operand_types("%", (type(other), type(self))) - - return number_modulo_operation(+other, self) - - def __pow__(self, other: number_types) -> NumberVar: - """Exponentiate two numbers. - - Args: - other: The other number. - - Returns: - The number exponent operation. - """ - if not isinstance(other, NUMBER_TYPES): - raise_unsupported_operand_types("**", (type(self), type(other))) - - return number_exponent_operation(self, +other) - - def __rpow__(self, other: number_types) -> NumberVar: - """Exponentiate two numbers. - - Args: - other: The other number. - - Returns: - The number exponent operation. - """ - if not isinstance(other, NUMBER_TYPES): - raise_unsupported_operand_types("**", (type(other), type(self))) - - return number_exponent_operation(+other, self) - - def __neg__(self) -> NumberVar: - """Negate the number. - - Returns: - The number negation operation. - """ - return number_negate_operation(self) # pyright: ignore [reportReturnType] - - def __invert__(self): - """Boolean NOT the number. - - Returns: - The boolean NOT operation. - """ - return boolean_not_operation(self.bool()) - - def __pos__(self) -> NumberVar: - """Positive the number. - - Returns: - The number. - """ - return self - - def __round__(self, ndigits: int | NumberVar = 0) -> NumberVar: - """Round the number. - - Args: - ndigits: The number of digits to round. - - Returns: - The number round operation. - """ - if not isinstance(ndigits, NUMBER_TYPES): - raise_unsupported_operand_types("round", (type(self), type(ndigits))) - - return number_round_operation(self, +ndigits) - - def __ceil__(self): - """Ceil the number. - - Returns: - The number ceil operation. - """ - return number_ceil_operation(self) - - def __floor__(self): - """Floor the number. - - Returns: - The number floor operation. - """ - return number_floor_operation(self) - - def __trunc__(self): - """Trunc the number. - - Returns: - The number trunc operation. - """ - return number_trunc_operation(self) - - def __lt__(self, other: number_types) -> BooleanVar: - """Less than comparison. - - Args: - other: The other number. - - Returns: - The result of the comparison. - """ - if not isinstance(other, NUMBER_TYPES): - raise_unsupported_operand_types("<", (type(self), type(other))) - return less_than_operation(+self, +other) - - def __le__(self, other: number_types) -> BooleanVar: - """Less than or equal comparison. - - Args: - other: The other number. - - Returns: - The result of the comparison. - """ - if not isinstance(other, NUMBER_TYPES): - raise_unsupported_operand_types("<=", (type(self), type(other))) - return less_than_or_equal_operation(+self, +other) - - def __eq__(self, other: Any): - """Equal comparison. - - Args: - other: The other number. - - Returns: - The result of the comparison. - """ - if isinstance(other, NUMBER_TYPES): - return equal_operation(+self, +other) - return equal_operation(self, other) - - def __ne__(self, other: Any): - """Not equal comparison. - - Args: - other: The other number. - - Returns: - The result of the comparison. - """ - if isinstance(other, NUMBER_TYPES): - return not_equal_operation(+self, +other) - return not_equal_operation(self, other) - - def __gt__(self, other: number_types) -> BooleanVar: - """Greater than comparison. - - Args: - other: The other number. - - Returns: - The result of the comparison. - """ - if not isinstance(other, NUMBER_TYPES): - raise_unsupported_operand_types(">", (type(self), type(other))) - return greater_than_operation(+self, +other) - - def __ge__(self, other: number_types) -> BooleanVar: - """Greater than or equal comparison. - - Args: - other: The other number. - - Returns: - The result of the comparison. - """ - if not isinstance(other, NUMBER_TYPES): - raise_unsupported_operand_types(">=", (type(self), type(other))) - return greater_than_or_equal_operation(+self, +other) - - def _is_strict_float(self) -> bool: - """Check if the number is a float. - - Returns: - bool: True if the number is a float. - """ - return safe_issubclass(self._var_type, float) - - def _is_strict_int(self) -> bool: - """Check if the number is an int. - - Returns: - bool: True if the number is an int. - """ - return safe_issubclass(self._var_type, int) - - def __format__(self, format_spec: str) -> str: - """Format the number. - - Args: - format_spec: The format specifier. - - Returns: - The formatted number. - - Raises: - VarValueError: If the format specifier is not supported. - """ - from .sequence import ( - get_decimal_string_operation, - get_decimal_string_separator_operation, - ) - - separator = "" - - if format_spec and format_spec[:1] == ",": - separator = "," - format_spec = format_spec[1:] - elif format_spec and format_spec[:1] == "_": - separator = "_" - format_spec = format_spec[1:] - - if ( - format_spec - and format_spec[-1] == "f" - and format_spec[0] == "." - and format_spec[1:-1].isdigit() - ): - how_many_decimals = int(format_spec[1:-1]) - return f"{get_decimal_string_operation(self, Var.create(how_many_decimals), Var.create(separator))}" - - if not format_spec and separator: - return ( - f"{get_decimal_string_separator_operation(self, Var.create(separator))}" - ) - - if format_spec: - msg = ( - f"Unknown format code '{format_spec}' for object of type 'NumberVar'. It is only supported to use ',', '_', and '.f' for float numbers." - "If possible, use computed variables instead: https://reflex.dev/docs/vars/computed-vars/" - ) - raise VarValueError(msg) - - return super().__format__(format_spec) - - -def binary_number_operation( - func: Callable[[NumberVar, NumberVar], str], -) -> Callable[[number_types, number_types], NumberVar]: - """Decorator to create a binary number operation. - - Args: - func: The binary number operation function. - - Returns: - The binary number operation. - """ - - @var_operation - def operation(lhs: NumberVar, rhs: NumberVar): - return var_operation_return( - js_expression=func(lhs, rhs), - var_type=unionize(lhs._var_type, rhs._var_type), - ) - - def wrapper(lhs: number_types, rhs: number_types) -> NumberVar: - """Create the binary number operation. - - Args: - lhs: The first number. - rhs: The second number. - - Returns: - The binary number operation. - """ - return operation(lhs, rhs) # pyright: ignore [reportReturnType, reportArgumentType] - - return wrapper - - -@binary_number_operation -def number_add_operation(lhs: NumberVar, rhs: NumberVar): - """Add two numbers. - - Args: - lhs: The first number. - rhs: The second number. - - Returns: - The number addition operation. - """ - return f"({lhs} + {rhs})" - - -@binary_number_operation -def number_subtract_operation(lhs: NumberVar, rhs: NumberVar): - """Subtract two numbers. - - Args: - lhs: The first number. - rhs: The second number. - - Returns: - The number subtraction operation. - """ - return f"({lhs} - {rhs})" - - -@var_operation -def number_abs_operation(value: NumberVar): - """Get the absolute value of the number. - - Args: - value: The number. - - Returns: - The number absolute operation. - """ - return var_operation_return( - js_expression=f"Math.abs({value})", var_type=value._var_type - ) - - -@binary_number_operation -def number_multiply_operation(lhs: NumberVar, rhs: NumberVar): - """Multiply two numbers. - - Args: - lhs: The first number. - rhs: The second number. - - Returns: - The number multiplication operation. - """ - return f"({lhs} * {rhs})" - - -@var_operation -def number_negate_operation( - value: NumberVar[NUMBER_T], -) -> CustomVarOperationReturn[NUMBER_T]: - """Negate the number. - - Args: - value: The number. - - Returns: - The number negation operation. - """ - return var_operation_return(js_expression=f"-({value})", var_type=value._var_type) - - -@binary_number_operation -def number_true_division_operation(lhs: NumberVar, rhs: NumberVar): - """Divide two numbers. - - Args: - lhs: The first number. - rhs: The second number. - - Returns: - The number true division operation. - """ - return f"({lhs} / {rhs})" - - -@binary_number_operation -def number_floor_division_operation(lhs: NumberVar, rhs: NumberVar): - """Floor divide two numbers. - - Args: - lhs: The first number. - rhs: The second number. - - Returns: - The number floor division operation. - """ - return f"Math.floor({lhs} / {rhs})" - - -@binary_number_operation -def number_modulo_operation(lhs: NumberVar, rhs: NumberVar): - """Modulo two numbers. - - Args: - lhs: The first number. - rhs: The second number. - - Returns: - The number modulo operation. - """ - return f"({lhs} % {rhs})" - - -@binary_number_operation -def number_exponent_operation(lhs: NumberVar, rhs: NumberVar): - """Exponentiate two numbers. - - Args: - lhs: The first number. - rhs: The second number. - - Returns: - The number exponent operation. - """ - return f"({lhs} ** {rhs})" - - -@var_operation -def number_round_operation(value: NumberVar, ndigits: NumberVar | int): - """Round the number. - - Args: - value: The number. - ndigits: The number of digits. - - Returns: - The number round operation. - """ - if (isinstance(ndigits, LiteralNumberVar) and ndigits._var_value == 0) or ( - isinstance(ndigits, int) and ndigits == 0 - ): - return var_operation_return(js_expression=f"Math.round({value})", var_type=int) - return var_operation_return( - js_expression=f"(+{value}.toFixed({ndigits}))", var_type=float - ) - - -@var_operation -def number_ceil_operation(value: NumberVar): - """Ceil the number. - - Args: - value: The number. - - Returns: - The number ceil operation. - """ - return var_operation_return(js_expression=f"Math.ceil({value})", var_type=int) - - -@var_operation -def number_floor_operation(value: NumberVar): - """Floor the number. - - Args: - value: The number. - - Returns: - The number floor operation. - """ - return var_operation_return(js_expression=f"Math.floor({value})", var_type=int) - - -@var_operation -def number_trunc_operation(value: NumberVar): - """Trunc the number. - - Args: - value: The number. - - Returns: - The number trunc operation. - """ - return var_operation_return(js_expression=f"Math.trunc({value})", var_type=int) - - -class BooleanVar(NumberVar[bool], python_types=bool): - """Base class for immutable boolean vars.""" - - def __invert__(self): - """NOT the boolean. - - Returns: - The boolean NOT operation. - """ - return boolean_not_operation(self) - - def __int__(self): - """Convert the boolean to an int. - - Returns: - The boolean to int operation. - """ - return boolean_to_number_operation(self) - - def __pos__(self): - """Convert the boolean to an int. - - Returns: - The boolean to int operation. - """ - return boolean_to_number_operation(self) - - def bool(self) -> BooleanVar: - """Boolean conversion. - - Returns: - The boolean value of the boolean. - """ - return self - - def __lt__(self, other: Any): - """Less than comparison. - - Args: - other: The other boolean. - - Returns: - The result of the comparison. - """ - return +self < other - - def __le__(self, other: Any): - """Less than or equal comparison. - - Args: - other: The other boolean. - - Returns: - The result of the comparison. - """ - return +self <= other - - def __gt__(self, other: Any): - """Greater than comparison. - - Args: - other: The other boolean. - - Returns: - The result of the comparison. - """ - return +self > other - - def __ge__(self, other: Any): - """Greater than or equal comparison. - - Args: - other: The other boolean. - - Returns: - The result of the comparison. - """ - return +self >= other - - -@var_operation -def boolean_to_number_operation(value: BooleanVar): - """Convert the boolean to a number. - - Args: - value: The boolean. - - Returns: - The boolean to number operation. - """ - return var_operation_return(js_expression=f"Number({value})", var_type=int) - - -def comparison_operator( - func: Callable[[Var, Var], str], -) -> Callable[[Var | Any, Var | Any], BooleanVar]: - """Decorator to create a comparison operation. - - Args: - func: The comparison operation function. - - Returns: - The comparison operation. - """ - - @var_operation - def operation(lhs: Var, rhs: Var): - return var_operation_return( - js_expression=func(lhs, rhs), - var_type=bool, - ) - - def wrapper(lhs: Var | Any, rhs: Var | Any) -> BooleanVar: - """Create the comparison operation. - - Args: - lhs: The first value. - rhs: The second value. - - Returns: - The comparison operation. - """ - return operation(lhs, rhs) - - return wrapper - - -@comparison_operator -def greater_than_operation(lhs: Var, rhs: Var): - """Greater than comparison. - - Args: - lhs: The first value. - rhs: The second value. - - Returns: - The result of the comparison. - """ - return f"({lhs} > {rhs})" - - -@comparison_operator -def greater_than_or_equal_operation(lhs: Var, rhs: Var): - """Greater than or equal comparison. - - Args: - lhs: The first value. - rhs: The second value. - - Returns: - The result of the comparison. - """ - return f"({lhs} >= {rhs})" - - -@comparison_operator -def less_than_operation(lhs: Var, rhs: Var): - """Less than comparison. - - Args: - lhs: The first value. - rhs: The second value. - - Returns: - The result of the comparison. - """ - return f"({lhs} < {rhs})" - - -@comparison_operator -def less_than_or_equal_operation(lhs: Var, rhs: Var): - """Less than or equal comparison. - - Args: - lhs: The first value. - rhs: The second value. - - Returns: - The result of the comparison. - """ - return f"({lhs} <= {rhs})" - - -@comparison_operator -def equal_operation(lhs: Var, rhs: Var): - """Equal comparison. - - Args: - lhs: The first value. - rhs: The second value. - - Returns: - The result of the comparison. - """ - return f"({lhs}?.valueOf?.() === {rhs}?.valueOf?.())" - - -@comparison_operator -def not_equal_operation(lhs: Var, rhs: Var): - """Not equal comparison. - - Args: - lhs: The first value. - rhs: The second value. - - Returns: - The result of the comparison. - """ - return f"({lhs}?.valueOf?.() !== {rhs}?.valueOf?.())" - - -@var_operation -def boolean_not_operation(value: BooleanVar): - """Boolean NOT the boolean. - - Args: - value: The boolean. - - Returns: - The boolean NOT operation. - """ - return var_operation_return(js_expression=f"!({value})", var_type=bool) - - -@dataclasses.dataclass( - eq=False, - frozen=True, - slots=True, -) -class LiteralNumberVar(LiteralVar, NumberVar[NUMBER_T]): - """Base class for immutable literal number vars.""" - - _var_value: float | int | decimal.Decimal = dataclasses.field(default=0) - - def json(self) -> str: - """Get the JSON representation of the var. - - Returns: - The JSON representation of the var. - - Raises: - PrimitiveUnserializableToJSONError: If the var is unserializable to JSON. - """ - if isinstance(self._var_value, decimal.Decimal): - return json.dumps(float(self._var_value)) - if math.isinf(self._var_value) or math.isnan(self._var_value): - msg = f"No valid JSON representation for {self}" - raise PrimitiveUnserializableToJSONError(msg) - return json.dumps(self._var_value) - - def __hash__(self) -> int: - """Calculate the hash value of the object. - - Returns: - int: The hash value of the object. - """ - return hash((type(self).__name__, self._var_value)) - - @classmethod - def _get_all_var_data_without_creating_var( - cls, value: float | int | decimal.Decimal - ) -> VarData | None: - """Get all the var data without creating the var. - - Args: - value: The value of the var. - - Returns: - The var data. - """ - return None - - @classmethod - def create( - cls, value: float | int | decimal.Decimal, _var_data: VarData | None = None - ): - """Create the number var. - - Args: - value: The value of the var. - _var_data: Additional hooks and imports associated with the Var. - - Returns: - The number var. - """ - if math.isinf(value): - js_expr = "Infinity" if value > 0 else "-Infinity" - elif math.isnan(value): - js_expr = "NaN" - else: - js_expr = str(value) - - return cls( - _js_expr=js_expr, - _var_type=type(value), - _var_data=_var_data, - _var_value=value, - ) - - -@dataclasses.dataclass( - eq=False, - frozen=True, - slots=True, -) -class LiteralBooleanVar(LiteralVar, BooleanVar): - """Base class for immutable literal boolean vars.""" - - _var_value: bool = dataclasses.field(default=False) - - def json(self) -> str: - """Get the JSON representation of the var. - - Returns: - The JSON representation of the var. - """ - return "true" if self._var_value else "false" - - def __hash__(self) -> int: - """Calculate the hash value of the object. - - Returns: - int: The hash value of the object. - """ - return hash((type(self).__name__, self._var_value)) - - @classmethod - def _get_all_var_data_without_creating_var(cls, value: bool) -> VarData | None: - """Get all the var data without creating the var. - - Args: - value: The value of the var. - - Returns: - The var data. - """ - return None - - @classmethod - def create(cls, value: bool, _var_data: VarData | None = None): - """Create the boolean var. - - Args: - value: The value of the var. - _var_data: Additional hooks and imports associated with the Var. - - Returns: - The boolean var. - """ - return cls( - _js_expr="true" if value else "false", - _var_type=bool, - _var_data=_var_data, - _var_value=value, - ) - - -number_types = NumberVar | int | float | decimal.Decimal -boolean_types = BooleanVar | bool - - -_IS_TRUE_IMPORT: ImportDict = { - f"$/{Dirs.STATE_PATH}": [ImportVar(tag="isTrue")], -} - -_IS_NOT_NULL_OR_UNDEFINED_IMPORT: ImportDict = { - f"$/{Dirs.STATE_PATH}": [ImportVar(tag="isNotNullOrUndefined")], -} - - -@var_operation -def boolify(value: Var): - """Convert the value to a boolean. - - Args: - value: The value. - - Returns: - The boolean value. - """ - return var_operation_return( - js_expression=f"isTrue({value})", - var_type=bool, - var_data=VarData(imports=_IS_TRUE_IMPORT), - ) - - -@var_operation -def is_not_none_operation(value: Var): - """Check if the value is not None. - - Args: - value: The value. - - Returns: - The boolean value. - """ - return var_operation_return( - js_expression=f"isNotNullOrUndefined({value})", - var_type=bool, - var_data=VarData(imports=_IS_NOT_NULL_OR_UNDEFINED_IMPORT), - ) - - -T = TypeVar("T") -U = TypeVar("U") - - -@var_operation -def ternary_operation( - condition: Var[bool], if_true: Var[T], if_false: Var[U] -) -> CustomVarOperationReturn[T | U]: - """Create a ternary operation. - - Args: - condition: The condition. - if_true: The value if the condition is true. - if_false: The value if the condition is false. - - Returns: - The ternary operation. - """ - type_value: type[T] | type[U] = unionize(if_true._var_type, if_false._var_type) - value: CustomVarOperationReturn[T | U] = var_operation_return( - js_expression=f"({condition} ? {if_true} : {if_false})", - var_type=type_value, - ) - return value - - -NUMBER_TYPES = (int, float, decimal.Decimal, NumberVar) +from reflex_core.vars.number import * # noqa: F401, F403 diff --git a/reflex/vars/object.py b/reflex/vars/object.py index af5efc9bc7a..71e691f4c6d 100644 --- a/reflex/vars/object.py +++ b/reflex/vars/object.py @@ -1,647 +1,3 @@ -"""Classes for immutable object vars.""" +"""Re-export from reflex_core.""" -from __future__ import annotations - -import collections.abc -import dataclasses -import typing -from collections.abc import Mapping -from importlib.util import find_spec -from typing import ( - Any, - NoReturn, - TypeVar, - get_args, - get_type_hints, - is_typeddict, - overload, -) - -from rich.markup import escape - -from reflex.utils import types -from reflex.utils.exceptions import VarAttributeError -from reflex.utils.types import ( - GenericType, - get_attribute_access_type, - get_origin, - safe_issubclass, - unionize, -) - -from .base import ( - CachedVarOperation, - LiteralVar, - Var, - VarData, - cached_property_no_lock, - figure_out_type, - var_operation, - var_operation_return, -) -from .number import BooleanVar, NumberVar, raise_unsupported_operand_types -from .sequence import ArrayVar, LiteralArrayVar, StringVar - -OBJECT_TYPE = TypeVar("OBJECT_TYPE", covariant=True) - -KEY_TYPE = TypeVar("KEY_TYPE") -VALUE_TYPE = TypeVar("VALUE_TYPE") - -ARRAY_INNER_TYPE = TypeVar("ARRAY_INNER_TYPE") - -OTHER_KEY_TYPE = TypeVar("OTHER_KEY_TYPE") - - -def _determine_value_type(var_type: GenericType): - origin_var_type = get_origin(var_type) or var_type - - if origin_var_type in types.UnionTypes: - return unionize(*[ - _determine_value_type(arg) - for arg in get_args(var_type) - if arg is not type(None) - ]) - - if is_typeddict(origin_var_type) or dataclasses.is_dataclass(origin_var_type): - annotations = get_type_hints(origin_var_type) - return unionize(*annotations.values()) - - if origin_var_type in [dict, Mapping, collections.abc.Mapping]: - args = get_args(var_type) - return args[1] if args else Any - - return Any - - -PYTHON_TYPES = (Mapping,) -if find_spec("pydantic"): - import pydantic - - PYTHON_TYPES += (pydantic.BaseModel,) - - -class ObjectVar(Var[OBJECT_TYPE], python_types=PYTHON_TYPES): - """Base class for immutable object vars.""" - - def _key_type(self) -> type: - """Get the type of the keys of the object. - - Returns: - The type of the keys of the object. - """ - return str - - @overload - def _value_type( - self: ObjectVar[Mapping[Any, VALUE_TYPE]], - ) -> type[VALUE_TYPE]: ... - - @overload - def _value_type(self) -> GenericType: ... - - def _value_type(self) -> GenericType: - """Get the type of the values of the object. - - Returns: - The type of the values of the object. - """ - return _determine_value_type(self._var_type) - - def keys(self) -> ArrayVar[list[str]]: - """Get the keys of the object. - - Returns: - The keys of the object. - """ - return object_keys_operation(self) - - @overload - def values( - self: ObjectVar[Mapping[Any, VALUE_TYPE]], - ) -> ArrayVar[list[VALUE_TYPE]]: ... - - @overload - def values(self) -> ArrayVar: ... - - def values(self) -> ArrayVar: - """Get the values of the object. - - Returns: - The values of the object. - """ - return object_values_operation(self) - - @overload - def entries( - self: ObjectVar[Mapping[Any, VALUE_TYPE]], - ) -> ArrayVar[list[tuple[str, VALUE_TYPE]]]: ... - - @overload - def entries(self) -> ArrayVar: ... - - def entries(self) -> ArrayVar: - """Get the entries of the object. - - Returns: - The entries of the object. - """ - return object_entries_operation(self) - - items = entries - - def length(self) -> NumberVar[int]: - """Get the length of the object. - - Returns: - The length of the object. - """ - return self.keys().length() - - def merge(self, other: ObjectVar): - """Merge two objects. - - Args: - other: The other object to merge. - - Returns: - The merged object. - """ - return object_merge_operation(self, other) - - # NoReturn is used here to catch when key value is Any - @overload - def __getitem__( # pyright: ignore [reportOverlappingOverload] - self: ObjectVar[Mapping[Any, NoReturn]], - key: Var | Any, - ) -> Var: ... - - @overload - def __getitem__( - self: (ObjectVar[Mapping[Any, bool]]), - key: Var | Any, - ) -> BooleanVar: ... - - @overload - def __getitem__( - self: ( - ObjectVar[Mapping[Any, int]] - | ObjectVar[Mapping[Any, float]] - | ObjectVar[Mapping[Any, int | float]] - ), - key: Var | Any, - ) -> NumberVar: ... - - @overload - def __getitem__( - self: ObjectVar[Mapping[Any, str]], - key: Var | Any, - ) -> StringVar: ... - - @overload - def __getitem__( - self: ObjectVar[Mapping[Any, list[ARRAY_INNER_TYPE]]], - key: Var | Any, - ) -> ArrayVar[list[ARRAY_INNER_TYPE]]: ... - - @overload - def __getitem__( - self: ObjectVar[Mapping[Any, tuple[ARRAY_INNER_TYPE, ...]]], - key: Var | Any, - ) -> ArrayVar[tuple[ARRAY_INNER_TYPE, ...]]: ... - - @overload - def __getitem__( - self: ObjectVar[Mapping[Any, Mapping[OTHER_KEY_TYPE, VALUE_TYPE]]], - key: Var | Any, - ) -> ObjectVar[Mapping[OTHER_KEY_TYPE, VALUE_TYPE]]: ... - - @overload - def __getitem__( - self: ObjectVar[Mapping[Any, VALUE_TYPE]], - key: Var | Any, - ) -> Var[VALUE_TYPE]: ... - - def __getitem__(self, key: Var | Any) -> Var: - """Get an item from the object. - - Args: - key: The key to get from the object. - - 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() - - def get(self, key: Var | Any, default: Var | Any | None = None) -> Var: - """Get an item from the object. - - Args: - key: The key to get from the object. - default: The default value if the key is not found. - - Returns: - The item from the object. - """ - from reflex_components_core.core.cond import cond - - if default is None: - default = Var.create(None) - - value = self.__getitem__(key) # pyright: ignore[reportUnknownVariableType,reportAttributeAccessIssue,reportUnknownMemberType] - - return cond( # pyright: ignore[reportUnknownVariableType] - value, - value, - default, - ) - - # NoReturn is used here to catch when key value is Any - @overload - def __getattr__( # pyright: ignore [reportOverlappingOverload] - self: ObjectVar[Mapping[Any, NoReturn]], - name: str, - ) -> Var: ... - - @overload - def __getattr__( - self: ( - ObjectVar[Mapping[Any, int]] - | ObjectVar[Mapping[Any, float]] - | ObjectVar[Mapping[Any, int | float]] - ), - name: str, - ) -> NumberVar: ... - - @overload - def __getattr__( - self: ObjectVar[Mapping[Any, str]], - name: str, - ) -> StringVar: ... - - @overload - def __getattr__( - self: ObjectVar[Mapping[Any, list[ARRAY_INNER_TYPE]]], - name: str, - ) -> ArrayVar[list[ARRAY_INNER_TYPE]]: ... - - @overload - def __getattr__( - self: ObjectVar[Mapping[Any, tuple[ARRAY_INNER_TYPE, ...]]], - name: str, - ) -> ArrayVar[tuple[ARRAY_INNER_TYPE, ...]]: ... - - @overload - def __getattr__( - self: ObjectVar[Mapping[Any, Mapping[OTHER_KEY_TYPE, VALUE_TYPE]]], - name: str, - ) -> ObjectVar[Mapping[OTHER_KEY_TYPE, VALUE_TYPE]]: ... - - @overload - def __getattr__( - self: ObjectVar, - name: str, - ) -> ObjectItemOperation: ... - - def __getattr__(self, name: str) -> Var: - """Get an attribute of the var. - - Args: - name: The name of the attribute. - - Returns: - The attribute of the var. - - Raises: - VarAttributeError: The State var has no such attribute or may have been annotated wrongly. - """ - if name.startswith("__") and name.endswith("__"): - return getattr(super(type(self), self), name) - - var_type = self._var_type - - var_type = types.value_inside_optional(var_type) - - fixed_type = get_origin(var_type) or var_type - - if ( - is_typeddict(fixed_type) - or ( - isinstance(fixed_type, type) - and not safe_issubclass(fixed_type, Mapping) - ) - or (fixed_type in types.UnionTypes) - ): - attribute_type = get_attribute_access_type(var_type, name) - if attribute_type is None: - msg = ( - f"The State var `{self!s}` of type {escape(str(self._var_type))} has no attribute '{name}' or may have been annotated " - f"wrongly." - ) - raise VarAttributeError(msg) - return ObjectItemOperation.create(self, name, attribute_type).guess_type() - return ObjectItemOperation.create(self, name).guess_type() - - def contains(self, key: Var | Any) -> BooleanVar: - """Check if the object contains a key. - - Args: - key: The key to check. - - Returns: - The result of the check. - """ - return object_has_own_property_operation(self, key) - - -class RestProp(ObjectVar[dict[str, Any]]): - """A special object var representing forwarded rest props.""" - - -@dataclasses.dataclass( - eq=False, - frozen=True, - slots=True, -) -class LiteralObjectVar(CachedVarOperation, ObjectVar[OBJECT_TYPE], LiteralVar): - """Base class for immutable literal object vars.""" - - _var_value: Mapping[Var | Any, Var | Any] = dataclasses.field(default_factory=dict) - - def _key_type(self) -> type: - """Get the type of the keys of the object. - - Returns: - The type of the keys of the object. - """ - args_list = typing.get_args(self._var_type) - return args_list[0] if args_list else Any # pyright: ignore [reportReturnType] - - def _value_type(self) -> type: - """Get the type of the values of the object. - - Returns: - The type of the values of the object. - """ - args_list = typing.get_args(self._var_type) - return args_list[1] if args_list else Any # pyright: ignore [reportReturnType] - - @cached_property_no_lock - def _cached_var_name(self) -> str: - """The name of the var. - - Returns: - The name of the var. - """ - return ( - "({ " - + ", ".join([ - f"[{LiteralVar.create(key)!s}] : {LiteralVar.create(value)!s}" - for key, value in self._var_value.items() - ]) - + " })" - ) - - def json(self) -> str: - """Get the JSON representation of the object. - - Returns: - The JSON representation of the object. - - Raises: - TypeError: The keys and values of the object must be literal vars to get the JSON representation - """ - keys_and_values = [] - for key, value in self._var_value.items(): - key = LiteralVar.create(key) - value = LiteralVar.create(value) - if not isinstance(key, LiteralVar) or not isinstance(value, LiteralVar): - msg = "The keys and values of the object must be literal vars to get the JSON representation." - raise TypeError(msg) - keys_and_values.append(f"{key.json()}:{value.json()}") - return "{" + ", ".join(keys_and_values) + "}" - - def __hash__(self) -> int: - """Get the hash of the var. - - Returns: - The hash of the var. - """ - return hash((type(self).__name__, self._js_expr)) - - @classmethod - def _get_all_var_data_without_creating_var( - cls, - value: Mapping, - ) -> VarData | None: - """Get all the var data without creating a var. - - Args: - value: The value to get the var data from. - - Returns: - The var data. - """ - return VarData.merge( - LiteralArrayVar._get_all_var_data_without_creating_var(value), - LiteralArrayVar._get_all_var_data_without_creating_var(value.values()), - ) - - @cached_property_no_lock - def _cached_get_all_var_data(self) -> VarData | None: - """Get all the var data. - - Returns: - The var data. - """ - return VarData.merge( - LiteralArrayVar._get_all_var_data_without_creating_var(self._var_value), - LiteralArrayVar._get_all_var_data_without_creating_var( - self._var_value.values() - ), - self._var_data, - ) - - @classmethod - def create( - cls, - _var_value: Mapping, - _var_type: type[OBJECT_TYPE] | None = None, - _var_data: VarData | None = None, - ) -> LiteralObjectVar[OBJECT_TYPE]: - """Create the literal object var. - - Args: - _var_value: The value of the var. - _var_type: The type of the var. - _var_data: Additional hooks and imports associated with the Var. - - Returns: - The literal object var. - - Raises: - TypeError: If the value is not a mapping type or a dataclass. - """ - if not isinstance(_var_value, collections.abc.Mapping): - from reflex.utils.serializers import serialize - - serialized = serialize(_var_value, get_type=False) - if not isinstance(serialized, collections.abc.Mapping): - msg = f"Expected a mapping type or a dataclass, got {_var_value!r} of type {type(_var_value).__name__}." - raise TypeError(msg) - - return LiteralObjectVar( - _js_expr="", - _var_type=(type(_var_value) if _var_type is None else _var_type), - _var_data=_var_data, - _var_value=serialized, - ) - - return LiteralObjectVar( - _js_expr="", - _var_type=(figure_out_type(_var_value) if _var_type is None else _var_type), - _var_data=_var_data, - _var_value=_var_value, - ) - - -@var_operation -def object_keys_operation(value: ObjectVar): - """Get the keys of an object. - - Args: - value: The object to get the keys from. - - Returns: - The keys of the object. - """ - return var_operation_return( - js_expression=f"Object.keys({value} ?? {{}})", - var_type=list[str], - ) - - -@var_operation -def object_values_operation(value: ObjectVar): - """Get the values of an object. - - Args: - value: The object to get the values from. - - Returns: - The values of the object. - """ - return var_operation_return( - js_expression=f"Object.values({value} ?? {{}})", - var_type=list[value._value_type()], - ) - - -@var_operation -def object_entries_operation(value: ObjectVar): - """Get the entries of an object. - - Args: - value: The object to get the entries from. - - Returns: - The entries of the object. - """ - return var_operation_return( - js_expression=f"Object.entries({value} ?? {{}})", - var_type=list[tuple[str, value._value_type()]], - ) - - -@var_operation -def object_merge_operation(lhs: ObjectVar, rhs: ObjectVar): - """Merge two objects. - - Args: - lhs: The first object to merge. - rhs: The second object to merge. - - Returns: - The merged object. - """ - return var_operation_return( - js_expression=f"({{...{lhs}, ...{rhs}}})", - var_type=Mapping[ - lhs._key_type() | rhs._key_type(), - lhs._value_type() | rhs._value_type(), - ], - ) - - -@dataclasses.dataclass( - eq=False, - frozen=True, - slots=True, -) -class ObjectItemOperation(CachedVarOperation, Var): - """Operation to get an item from an object.""" - - _object: ObjectVar = dataclasses.field( - default_factory=lambda: LiteralObjectVar.create({}) - ) - _key: Var | Any = dataclasses.field(default_factory=lambda: LiteralVar.create(None)) - - @cached_property_no_lock - def _cached_var_name(self) -> str: - """The name of the operation. - - Returns: - The name of the operation. - """ - return f"{self._object!s}?.[{self._key!s}]" - - @classmethod - def create( - cls, - object: ObjectVar, - key: Var | Any, - _var_type: GenericType | None = None, - _var_data: VarData | None = None, - ) -> ObjectItemOperation: - """Create the object item operation. - - Args: - object: The object to get the item from. - key: The key to get from the object. - _var_type: The type of the item. - _var_data: Additional hooks and imports associated with the operation. - - Returns: - The object item operation. - """ - return cls( - _js_expr="", - _var_type=object._value_type() if _var_type is None else _var_type, - _var_data=_var_data, - _object=object, - _key=key if isinstance(key, Var) else LiteralVar.create(key), - ) - - -@var_operation -def object_has_own_property_operation(object: ObjectVar, key: Var): - """Check if an object has a key. - - Args: - object: The object to check. - key: The key to check. - - Returns: - The result of the check. - """ - return var_operation_return( - js_expression=f"{object}.hasOwnProperty({key})", - var_type=bool, - ) +from reflex_core.vars.object import * # noqa: F401, F403 diff --git a/reflex/vars/sequence.py b/reflex/vars/sequence.py index c0475a9b00f..a6bce968e9e 100644 --- a/reflex/vars/sequence.py +++ b/reflex/vars/sequence.py @@ -1,1841 +1,3 @@ -"""Collection of string classes and utilities.""" +"""Re-export from reflex_core.""" -from __future__ import annotations - -import collections.abc -import dataclasses -import decimal -import inspect -import json -import re -from collections.abc import Iterable, Mapping, Sequence -from typing import TYPE_CHECKING, Any, Literal, TypeVar, get_args, overload - -from typing_extensions import TypeVar as TypingExtensionsTypeVar - -from reflex import constants -from reflex.constants.base import REFLEX_VAR_OPENING_TAG -from reflex.utils import types -from reflex.utils.exceptions import VarTypeError -from reflex.utils.types import GenericType, get_origin - -from .base import ( - CachedVarOperation, - CustomVarOperationReturn, - LiteralVar, - Var, - VarData, - _global_vars, - cached_property_no_lock, - figure_out_type, - get_unique_variable_name, - unionize, - var_operation, - var_operation_return, -) -from .number import ( - BooleanVar, - LiteralNumberVar, - NumberVar, - raise_unsupported_operand_types, -) - -if TYPE_CHECKING: - from .base import DATACLASS_TYPE, SQLA_TYPE - from .function import FunctionVar - from .object import ObjectVar - -ARRAY_VAR_TYPE = TypeVar("ARRAY_VAR_TYPE", bound=Sequence, covariant=True) -OTHER_ARRAY_VAR_TYPE = TypeVar("OTHER_ARRAY_VAR_TYPE", bound=Sequence, covariant=True) -MAPPING_VAR_TYPE = TypeVar("MAPPING_VAR_TYPE", bound=Mapping, covariant=True) - -OTHER_TUPLE = TypeVar("OTHER_TUPLE") - -INNER_ARRAY_VAR = TypeVar("INNER_ARRAY_VAR") - - -KEY_TYPE = TypeVar("KEY_TYPE") -VALUE_TYPE = TypeVar("VALUE_TYPE") - - -class ArrayVar(Var[ARRAY_VAR_TYPE], python_types=(Sequence, set)): - """Base class for immutable array vars.""" - - def join(self, sep: StringVar | str = "") -> StringVar: - """Join the elements of the array. - - Args: - sep: The separator between elements. - - Returns: - The joined elements. - """ - if not isinstance(sep, (StringVar, str)): - raise_unsupported_operand_types("join", (type(self), type(sep))) - if ( - isinstance(self, LiteralArrayVar) - and ( - len( - args := [ - x - for x in self._var_value - if isinstance(x, (LiteralStringVar, str)) - ] - ) - == len(self._var_value) - ) - and isinstance(sep, (LiteralStringVar, str)) - ): - sep_str = sep._var_value if isinstance(sep, LiteralStringVar) else sep - return LiteralStringVar.create( - sep_str.join( - i._var_value if isinstance(i, LiteralStringVar) else i for i in args - ) - ) - return array_join_operation(self, sep) - - def reverse(self) -> ArrayVar[ARRAY_VAR_TYPE]: - """Reverse the array. - - Returns: - The reversed array. - """ - return array_reverse_operation(self) - - def __add__(self, other: ArrayVar[ARRAY_VAR_TYPE]) -> ArrayVar[ARRAY_VAR_TYPE]: - """Concatenate two arrays. - - Parameters: - other: The other array to concatenate. - - Returns: - ArrayConcatOperation: The concatenation of the two arrays. - """ - if not isinstance(other, ArrayVar): - raise_unsupported_operand_types("+", (type(self), type(other))) - - return array_concat_operation(self, other) - - @overload - def __getitem__(self, i: slice) -> ArrayVar[ARRAY_VAR_TYPE]: ... - - @overload - def __getitem__( - self: ( - ArrayVar[tuple[int, OTHER_TUPLE]] - | ArrayVar[tuple[float, OTHER_TUPLE]] - | ArrayVar[tuple[int | float, OTHER_TUPLE]] - ), - i: Literal[0, -2], - ) -> NumberVar: ... - - @overload - def __getitem__( - self: ArrayVar[tuple[Any, bool]], i: Literal[1, -1] - ) -> BooleanVar: ... - - @overload - def __getitem__( - self: ( - ArrayVar[tuple[Any, int]] - | ArrayVar[tuple[Any, float]] - | ArrayVar[tuple[Any, int | float]] - ), - i: Literal[1, -1], - ) -> NumberVar: ... - - @overload - def __getitem__( # pyright: ignore [reportOverlappingOverload] - self: ArrayVar[tuple[str, Any]], i: Literal[0, -2] - ) -> StringVar: ... - - @overload - def __getitem__( - self: ArrayVar[tuple[Any, str]], i: Literal[1, -1] - ) -> StringVar: ... - - @overload - def __getitem__( - self: ArrayVar[tuple[bool, Any]], i: Literal[0, -2] - ) -> BooleanVar: ... - - @overload - def __getitem__( - self: ArrayVar[Sequence[bool]], i: int | NumberVar - ) -> BooleanVar: ... - - @overload - def __getitem__( - self: ( - ArrayVar[Sequence[int]] - | ArrayVar[Sequence[float]] - | ArrayVar[Sequence[int | float]] - ), - i: int | NumberVar, - ) -> NumberVar: ... - - @overload - def __getitem__(self: ArrayVar[Sequence[str]], i: int | NumberVar) -> StringVar: ... - - @overload - def __getitem__( - self: ArrayVar[Sequence[OTHER_ARRAY_VAR_TYPE]], - i: int | NumberVar, - ) -> ArrayVar[OTHER_ARRAY_VAR_TYPE]: ... - - @overload - def __getitem__( - self: ArrayVar[Sequence[MAPPING_VAR_TYPE]], - i: int | NumberVar, - ) -> ObjectVar[MAPPING_VAR_TYPE]: ... - - @overload - def __getitem__( - self: ArrayVar[Sequence[SQLA_TYPE]], - i: int | NumberVar, - ) -> ObjectVar[SQLA_TYPE]: ... - - @overload - def __getitem__( - self: ArrayVar[Sequence[DATACLASS_TYPE]], - i: int | NumberVar, - ) -> ObjectVar[DATACLASS_TYPE]: ... - - @overload - def __getitem__(self, i: int | NumberVar) -> Var: ... - - def __getitem__(self, i: Any) -> ArrayVar[ARRAY_VAR_TYPE] | Var: - """Get a slice of the array. - - Args: - i: The slice. - - Returns: - The array slice operation. - """ - if isinstance(i, slice): - return ArraySliceOperation.create(self, i) - if not isinstance(i, (int, NumberVar)) or ( - isinstance(i, NumberVar) and i._is_strict_float() - ): - raise_unsupported_operand_types("[]", (type(self), type(i))) - return array_item_operation(self, i) - - def length(self) -> NumberVar[int]: - """Get the length of the array. - - Returns: - The length of the array. - """ - return array_length_operation(self) - - @overload - @classmethod - def range(cls, stop: int | NumberVar, /) -> ArrayVar[list[int]]: ... - - @overload - @classmethod - def range( - cls, - start: int | NumberVar, - end: int | NumberVar, - step: int | NumberVar = 1, - /, - ) -> ArrayVar[list[int]]: ... - - @overload - @classmethod - def range( - cls, - first_endpoint: int | NumberVar, - second_endpoint: int | NumberVar | None = None, - step: int | NumberVar | None = None, - ) -> ArrayVar[list[int]]: ... - - @classmethod - def range( - cls, - first_endpoint: int | NumberVar, - second_endpoint: int | NumberVar | None = None, - step: int | NumberVar | None = None, - ) -> ArrayVar[list[int]]: - """Create a range of numbers. - - Args: - first_endpoint: The end of the range if second_endpoint is not provided, otherwise the start of the range. - second_endpoint: The end of the range. - step: The step of the range. - - Returns: - The range of numbers. - """ - if any( - not isinstance(i, (int, NumberVar)) - for i in (first_endpoint, second_endpoint, step) - if i is not None - ): - raise_unsupported_operand_types( - "range", (type(first_endpoint), type(second_endpoint), type(step)) - ) - if second_endpoint is None: - start = 0 - end = first_endpoint - else: - start = first_endpoint - end = second_endpoint - - return array_range_operation(start, end, step or 1) - - @overload - def contains(self, other: Any) -> BooleanVar: ... - - @overload - def contains(self, other: Any, field: StringVar | str) -> BooleanVar: ... - - def contains(self, other: Any, field: Any = None) -> BooleanVar: - """Check if the array contains an element. - - Args: - other: The element to check for. - field: The field to check. - - Returns: - The array contains operation. - """ - if field is not None: - if not isinstance(field, (StringVar, str)): - raise_unsupported_operand_types("contains", (type(self), type(field))) - return array_contains_field_operation(self, other, field) - return array_contains_operation(self, other) - - def pluck(self, field: StringVar | str) -> ArrayVar: - """Pluck a field from the array. - - Args: - field: The field to pluck from the array. - - Returns: - The array pluck operation. - """ - return array_pluck_operation(self, field) - - def __mul__(self, other: NumberVar | int) -> ArrayVar[ARRAY_VAR_TYPE]: - """Multiply the sequence by a number or integer. - - Parameters: - other: The number or integer to multiply the sequence by. - - Returns: - ArrayVar[ARRAY_VAR_TYPE]: The result of multiplying the sequence by the given number or integer. - """ - if not isinstance(other, (NumberVar, int)) or ( - isinstance(other, NumberVar) and other._is_strict_float() - ): - raise_unsupported_operand_types("*", (type(self), type(other))) - - return repeat_array_operation(self, other) - - __rmul__ = __mul__ - - @overload - def __lt__(self, other: ArrayVar[ARRAY_VAR_TYPE]) -> BooleanVar: ... - - @overload - def __lt__(self, other: list | tuple) -> BooleanVar: ... - - def __lt__(self, other: Any): - """Check if the array is less than another array. - - Args: - other: The other array. - - Returns: - The array less than operation. - """ - if not isinstance(other, (ArrayVar, list, tuple)): - raise_unsupported_operand_types("<", (type(self), type(other))) - - return array_lt_operation(self, other) - - @overload - def __gt__(self, other: ArrayVar[ARRAY_VAR_TYPE]) -> BooleanVar: ... - - @overload - def __gt__(self, other: list | tuple) -> BooleanVar: ... - - def __gt__(self, other: Any): - """Check if the array is greater than another array. - - Args: - other: The other array. - - Returns: - The array greater than operation. - """ - if not isinstance(other, (ArrayVar, list, tuple)): - raise_unsupported_operand_types(">", (type(self), type(other))) - - return array_gt_operation(self, other) - - @overload - def __le__(self, other: ArrayVar[ARRAY_VAR_TYPE]) -> BooleanVar: ... - - @overload - def __le__(self, other: list | tuple) -> BooleanVar: ... - - def __le__(self, other: Any): - """Check if the array is less than or equal to another array. - - Args: - other: The other array. - - Returns: - The array less than or equal operation. - """ - if not isinstance(other, (ArrayVar, list, tuple)): - raise_unsupported_operand_types("<=", (type(self), type(other))) - - return array_le_operation(self, other) - - @overload - def __ge__(self, other: ArrayVar[ARRAY_VAR_TYPE]) -> BooleanVar: ... - - @overload - def __ge__(self, other: list | tuple) -> BooleanVar: ... - - def __ge__(self, other: Any): - """Check if the array is greater than or equal to another array. - - Args: - other: The other array. - - Returns: - The array greater than or equal operation. - """ - if not isinstance(other, (ArrayVar, list, tuple)): - raise_unsupported_operand_types(">=", (type(self), type(other))) - - return array_ge_operation(self, other) - - def foreach(self, fn: Any): - """Apply a function to each element of the array. - - Args: - fn: The function to apply. - - Returns: - The array after applying the function. - - Raises: - VarTypeError: If the function takes more than one argument. - """ - from .function import ArgsFunctionOperation - - if not callable(fn): - raise_unsupported_operand_types("foreach", (type(self), type(fn))) - # get the number of arguments of the function - num_args = len(inspect.signature(fn).parameters) - if num_args > 1: - msg = "The function passed to foreach should take at most one argument." - raise VarTypeError(msg) - - if num_args == 0: - return_value = fn() - function_var = ArgsFunctionOperation.create((), return_value) - else: - # generic number var - number_var = Var("").to(NumberVar, int) - - first_arg_type = self[number_var]._var_type - - arg_name = get_unique_variable_name() - - # get first argument type - first_arg = Var( - _js_expr=arg_name, - _var_type=first_arg_type, - ).guess_type() - - function_var = ArgsFunctionOperation.create( - (arg_name,), - Var.create(fn(first_arg)), - ) - - return map_array_operation(self, function_var) - - -@dataclasses.dataclass( - eq=False, - frozen=True, - slots=True, -) -class LiteralArrayVar(CachedVarOperation, LiteralVar, ArrayVar[ARRAY_VAR_TYPE]): - """Base class for immutable literal array vars.""" - - _var_value: Sequence[Var | Any] = dataclasses.field(default=()) - - @cached_property_no_lock - def _cached_var_name(self) -> str: - """The name of the var. - - Returns: - The name of the var. - """ - return ( - "[" - + ", ".join([ - str(LiteralVar.create(element)) for element in self._var_value - ]) - + "]" - ) - - @classmethod - def _get_all_var_data_without_creating_var(cls, value: Iterable) -> VarData | None: - """Get all the VarData associated with the Var without creating a Var. - - Args: - value: The value to get the VarData for. - - Returns: - The VarData associated with the Var. - """ - return VarData.merge(*[ - LiteralVar._get_all_var_data_without_creating_var_dispatch(element) - for element in value - ]) - - @cached_property_no_lock - def _cached_get_all_var_data(self) -> VarData | None: - """Get all the VarData associated with the Var. - - Returns: - The VarData associated with the Var. - """ - return VarData.merge( - *[ - LiteralVar._get_all_var_data_without_creating_var_dispatch(element) - for element in self._var_value - ], - self._var_data, - ) - - def __hash__(self) -> int: - """Get the hash of the var. - - Returns: - The hash of the var. - """ - return hash((self.__class__.__name__, self._js_expr)) - - def json(self) -> str: - """Get the JSON representation of the var. - - Returns: - The JSON representation of the var. - - Raises: - TypeError: If the array elements are not of type LiteralVar. - """ - elements = [] - for element in self._var_value: - element_var = LiteralVar.create(element) - if not isinstance(element_var, LiteralVar): - msg = f"Array elements must be of type LiteralVar, not {type(element_var)}" - raise TypeError(msg) - elements.append(element_var.json()) - - return "[" + ", ".join(elements) + "]" - - @classmethod - def create( - cls, - value: OTHER_ARRAY_VAR_TYPE, - _var_type: type[OTHER_ARRAY_VAR_TYPE] | None = None, - _var_data: VarData | None = None, - ) -> LiteralArrayVar[OTHER_ARRAY_VAR_TYPE]: - """Create a var from a string value. - - Args: - value: The value to create the var from. - _var_type: The type of the var. - _var_data: Additional hooks and imports associated with the Var. - - Returns: - The var. - """ - return LiteralArrayVar( - _js_expr="", - _var_type=figure_out_type(value) if _var_type is None else _var_type, - _var_data=_var_data, - _var_value=value, - ) - - -STRING_TYPE = TypingExtensionsTypeVar("STRING_TYPE", default=str) - - -class StringVar(Var[STRING_TYPE], python_types=str): - """Base class for immutable string vars.""" - - def __add__(self, other: StringVar | str) -> ConcatVarOperation: - """Concatenate two strings. - - Args: - other: The other string. - - Returns: - The string concatenation operation. - """ - if not isinstance(other, (StringVar, str)): - raise_unsupported_operand_types("+", (type(self), type(other))) - - return ConcatVarOperation.create(self, other) - - def __radd__(self, other: StringVar | str) -> ConcatVarOperation: - """Concatenate two strings. - - Args: - other: The other string. - - Returns: - The string concatenation operation. - """ - if not isinstance(other, (StringVar, str)): - raise_unsupported_operand_types("+", (type(other), type(self))) - - return ConcatVarOperation.create(other, self) - - def __mul__(self, other: NumberVar | int) -> StringVar: - """Multiply the sequence by a number or an integer. - - Args: - other: The number or integer to multiply the sequence by. - - Returns: - StringVar: The resulting sequence after multiplication. - """ - if not isinstance(other, (NumberVar, int)): - raise_unsupported_operand_types("*", (type(self), type(other))) - - return (self.split() * other).join() - - def __rmul__(self, other: NumberVar | int) -> StringVar: - """Multiply the sequence by a number or an integer. - - Args: - other: The number or integer to multiply the sequence by. - - Returns: - StringVar: The resulting sequence after multiplication. - """ - if not isinstance(other, (NumberVar, int)): - raise_unsupported_operand_types("*", (type(other), type(self))) - - return (self.split() * other).join() - - @overload - def __getitem__(self, i: slice) -> StringVar: ... - - @overload - def __getitem__(self, i: int | NumberVar) -> StringVar: ... - - def __getitem__(self, i: Any) -> StringVar: - """Get a slice of the string. - - Args: - i: The slice. - - Returns: - The string slice operation. - """ - if isinstance(i, slice): - return self.split()[i].join() - if not isinstance(i, (int, NumberVar)) or ( - isinstance(i, NumberVar) and i._is_strict_float() - ): - raise_unsupported_operand_types("[]", (type(self), type(i))) - return string_item_operation(self, i) - - def length(self) -> NumberVar: - """Get the length of the string. - - Returns: - The string length operation. - """ - return self.split().length() - - def lower(self) -> StringVar: - """Convert the string to lowercase. - - Returns: - The string lower operation. - """ - return string_lower_operation(self) - - def upper(self) -> StringVar: - """Convert the string to uppercase. - - Returns: - The string upper operation. - """ - return string_upper_operation(self) - - def title(self) -> StringVar: - """Convert the string to title case. - - Returns: - The string title operation. - """ - return string_title_operation(self) - - def capitalize(self) -> StringVar: - """Capitalize the string. - - Returns: - The string capitalize operation. - """ - return string_capitalize_operation(self) - - def strip(self) -> StringVar: - """Strip the string. - - Returns: - The string strip operation. - """ - return string_strip_operation(self) - - def reversed(self) -> StringVar: - """Reverse the string. - - Returns: - The string reverse operation. - """ - return self.split().reverse().join() - - def contains( - self, other: StringVar | str, field: StringVar | str | None = None - ) -> BooleanVar: - """Check if the string contains another string. - - Args: - other: The other string. - field: The field to check. - - Returns: - The string contains operation. - """ - if not isinstance(other, (StringVar, str)): - raise_unsupported_operand_types("contains", (type(self), type(other))) - if field is not None: - if not isinstance(field, (StringVar, str)): - raise_unsupported_operand_types("contains", (type(self), type(field))) - return string_contains_field_operation(self, other, field) - return string_contains_operation(self, other) - - def split(self, separator: StringVar | str = "") -> ArrayVar[list[str]]: - """Split the string. - - Args: - separator: The separator. - - Returns: - The string split operation. - """ - if not isinstance(separator, (StringVar, str)): - raise_unsupported_operand_types("split", (type(self), type(separator))) - return string_split_operation(self, separator) - - def startswith(self, prefix: StringVar | str) -> BooleanVar: - """Check if the string starts with a prefix. - - Args: - prefix: The prefix. - - Returns: - The string starts with operation. - """ - if not isinstance(prefix, (StringVar, str)): - raise_unsupported_operand_types("startswith", (type(self), type(prefix))) - return string_starts_with_operation(self, prefix) - - def endswith(self, suffix: StringVar | str) -> BooleanVar: - """Check if the string ends with a suffix. - - Args: - suffix: The suffix. - - Returns: - The string ends with operation. - """ - if not isinstance(suffix, (StringVar, str)): - raise_unsupported_operand_types("endswith", (type(self), type(suffix))) - return string_ends_with_operation(self, suffix) - - def __lt__(self, other: StringVar | str) -> BooleanVar: - """Check if the string is less than another string. - - Args: - other: The other string. - - Returns: - The string less than operation. - """ - if not isinstance(other, (StringVar, str)): - raise_unsupported_operand_types("<", (type(self), type(other))) - - return string_lt_operation(self, other) - - def __gt__(self, other: StringVar | str) -> BooleanVar: - """Check if the string is greater than another string. - - Args: - other: The other string. - - Returns: - The string greater than operation. - """ - if not isinstance(other, (StringVar, str)): - raise_unsupported_operand_types(">", (type(self), type(other))) - - return string_gt_operation(self, other) - - def __le__(self, other: StringVar | str) -> BooleanVar: - """Check if the string is less than or equal to another string. - - Args: - other: The other string. - - Returns: - The string less than or equal operation. - """ - if not isinstance(other, (StringVar, str)): - raise_unsupported_operand_types("<=", (type(self), type(other))) - - return string_le_operation(self, other) - - def __ge__(self, other: StringVar | str) -> BooleanVar: - """Check if the string is greater than or equal to another string. - - Args: - other: The other string. - - Returns: - The string greater than or equal operation. - """ - if not isinstance(other, (StringVar, str)): - raise_unsupported_operand_types(">=", (type(self), type(other))) - - 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): - """Check if a string is less than another string. - - Args: - lhs: The left-hand side string. - rhs: The right-hand side string. - - Returns: - The string less than operation. - """ - return var_operation_return(js_expression=f"{lhs} < {rhs}", var_type=bool) - - -@var_operation -def string_gt_operation(lhs: StringVar[Any] | str, rhs: StringVar[Any] | str): - """Check if a string is greater than another string. - - Args: - lhs: The left-hand side string. - rhs: The right-hand side string. - - Returns: - The string greater than operation. - """ - return var_operation_return(js_expression=f"{lhs} > {rhs}", var_type=bool) - - -@var_operation -def string_le_operation(lhs: StringVar[Any] | str, rhs: StringVar[Any] | str): - """Check if a string is less than or equal to another string. - - Args: - lhs: The left-hand side string. - rhs: The right-hand side string. - - Returns: - The string less than or equal operation. - """ - return var_operation_return(js_expression=f"{lhs} <= {rhs}", var_type=bool) - - -@var_operation -def string_ge_operation(lhs: StringVar[Any] | str, rhs: StringVar[Any] | str): - """Check if a string is greater than or equal to another string. - - Args: - lhs: The left-hand side string. - rhs: The right-hand side string. - - Returns: - The string greater than or equal operation. - """ - return var_operation_return(js_expression=f"{lhs} >= {rhs}", var_type=bool) - - -@var_operation -def string_lower_operation(string: StringVar[Any]): - """Convert a string to lowercase. - - Args: - string: The string to convert. - - Returns: - The lowercase string. - """ - return var_operation_return(js_expression=f"{string}.toLowerCase()", var_type=str) - - -@var_operation -def string_upper_operation(string: StringVar[Any]): - """Convert a string to uppercase. - - Args: - string: The string to convert. - - Returns: - The uppercase string. - """ - return var_operation_return(js_expression=f"{string}.toUpperCase()", var_type=str) - - -@var_operation -def string_title_operation(string: StringVar[Any]): - """Convert a string to title case. - - Args: - string: The string to convert. - - Returns: - The title case string. - """ - return var_operation_return( - js_expression=f"{string}.split(' ').map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()).join(' ')", - var_type=str, - ) - - -@var_operation -def string_capitalize_operation(string: StringVar[Any]): - """Capitalize a string. - - Args: - string: The string to capitalize. - - Returns: - The capitalized string. - """ - return var_operation_return( - js_expression=f"(((s) => s.charAt(0).toUpperCase() + s.slice(1).toLowerCase())({string}))", - var_type=str, - ) - - -@var_operation -def string_strip_operation(string: StringVar[Any]): - """Strip a string. - - Args: - string: The string to strip. - - Returns: - The stripped string. - """ - return var_operation_return(js_expression=f"{string}.trim()", var_type=str) - - -@var_operation -def string_contains_field_operation( - haystack: StringVar[Any], needle: StringVar[Any] | str, field: StringVar[Any] | str -): - """Check if a string contains another string. - - Args: - haystack: The haystack. - needle: The needle. - field: The field to check. - - Returns: - The string contains operation. - """ - return var_operation_return( - js_expression=f"{haystack}.some(obj => obj[{field}] === {needle})", - var_type=bool, - ) - - -@var_operation -def string_contains_operation(haystack: StringVar[Any], needle: StringVar[Any] | str): - """Check if a string contains another string. - - Args: - haystack: The haystack. - needle: The needle. - - Returns: - The string contains operation. - """ - return var_operation_return( - js_expression=f"{haystack}.includes({needle})", var_type=bool - ) - - -@var_operation -def string_starts_with_operation( - full_string: StringVar[Any], prefix: StringVar[Any] | str -): - """Check if a string starts with a prefix. - - Args: - full_string: The full string. - prefix: The prefix. - - Returns: - Whether the string starts with the prefix. - """ - return var_operation_return( - js_expression=f"{full_string}.startsWith({prefix})", var_type=bool - ) - - -@var_operation -def string_ends_with_operation( - full_string: StringVar[Any], suffix: StringVar[Any] | str -): - """Check if a string ends with a suffix. - - Args: - full_string: The full string. - suffix: The suffix. - - Returns: - Whether the string ends with the suffix. - """ - return var_operation_return( - js_expression=f"{full_string}.endsWith({suffix})", var_type=bool - ) - - -@var_operation -def string_item_operation(string: StringVar[Any], index: NumberVar | int): - """Get an item from a string. - - Args: - string: The string. - index: The index of the item. - - Returns: - The item from the string. - """ - return var_operation_return(js_expression=f"{string}?.at?.({index})", var_type=str) - - -@var_operation -def array_join_operation(array: ArrayVar, sep: StringVar[Any] | str = ""): - """Join the elements of an array. - - Args: - array: The array. - sep: The separator. - - Returns: - The joined elements. - """ - return var_operation_return(js_expression=f"{array}.join({sep})", var_type=str) - - -@var_operation -def string_replace_operation( - string: StringVar[Any], search_value: StringVar | str, new_value: StringVar | str -): - """Replace a string with a value. - - Args: - string: The string. - search_value: The string to search. - new_value: The value to be replaced with. - - Returns: - The string replace operation. - """ - return var_operation_return( - js_expression=f"{string}.replaceAll({search_value}, {new_value})", - var_type=str, - ) - - -@var_operation -def get_decimal_string_separator_operation(value: NumberVar, separator: StringVar): - """Get the decimal string separator. - - Args: - value: The number. - separator: The separator. - - Returns: - The decimal string separator. - """ - return var_operation_return( - js_expression=f"({value}.toLocaleString('en-US').replaceAll(',', {separator}))", - var_type=str, - ) - - -@var_operation -def get_decimal_string_operation( - value: NumberVar, decimals: NumberVar, separator: StringVar -): - """Get the decimal string of the number. - - Args: - value: The number. - decimals: The number of decimals. - separator: The separator. - - Returns: - The decimal string of the number. - """ - return var_operation_return( - js_expression=f"({value}.toLocaleString('en-US', ((decimals) => ({{minimumFractionDigits: decimals, maximumFractionDigits: decimals}}))({decimals})).replaceAll(',', {separator}))", - var_type=str, - ) - - -# Compile regex for finding reflex var tags. -_decode_var_pattern_re = ( - rf"{constants.REFLEX_VAR_OPENING_TAG}(.*?){constants.REFLEX_VAR_CLOSING_TAG}" -) -_decode_var_pattern = re.compile(_decode_var_pattern_re, flags=re.DOTALL) - - -@dataclasses.dataclass( - eq=False, - frozen=True, - slots=True, -) -class LiteralStringVar(LiteralVar, StringVar[str]): - """Base class for immutable literal string vars.""" - - _var_value: str = dataclasses.field(default="") - - @classmethod - def _get_all_var_data_without_creating_var(cls, value: str) -> VarData | None: - """Get all the VarData associated with the Var without creating a Var. - - Args: - value: The value to get the VarData for. - - Returns: - The VarData associated with the Var. - """ - if REFLEX_VAR_OPENING_TAG not in value: - return None - return cls.create(value)._get_all_var_data() - - @classmethod - def create( - cls, - value: str, - _var_type: GenericType | None = None, - _var_data: VarData | None = None, - ) -> StringVar: - """Create a var from a string value. - - Args: - value: The value to create the var from. - _var_type: The type of the var. - _var_data: Additional hooks and imports associated with the Var. - - Returns: - The var. - """ - # Determine var type in case the value is inherited from str. - _var_type = _var_type or type(value) or str - - if REFLEX_VAR_OPENING_TAG in value: - strings_and_vals: list[Var | str] = [] - offset = 0 - - # Find all tags - while m := _decode_var_pattern.search(value): - start, end = m.span() - - strings_and_vals.append(value[:start]) - - serialized_data = m.group(1) - - if serialized_data.isnumeric() or ( - serialized_data[0] == "-" and serialized_data[1:].isnumeric() - ): - # This is a global immutable var. - var = _global_vars[int(serialized_data)] - strings_and_vals.append(var) - value = value[(end + len(var._js_expr)) :] - - offset += end - start - - strings_and_vals.append(value) - - filtered_strings_and_vals = [ - s for s in strings_and_vals if isinstance(s, Var) or s - ] - if len(filtered_strings_and_vals) == 1: - only_string = filtered_strings_and_vals[0] - if isinstance(only_string, str): - return LiteralVar.create(only_string).to(StringVar, _var_type) - return only_string.to(StringVar, only_string._var_type) - - if len( - literal_strings := [ - s - for s in filtered_strings_and_vals - if isinstance(s, (str, LiteralStringVar)) - ] - ) == len(filtered_strings_and_vals): - return LiteralStringVar.create( - "".join( - s._var_value if isinstance(s, LiteralStringVar) else s - for s in literal_strings - ), - _var_type=_var_type, - _var_data=VarData.merge( - _var_data, - *( - s._get_all_var_data() - for s in filtered_strings_and_vals - if isinstance(s, Var) - ), - ), - ) - - concat_result = ConcatVarOperation.create( - *filtered_strings_and_vals, - _var_data=_var_data, - ) - - return ( - concat_result - if _var_type is str - else concat_result.to(StringVar, _var_type) - ) - - return LiteralStringVar( - _js_expr=json.dumps(value), - _var_type=_var_type, - _var_data=_var_data, - _var_value=value, - ) - - def __hash__(self) -> int: - """Get the hash of the var. - - Returns: - The hash of the var. - """ - return hash((type(self).__name__, self._var_value)) - - def json(self) -> str: - """Get the JSON representation of the var. - - Returns: - The JSON representation of the var. - """ - return json.dumps(self._var_value) - - -@dataclasses.dataclass( - eq=False, - frozen=True, - slots=True, -) -class ConcatVarOperation(CachedVarOperation, StringVar[str]): - """Representing a concatenation of literal string vars.""" - - _var_value: tuple[Var, ...] = dataclasses.field(default_factory=tuple) - - @cached_property_no_lock - def _cached_var_name(self) -> str: - """The name of the var. - - Returns: - The name of the var. - """ - list_of_strs: list[str | Var] = [] - last_string = "" - for var in self._var_value: - if isinstance(var, LiteralStringVar): - last_string += var._var_value - else: - if last_string: - list_of_strs.append(last_string) - last_string = "" - list_of_strs.append(var) - - if last_string: - list_of_strs.append(last_string) - - list_of_strs_filtered = [ - str(LiteralVar.create(s)) for s in list_of_strs if isinstance(s, Var) or s - ] - - if len(list_of_strs_filtered) == 1: - return list_of_strs_filtered[0] - - return "(" + "+".join(list_of_strs_filtered) + ")" - - @cached_property_no_lock - def _cached_get_all_var_data(self) -> VarData | None: - """Get all the VarData asVarDatae Var. - - Returns: - The VarData associated with the Var. - """ - return VarData.merge( - *[ - var._get_all_var_data() - for var in self._var_value - if isinstance(var, Var) - ], - self._var_data, - ) - - @classmethod - def create( - cls, - *value: Var | str, - _var_data: VarData | None = None, - ) -> ConcatVarOperation: - """Create a var from a string value. - - Args: - *value: The values to concatenate. - _var_data: Additional hooks and imports associated with the Var. - - Returns: - The var. - """ - return cls( - _js_expr="", - _var_type=str, - _var_data=_var_data, - _var_value=tuple(map(LiteralVar.create, value)), - ) - - -@var_operation -def string_split_operation(string: StringVar[Any], sep: StringVar | str = ""): - """Split a string. - - Args: - string: The string to split. - sep: The separator. - - Returns: - The split string. - """ - return var_operation_return( - js_expression=f"{string}.split({sep})", var_type=list[str] - ) - - -@dataclasses.dataclass( - eq=False, - frozen=True, - slots=True, -) -class ArraySliceOperation(CachedVarOperation, ArrayVar): - """Base class for immutable string vars that are the result of a string slice operation.""" - - _array: ArrayVar = dataclasses.field( - default_factory=lambda: LiteralArrayVar.create([]) - ) - _start: NumberVar | int = dataclasses.field(default_factory=lambda: 0) - _stop: NumberVar | int = dataclasses.field(default_factory=lambda: 0) - _step: NumberVar | int = dataclasses.field(default_factory=lambda: 1) - - @cached_property_no_lock - def _cached_var_name(self) -> str: - """The name of the var. - - Returns: - The name of the var. - - Raises: - ValueError: If the slice step is zero. - """ - start, end, step = self._start, self._stop, self._step - - normalized_start = ( - LiteralVar.create(start) if start is not None else Var(_js_expr="undefined") - ) - normalized_end = ( - LiteralVar.create(end) if end is not None else Var(_js_expr="undefined") - ) - if step is None: - return f"{self._array!s}.slice({normalized_start!s}, {normalized_end!s})" - if not isinstance(step, Var): - if step < 0: - actual_start = end + 1 if end is not None else 0 - actual_end = start + 1 if start is not None else self._array.length() - return str(self._array[actual_start:actual_end].reverse()[::-step]) - if step == 0: - msg = "slice step cannot be zero" - raise ValueError(msg) - return f"{self._array!s}.slice({normalized_start!s}, {normalized_end!s}).filter((_, i) => i % {step!s} === 0)" - - actual_start_reverse = end + 1 if end is not None else 0 - actual_end_reverse = start + 1 if start is not None else self._array.length() - - return f"{self.step!s} > 0 ? {self._array!s}.slice({normalized_start!s}, {normalized_end!s}).filter((_, i) => i % {step!s} === 0) : {self._array!s}.slice({actual_start_reverse!s}, {actual_end_reverse!s}).reverse().filter((_, i) => i % {-step!s} === 0)" - - @classmethod - def create( - cls, - array: ArrayVar, - slice: slice, - _var_data: VarData | None = None, - ) -> ArraySliceOperation: - """Create a var from a string value. - - Args: - array: The array. - slice: The slice. - _var_data: Additional hooks and imports associated with the Var. - - Returns: - The var. - """ - return cls( - _js_expr="", - _var_type=array._var_type, - _var_data=_var_data, - _array=array, - _start=slice.start, - _stop=slice.stop, - _step=slice.step, - ) - - -@var_operation -def array_pluck_operation( - array: ArrayVar[ARRAY_VAR_TYPE], - field: StringVar | str, -) -> CustomVarOperationReturn[ARRAY_VAR_TYPE]: - """Pluck a field from an array of objects. - - Args: - array: The array to pluck from. - field: The field to pluck from the objects in the array. - - Returns: - The reversed array. - """ - return var_operation_return( - js_expression=f"{array}.map(e=>e?.[{field}])", - var_type=array._var_type, - ) - - -@var_operation -def array_reverse_operation( - array: ArrayVar[ARRAY_VAR_TYPE], -) -> CustomVarOperationReturn[ARRAY_VAR_TYPE]: - """Reverse an array. - - Args: - array: The array to reverse. - - Returns: - The reversed array. - """ - return var_operation_return( - js_expression=f"{array}.slice().reverse()", - var_type=array._var_type, - ) - - -@var_operation -def array_lt_operation(lhs: ArrayVar | list | tuple, rhs: ArrayVar | list | tuple): - """Check if an array is less than another array. - - Args: - lhs: The left-hand side array. - rhs: The right-hand side array. - - Returns: - The array less than operation. - """ - return var_operation_return(js_expression=f"{lhs} < {rhs}", var_type=bool) - - -@var_operation -def array_gt_operation(lhs: ArrayVar | list | tuple, rhs: ArrayVar | list | tuple): - """Check if an array is greater than another array. - - Args: - lhs: The left-hand side array. - rhs: The right-hand side array. - - Returns: - The array greater than operation. - """ - return var_operation_return(js_expression=f"{lhs} > {rhs}", var_type=bool) - - -@var_operation -def array_le_operation(lhs: ArrayVar | list | tuple, rhs: ArrayVar | list | tuple): - """Check if an array is less than or equal to another array. - - Args: - lhs: The left-hand side array. - rhs: The right-hand side array. - - Returns: - The array less than or equal operation. - """ - return var_operation_return(js_expression=f"{lhs} <= {rhs}", var_type=bool) - - -@var_operation -def array_ge_operation(lhs: ArrayVar | list | tuple, rhs: ArrayVar | list | tuple): - """Check if an array is greater than or equal to another array. - - Args: - lhs: The left-hand side array. - rhs: The right-hand side array. - - Returns: - The array greater than or equal operation. - """ - return var_operation_return(js_expression=f"{lhs} >= {rhs}", var_type=bool) - - -@var_operation -def array_length_operation(array: ArrayVar): - """Get the length of an array. - - Args: - array: The array. - - Returns: - The length of the array. - """ - return var_operation_return( - js_expression=f"{array}.length", - var_type=int, - ) - - -def is_tuple_type(t: GenericType) -> bool: - """Check if a type is a tuple type. - - Args: - t: The type to check. - - Returns: - Whether the type is a tuple type. - """ - return get_origin(t) is tuple - - -def _determine_value_of_array_index( - var_type: GenericType, index: int | float | decimal.Decimal | None = None -): - """Determine the value of an array index. - - Args: - var_type: The type of the array. - index: The index of the array. - - Returns: - The value of the array index. - """ - origin_var_type = get_origin(var_type) or var_type - if origin_var_type in types.UnionTypes: - return unionize(*[ - _determine_value_of_array_index(t, index) - for t in get_args(var_type) - if t is not type(None) - ]) - if origin_var_type is range: - return int - if origin_var_type in [ - Sequence, - Iterable, - list, - set, - collections.abc.Sequence, - collections.abc.Iterable, - ]: - args = get_args(var_type) - return args[0] if args else Any - if origin_var_type is tuple: - args = get_args(var_type) - if len(args) == 2 and args[1] is ...: - return args[0] - return ( - args[int(index) % len(args)] - if args and index is not None - else (unionize(*args) if args else Any) - ) - return Any - - -@var_operation -def array_item_operation(array: ArrayVar, index: NumberVar | int): - """Get an item from an array. - - Args: - array: The array. - index: The index of the item. - - Returns: - The item from the array. - """ - element_type = _determine_value_of_array_index( - array._var_type, - ( - index - if isinstance(index, int) - else (index._var_value if isinstance(index, LiteralNumberVar) else None) - ), - ) - - return var_operation_return( - js_expression=f"{array!s}?.at?.({index!s})", - var_type=element_type, - ) - - -@var_operation -def array_range_operation( - start: NumberVar | int, stop: NumberVar | int, step: NumberVar | int -): - """Create a range of numbers. - - Args: - start: The start of the range. - stop: The end of the range. - step: The step of the range. - - Returns: - The range of numbers. - """ - return var_operation_return( - js_expression=f"Array.from({{ length: Math.ceil(({stop!s} - {start!s}) / {step!s}) }}, (_, i) => {start!s} + i * {step!s})", - var_type=list[int], - ) - - -@var_operation -def array_contains_field_operation( - haystack: ArrayVar, needle: Any | Var, field: StringVar | str -): - """Check if an array contains an element. - - Args: - haystack: The array to check. - needle: The element to check for. - field: The field to check. - - Returns: - The array contains operation. - """ - return var_operation_return( - js_expression=f"{haystack}.some(obj => obj[{field}] === {needle})", - var_type=bool, - ) - - -@var_operation -def array_contains_operation( - haystack: ArrayVar, needle: Any | Var -) -> CustomVarOperationReturn[bool]: - """Check if an array contains an element. - - Args: - haystack: The array to check. - needle: The element to check for. - - Returns: - The array contains operation. - """ - return var_operation_return( - js_expression=f"{haystack}.includes({needle})", - var_type=bool, - ) - - -@var_operation -def repeat_array_operation( - array: ArrayVar[ARRAY_VAR_TYPE], count: NumberVar | int -) -> CustomVarOperationReturn[ARRAY_VAR_TYPE]: - """Repeat an array a number of times. - - Args: - array: The array to repeat. - count: The number of times to repeat the array. - - Returns: - The repeated array. - """ - return var_operation_return( - js_expression=f"Array.from({{ length: {count} }}).flatMap(() => {array})", - var_type=array._var_type, - ) - - -@var_operation -def map_array_operation( - array: ArrayVar[ARRAY_VAR_TYPE], - function: FunctionVar, -) -> CustomVarOperationReturn[list[Any]]: - """Map a function over an array. - - Args: - array: The array. - function: The function to map. - - Returns: - The mapped array. - """ - return var_operation_return( - js_expression=f"{array}.map({function})", var_type=list[Any] - ) - - -@var_operation -def array_concat_operation( - lhs: ArrayVar[ARRAY_VAR_TYPE], rhs: ArrayVar[ARRAY_VAR_TYPE] -) -> CustomVarOperationReturn[ARRAY_VAR_TYPE]: - """Concatenate two arrays. - - Args: - lhs: The left-hand side array. - rhs: The right-hand side array. - - Returns: - The concatenated array. - """ - return var_operation_return( - js_expression=f"[...{lhs}, ...{rhs}]", - var_type=lhs._var_type | rhs._var_type, - ) - - -class RangeVar(ArrayVar[Sequence[int]], python_types=range): - """Base class for immutable range vars.""" - - -@dataclasses.dataclass( - eq=False, - frozen=True, - slots=True, -) -class LiteralRangeVar(CachedVarOperation, LiteralVar, RangeVar): - """Base class for immutable literal range vars.""" - - _var_value: range = dataclasses.field(default_factory=lambda: range(0)) - - @classmethod - def create( - cls, - value: range, - _var_type: type[range] | None = None, - _var_data: VarData | None = None, - ) -> RangeVar: - """Create a var from a string value. - - Args: - value: The value to create the var from. - _var_type: The type of the var. - _var_data: Additional hooks and imports associated with the Var. - - Returns: - The var. - """ - return cls( - _js_expr="", - _var_type=_var_type or range, - _var_data=_var_data, - _var_value=value, - ) - - def __hash__(self) -> int: - """Get the hash of the var. - - Returns: - The hash of the var. - """ - return hash(( - self.__class__.__name__, - self._var_value.start, - self._var_value.stop, - self._var_value.step, - )) - - @cached_property_no_lock - def _cached_var_name(self) -> str: - """The name of the var. - - Returns: - The name of the var. - """ - return f"Array.from({{ length: Math.ceil(({self._var_value.stop!s} - {self._var_value.start!s}) / {self._var_value.step!s}) }}, (_, i) => {self._var_value.start!s} + i * {self._var_value.step!s})" - - @cached_property_no_lock - def _cached_get_all_var_data(self) -> VarData | None: - """Get all the var data. - - Returns: - The var data. - """ - return self._var_data - - def json(self) -> str: - """Get the JSON representation of the var. - - Returns: - The JSON representation of the var. - """ - return json.dumps( - list(self._var_value), - ) +from reflex_core.vars.sequence import * # noqa: F401, F403 diff --git a/scripts/hatch_build.py b/scripts/hatch_build.py index 088946c7393..5ff53bae90a 100644 --- a/scripts/hatch_build.py +++ b/scripts/hatch_build.py @@ -58,7 +58,7 @@ def initialize(self, version: str, build_data: dict[str, Any]) -> None: file.unlink(missing_ok=True) subprocess.run( - [sys.executable, "-m", "reflex.utils.pyi_generator"], + [sys.executable, "-m", "reflex_core.utils.pyi_generator"], check=True, ) self.marker().touch() diff --git a/uv.lock b/uv.lock index fb7f48721ae..18c2475e6f7 100644 --- a/uv.lock +++ b/uv.lock @@ -28,6 +28,7 @@ members = [ "reflex-components-react-router", "reflex-components-recharts", "reflex-components-sonner", + "reflex-core", "reflex-docgen", ] @@ -708,10 +709,14 @@ name = "hatch-reflex-pyi" source = { editable = "packages/hatch-reflex-pyi" } dependencies = [ { name = "hatchling" }, + { name = "reflex-core" }, ] [package.metadata] -requires-dist = [{ name = "hatchling" }] +requires-dist = [ + { name = "hatchling" }, + { name = "reflex-core", editable = "packages/reflex-core" }, +] [[package]] name = "hatchling" @@ -2077,6 +2082,7 @@ dependencies = [ { name = "reflex-components-react-router" }, { name = "reflex-components-recharts" }, { name = "reflex-components-sonner" }, + { name = "reflex-core" }, { name = "reflex-hosting-cli" }, { name = "rich" }, { name = "sqlmodel" }, @@ -2157,6 +2163,7 @@ requires-dist = [ { name = "reflex-components-react-router", editable = "packages/reflex-components-react-router" }, { name = "reflex-components-recharts", editable = "packages/reflex-components-recharts" }, { name = "reflex-components-sonner", editable = "packages/reflex-components-sonner" }, + { name = "reflex-core", editable = "packages/reflex-core" }, { name = "reflex-hosting-cli", specifier = ">=0.1.61" }, { name = "rich", specifier = ">=13,<15" }, { name = "sqlmodel", specifier = ">=0.0.27,<0.1" }, @@ -2330,6 +2337,24 @@ dependencies = [ [package.metadata] requires-dist = [{ name = "reflex-components-lucide", editable = "packages/reflex-components-lucide" }] +[[package]] +name = "reflex-core" +source = { editable = "packages/reflex-core" } +dependencies = [ + { name = "packaging" }, + { name = "pydantic" }, + { name = "rich" }, + { name = "typing-extensions" }, +] + +[package.metadata] +requires-dist = [ + { name = "packaging", specifier = ">=24.2,<27" }, + { name = "pydantic", specifier = ">=1.10.21,<3.0" }, + { name = "rich", specifier = ">=13,<15" }, + { name = "typing-extensions", specifier = ">=4.13.0" }, +] + [[package]] name = "reflex-docgen" source = { editable = "packages/reflex-docgen" } From 3bac3415d3ed19bb8b733995328585169491dd5a Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Thu, 26 Mar 2026 13:41:22 -0700 Subject: [PATCH 040/113] move reflex event --- .../src/reflex_core/components/__init__.py | 1 + .../src/reflex_core/components/field.py | 183 ++ packages/reflex-core/src/reflex_core/event.py | 2756 ++++++++++++++++ .../src/reflex_core/utils/format.py | 6 +- .../reflex-core/src/reflex_core/vars/base.py | 4 +- reflex/components/field.py | 184 +- reflex/event.py | 2757 +---------------- 7 files changed, 2949 insertions(+), 2942 deletions(-) create mode 100644 packages/reflex-core/src/reflex_core/components/__init__.py create mode 100644 packages/reflex-core/src/reflex_core/components/field.py create mode 100644 packages/reflex-core/src/reflex_core/event.py diff --git a/packages/reflex-core/src/reflex_core/components/__init__.py b/packages/reflex-core/src/reflex_core/components/__init__.py new file mode 100644 index 00000000000..44b8f771ba9 --- /dev/null +++ b/packages/reflex-core/src/reflex_core/components/__init__.py @@ -0,0 +1 @@ +"""Reflex core components.""" diff --git a/packages/reflex-core/src/reflex_core/components/field.py b/packages/reflex-core/src/reflex_core/components/field.py new file mode 100644 index 00000000000..9b2bf2dfec8 --- /dev/null +++ b/packages/reflex-core/src/reflex_core/components/field.py @@ -0,0 +1,183 @@ +"""Shared field infrastructure for components and props.""" + +from __future__ import annotations + +from collections.abc import Callable +from dataclasses import _MISSING_TYPE, MISSING +from typing import Annotated, Any, Generic, TypeVar, get_origin + +from reflex_core.utils import types +from reflex_core.utils.compat import annotations_from_namespace + +FIELD_TYPE = TypeVar("FIELD_TYPE") + + +class BaseField(Generic[FIELD_TYPE]): + """Base field class used by internal metadata classes.""" + + def __init__( + self, + default: FIELD_TYPE | _MISSING_TYPE = MISSING, + default_factory: Callable[[], FIELD_TYPE] | None = None, + annotated_type: type[Any] | _MISSING_TYPE = MISSING, + ) -> None: + """Initialize the field. + + Args: + default: The default value for the field. + default_factory: The default factory for the field. + annotated_type: The annotated type for the field. + """ + self.default = default + self.default_factory = default_factory + self.outer_type_ = self.annotated_type = annotated_type + + # Process type annotation + type_origin = get_origin(annotated_type) or annotated_type + if type_origin is Annotated: + type_origin = annotated_type.__origin__ # pyright: ignore [reportAttributeAccessIssue] + # For Annotated types, use the actual type inside the annotation + self.type_ = annotated_type + else: + # For other types (including Union), preserve the original type + self.type_ = annotated_type + self.type_origin = type_origin + + def default_value(self) -> FIELD_TYPE: + """Get the default value for the field. + + Returns: + The default value for the field. + + Raises: + ValueError: If no default value or factory is provided. + """ + if self.default is not MISSING: + return self.default + if self.default_factory is not None: + return self.default_factory() + msg = "No default value or factory provided." + raise ValueError(msg) + + +class FieldBasedMeta(type): + """Shared metaclass for field-based classes like components and props. + + Provides common field inheritance and processing logic for both + PropsBaseMeta and BaseComponentMeta. + """ + + def __new__( + cls, name: str, bases: tuple[type, ...], namespace: dict[str, Any] + ) -> type: + """Create a new field-based class. + + Args: + name: The name of the class. + bases: The base classes. + namespace: The class namespace. + + Returns: + The new class. + """ + # Collect inherited fields from base classes + inherited_fields = cls._collect_inherited_fields(bases) + + # Get annotations from the namespace + annotations = cls._resolve_annotations(namespace, name) + + # Process field overrides (fields with values but no annotations) + own_fields = cls._process_field_overrides( + namespace, annotations, inherited_fields + ) + + # Process annotated fields + own_fields.update( + cls._process_annotated_fields(namespace, annotations, inherited_fields) + ) + + # Finalize fields and store on class + cls._finalize_fields(namespace, inherited_fields, own_fields) + + return super().__new__(cls, name, bases, namespace) + + @classmethod + def _collect_inherited_fields(cls, bases: tuple[type, ...]) -> dict[str, Any]: + inherited_fields: dict[str, Any] = {} + + # Collect inherited fields from base classes + for base in bases[::-1]: + if hasattr(base, "_inherited_fields"): + inherited_fields.update(base._inherited_fields) + for base in bases[::-1]: + if hasattr(base, "_own_fields"): + inherited_fields.update(base._own_fields) + + return inherited_fields + + @classmethod + def _resolve_annotations( + cls, namespace: dict[str, Any], name: str + ) -> dict[str, Any]: + return types.resolve_annotations( + annotations_from_namespace(namespace), + namespace["__module__"], + ) + + @classmethod + def _process_field_overrides( + cls, + namespace: dict[str, Any], + annotations: dict[str, Any], + inherited_fields: dict[str, Any], + ) -> dict[str, Any]: + own_fields: dict[str, Any] = {} + + for key, value in namespace.items(): + if key not in annotations and key in inherited_fields: + inherited_field = inherited_fields[key] + new_field = cls._create_field( + annotated_type=inherited_field.annotated_type, + default=value, + default_factory=None, + ) + own_fields[key] = new_field + + return own_fields + + @classmethod + def _process_annotated_fields( + cls, + namespace: dict[str, Any], + annotations: dict[str, Any], + inherited_fields: dict[str, Any], + ) -> dict[str, Any]: + raise NotImplementedError + + @classmethod + def _create_field( + cls, + annotated_type: Any, + default: Any = MISSING, + default_factory: Callable[[], Any] | None = None, + ) -> Any: + raise NotImplementedError + + @classmethod + def _finalize_fields( + cls, + namespace: dict[str, Any], + inherited_fields: dict[str, Any], + own_fields: dict[str, Any], + ) -> None: + # Combine all fields + all_fields = inherited_fields | own_fields + + # Set field names for compatibility + for field_name, field in all_fields.items(): + field._name = field_name + + # Store field mappings on the class + namespace["_own_fields"] = own_fields + namespace["_inherited_fields"] = inherited_fields + namespace["_fields"] = all_fields diff --git a/packages/reflex-core/src/reflex_core/event.py b/packages/reflex-core/src/reflex_core/event.py new file mode 100644 index 00000000000..c815a93076c --- /dev/null +++ b/packages/reflex-core/src/reflex_core/event.py @@ -0,0 +1,2756 @@ +"""Define event classes to connect the frontend and backend.""" + +from __future__ import annotations + +import dataclasses +import inspect +import sys +import types +import warnings +from base64 import b64encode +from collections.abc import Callable, Mapping, Sequence +from functools import lru_cache, partial +from typing import ( + TYPE_CHECKING, + Annotated, + Any, + Generic, + Literal, + NoReturn, + Protocol, + TypeVar, + get_args, + get_origin, + get_type_hints, + overload, +) + +from typing_extensions import Self, TypeAliasType, TypedDict, TypeVarTuple, Unpack + +from reflex import constants +from reflex_core.components.field import BaseField +from reflex_core.constants.compiler import CompileVars, Hooks, Imports +from reflex_core.constants.state import FRONTEND_EVENT_STATE +from reflex_core.utils import format +from reflex_core.utils.decorator import once +from reflex_core.utils.exceptions import ( + EventFnArgMismatchError, + EventHandlerArgTypeMismatchError, + MissingAnnotationError, +) +from reflex_core.utils.types import ( + ArgsSpec, + GenericType, + Unset, + safe_issubclass, + typehint_issubclass, +) +from reflex_core.vars import VarData +from reflex_core.vars.base import LiteralVar, Var +from reflex_core.vars.function import ( + ArgsFunctionOperation, + ArgsFunctionOperationBuilder, + BuilderFunctionVar, + FunctionArgs, + FunctionStringVar, + FunctionVar, + VarOperationCall, +) +from reflex_core.vars.object import ObjectVar + + +@dataclasses.dataclass( + init=True, + frozen=True, +) +class Event: + """An event that describes any state change in the app. + + Attributes: + token: The token to specify the client that the event is for. + name: The event name. + router_data: The routing data where event occurred. + payload: The event payload. + """ + + token: str + + name: str + + router_data: dict[str, Any] = dataclasses.field(default_factory=dict) + + payload: dict[str, Any] = dataclasses.field(default_factory=dict) + + @property + def substate_token(self) -> str: + """Get the substate token for the event. + + Returns: + The substate token. + """ + substate = self.name.rpartition(".")[0] + return f"{self.token}_{substate}" + + +_EVENT_FIELDS: set[str] = {f.name for f in dataclasses.fields(Event)} +_EMPTY_EVENTS = LiteralVar.create([]) +_EMPTY_EVENT_ACTIONS = LiteralVar.create({}) + +BACKGROUND_TASK_MARKER = "_reflex_background_task" +EVENT_ACTIONS_MARKER = "_rx_event_actions" +UPLOAD_FILES_CLIENT_HANDLER = "uploadFiles" + + +def _handler_name(handler: EventHandler) -> str: + """Get a stable fully qualified handler name for errors. + + Args: + handler: The handler to name. + + Returns: + The fully qualified handler name. + """ + if handler.state_full_name: + return f"{handler.state_full_name}.{handler.fn.__name__}" + return handler.fn.__qualname__ + + +def resolve_upload_handler_param(handler: EventHandler) -> tuple[str, Any]: + """Validate and resolve the UploadFile list parameter for a handler. + + Args: + handler: The event handler to inspect. + + Returns: + The parameter name and annotation for the upload file argument. + + Raises: + UploadTypeError: If the handler is a background task. + UploadValueError: If the handler does not accept ``list[rx.UploadFile]``. + """ + from reflex._upload import UploadFile + from reflex_core.utils.exceptions import UploadTypeError, UploadValueError + + handler_name = _handler_name(handler) + if handler.is_background: + msg = ( + f"@rx.event(background=True) is not supported for upload handler " + f"`{handler_name}`." + ) + raise UploadTypeError(msg) + + func = handler.fn.func if isinstance(handler.fn, partial) else handler.fn + for name, annotation in get_type_hints(func).items(): + if name == "return" or get_origin(annotation) is not list: + continue + args = get_args(annotation) + if len(args) == 1 and typehint_issubclass(args[0], UploadFile): + return name, annotation + + msg = ( + f"`{handler_name}` handler should have a parameter annotated as " + "list[rx.UploadFile]" + ) + raise UploadValueError(msg) + + +def resolve_upload_chunk_handler_param(handler: EventHandler) -> tuple[str, type]: + """Validate and resolve the UploadChunkIterator parameter for a handler. + + Args: + handler: The event handler to inspect. + + Returns: + The parameter name and annotation for the iterator argument. + + Raises: + UploadTypeError: If the handler is not a background task. + UploadValueError: If the handler does not accept an UploadChunkIterator. + """ + from reflex._upload import UploadChunkIterator + from reflex_core.utils.exceptions import UploadTypeError, UploadValueError + + handler_name = _handler_name(handler) + if not handler.is_background: + msg = f"@rx.event(background=True) is required for upload_files_chunk handler `{handler_name}`." + raise UploadTypeError(msg) + + func = handler.fn.func if isinstance(handler.fn, partial) else handler.fn + for name, annotation in get_type_hints(func).items(): + if name == "return": + continue + if annotation is UploadChunkIterator: + return name, annotation + + msg = ( + f"`{handler_name}` handler should have a parameter annotated as " + "rx.UploadChunkIterator" + ) + raise UploadValueError(msg) + + +@dataclasses.dataclass( + init=True, + frozen=True, + kw_only=True, +) +class EventActionsMixin: + """Mixin for DOM event actions. + + Attributes: + event_actions: Whether to `preventDefault` or `stopPropagation` on the event. + """ + + event_actions: dict[str, bool | int] = dataclasses.field(default_factory=dict) + + @property + def stop_propagation(self) -> Self: + """Stop the event from bubbling up the DOM tree. + + Returns: + New EventHandler-like with stopPropagation set to True. + """ + return dataclasses.replace( + self, + event_actions={**self.event_actions, "stopPropagation": True}, + ) + + @property + def prevent_default(self) -> Self: + """Prevent the default behavior of the event. + + Returns: + New EventHandler-like with preventDefault set to True. + """ + return dataclasses.replace( + self, + event_actions={**self.event_actions, "preventDefault": True}, + ) + + def throttle(self, limit_ms: int) -> Self: + """Throttle the event handler. + + Args: + limit_ms: The time in milliseconds to throttle the event handler. + + Returns: + New EventHandler-like with throttle set to limit_ms. + """ + return dataclasses.replace( + self, + event_actions={**self.event_actions, "throttle": limit_ms}, + ) + + def debounce(self, delay_ms: int) -> Self: + """Debounce the event handler. + + Args: + delay_ms: The time in milliseconds to debounce the event handler. + + Returns: + New EventHandler-like with debounce set to delay_ms. + """ + return dataclasses.replace( + self, + event_actions={**self.event_actions, "debounce": delay_ms}, + ) + + @property + def temporal(self) -> Self: + """Do not queue the event if the backend is down. + + Returns: + New EventHandler-like with temporal set to True. + """ + return dataclasses.replace( + self, + event_actions={**self.event_actions, "temporal": True}, + ) + + +@dataclasses.dataclass( + init=True, + frozen=True, + kw_only=True, +) +class EventHandler(EventActionsMixin): + """An event handler responds to an event to update the state. + + Attributes: + fn: The function to call in response to the event. + state_full_name: The full name of the state class this event handler is attached to. Empty string means this event handler is a server side event. + """ + + fn: Any = dataclasses.field(default=None) + + state_full_name: str = dataclasses.field(default="") + + def __hash__(self): + """Get the hash of the event handler. + + Returns: + The hash of the event handler. + """ + return hash((tuple(self.event_actions.items()), self.fn, self.state_full_name)) + + def get_parameters(self) -> Mapping[str, inspect.Parameter]: + """Get the parameters of the function. + + Returns: + The parameters of the function. + """ + if self.fn is None: + return {} + return inspect.signature(self.fn).parameters + + @property + def _parameters(self) -> Mapping[str, inspect.Parameter]: + """Get the parameters of the function. + + Returns: + The parameters of the function. + """ + if (parameters := getattr(self, "__parameters", None)) is None: + parameters = {**self.get_parameters()} + object.__setattr__(self, "__parameters", parameters) + return parameters + + @classmethod + def __class_getitem__(cls, args_spec: str) -> Annotated: + """Get a typed EventHandler. + + Args: + args_spec: The args_spec of the EventHandler. + + Returns: + The EventHandler class item. + """ + return Annotated[cls, args_spec] + + @property + def is_background(self) -> bool: + """Whether the event handler is a background task. + + Returns: + True if the event handler is marked as a background task. + """ + return getattr(self.fn, BACKGROUND_TASK_MARKER, False) + + def __call__(self, *args: Any, **kwargs: Any) -> EventSpec: + """Pass arguments to the handler to get an event spec. + + This method configures event handlers that take in arguments. + + Args: + *args: The arguments to pass to the handler. + **kwargs: The keyword arguments to pass to the handler. + + Returns: + The event spec, containing both the function and args. + + Raises: + EventHandlerTypeError: If the arguments are invalid. + """ + from reflex_core.utils.exceptions import EventHandlerTypeError + + # Get the function args. + fn_args = list(self._parameters)[1:] + + if not isinstance( + repeated_arg := next( + (kwarg for kwarg in kwargs if kwarg in fn_args[: len(args)]), Unset() + ), + Unset, + ): + msg = f"Event handler {self.fn.__name__} received repeated argument {repeated_arg}." + raise EventHandlerTypeError(msg) + + if not isinstance( + extra_arg := next( + (kwarg for kwarg in kwargs if kwarg not in fn_args), Unset() + ), + Unset, + ): + msg = ( + f"Event handler {self.fn.__name__} received extra argument {extra_arg}." + ) + raise EventHandlerTypeError(msg) + + fn_args = fn_args[: len(args)] + list(kwargs) + + fn_args = (Var(_js_expr=arg) for arg in fn_args) + + # Construct the payload. + values = [] + for arg in [*args, *kwargs.values()]: + # Special case for file uploads. + if isinstance(arg, (FileUpload, UploadFilesChunk)): + return arg.as_event_spec(handler=self) + + # Otherwise, convert to JSON. + try: + values.append(LiteralVar.create(arg)) + except TypeError as e: + msg = f"Arguments to event handlers must be Vars or JSON-serializable. Got {arg} of type {type(arg)}." + raise EventHandlerTypeError(msg) from e + payload = tuple(zip(fn_args, values, strict=False)) + + # Return the event spec. + return EventSpec( + handler=self, args=payload, event_actions=self.event_actions.copy() + ) + + +@dataclasses.dataclass( + init=True, + frozen=True, + kw_only=True, +) +class EventSpec(EventActionsMixin): + """An event specification. + + Whereas an Event object is passed during runtime, a spec is used + during compile time to outline the structure of an event. + + Attributes: + handler: The event handler. + client_handler_name: The handler on the client to process event. + args: The arguments to pass to the function. + """ + + handler: EventHandler = dataclasses.field(default=None) # pyright: ignore [reportAssignmentType] + + client_handler_name: str = dataclasses.field(default="") + + args: tuple[tuple[Var, Var], ...] = dataclasses.field(default_factory=tuple) + + def __init__( + self, + handler: EventHandler, + event_actions: dict[str, bool | int] | None = None, + client_handler_name: str = "", + args: tuple[tuple[Var, Var], ...] = (), + ): + """Initialize an EventSpec. + + Args: + event_actions: The event actions. + handler: The event handler. + client_handler_name: The client handler name. + args: The arguments to pass to the function. + """ + if event_actions is None: + event_actions = {} + object.__setattr__(self, "event_actions", event_actions) + object.__setattr__(self, "handler", handler) + object.__setattr__(self, "client_handler_name", client_handler_name) + object.__setattr__(self, "args", args or ()) + + def with_args(self, args: tuple[tuple[Var, Var], ...]) -> EventSpec: + """Copy the event spec, with updated args. + + Args: + args: The new args to pass to the function. + + Returns: + A copy of the event spec, with the new args. + """ + return type(self)( + handler=self.handler, + client_handler_name=self.client_handler_name, + args=args, + event_actions=self.event_actions.copy(), + ) + + def add_args(self, *args: Var) -> EventSpec: + """Add arguments to the event spec. + + Args: + *args: The arguments to add positionally. + + Returns: + The event spec with the new arguments. + + Raises: + EventHandlerTypeError: If the arguments are invalid. + """ + from reflex_core.utils.exceptions import EventHandlerTypeError + + # Get the remaining unfilled function args. + fn_args = list(self.handler._parameters)[1 + len(self.args) :] + fn_args = (Var(_js_expr=arg) for arg in fn_args) + + # Construct the payload. + values = [] + arg = None + try: + for arg in args: + values.append(LiteralVar.create(value=arg)) # noqa: PERF401, RUF100 + except TypeError as e: + msg = f"Arguments to event handlers must be Vars or JSON-serializable. Got {arg} of type {type(arg)}." + raise EventHandlerTypeError(msg) from e + new_payload = tuple(zip(fn_args, values, strict=False)) + return self.with_args(self.args + new_payload) + + +@dataclasses.dataclass( + frozen=True, +) +class CallableEventSpec(EventSpec): + """Decorate an EventSpec-returning function to act as both a EventSpec and a function. + + This is used as a compatibility shim for replacing EventSpec objects in the + API with functions that return a family of EventSpec. + """ + + fn: Callable[..., EventSpec] | None = None + + def __init__(self, fn: Callable[..., EventSpec] | None = None, **kwargs): + """Initialize a CallableEventSpec. + + Args: + fn: The function to decorate. + **kwargs: The kwargs to pass to the EventSpec constructor. + """ + if fn is not None: + default_event_spec = fn() + super().__init__( + event_actions=default_event_spec.event_actions, + client_handler_name=default_event_spec.client_handler_name, + args=default_event_spec.args, + handler=default_event_spec.handler, + **kwargs, + ) + object.__setattr__(self, "fn", fn) + else: + super().__init__(**kwargs) + + def __call__(self, *args, **kwargs) -> EventSpec: + """Call the decorated function. + + Args: + *args: The args to pass to the function. + **kwargs: The kwargs to pass to the function. + + Returns: + The EventSpec returned from calling the function. + + Raises: + EventHandlerTypeError: If the CallableEventSpec has no associated function. + """ + from reflex_core.utils.exceptions import EventHandlerTypeError + + if self.fn is None: + msg = "CallableEventSpec has no associated function." + raise EventHandlerTypeError(msg) + return self.fn(*args, **kwargs) + + +@dataclasses.dataclass( + init=True, + frozen=True, +) +class EventChain(EventActionsMixin): + """Container for a chain of events that will be executed in order.""" + + events: Sequence[EventSpec | EventVar | FunctionVar | EventCallback] = ( + dataclasses.field(default_factory=list) + ) + + args_spec: Callable | Sequence[Callable] | None = dataclasses.field(default=None) + + invocation: Var | None = dataclasses.field(default=None) + + @classmethod + def create( + cls, + value: EventType, + args_spec: ArgsSpec | Sequence[ArgsSpec], + key: str | None = None, + **event_chain_kwargs, + ) -> EventChain | Var: + """Create an event chain from a variety of input types. + + Args: + value: The value to create the event chain from. + args_spec: The args_spec of the event trigger being bound. + key: The key of the event trigger being bound. + **event_chain_kwargs: Additional kwargs to pass to the EventChain constructor. + + Returns: + The event chain. + + Raises: + ValueError: If the value is not a valid event chain. + """ + # If it's an event chain var, return it. + if isinstance(value, Var): + # Only pass through literal/prebuilt chains. Other EventChainVar values may be + # FunctionVars cast with `.to(EventChain)` and still need wrapping so + # event_chain_kwargs can compose onto the resulting chain. + if isinstance(value, LiteralEventChainVar): + if event_chain_kwargs: + warnings.warn( + f"event_chain_kwargs {event_chain_kwargs!r} are ignored for " + "EventChainVar values.", + stacklevel=2, + ) + return value + if isinstance(value, (EventVar, FunctionVar)): + value = [value] + elif safe_issubclass(value._var_type, (EventChain, EventSpec)): + return cls.create( + value=value.guess_type(), + args_spec=args_spec, + key=key, + **event_chain_kwargs, + ) + else: + msg = f"Invalid event chain: {value!s} of type {value._var_type}" + raise ValueError(msg) + elif isinstance(value, EventChain): + # Trust that the caller knows what they're doing passing an EventChain directly + return value + + # If the input is a single event handler, wrap it in a list. + if isinstance(value, (EventHandler, EventSpec)): + value = [value] + + events: list[EventSpec | EventVar | FunctionVar] = [] + + # If the input is a list of event handlers, create an event chain. + if isinstance(value, list): + for v in value: + if isinstance(v, (EventHandler, EventSpec)): + # Call the event handler to get the event. + events.append(call_event_handler(v, args_spec, key=key)) + elif isinstance(v, (EventVar, EventChainVar)): + events.append(v) + elif isinstance(v, FunctionVar): + # Apply the args_spec transformations as partial arguments to the function. + events.append(v.partial(*parse_args_spec(args_spec)[0])) + elif isinstance(v, Callable): + # Call the lambda to get the event chain. + events.extend(call_event_fn(v, args_spec, key=key)) + else: + msg = f"Invalid event: {v}" + raise ValueError(msg) + + # If the input is a callable, create an event chain. + elif isinstance(value, Callable): + events.extend(call_event_fn(value, args_spec, key=key)) + + # Otherwise, raise an error. + else: + msg = f"Invalid event chain: {value}" + raise ValueError(msg) + + # Add args to the event specs if necessary. + events = [ + (e.with_args(get_handler_args(e)) if isinstance(e, EventSpec) else e) + for e in events + ] + + # Return the event chain. + return cls( + events=events, + args_spec=args_spec, + **event_chain_kwargs, + ) + + +@dataclasses.dataclass( + init=True, + frozen=True, +) +class JavascriptHTMLInputElement: + """Interface for a Javascript HTMLInputElement https://developer.mozilla.org/en-US/docs/Web/API/HTMLInputElement.""" + + value: str = "" + checked: bool = False + + +@dataclasses.dataclass( + init=True, + frozen=True, +) +class JavascriptInputEvent: + """Interface for a Javascript InputEvent https://developer.mozilla.org/en-US/docs/Web/API/InputEvent.""" + + target: JavascriptHTMLInputElement = JavascriptHTMLInputElement() + + +@dataclasses.dataclass( + init=True, + frozen=True, +) +class JavascriptKeyboardEvent: + """Interface for a Javascript KeyboardEvent https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent.""" + + key: str = "" + altKey: bool = False # noqa: N815 + ctrlKey: bool = False # noqa: N815 + metaKey: bool = False # noqa: N815 + shiftKey: bool = False # noqa: N815 + + +def input_event(e: ObjectVar[JavascriptInputEvent]) -> tuple[Var[str]]: + """Get the value from an input event. + + Args: + e: The input event. + + Returns: + The value from the input event. + """ + return (e.target.value,) + + +def int_input_event(e: ObjectVar[JavascriptInputEvent]) -> tuple[Var[int]]: + """Get the value from an input event as an int. + + Args: + e: The input event. + + Returns: + The value from the input event as an int. + """ + return (Var("Number").to(FunctionVar).call(e.target.value).to(int),) + + +def float_input_event(e: ObjectVar[JavascriptInputEvent]) -> tuple[Var[float]]: + """Get the value from an input event as a float. + + Args: + e: The input event. + + Returns: + The value from the input event as a float. + """ + return (Var("Number").to(FunctionVar).call(e.target.value).to(float),) + + +def checked_input_event(e: ObjectVar[JavascriptInputEvent]) -> tuple[Var[bool]]: + """Get the checked state from an input event. + + Args: + e: The input event. + + Returns: + The checked state from the input event. + """ + return (e.target.checked,) + + +FORM_DATA = Var(_js_expr="form_data") + + +def on_submit_event() -> tuple[Var[dict[str, Any]]]: + """Event handler spec for the on_submit event. + + Returns: + The event handler spec. + """ + return (FORM_DATA,) + + +def on_submit_string_event() -> tuple[Var[dict[str, str]]]: + """Event handler spec for the on_submit event. + + Returns: + The event handler spec. + """ + return (FORM_DATA,) + + +class KeyInputInfo(TypedDict): + """Information about a key input event.""" + + alt_key: bool + ctrl_key: bool + meta_key: bool + shift_key: bool + + +def key_event( + e: ObjectVar[JavascriptKeyboardEvent], +) -> tuple[Var[str], Var[KeyInputInfo]]: + """Get the key from a keyboard event. + + Args: + e: The keyboard event. + + Returns: + The key from the keyboard event. + """ + return ( + e.key.to(str), + Var.create( + { + "alt_key": e.altKey, + "ctrl_key": e.ctrlKey, + "meta_key": e.metaKey, + "shift_key": e.shiftKey, + }, + ).to(KeyInputInfo), + ) + + +@dataclasses.dataclass( + init=True, + frozen=True, +) +class JavascriptMouseEvent: + """Interface for a Javascript MouseEvent https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent.""" + + button: int = 0 + buttons: list[int] = dataclasses.field(default_factory=list) + clientX: int = 0 # noqa: N815 + clientY: int = 0 # noqa: N815 + altKey: bool = False # noqa: N815 + ctrlKey: bool = False # noqa: N815 + metaKey: bool = False # noqa: N815 + shiftKey: bool = False # noqa: N815 + + +class JavascriptPointerEvent(JavascriptMouseEvent): + """Interface for a Javascript PointerEvent https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent. + + Inherits from JavascriptMouseEvent. + """ + + +class MouseEventInfo(TypedDict): + """Information about a mouse event.""" + + button: int + buttons: int + client_x: int + client_y: int + alt_key: bool + ctrl_key: bool + meta_key: bool + shift_key: bool + + +class PointerEventInfo(MouseEventInfo): + """Information about a pointer event.""" + + +def pointer_event_spec( + e: ObjectVar[JavascriptPointerEvent], +) -> tuple[Var[PointerEventInfo]]: + """Get the pointer event information. + + Args: + e: The pointer event. + + Returns: + The pointer event information. + """ + return ( + Var.create( + { + "button": e.button, + "buttons": e.buttons, + "client_x": e.clientX, + "client_y": e.clientY, + "alt_key": e.altKey, + "ctrl_key": e.ctrlKey, + "meta_key": e.metaKey, + "shift_key": e.shiftKey, + }, + ).to(PointerEventInfo), + ) + + +def no_args_event_spec() -> tuple[()]: + """Empty event handler. + + Returns: + An empty tuple. + """ + return () + + +T = TypeVar("T") +U = TypeVar("U") + + +class IdentityEventReturn(Generic[T], Protocol): + """Protocol for an identity event return.""" + + def __call__(self, *values: Var[T]) -> tuple[Var[T], ...]: + """Return the input values. + + Args: + *values: The values to return. + + Returns: + The input values. + """ + return values + + +@overload +def passthrough_event_spec( # pyright: ignore [reportOverlappingOverload] + event_type: type[T], / +) -> Callable[[Var[T]], tuple[Var[T]]]: ... + + +@overload +def passthrough_event_spec( + event_type_1: type[T], event_type2: type[U], / +) -> Callable[[Var[T], Var[U]], tuple[Var[T], Var[U]]]: ... + + +@overload +def passthrough_event_spec(*event_types: type[T]) -> IdentityEventReturn[T]: ... + + +def passthrough_event_spec(*event_types: type[T]) -> IdentityEventReturn[T]: # pyright: ignore [reportInconsistentOverload] + """A helper function that returns the input event as output. + + Args: + *event_types: The types of the events. + + Returns: + A function that returns the input event as output. + """ + + def inner(*values: Var[T]) -> tuple[Var[T], ...]: + return values + + inner_type = tuple(Var[event_type] for event_type in event_types) + return_annotation = tuple[inner_type] + + inner.__signature__ = inspect.signature(inner).replace( # pyright: ignore [reportFunctionMemberAccess] + parameters=[ + inspect.Parameter( + f"ev_{i}", + kind=inspect.Parameter.POSITIONAL_OR_KEYWORD, + annotation=Var[event_type], + ) + for i, event_type in enumerate(event_types) + ], + return_annotation=return_annotation, + ) + for i, event_type in enumerate(event_types): + inner.__annotations__[f"ev_{i}"] = Var[event_type] + inner.__annotations__["return"] = return_annotation + + return inner + + +@dataclasses.dataclass( + init=True, + frozen=True, +) +class FileUpload: + """Class to represent a file upload.""" + + upload_id: str | None = None + on_upload_progress: EventHandler | Callable | None = None + extra_headers: dict[str, str] | None = None + + @staticmethod + def on_upload_progress_args_spec(_prog: Var[dict[str, int | float | bool]]): + """Args spec for on_upload_progress event handler. + + Returns: + The arg mapping passed to backend event handler + """ + return [_prog] + + def _as_event_spec( + self, + handler: EventHandler, + *, + client_handler_name: str, + upload_param_name: str, + ) -> EventSpec: + """Create an upload EventSpec. + + Args: + handler: The event handler. + client_handler_name: The client handler name. + upload_param_name: The upload argument name in the event handler. + + Returns: + The upload EventSpec. + + Raises: + ValueError: If the on_upload_progress is not a valid event handler. + """ + from reflex_components_core.core.upload import ( + DEFAULT_UPLOAD_ID, + upload_files_context_var_data, + ) + + upload_id = self.upload_id if self.upload_id is not None else DEFAULT_UPLOAD_ID + upload_files_var = Var( + _js_expr="filesById", + _var_type=dict[str, Any], + _var_data=VarData.merge(upload_files_context_var_data), + ).to(ObjectVar)[LiteralVar.create(upload_id)] + spec_args = [ + ( + Var(_js_expr="files"), + upload_files_var, + ), + ( + Var(_js_expr="upload_param_name"), + LiteralVar.create(upload_param_name), + ), + ( + Var(_js_expr="upload_id"), + LiteralVar.create(upload_id), + ), + ( + Var(_js_expr="extra_headers"), + LiteralVar.create( + self.extra_headers if self.extra_headers is not None else {} + ), + ), + ] + if upload_param_name != "files": + spec_args.insert( + 1, + ( + Var(_js_expr=upload_param_name), + upload_files_var, + ), + ) + if self.on_upload_progress is not None: + on_upload_progress = self.on_upload_progress + if isinstance(on_upload_progress, EventHandler): + events = [ + call_event_handler( + on_upload_progress, + self.on_upload_progress_args_spec, + ), + ] + elif isinstance(on_upload_progress, Callable): + # Call the lambda to get the event chain. + events = call_event_fn( + on_upload_progress, self.on_upload_progress_args_spec + ) + else: + msg = f"{on_upload_progress} is not a valid event handler." + raise ValueError(msg) + if isinstance(events, Var): + msg = f"{on_upload_progress} cannot return a var {events}." + raise ValueError(msg) + on_upload_progress_chain = EventChain( + events=[*events], + args_spec=self.on_upload_progress_args_spec, + ) + formatted_chain = str(format.format_prop(on_upload_progress_chain)) + spec_args.append( + ( + Var(_js_expr="on_upload_progress"), + FunctionStringVar( + formatted_chain.strip("{}"), + ).to(FunctionVar, EventChain), + ), + ) + return EventSpec( + handler=handler, + client_handler_name=client_handler_name, + args=tuple(spec_args), + event_actions=handler.event_actions.copy(), + ) + + def as_event_spec(self, handler: EventHandler) -> EventSpec: + """Get the EventSpec for the file upload. + + Args: + handler: The event handler. + + Returns: + The event spec for the handler. + """ + from reflex_core.utils.exceptions import UploadValueError + + try: + upload_param_name, _annotation = resolve_upload_handler_param(handler) + except UploadValueError: + upload_param_name = "files" + return self._as_event_spec( + handler, + client_handler_name=UPLOAD_FILES_CLIENT_HANDLER, + upload_param_name=upload_param_name, + ) + + +# Alias for rx.upload_files +upload_files = FileUpload + + +@dataclasses.dataclass( + init=True, + frozen=True, +) +class UploadFilesChunk(FileUpload): + """Class to represent a streaming file upload.""" + + def as_event_spec(self, handler: EventHandler) -> EventSpec: + """Get the EventSpec for the streaming file upload. + + Args: + handler: The event handler. + + Returns: + The event spec for the handler. + """ + upload_param_name, _annotation = resolve_upload_chunk_handler_param(handler) + return self._as_event_spec( + handler, + client_handler_name=UPLOAD_FILES_CLIENT_HANDLER, + upload_param_name=upload_param_name, + ) + + +# Alias for rx.upload_files_chunk +upload_files_chunk = UploadFilesChunk + + +# Special server-side events. +def server_side(name: str, sig: inspect.Signature, **kwargs) -> EventSpec: + """A server-side event. + + Args: + name: The name of the event. + sig: The function signature of the event. + **kwargs: The arguments to pass to the event. + + Returns: + An event spec for a server-side event. + """ + + def fn(): + return None + + fn.__qualname__ = name + fn.__signature__ = sig # pyright: ignore [reportFunctionMemberAccess] + return EventSpec( + handler=EventHandler(fn=fn, state_full_name=FRONTEND_EVENT_STATE), + args=tuple( + ( + Var(_js_expr=k), + LiteralVar.create(v), + ) + for k, v in kwargs.items() + ), + ) + + +@overload +def redirect( + path: str | Var[str], + *, + is_external: Literal[False] = False, + replace: bool = False, +) -> EventSpec: ... + + +@overload +def redirect( + path: str | Var[str], + *, + is_external: Literal[True], + popup: bool = False, +) -> EventSpec: ... + + +def redirect( + path: str | Var[str], + *, + is_external: bool = False, + popup: bool = False, + replace: bool = False, +) -> EventSpec: + """Redirect to a new path. + + Args: + path: The path to redirect to. + is_external: Whether to open in new tab or not. + popup: Whether to open in a new window or not. + replace: If True, the current page will not create a new history entry. + + Returns: + An event to redirect to the path. + """ + return server_side( + "_redirect", + get_fn_signature(redirect), + path=path, + external=is_external, + popup=popup, + replace=replace, + ) + + +def console_log(message: str | Var[str]) -> EventSpec: + """Do a console.log on the browser. + + Args: + message: The message to log. + + Returns: + An event to log the message. + """ + return run_script(Var("console").to(dict).log.to(FunctionVar).call(message)) + + +@once +def noop() -> EventSpec: + """Do nothing. + + Returns: + An event to do nothing. + """ + return run_script(Var.create(None)) + + +def back() -> EventSpec: + """Do a history.back on the browser. + + Returns: + An event to go back one page. + """ + return run_script( + Var("window").to(dict).history.to(dict).back.to(FunctionVar).call() + ) + + +def window_alert(message: str | Var[str]) -> EventSpec: + """Create a window alert on the browser. + + Args: + message: The message to alert. + + Returns: + An event to alert the message. + """ + return run_script(Var("window").to(dict).alert.to(FunctionVar).call(message)) + + +def set_focus(ref: str) -> EventSpec: + """Set focus to specified ref. + + Args: + ref: The ref. + + Returns: + An event to set focus on the ref + """ + return server_side( + "_set_focus", + get_fn_signature(set_focus), + ref=LiteralVar.create(format.format_ref(ref)), + ) + + +def blur_focus(ref: str) -> EventSpec: + """Blur focus of specified ref. + + Args: + ref: The ref. + + Returns: + An event to blur focus on the ref + """ + return server_side( + "_blur_focus", + get_fn_signature(blur_focus), + ref=LiteralVar.create(format.format_ref(ref)), + ) + + +def scroll_to(elem_id: str, align_to_top: bool | Var[bool] = True) -> EventSpec: + """Select the id of a html element for scrolling into view. + + Args: + elem_id: The id of the element to scroll to. + align_to_top: Whether to scroll to the top (True) or bottom (False) of the element. + + Returns: + An EventSpec to scroll the page to the selected element. + """ + get_element_by_id = FunctionStringVar.create("document.getElementById") + + return run_script( + get_element_by_id + .call(elem_id) + .to(ObjectVar) + .scrollIntoView.to(FunctionVar) + .call(align_to_top), + ) + + +def set_value(ref: str, value: Any) -> EventSpec: + """Set the value of a ref. + + Args: + ref: The ref. + value: The value to set. + + Returns: + An event to set the ref. + """ + return server_side( + "_set_value", + get_fn_signature(set_value), + ref=LiteralVar.create(format.format_ref(ref)), + value=value, + ) + + +def remove_cookie(key: str, options: dict[str, Any] | None = None) -> EventSpec: + """Remove a cookie on the frontend. + + Args: + key: The key identifying the cookie to be removed. + options: Support all the cookie options from RFC 6265 + + Returns: + EventSpec: An event to remove a cookie. + """ + options = options or {} + options["path"] = options.get("path", "/") + return server_side( + "_remove_cookie", + get_fn_signature(remove_cookie), + key=key, + options=options, + ) + + +def clear_local_storage() -> EventSpec: + """Set a value in the local storage on the frontend. + + Returns: + EventSpec: An event to clear the local storage. + """ + return server_side( + "_clear_local_storage", + get_fn_signature(clear_local_storage), + ) + + +def remove_local_storage(key: str) -> EventSpec: + """Set a value in the local storage on the frontend. + + Args: + key: The key identifying the variable in the local storage to remove. + + Returns: + EventSpec: An event to remove an item based on the provided key in local storage. + """ + return server_side( + "_remove_local_storage", + get_fn_signature(remove_local_storage), + key=key, + ) + + +def clear_session_storage() -> EventSpec: + """Set a value in the session storage on the frontend. + + Returns: + EventSpec: An event to clear the session storage. + """ + return server_side( + "_clear_session_storage", + get_fn_signature(clear_session_storage), + ) + + +def remove_session_storage(key: str) -> EventSpec: + """Set a value in the session storage on the frontend. + + Args: + key: The key identifying the variable in the session storage to remove. + + Returns: + EventSpec: An event to remove an item based on the provided key in session storage. + """ + return server_side( + "_remove_session_storage", + get_fn_signature(remove_session_storage), + key=key, + ) + + +def set_clipboard(content: str | Var[str]) -> EventSpec: + """Set the text in content in the clipboard. + + Args: + content: The text to add to clipboard. + + Returns: + EventSpec: An event to set some content in the clipboard. + """ + return run_script( + Var("navigator") + .to(dict) + .clipboard.to(dict) + .writeText.to(FunctionVar) + .call(content) + ) + + +def download( + url: str | Var | None = None, + filename: str | Var | None = None, + data: str | bytes | Var | None = None, + mime_type: str | Var | None = None, +) -> EventSpec: + """Download the file at a given path or with the specified data. + + Args: + url: The URL to the file to download. + filename: The name that the file should be saved as after download. + data: The data to download. + mime_type: The mime type of the data to download. + + Returns: + EventSpec: An event to download the associated file. + + Raises: + ValueError: If the URL provided is invalid, both URL and data are provided, + or the data is not an expected type. + """ + from reflex_components_core.core.cond import cond + + if isinstance(url, str): + if not url.startswith("/"): + msg = "The URL argument should start with a /" + raise ValueError(msg) + + # if filename is not provided, infer it from url + if filename is None: + filename = url.rpartition("/")[-1] + + if filename is None: + filename = "" + + if data is not None: + if url is not None: + msg = "Cannot provide both URL and data to download." + raise ValueError(msg) + + if isinstance(data, str): + if mime_type is None: + mime_type = "text/plain" + # Caller provided a plain text string to download. + url = f"data:{mime_type};base64," + b64encode(data.encode("utf-8")).decode( + "utf-8" + ) + elif isinstance(data, Var): + if mime_type is None: + mime_type = "text/plain" + # Need to check on the frontend if the Var already looks like a data: URI. + + is_data_url = (data.js_type() == "string") & ( + data.to(str).startswith("data:") + ) + + # If it's a data: URI, use it as is, otherwise convert the Var to JSON in a data: URI. + url = cond( + is_data_url, + data.to(str), + f"data:{mime_type}," + data.to_string(), + ) + elif isinstance(data, bytes): + if mime_type is None: + mime_type = "application/octet-stream" + # Caller provided bytes, so base64 encode it as a data: URI. + b64_data = b64encode(data).decode("utf-8") + url = f"data:{mime_type};base64," + b64_data + else: + msg = f"Invalid data type {type(data)} for download. Use `str` or `bytes`." + raise ValueError(msg) + + return server_side( + "_download", + get_fn_signature(download), + url=url, + filename=filename, + ) + + +def call_script( + javascript_code: str | Var[str], + callback: EventType[Any] | None = None, +) -> EventSpec: + """Create an event handler that executes arbitrary javascript code. + + Args: + javascript_code: The code to execute. + callback: EventHandler that will receive the result of evaluating the javascript code. + + Returns: + EventSpec: An event that will execute the client side javascript. + """ + callback_kwargs = {"callback": None} + if callback is not None: + callback_kwargs = { + "callback": str( + format.format_queue_events( + callback, + args_spec=lambda result: [result], + ) + ), + } + if isinstance(javascript_code, str): + # When there is VarData, include it and eval the JS code inline on the client. + javascript_code, original_code = ( + LiteralVar.create(javascript_code), + javascript_code, + ) + if not javascript_code._get_all_var_data(): + # Without VarData, cast to string and eval the code in the event loop. + javascript_code = str(Var(_js_expr=original_code)) + + return server_side( + "_call_script", + get_fn_signature(call_script), + javascript_code=javascript_code, + **callback_kwargs, + ) + + +def call_function( + javascript_code: str | Var, + callback: EventType[Any] | None = None, +) -> EventSpec: + """Create an event handler that executes arbitrary javascript code. + + Args: + javascript_code: The code to execute. + callback: EventHandler that will receive the result of evaluating the javascript code. + + Returns: + EventSpec: An event that will execute the client side javascript. + """ + callback_kwargs = {"callback": None} + if callback is not None: + callback_kwargs = { + "callback": str( + format.format_queue_events( + callback, + args_spec=lambda result: [result], + ), + ) + } + + javascript_code = ( + Var(javascript_code) if isinstance(javascript_code, str) else javascript_code + ) + + return server_side( + "_call_function", + get_fn_signature(call_function), + function=javascript_code, + **callback_kwargs, + ) + + +def run_script( + javascript_code: str | Var, + callback: EventType[Any] | None = None, +) -> EventSpec: + """Create an event handler that executes arbitrary javascript code. + + Args: + javascript_code: The code to execute. + callback: EventHandler that will receive the result of evaluating the javascript code. + + Returns: + EventSpec: An event that will execute the client side javascript. + """ + javascript_code = ( + Var(javascript_code) if isinstance(javascript_code, str) else javascript_code + ) + + return call_function(ArgsFunctionOperation.create((), javascript_code), callback) + + +def get_event(state: BaseState, event: str): + """Get the event from the given state. + + Args: + state: The state. + event: The event. + + Returns: + The event. + """ + return f"{state.get_name()}.{event}" + + +def get_hydrate_event(state: BaseState) -> str: + """Get the name of the hydrate event for the state. + + Args: + state: The state. + + Returns: + The name of the hydrate event. + """ + return get_event(state, constants.CompileVars.HYDRATE) + + +def _values_returned_from_event(event_spec_annotations: list[Any]) -> list[Any]: + return [ + event_spec_return_type + for event_spec_annotation in event_spec_annotations + if (event_spec_return_type := event_spec_annotation.get("return")) is not None + and get_origin(event_spec_return_type) is tuple + ] + + +def _check_event_args_subclass_of_callback( + callback_params_names: list[str], + provided_event_types: list[Any], + callback_param_name_to_type: dict[str, Any], + callback_name: str = "", + key: str = "", +): + """Check if the event handler arguments are subclass of the callback. + + Args: + callback_params_names: The names of the callback parameters. + provided_event_types: The event types. + callback_param_name_to_type: The callback parameter name to type mapping. + callback_name: The name of the callback. + key: The key. + + Raises: + TypeError: If the event handler arguments are invalid. + EventHandlerArgTypeMismatchError: If the event handler arguments do not match the callback. + + # noqa: DAR401 delayed_exceptions[] + # noqa: DAR402 EventHandlerArgTypeMismatchError + """ + from reflex_core.utils import console + + type_match_found: dict[str, bool] = {} + delayed_exceptions: list[EventHandlerArgTypeMismatchError] = [] + + for event_spec_index, event_spec_return_type in enumerate(provided_event_types): + args = get_args(event_spec_return_type) + + args_types_without_vars = [ + arg if get_origin(arg) is not Var else get_args(arg)[0] for arg in args + ] + + # check that args of event handler are matching the spec if type hints are provided + for i, arg in enumerate(callback_params_names[: len(args_types_without_vars)]): + if arg not in callback_param_name_to_type: + continue + + type_match_found.setdefault(arg, False) + + try: + compare_result = typehint_issubclass( + args_types_without_vars[i], callback_param_name_to_type[arg] + ) + except TypeError as te: + callback_name_context = f" of {callback_name}" if callback_name else "" + key_context = f" for {key}" if key else "" + msg = f"Could not compare types {args_types_without_vars[i]} and {callback_param_name_to_type[arg]} for argument {arg}{callback_name_context}{key_context}." + raise TypeError(msg) from te + + if compare_result: + type_match_found[arg] = True + continue + type_match_found[arg] = False + as_annotated_in = ( + f" as annotated in {callback_name}" if callback_name else "" + ) + delayed_exceptions.append( + EventHandlerArgTypeMismatchError( + f"Event handler {key} expects {args_types_without_vars[i]} for argument {arg} but got {callback_param_name_to_type[arg]}{as_annotated_in} instead." + ) + ) + + if all(type_match_found.values()): + delayed_exceptions.clear() + if event_spec_index: + args = get_args(provided_event_types[0]) + + args_types_without_vars = [ + arg if get_origin(arg) is not Var else get_args(arg)[0] + for arg in args + ] + + expect_string = ", ".join( + repr(arg) for arg in args_types_without_vars + ).replace("[", "\\[") + + given_string = ", ".join( + repr(callback_param_name_to_type.get(arg, Any)) + for arg in callback_params_names + ).replace("[", "\\[") + + as_annotated_in = ( + f" as annotated in {callback_name}" if callback_name else "" + ) + + console.warn( + f"Event handler {key} expects ({expect_string}) -> () but got ({given_string}) -> (){as_annotated_in} instead. " + f"This may lead to unexpected behavior but is intentionally ignored for {key}." + ) + break + + if delayed_exceptions: + raise delayed_exceptions[0] + + +def call_event_handler( + event_callback: EventHandler | EventSpec, + event_spec: ArgsSpec | Sequence[ArgsSpec], + key: str | None = None, +) -> EventSpec: + """Call an event handler to get the event spec. + + This function will inspect the function signature of the event handler. + If it takes in an arg, the arg will be passed to the event handler. + Otherwise, the event handler will be called with no args. + + Args: + event_callback: The event handler. + event_spec: The lambda that define the argument(s) to pass to the event handler. + key: The key to pass to the event handler. + + Returns: + The event spec from calling the event handler. + """ + event_spec_args, event_annotations = parse_args_spec(event_spec) + + event_spec_return_types = _values_returned_from_event(event_annotations) + + if isinstance(event_callback, EventSpec): + parameters = event_callback.handler._parameters + + check_fn_match_arg_spec( + event_callback.handler.fn, + parameters, + event_spec_args, + key, + bool(event_callback.handler.state_full_name) + len(event_callback.args), + event_callback.handler.fn.__qualname__, + ) + + event_callback_spec_args = list(parameters) + + try: + type_hints_of_provided_callback = get_type_hints(event_callback.handler.fn) + except NameError: + type_hints_of_provided_callback = {} + + argument_names = [str(arg) for arg, value in event_callback.args] + + _check_event_args_subclass_of_callback( + [ + arg + for arg in event_callback_spec_args[ + bool(event_callback.handler.state_full_name) : + ] + if arg not in argument_names + ], + event_spec_return_types, + type_hints_of_provided_callback, + event_callback.handler.fn.__qualname__, + key or "", + ) + + # Handle partial application of EventSpec args + return event_callback.add_args(*event_spec_args) + + parameters = event_callback._parameters + + check_fn_match_arg_spec( + event_callback.fn, + parameters, + event_spec_args, + key, + bool(event_callback.state_full_name), + event_callback.fn.__qualname__, + ) + + if event_spec_return_types: + event_callback_spec_args = list(parameters) + + try: + type_hints_of_provided_callback = get_type_hints(event_callback.fn) + except NameError: + type_hints_of_provided_callback = {} + + _check_event_args_subclass_of_callback( + event_callback_spec_args[1:], + event_spec_return_types, + type_hints_of_provided_callback, + event_callback.fn.__qualname__, + key or "", + ) + + return event_callback(*event_spec_args) + + +def unwrap_var_annotation(annotation: GenericType): + """Unwrap a Var annotation or return it as is if it's not Var[X]. + + Args: + annotation: The annotation to unwrap. + + Returns: + The unwrapped annotation. + """ + if get_origin(annotation) in (Var, ObjectVar) and (args := get_args(annotation)): + return args[0] + return annotation + + +def resolve_annotation(annotations: dict[str, Any], arg_name: str, spec: ArgsSpec): + """Resolve the annotation for the given argument name. + + Args: + annotations: The annotations. + arg_name: The argument name. + spec: The specs which the annotations come from. + + Returns: + The resolved annotation. + + Raises: + MissingAnnotationError: If the annotation is missing for non-lambda methods. + """ + annotation = annotations.get(arg_name) + if annotation is None: + if not isinstance(spec, types.LambdaType): + raise MissingAnnotationError(var_name=arg_name) + return dict[str, dict] + return annotation + + +@lru_cache +def parse_args_spec(arg_spec: ArgsSpec | Sequence[ArgsSpec]): + """Parse the args provided in the ArgsSpec of an event trigger. + + Args: + arg_spec: The spec of the args. + + Returns: + The parsed args. + """ + # if there's multiple, the first is the default + if isinstance(arg_spec, Sequence): + annotations = [get_type_hints(one_arg_spec) for one_arg_spec in arg_spec] + arg_spec = arg_spec[0] + else: + annotations = [get_type_hints(arg_spec)] + + spec = inspect.getfullargspec(arg_spec) + + return list( + arg_spec(*[ + Var(f"_{l_arg}").to( + unwrap_var_annotation( + resolve_annotation(annotations[0], l_arg, spec=arg_spec) + ) + ) + for l_arg in spec.args + ]) + ), annotations + + +def args_specs_from_fields( + fields_dict: Mapping[str, BaseField], +) -> dict[str, ArgsSpec | Sequence[ArgsSpec]]: + """Get the event triggers and arg specs from the given fields. + + Args: + fields_dict: The fields, keyed by name + + Returns: + The args spec for any field annotated as EventHandler. + """ + return { + name: ( + metadata[0] + if ( + (metadata := getattr(field.annotated_type, "__metadata__", None)) + is not None + ) + else no_args_event_spec + ) + for name, field in fields_dict.items() + if field.type_origin is EventHandler + } + + +def check_fn_match_arg_spec( + user_func: Callable, + user_func_parameters: Mapping[str, inspect.Parameter], + event_spec_args: Sequence[Var], + key: str | None = None, + number_of_bound_args: int = 0, + func_name: str | None = None, +): + """Ensures that the function signature matches the passed argument specification + or raises an EventFnArgMismatchError if they do not. + + Args: + user_func: The function to be validated. + user_func_parameters: The parameters of the function to be validated. + event_spec_args: The argument specification for the event trigger. + key: The key of the event trigger. + number_of_bound_args: The number of bound arguments to the function. + func_name: The name of the function to be validated. + + Raises: + EventFnArgMismatchError: Raised if the number of mandatory arguments do not match + """ + user_args = list(user_func_parameters) + # Drop the first argument if it's a bound method + if inspect.ismethod(user_func) and user_func.__self__ is not None: + user_args = user_args[1:] + + user_default_args = [ + p.default + for p in user_func_parameters.values() + if p.default is not inspect.Parameter.empty + ] + number_of_user_args = len(user_args) - number_of_bound_args + number_of_user_default_args = len(user_default_args) if user_default_args else 0 + + number_of_event_args = len(event_spec_args) + + if number_of_user_args - number_of_user_default_args > number_of_event_args: + msg = ( + f"Event {key} only provides {number_of_event_args} arguments, but " + f"{func_name or user_func} requires at least {number_of_user_args - number_of_user_default_args} " + "arguments to be passed to the event handler.\n" + "See https://reflex.dev/docs/events/event-arguments/" + ) + raise EventFnArgMismatchError(msg) + + +def call_event_fn( + fn: Callable, + arg_spec: ArgsSpec | Sequence[ArgsSpec], + key: str | None = None, +) -> list[EventSpec | FunctionVar | EventVar]: + """Call a function to a list of event specs. + + The function should return a single event-like value or a heterogeneous + sequence of event-like values. + + Args: + fn: The function to call. + arg_spec: The argument spec for the event trigger. + key: The key to pass to the event handler. + + Returns: + The event-like values from calling the function. + + Raises: + EventHandlerValueError: If the lambda returns an unusable value. + """ + # Import here to avoid circular imports. + from reflex_core.event import EventHandler, EventSpec + from reflex_core.utils.exceptions import EventHandlerValueError + + parsed_args, _ = parse_args_spec(arg_spec) + + parameters = inspect.signature(fn).parameters + + # Check that fn signature matches arg_spec + check_fn_match_arg_spec(fn, parameters, parsed_args, key=key) + + number_of_fn_args = len(parameters) + + # Call the function with the parsed args. + out = fn(*[*parsed_args][:number_of_fn_args]) + + # Normalize common heterogeneous event collections into individual events + # while keeping other scalar values for validation below. + out = list(out) if isinstance(out, (list, tuple)) else [out] + + # Convert any event specs to event specs. + events = [] + for e in out: + if isinstance(e, EventHandler): + # An un-called EventHandler gets all of the args of the event trigger. + e = call_event_handler(e, arg_spec, key=key) + + if isinstance(e, EventChain): + # Nested EventChain is treated like a FunctionVar. + e = Var.create(e) + + # Make sure the event spec is valid. + if not isinstance(e, (EventSpec, FunctionVar, EventVar)): + hint = "" + if isinstance(e, VarOperationCall): + hint = " Hint: use `fn.partial(...)` instead of calling the FunctionVar directly." + msg = ( + f"Invalid event chain for {key}: {fn} -> {e}: A lambda inside an EventChain " + "list must return `EventSpec | EventHandler | EventChain | EventVar | FunctionVar` " + "or a heterogeneous sequence of these types. " + f"Got: {type(e)}.{hint}" + ) + raise EventHandlerValueError(msg) + + # Add the event spec to the chain. + events.append(e) + + # Return the events. + return events + + +def get_handler_args( + event_spec: EventSpec, +) -> tuple[tuple[Var, Var], ...]: + """Get the handler args for the given event spec. + + Args: + event_spec: The event spec. + + Returns: + The handler args. + """ + args = event_spec.handler._parameters + + return event_spec.args if len(args) > 1 else () + + +def fix_events( + events: list[EventSpec | EventHandler] | None, + token: str, + router_data: dict[str, Any] | None = None, +) -> list[Event]: + """Fix a list of events returned by an event handler. + + Args: + events: The events to fix. + token: The user token. + router_data: The optional router data to set in the event. + + Returns: + The fixed events. + + Raises: + ValueError: If the event type is not what was expected. + """ + # If the event handler returns nothing, return an empty list. + if events is None: + return [] + + # If the handler returns a single event, wrap it in a list. + if not isinstance(events, list): + events = [events] + + # Fix the events created by the handler. + out = [] + for e in events: + if callable(e) and getattr(e, "__name__", "") == "": + # A lambda was returned, assume the user wants to call it with no args. + e = e() + if isinstance(e, Event): + # If the event is already an event, append it to the list. + out.append(e) + continue + if not isinstance(e, (EventHandler, EventSpec)): + e = EventHandler(fn=e) + # Otherwise, create an event from the event spec. + if isinstance(e, EventHandler): + e = e() + if not isinstance(e, EventSpec): + msg = f"Unexpected event type, {type(e)}." + raise ValueError(msg) + name = format.format_event_handler(e.handler) + payload = {k._js_expr: v._decode() for k, v in e.args} + + # Filter router_data to reduce payload size + event_router_data = { + k: v + for k, v in (router_data or {}).items() + if k in constants.route.ROUTER_DATA_INCLUDE + } + # Create an event and append it to the list. + out.append( + Event( + token=token, + name=name, + payload=payload, + router_data=event_router_data, + ) + ) + + return out + + +def get_fn_signature(fn: Callable) -> inspect.Signature: + """Get the signature of a function. + + Args: + fn: The function. + + Returns: + The signature of the function. + """ + signature = inspect.signature(fn) + new_param = inspect.Parameter( + FRONTEND_EVENT_STATE, inspect.Parameter.POSITIONAL_OR_KEYWORD, annotation=Any + ) + return signature.replace(parameters=(new_param, *signature.parameters.values())) + + +# These chains can be used for their side effects when no other events are desired. +stop_propagation = noop().stop_propagation +prevent_default = noop().prevent_default + + +class EventVar(ObjectVar, python_types=(EventSpec, EventHandler)): + """Base class for event vars.""" + + def bool(self) -> NoReturn: + """Get the boolean value of the var. + + Raises: + TypeError: EventVar cannot be converted to a boolean. + """ + msg = f"Cannot convert {self._js_expr} of type {type(self).__name__} to bool." + raise TypeError(msg) + + +@dataclasses.dataclass( + eq=False, + frozen=True, + slots=True, +) +class LiteralEventVar(VarOperationCall, LiteralVar, EventVar): + """A literal event var.""" + + _var_value: EventSpec = dataclasses.field(default=None) # pyright: ignore [reportAssignmentType] + + def __hash__(self) -> int: + """Get the hash of the var. + + Returns: + The hash of the var. + """ + return hash((type(self).__name__, self._js_expr)) + + @classmethod + def create( + cls, + value: EventSpec | EventHandler, + _var_data: VarData | None = None, + ) -> LiteralEventVar: + """Create a new LiteralEventVar instance. + + Args: + value: The value of the var. + _var_data: The data of the var. + + Returns: + The created LiteralEventVar instance. + + Raises: + EventFnArgMismatchError: If the event handler takes arguments. + """ + if isinstance(value, EventHandler): + + def no_args(): + return () + + try: + value = call_event_handler(value, no_args) + except EventFnArgMismatchError: + msg = f"Event handler {value.fn.__qualname__} used inside of a rx.cond() must not take any arguments." + raise EventFnArgMismatchError(msg) from None + + return cls( + _js_expr="", + _var_type=EventSpec, + _var_data=_var_data, + _var_value=value, + _func=FunctionStringVar("ReflexEvent"), + _args=( + # event handler name + ".".join( + filter( + None, + format.get_event_handler_parts(value.handler), + ) + ), + # event handler args + {str(name): value for name, value in value.args}, + # event actions + value.event_actions, + # client handler name + *([value.client_handler_name] if value.client_handler_name else []), + ), + ) + + +class EventChainVar(BuilderFunctionVar, python_types=EventChain): + """Base class for event chain vars.""" + + def bool(self) -> NoReturn: + """Get the boolean value of the var. + + Raises: + TypeError: EventChainVar cannot be converted to a boolean. + """ + msg = f"Cannot convert {self._js_expr} of type {type(self).__name__} to bool." + raise TypeError(msg) + + +@dataclasses.dataclass( + eq=False, + frozen=True, + slots=True, +) +# Note: LiteralVar is second in the inheritance list allowing it act like a +# CachedVarOperation (ArgsFunctionOperation) and get the _js_expr from the +# _cached_var_name property. +class LiteralEventChainVar(ArgsFunctionOperationBuilder, LiteralVar, EventChainVar): + """A literal event chain var.""" + + _var_value: EventChain = dataclasses.field(default=None) # pyright: ignore [reportAssignmentType] + + def __hash__(self) -> int: + """Get the hash of the var. + + Returns: + The hash of the var. + """ + return hash((type(self).__name__, self._js_expr)) + + @classmethod + def create( + cls, + value: EventChain, + _var_data: VarData | None = None, + ) -> LiteralEventChainVar: + """Create a new LiteralEventChainVar instance. + + Args: + value: The value of the var. + _var_data: The data of the var. + + Returns: + The created LiteralEventChainVar instance. + + Raises: + ValueError: If the invocation is not a FunctionVar. + """ + arg_spec = ( + value.args_spec[0] + if isinstance(value.args_spec, Sequence) + else value.args_spec + ) + sig = inspect.signature(arg_spec) # pyright: ignore [reportArgumentType] + arg_vars = () + if sig.parameters: + arg_def = tuple(f"_{p}" for p in sig.parameters) + arg_vars = tuple(Var(_js_expr=arg) for arg in arg_def) + arg_def_expr = LiteralVar.create(list(arg_vars)) + else: + # add a default argument for addEvents if none were specified in value.args_spec + # used to trigger the preventDefault() on the event. + arg_def = ("...args",) + arg_def_expr = Var(_js_expr="args") + + if value.invocation is None: + invocation = FunctionStringVar.create( + CompileVars.ADD_EVENTS, + _var_data=VarData( + imports=Imports.EVENTS, + hooks={Hooks.EVENTS: None}, + ), + ) + else: + invocation = value.invocation + + if invocation is not None and not isinstance(invocation, FunctionVar): + msg = f"EventChain invocation must be a FunctionVar, got {invocation!s} of type {invocation._var_type!s}." + raise ValueError(msg) + assert invocation is not None + + call_args = arg_vars if sig.parameters else (Var(_js_expr="...args"),) + statements = [ + ( + event.call(*call_args) + if isinstance(event, FunctionVar) + else invocation.call( + LiteralVar.create([LiteralVar.create(event)]), + arg_def_expr, + _EMPTY_EVENT_ACTIONS, + ) + ) + for event in value.events + ] + + if not statements: + statements.append( + invocation.call( + _EMPTY_EVENTS, + arg_def_expr, + _EMPTY_EVENT_ACTIONS, + ) + ) + + if len(statements) == 1 and not value.event_actions: + return_expr = statements[0] + else: + statement_block = Var( + _js_expr=f"{{{''.join(f'{statement};' for statement in statements)}}}", + ) + if value.event_actions: + apply_event_actions = FunctionStringVar.create( + CompileVars.APPLY_EVENT_ACTIONS, + _var_data=VarData( + imports=Imports.EVENTS, + hooks={Hooks.EVENTS: None}, + ), + ) + return_expr = apply_event_actions.call( + ArgsFunctionOperation.create((), statement_block), + value.event_actions, + *call_args, + ) + else: + return_expr = statement_block + + return cls( + _js_expr="", + _var_type=EventChain, + _var_data=_var_data, + _args=FunctionArgs(arg_def), + _return_expr=return_expr, + _var_value=value, + ) + + +P = TypeVarTuple("P") +Q = TypeVarTuple("Q") +V = TypeVar("V") +V2 = TypeVar("V2") +V3 = TypeVar("V3") +V4 = TypeVar("V4") +V5 = TypeVar("V5") + + +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]): + """Initialize the descriptor with the function to be wrapped. + + Args: + func: The function to be wrapped. + """ + self.func = func + + @overload + def __call__( + self: EventCallback[Unpack[Q]], + ) -> EventCallback[Unpack[Q]]: ... + + @overload + def __call__( + self: EventCallback[V, Unpack[Q]], value: V | Var[V] + ) -> EventCallback[Unpack[Q]]: ... + + @overload + def __call__( + self: EventCallback[V, V2, Unpack[Q]], + value: V | Var[V], + value2: V2 | Var[V2], + ) -> EventCallback[Unpack[Q]]: ... + + @overload + def __call__( + self: EventCallback[V, V2, V3, Unpack[Q]], + value: V | Var[V], + value2: V2 | Var[V2], + value3: V3 | Var[V3], + ) -> EventCallback[Unpack[Q]]: ... + + @overload + def __call__( + self: EventCallback[V, V2, V3, V4, Unpack[Q]], + value: V | Var[V], + value2: V2 | Var[V2], + value3: V3 | Var[V3], + value4: V4 | Var[V4], + ) -> EventCallback[Unpack[Q]]: ... + + def __call__(self, *values) -> EventCallback: # pyright: ignore [reportInconsistentOverload] + """Call the function with the values. + + Args: + *values: The values to call the function with. + + Returns: + The function with the values. + """ + return self.func(*values) # pyright: ignore [reportArgumentType] + + @overload + def __get__( + self: EventCallback[Unpack[P]], instance: None, owner: Any + ) -> EventCallback[Unpack[P]]: ... + + @overload + def __get__(self, instance: Any, owner: Any) -> Callable[[Unpack[P]]]: ... + + def __get__(self, instance: Any, owner: Any) -> Callable: + """Get the function with the instance bound to it. + + Args: + instance: The instance to bind to the function. + owner: The owner of the function. + + Returns: + The function with the instance bound to it + """ + if instance is None: + return self.func + + return partial(self.func, instance) + + +class LambdaEventCallback(Protocol[Unpack[P]]): + """A protocol for a lambda event callback.""" + + __code__: types.CodeType + + @overload + def __call__(self: LambdaEventCallback[()]) -> Any: ... + + @overload + def __call__(self: LambdaEventCallback[V], value: Var[V], /) -> Any: ... + + @overload + def __call__( + self: LambdaEventCallback[V, V2], value: Var[V], value2: Var[V2], / + ) -> Any: ... + + @overload + def __call__( + self: LambdaEventCallback[V, V2, V3], + value: Var[V], + value2: Var[V2], + value3: Var[V3], + /, + ) -> Any: ... + + def __call__(self, *args: Var) -> Any: + """Call the lambda with the args. + + Args: + *args: The args to call the lambda with. + """ + + +ARGS = TypeVarTuple("ARGS") + + +LAMBDA_OR_STATE = TypeAliasType( + "LAMBDA_OR_STATE", + LambdaEventCallback[Unpack[ARGS]] | EventCallback[Unpack[ARGS]], + type_params=(ARGS,), +) + +ItemOrList = V | list[V] + +BASIC_EVENT_TYPES = TypeAliasType( + "BASIC_EVENT_TYPES", EventSpec | EventHandler | Var[Any], type_params=() +) + +IndividualEventType = TypeAliasType( + "IndividualEventType", + LAMBDA_OR_STATE[Unpack[ARGS]] | BASIC_EVENT_TYPES, + type_params=(ARGS,), +) + +EventType = TypeAliasType( + "EventType", + ItemOrList[LAMBDA_OR_STATE[Unpack[ARGS]] | BASIC_EVENT_TYPES], + type_params=(ARGS,), +) + + +if TYPE_CHECKING: + from reflex.state import BaseState + + BASE_STATE = TypeVar("BASE_STATE", bound=BaseState) +else: + BASE_STATE = TypeVar("BASE_STATE") + + +class EventNamespace: + """A namespace for event related classes.""" + + # Core Event Classes + Event = Event + EventActionsMixin = EventActionsMixin + EventHandler = EventHandler + EventSpec = EventSpec + CallableEventSpec = CallableEventSpec + EventChain = EventChain + EventVar = EventVar + LiteralEventVar = LiteralEventVar + EventChainVar = EventChainVar + LiteralEventChainVar = LiteralEventChainVar + EventCallback = EventCallback + LambdaEventCallback = LambdaEventCallback + + # Javascript Event Classes + JavascriptHTMLInputElement = JavascriptHTMLInputElement + JavascriptInputEvent = JavascriptInputEvent + JavascriptKeyboardEvent = JavascriptKeyboardEvent + JavascriptMouseEvent = JavascriptMouseEvent + JavascriptPointerEvent = JavascriptPointerEvent + + # Type Info Classes + KeyInputInfo = KeyInputInfo + MouseEventInfo = MouseEventInfo + PointerEventInfo = PointerEventInfo + IdentityEventReturn = IdentityEventReturn + + # File Upload + FileUpload = FileUpload + UploadFilesChunk = UploadFilesChunk + + # Type Aliases + EventType = EventType + LAMBDA_OR_STATE = LAMBDA_OR_STATE + BASIC_EVENT_TYPES = BASIC_EVENT_TYPES + IndividualEventType = IndividualEventType + + # Constants + BACKGROUND_TASK_MARKER = BACKGROUND_TASK_MARKER + EVENT_ACTIONS_MARKER = EVENT_ACTIONS_MARKER + _EVENT_FIELDS = _EVENT_FIELDS + FORM_DATA = FORM_DATA + upload_files = upload_files + upload_files_chunk = upload_files_chunk + stop_propagation = stop_propagation + prevent_default = prevent_default + + # Private/Internal Functions + resolve_upload_handler_param = staticmethod(resolve_upload_handler_param) + resolve_upload_chunk_handler_param = staticmethod( + resolve_upload_chunk_handler_param + ) + _values_returned_from_event = staticmethod(_values_returned_from_event) + _check_event_args_subclass_of_callback = staticmethod( + _check_event_args_subclass_of_callback + ) + + @overload + def __new__( + cls, + func: None = None, + *, + background: bool | None = None, + stop_propagation: bool | None = None, + prevent_default: bool | None = None, + throttle: int | None = None, + debounce: int | None = None, + temporal: bool | None = None, + ) -> Callable[ + [Callable[[BASE_STATE, Unpack[P]], Any]], EventCallback[Unpack[P]] # pyright: ignore [reportInvalidTypeVarUse] + ]: ... + + @overload + def __new__( + cls, + func: Callable[[BASE_STATE, Unpack[P]], Any], + *, + background: bool | None = None, + stop_propagation: bool | None = None, + prevent_default: bool | None = None, + throttle: int | None = None, + debounce: int | None = None, + temporal: bool | None = None, + ) -> EventCallback[Unpack[P]]: ... + + def __new__( + cls, + func: Callable[[BASE_STATE, Unpack[P]], Any] | None = None, + *, + background: bool | None = None, + stop_propagation: bool | None = None, + prevent_default: bool | None = None, + throttle: int | None = None, + debounce: int | None = None, + temporal: bool | None = None, + ) -> ( + EventCallback[Unpack[P]] + | Callable[[Callable[[BASE_STATE, Unpack[P]], Any]], EventCallback[Unpack[P]]] + ): + """Wrap a function to be used as an event. + + Args: + func: The function to wrap. + background: Whether the event should be run in the background. Defaults to False. + stop_propagation: Whether to stop the event from bubbling up the DOM tree. + prevent_default: Whether to prevent the default behavior of the event. + throttle: Throttle the event handler to limit calls (in milliseconds). + debounce: Debounce the event handler to delay calls (in milliseconds). + temporal: Whether the event should be dropped when the backend is down. + + Returns: + The wrapped function. + + Raises: + TypeError: If background is True and the function is not a coroutine or async generator. # noqa: DAR402 + """ + + def _build_event_actions(): + """Build event_actions dict from decorator parameters. + + Returns: + Dict of event actions to apply, or empty dict if none specified. + """ + if not any([ + stop_propagation, + prevent_default, + throttle, + debounce, + temporal, + ]): + return {} + + event_actions = {} + if stop_propagation is not None: + event_actions["stopPropagation"] = stop_propagation + if prevent_default is not None: + event_actions["preventDefault"] = prevent_default + if throttle is not None: + event_actions["throttle"] = throttle + if debounce is not None: + event_actions["debounce"] = debounce + if temporal is not None: + event_actions["temporal"] = temporal + return event_actions + + def wrapper( + func: Callable[[BASE_STATE, Unpack[P]], T], + ) -> EventCallback[Unpack[P]]: + if background is True: + if not inspect.iscoroutinefunction( + func + ) and not inspect.isasyncgenfunction(func): + msg = "Background task must be async function or generator." + raise TypeError(msg) + setattr(func, BACKGROUND_TASK_MARKER, True) + if getattr(func, "__name__", "").startswith("_"): + msg = "Event handlers cannot be private." + raise ValueError(msg) + + qualname: str | None = getattr(func, "__qualname__", None) + + if qualname and ( + len(func_path := qualname.split(".")) == 1 + or func_path[-2] == "" + ): + from reflex.state import BaseState + + types = get_type_hints(func) + state_arg_name = next(iter(inspect.signature(func).parameters), None) + state_cls = state_arg_name and types.get(state_arg_name) + if state_cls and issubclass(state_cls, BaseState): + name = ( + (func.__module__ + "." + qualname) + .replace(".", "_") + .replace("", "_") + .removeprefix("_") + ) + object.__setattr__(func, "__name__", name) + object.__setattr__(func, "__qualname__", name) + state_cls._add_event_handler(name, func) + event_callback = getattr(state_cls, name) + + # Apply decorator event actions + event_actions = _build_event_actions() + if event_actions: + # Create new EventCallback with updated event_actions + event_callback = dataclasses.replace( + event_callback, event_actions=event_actions + ) + + return event_callback + + # Store decorator event actions on the function for later processing + event_actions = _build_event_actions() + if event_actions: + setattr(func, EVENT_ACTIONS_MARKER, event_actions) + return func # pyright: ignore [reportReturnType] + + if func is not None: + return wrapper(func) + return wrapper + + get_event = staticmethod(get_event) + get_hydrate_event = staticmethod(get_hydrate_event) + fix_events = staticmethod(fix_events) + call_event_handler = staticmethod(call_event_handler) + call_event_fn = staticmethod(call_event_fn) + get_handler_args = staticmethod(get_handler_args) + check_fn_match_arg_spec = staticmethod(check_fn_match_arg_spec) + resolve_annotation = staticmethod(resolve_annotation) + parse_args_spec = staticmethod(parse_args_spec) + args_specs_from_fields = staticmethod(args_specs_from_fields) + unwrap_var_annotation = staticmethod(unwrap_var_annotation) + get_fn_signature = staticmethod(get_fn_signature) + + # Event Spec Functions + passthrough_event_spec = staticmethod(passthrough_event_spec) + input_event = staticmethod(input_event) + int_input_event = staticmethod(int_input_event) + float_input_event = staticmethod(float_input_event) + checked_input_event = staticmethod(checked_input_event) + key_event = staticmethod(key_event) + pointer_event_spec = staticmethod(pointer_event_spec) + no_args_event_spec = staticmethod(no_args_event_spec) + on_submit_event = staticmethod(on_submit_event) + on_submit_string_event = staticmethod(on_submit_string_event) + + # Server Side Events + server_side = staticmethod(server_side) + redirect = staticmethod(redirect) + console_log = staticmethod(console_log) + noop = staticmethod(noop) + back = staticmethod(back) + window_alert = staticmethod(window_alert) + set_focus = staticmethod(set_focus) + blur_focus = staticmethod(blur_focus) + scroll_to = staticmethod(scroll_to) + set_value = staticmethod(set_value) + remove_cookie = staticmethod(remove_cookie) + clear_local_storage = staticmethod(clear_local_storage) + remove_local_storage = staticmethod(remove_local_storage) + clear_session_storage = staticmethod(clear_session_storage) + remove_session_storage = staticmethod(remove_session_storage) + set_clipboard = staticmethod(set_clipboard) + download = staticmethod(download) + call_script = staticmethod(call_script) + call_function = staticmethod(call_function) + run_script = staticmethod(run_script) + __file__ = __file__ + + +event = EventNamespace +event.event = event # pyright: ignore[reportAttributeAccessIssue] +sys.modules[__name__] = event # pyright: ignore[reportArgumentType] diff --git a/packages/reflex-core/src/reflex_core/utils/format.py b/packages/reflex-core/src/reflex_core/utils/format.py index f23caef748b..5f50474fc5c 100644 --- a/packages/reflex-core/src/reflex_core/utils/format.py +++ b/packages/reflex-core/src/reflex_core/utils/format.py @@ -14,7 +14,7 @@ if TYPE_CHECKING: from reflex.components.component import ComponentStyle - from reflex.event import ArgsSpec, EventChain, EventHandler, EventSpec, EventType + from reflex_core.event import ArgsSpec, EventChain, EventHandler, EventSpec, EventType WRAP_MAP = { "{": "}", @@ -378,7 +378,7 @@ def format_prop( ValueError: If the prop is not a string. """ # import here to avoid circular import. - from reflex.event import EventChain + from reflex_core.event import EventChain from reflex_core.utils import serializers from reflex_core.vars import Var @@ -543,7 +543,7 @@ def format_queue_events( Raises: ValueError: If a lambda function is given which returns a Var. """ - from reflex.event import ( + from reflex_core.event import ( EventChain, EventHandler, EventSpec, diff --git a/packages/reflex-core/src/reflex_core/vars/base.py b/packages/reflex-core/src/reflex_core/vars/base.py index e91e8ff8f5c..9034ffa588d 100644 --- a/packages/reflex-core/src/reflex_core/vars/base.py +++ b/packages/reflex-core/src/reflex_core/vars/base.py @@ -1482,7 +1482,7 @@ def _create_literal_var( ): return resulting_var - from reflex.event import EventHandler + from reflex_core.event import EventHandler from reflex_core.utils.format import get_event_handler_parts if isinstance(value, EventHandler): @@ -1564,7 +1564,7 @@ def _get_all_var_data_without_creating_var_dispatch( ): return resulting_var._get_all_var_data() - from reflex.event import EventHandler + from reflex_core.event import EventHandler from reflex_core.utils.format import get_event_handler_parts if isinstance(value, EventHandler): diff --git a/reflex/components/field.py b/reflex/components/field.py index 43944485e2b..a371bb2d55b 100644 --- a/reflex/components/field.py +++ b/reflex/components/field.py @@ -1,183 +1,3 @@ -"""Shared field infrastructure for components and props.""" +"""Re-export from reflex_core.""" -from __future__ import annotations - -from collections.abc import Callable -from dataclasses import _MISSING_TYPE, MISSING -from typing import Annotated, Any, Generic, TypeVar, get_origin - -from reflex.utils import types -from reflex.utils.compat import annotations_from_namespace - -FIELD_TYPE = TypeVar("FIELD_TYPE") - - -class BaseField(Generic[FIELD_TYPE]): - """Base field class used by internal metadata classes.""" - - def __init__( - self, - default: FIELD_TYPE | _MISSING_TYPE = MISSING, - default_factory: Callable[[], FIELD_TYPE] | None = None, - annotated_type: type[Any] | _MISSING_TYPE = MISSING, - ) -> None: - """Initialize the field. - - Args: - default: The default value for the field. - default_factory: The default factory for the field. - annotated_type: The annotated type for the field. - """ - self.default = default - self.default_factory = default_factory - self.outer_type_ = self.annotated_type = annotated_type - - # Process type annotation - type_origin = get_origin(annotated_type) or annotated_type - if type_origin is Annotated: - type_origin = annotated_type.__origin__ # pyright: ignore [reportAttributeAccessIssue] - # For Annotated types, use the actual type inside the annotation - self.type_ = annotated_type - else: - # For other types (including Union), preserve the original type - self.type_ = annotated_type - self.type_origin = type_origin - - def default_value(self) -> FIELD_TYPE: - """Get the default value for the field. - - Returns: - The default value for the field. - - Raises: - ValueError: If no default value or factory is provided. - """ - if self.default is not MISSING: - return self.default - if self.default_factory is not None: - return self.default_factory() - msg = "No default value or factory provided." - raise ValueError(msg) - - -class FieldBasedMeta(type): - """Shared metaclass for field-based classes like components and props. - - Provides common field inheritance and processing logic for both - PropsBaseMeta and BaseComponentMeta. - """ - - def __new__( - cls, name: str, bases: tuple[type, ...], namespace: dict[str, Any] - ) -> type: - """Create a new field-based class. - - Args: - name: The name of the class. - bases: The base classes. - namespace: The class namespace. - - Returns: - The new class. - """ - # Collect inherited fields from base classes - inherited_fields = cls._collect_inherited_fields(bases) - - # Get annotations from the namespace - annotations = cls._resolve_annotations(namespace, name) - - # Process field overrides (fields with values but no annotations) - own_fields = cls._process_field_overrides( - namespace, annotations, inherited_fields - ) - - # Process annotated fields - own_fields.update( - cls._process_annotated_fields(namespace, annotations, inherited_fields) - ) - - # Finalize fields and store on class - cls._finalize_fields(namespace, inherited_fields, own_fields) - - return super().__new__(cls, name, bases, namespace) - - @classmethod - def _collect_inherited_fields(cls, bases: tuple[type, ...]) -> dict[str, Any]: - inherited_fields: dict[str, Any] = {} - - # Collect inherited fields from base classes - for base in bases[::-1]: - if hasattr(base, "_inherited_fields"): - inherited_fields.update(base._inherited_fields) - for base in bases[::-1]: - if hasattr(base, "_own_fields"): - inherited_fields.update(base._own_fields) - - return inherited_fields - - @classmethod - def _resolve_annotations( - cls, namespace: dict[str, Any], name: str - ) -> dict[str, Any]: - return types.resolve_annotations( - annotations_from_namespace(namespace), - namespace["__module__"], - ) - - @classmethod - def _process_field_overrides( - cls, - namespace: dict[str, Any], - annotations: dict[str, Any], - inherited_fields: dict[str, Any], - ) -> dict[str, Any]: - own_fields: dict[str, Any] = {} - - for key, value in namespace.items(): - if key not in annotations and key in inherited_fields: - inherited_field = inherited_fields[key] - new_field = cls._create_field( - annotated_type=inherited_field.annotated_type, - default=value, - default_factory=None, - ) - own_fields[key] = new_field - - return own_fields - - @classmethod - def _process_annotated_fields( - cls, - namespace: dict[str, Any], - annotations: dict[str, Any], - inherited_fields: dict[str, Any], - ) -> dict[str, Any]: - raise NotImplementedError - - @classmethod - def _create_field( - cls, - annotated_type: Any, - default: Any = MISSING, - default_factory: Callable[[], Any] | None = None, - ) -> Any: - raise NotImplementedError - - @classmethod - def _finalize_fields( - cls, - namespace: dict[str, Any], - inherited_fields: dict[str, Any], - own_fields: dict[str, Any], - ) -> None: - # Combine all fields - all_fields = inherited_fields | own_fields - - # Set field names for compatibility - for field_name, field in all_fields.items(): - field._name = field_name - - # Store field mappings on the class - namespace["_own_fields"] = own_fields - namespace["_inherited_fields"] = inherited_fields - namespace["_fields"] = all_fields +from reflex_core.components.field import * # noqa: F401, F403 diff --git a/reflex/event.py b/reflex/event.py index e2fbe32cef1..e986be1d651 100644 --- a/reflex/event.py +++ b/reflex/event.py @@ -1,2756 +1,3 @@ -"""Define event classes to connect the frontend and backend.""" +"""Re-export from reflex_core.""" -from __future__ import annotations - -import dataclasses -import inspect -import sys -import types -import warnings -from base64 import b64encode -from collections.abc import Callable, Mapping, Sequence -from functools import lru_cache, partial -from typing import ( - TYPE_CHECKING, - Annotated, - Any, - Generic, - Literal, - NoReturn, - Protocol, - TypeVar, - get_args, - get_origin, - get_type_hints, - overload, -) - -from typing_extensions import Self, TypeAliasType, TypedDict, TypeVarTuple, Unpack - -from reflex import constants -from reflex.components.field import BaseField -from reflex.constants.compiler import CompileVars, Hooks, Imports -from reflex.constants.state import FRONTEND_EVENT_STATE -from reflex.utils import format -from reflex.utils.decorator import once -from reflex.utils.exceptions import ( - EventFnArgMismatchError, - EventHandlerArgTypeMismatchError, - MissingAnnotationError, -) -from reflex.utils.types import ( - ArgsSpec, - GenericType, - Unset, - safe_issubclass, - typehint_issubclass, -) -from reflex.vars import VarData -from reflex.vars.base import LiteralVar, Var -from reflex.vars.function import ( - ArgsFunctionOperation, - ArgsFunctionOperationBuilder, - BuilderFunctionVar, - FunctionArgs, - FunctionStringVar, - FunctionVar, - VarOperationCall, -) -from reflex.vars.object import ObjectVar - - -@dataclasses.dataclass( - init=True, - frozen=True, -) -class Event: - """An event that describes any state change in the app. - - Attributes: - token: The token to specify the client that the event is for. - name: The event name. - router_data: The routing data where event occurred. - payload: The event payload. - """ - - token: str - - name: str - - router_data: dict[str, Any] = dataclasses.field(default_factory=dict) - - payload: dict[str, Any] = dataclasses.field(default_factory=dict) - - @property - def substate_token(self) -> str: - """Get the substate token for the event. - - Returns: - The substate token. - """ - substate = self.name.rpartition(".")[0] - return f"{self.token}_{substate}" - - -_EVENT_FIELDS: set[str] = {f.name for f in dataclasses.fields(Event)} -_EMPTY_EVENTS = LiteralVar.create([]) -_EMPTY_EVENT_ACTIONS = LiteralVar.create({}) - -BACKGROUND_TASK_MARKER = "_reflex_background_task" -EVENT_ACTIONS_MARKER = "_rx_event_actions" -UPLOAD_FILES_CLIENT_HANDLER = "uploadFiles" - - -def _handler_name(handler: EventHandler) -> str: - """Get a stable fully qualified handler name for errors. - - Args: - handler: The handler to name. - - Returns: - The fully qualified handler name. - """ - if handler.state_full_name: - return f"{handler.state_full_name}.{handler.fn.__name__}" - return handler.fn.__qualname__ - - -def resolve_upload_handler_param(handler: EventHandler) -> tuple[str, Any]: - """Validate and resolve the UploadFile list parameter for a handler. - - Args: - handler: The event handler to inspect. - - Returns: - The parameter name and annotation for the upload file argument. - - Raises: - UploadTypeError: If the handler is a background task. - UploadValueError: If the handler does not accept ``list[rx.UploadFile]``. - """ - from reflex._upload import UploadFile - from reflex.utils.exceptions import UploadTypeError, UploadValueError - - handler_name = _handler_name(handler) - if handler.is_background: - msg = ( - f"@rx.event(background=True) is not supported for upload handler " - f"`{handler_name}`." - ) - raise UploadTypeError(msg) - - func = handler.fn.func if isinstance(handler.fn, partial) else handler.fn - for name, annotation in get_type_hints(func).items(): - if name == "return" or get_origin(annotation) is not list: - continue - args = get_args(annotation) - if len(args) == 1 and typehint_issubclass(args[0], UploadFile): - return name, annotation - - msg = ( - f"`{handler_name}` handler should have a parameter annotated as " - "list[rx.UploadFile]" - ) - raise UploadValueError(msg) - - -def resolve_upload_chunk_handler_param(handler: EventHandler) -> tuple[str, type]: - """Validate and resolve the UploadChunkIterator parameter for a handler. - - Args: - handler: The event handler to inspect. - - Returns: - The parameter name and annotation for the iterator argument. - - Raises: - UploadTypeError: If the handler is not a background task. - UploadValueError: If the handler does not accept an UploadChunkIterator. - """ - from reflex._upload import UploadChunkIterator - from reflex.utils.exceptions import UploadTypeError, UploadValueError - - handler_name = _handler_name(handler) - if not handler.is_background: - msg = f"@rx.event(background=True) is required for upload_files_chunk handler `{handler_name}`." - raise UploadTypeError(msg) - - func = handler.fn.func if isinstance(handler.fn, partial) else handler.fn - for name, annotation in get_type_hints(func).items(): - if name == "return": - continue - if annotation is UploadChunkIterator: - return name, annotation - - msg = ( - f"`{handler_name}` handler should have a parameter annotated as " - "rx.UploadChunkIterator" - ) - raise UploadValueError(msg) - - -@dataclasses.dataclass( - init=True, - frozen=True, - kw_only=True, -) -class EventActionsMixin: - """Mixin for DOM event actions. - - Attributes: - event_actions: Whether to `preventDefault` or `stopPropagation` on the event. - """ - - event_actions: dict[str, bool | int] = dataclasses.field(default_factory=dict) - - @property - def stop_propagation(self) -> Self: - """Stop the event from bubbling up the DOM tree. - - Returns: - New EventHandler-like with stopPropagation set to True. - """ - return dataclasses.replace( - self, - event_actions={**self.event_actions, "stopPropagation": True}, - ) - - @property - def prevent_default(self) -> Self: - """Prevent the default behavior of the event. - - Returns: - New EventHandler-like with preventDefault set to True. - """ - return dataclasses.replace( - self, - event_actions={**self.event_actions, "preventDefault": True}, - ) - - def throttle(self, limit_ms: int) -> Self: - """Throttle the event handler. - - Args: - limit_ms: The time in milliseconds to throttle the event handler. - - Returns: - New EventHandler-like with throttle set to limit_ms. - """ - return dataclasses.replace( - self, - event_actions={**self.event_actions, "throttle": limit_ms}, - ) - - def debounce(self, delay_ms: int) -> Self: - """Debounce the event handler. - - Args: - delay_ms: The time in milliseconds to debounce the event handler. - - Returns: - New EventHandler-like with debounce set to delay_ms. - """ - return dataclasses.replace( - self, - event_actions={**self.event_actions, "debounce": delay_ms}, - ) - - @property - def temporal(self) -> Self: - """Do not queue the event if the backend is down. - - Returns: - New EventHandler-like with temporal set to True. - """ - return dataclasses.replace( - self, - event_actions={**self.event_actions, "temporal": True}, - ) - - -@dataclasses.dataclass( - init=True, - frozen=True, - kw_only=True, -) -class EventHandler(EventActionsMixin): - """An event handler responds to an event to update the state. - - Attributes: - fn: The function to call in response to the event. - state_full_name: The full name of the state class this event handler is attached to. Empty string means this event handler is a server side event. - """ - - fn: Any = dataclasses.field(default=None) - - state_full_name: str = dataclasses.field(default="") - - def __hash__(self): - """Get the hash of the event handler. - - Returns: - The hash of the event handler. - """ - return hash((tuple(self.event_actions.items()), self.fn, self.state_full_name)) - - def get_parameters(self) -> Mapping[str, inspect.Parameter]: - """Get the parameters of the function. - - Returns: - The parameters of the function. - """ - if self.fn is None: - return {} - return inspect.signature(self.fn).parameters - - @property - def _parameters(self) -> Mapping[str, inspect.Parameter]: - """Get the parameters of the function. - - Returns: - The parameters of the function. - """ - if (parameters := getattr(self, "__parameters", None)) is None: - parameters = {**self.get_parameters()} - object.__setattr__(self, "__parameters", parameters) - return parameters - - @classmethod - def __class_getitem__(cls, args_spec: str) -> Annotated: - """Get a typed EventHandler. - - Args: - args_spec: The args_spec of the EventHandler. - - Returns: - The EventHandler class item. - """ - return Annotated[cls, args_spec] - - @property - def is_background(self) -> bool: - """Whether the event handler is a background task. - - Returns: - True if the event handler is marked as a background task. - """ - return getattr(self.fn, BACKGROUND_TASK_MARKER, False) - - def __call__(self, *args: Any, **kwargs: Any) -> EventSpec: - """Pass arguments to the handler to get an event spec. - - This method configures event handlers that take in arguments. - - Args: - *args: The arguments to pass to the handler. - **kwargs: The keyword arguments to pass to the handler. - - Returns: - The event spec, containing both the function and args. - - Raises: - EventHandlerTypeError: If the arguments are invalid. - """ - from reflex.utils.exceptions import EventHandlerTypeError - - # Get the function args. - fn_args = list(self._parameters)[1:] - - if not isinstance( - repeated_arg := next( - (kwarg for kwarg in kwargs if kwarg in fn_args[: len(args)]), Unset() - ), - Unset, - ): - msg = f"Event handler {self.fn.__name__} received repeated argument {repeated_arg}." - raise EventHandlerTypeError(msg) - - if not isinstance( - extra_arg := next( - (kwarg for kwarg in kwargs if kwarg not in fn_args), Unset() - ), - Unset, - ): - msg = ( - f"Event handler {self.fn.__name__} received extra argument {extra_arg}." - ) - raise EventHandlerTypeError(msg) - - fn_args = fn_args[: len(args)] + list(kwargs) - - fn_args = (Var(_js_expr=arg) for arg in fn_args) - - # Construct the payload. - values = [] - for arg in [*args, *kwargs.values()]: - # Special case for file uploads. - if isinstance(arg, (FileUpload, UploadFilesChunk)): - return arg.as_event_spec(handler=self) - - # Otherwise, convert to JSON. - try: - values.append(LiteralVar.create(arg)) - except TypeError as e: - msg = f"Arguments to event handlers must be Vars or JSON-serializable. Got {arg} of type {type(arg)}." - raise EventHandlerTypeError(msg) from e - payload = tuple(zip(fn_args, values, strict=False)) - - # Return the event spec. - return EventSpec( - handler=self, args=payload, event_actions=self.event_actions.copy() - ) - - -@dataclasses.dataclass( - init=True, - frozen=True, - kw_only=True, -) -class EventSpec(EventActionsMixin): - """An event specification. - - Whereas an Event object is passed during runtime, a spec is used - during compile time to outline the structure of an event. - - Attributes: - handler: The event handler. - client_handler_name: The handler on the client to process event. - args: The arguments to pass to the function. - """ - - handler: EventHandler = dataclasses.field(default=None) # pyright: ignore [reportAssignmentType] - - client_handler_name: str = dataclasses.field(default="") - - args: tuple[tuple[Var, Var], ...] = dataclasses.field(default_factory=tuple) - - def __init__( - self, - handler: EventHandler, - event_actions: dict[str, bool | int] | None = None, - client_handler_name: str = "", - args: tuple[tuple[Var, Var], ...] = (), - ): - """Initialize an EventSpec. - - Args: - event_actions: The event actions. - handler: The event handler. - client_handler_name: The client handler name. - args: The arguments to pass to the function. - """ - if event_actions is None: - event_actions = {} - object.__setattr__(self, "event_actions", event_actions) - object.__setattr__(self, "handler", handler) - object.__setattr__(self, "client_handler_name", client_handler_name) - object.__setattr__(self, "args", args or ()) - - def with_args(self, args: tuple[tuple[Var, Var], ...]) -> EventSpec: - """Copy the event spec, with updated args. - - Args: - args: The new args to pass to the function. - - Returns: - A copy of the event spec, with the new args. - """ - return type(self)( - handler=self.handler, - client_handler_name=self.client_handler_name, - args=args, - event_actions=self.event_actions.copy(), - ) - - def add_args(self, *args: Var) -> EventSpec: - """Add arguments to the event spec. - - Args: - *args: The arguments to add positionally. - - Returns: - The event spec with the new arguments. - - Raises: - EventHandlerTypeError: If the arguments are invalid. - """ - from reflex.utils.exceptions import EventHandlerTypeError - - # Get the remaining unfilled function args. - fn_args = list(self.handler._parameters)[1 + len(self.args) :] - fn_args = (Var(_js_expr=arg) for arg in fn_args) - - # Construct the payload. - values = [] - arg = None - try: - for arg in args: - values.append(LiteralVar.create(value=arg)) # noqa: PERF401, RUF100 - except TypeError as e: - msg = f"Arguments to event handlers must be Vars or JSON-serializable. Got {arg} of type {type(arg)}." - raise EventHandlerTypeError(msg) from e - new_payload = tuple(zip(fn_args, values, strict=False)) - return self.with_args(self.args + new_payload) - - -@dataclasses.dataclass( - frozen=True, -) -class CallableEventSpec(EventSpec): - """Decorate an EventSpec-returning function to act as both a EventSpec and a function. - - This is used as a compatibility shim for replacing EventSpec objects in the - API with functions that return a family of EventSpec. - """ - - fn: Callable[..., EventSpec] | None = None - - def __init__(self, fn: Callable[..., EventSpec] | None = None, **kwargs): - """Initialize a CallableEventSpec. - - Args: - fn: The function to decorate. - **kwargs: The kwargs to pass to the EventSpec constructor. - """ - if fn is not None: - default_event_spec = fn() - super().__init__( - event_actions=default_event_spec.event_actions, - client_handler_name=default_event_spec.client_handler_name, - args=default_event_spec.args, - handler=default_event_spec.handler, - **kwargs, - ) - object.__setattr__(self, "fn", fn) - else: - super().__init__(**kwargs) - - def __call__(self, *args, **kwargs) -> EventSpec: - """Call the decorated function. - - Args: - *args: The args to pass to the function. - **kwargs: The kwargs to pass to the function. - - Returns: - The EventSpec returned from calling the function. - - Raises: - EventHandlerTypeError: If the CallableEventSpec has no associated function. - """ - from reflex.utils.exceptions import EventHandlerTypeError - - if self.fn is None: - msg = "CallableEventSpec has no associated function." - raise EventHandlerTypeError(msg) - return self.fn(*args, **kwargs) - - -@dataclasses.dataclass( - init=True, - frozen=True, -) -class EventChain(EventActionsMixin): - """Container for a chain of events that will be executed in order.""" - - events: Sequence[EventSpec | EventVar | FunctionVar | EventCallback] = ( - dataclasses.field(default_factory=list) - ) - - args_spec: Callable | Sequence[Callable] | None = dataclasses.field(default=None) - - invocation: Var | None = dataclasses.field(default=None) - - @classmethod - def create( - cls, - value: EventType, - args_spec: ArgsSpec | Sequence[ArgsSpec], - key: str | None = None, - **event_chain_kwargs, - ) -> EventChain | Var: - """Create an event chain from a variety of input types. - - Args: - value: The value to create the event chain from. - args_spec: The args_spec of the event trigger being bound. - key: The key of the event trigger being bound. - **event_chain_kwargs: Additional kwargs to pass to the EventChain constructor. - - Returns: - The event chain. - - Raises: - ValueError: If the value is not a valid event chain. - """ - # If it's an event chain var, return it. - if isinstance(value, Var): - # Only pass through literal/prebuilt chains. Other EventChainVar values may be - # FunctionVars cast with `.to(EventChain)` and still need wrapping so - # event_chain_kwargs can compose onto the resulting chain. - if isinstance(value, LiteralEventChainVar): - if event_chain_kwargs: - warnings.warn( - f"event_chain_kwargs {event_chain_kwargs!r} are ignored for " - "EventChainVar values.", - stacklevel=2, - ) - return value - if isinstance(value, (EventVar, FunctionVar)): - value = [value] - elif safe_issubclass(value._var_type, (EventChain, EventSpec)): - return cls.create( - value=value.guess_type(), - args_spec=args_spec, - key=key, - **event_chain_kwargs, - ) - else: - msg = f"Invalid event chain: {value!s} of type {value._var_type}" - raise ValueError(msg) - elif isinstance(value, EventChain): - # Trust that the caller knows what they're doing passing an EventChain directly - return value - - # If the input is a single event handler, wrap it in a list. - if isinstance(value, (EventHandler, EventSpec)): - value = [value] - - events: list[EventSpec | EventVar | FunctionVar] = [] - - # If the input is a list of event handlers, create an event chain. - if isinstance(value, list): - for v in value: - if isinstance(v, (EventHandler, EventSpec)): - # Call the event handler to get the event. - events.append(call_event_handler(v, args_spec, key=key)) - elif isinstance(v, (EventVar, EventChainVar)): - events.append(v) - elif isinstance(v, FunctionVar): - # Apply the args_spec transformations as partial arguments to the function. - events.append(v.partial(*parse_args_spec(args_spec)[0])) - elif isinstance(v, Callable): - # Call the lambda to get the event chain. - events.extend(call_event_fn(v, args_spec, key=key)) - else: - msg = f"Invalid event: {v}" - raise ValueError(msg) - - # If the input is a callable, create an event chain. - elif isinstance(value, Callable): - events.extend(call_event_fn(value, args_spec, key=key)) - - # Otherwise, raise an error. - else: - msg = f"Invalid event chain: {value}" - raise ValueError(msg) - - # Add args to the event specs if necessary. - events = [ - (e.with_args(get_handler_args(e)) if isinstance(e, EventSpec) else e) - for e in events - ] - - # Return the event chain. - return cls( - events=events, - args_spec=args_spec, - **event_chain_kwargs, - ) - - -@dataclasses.dataclass( - init=True, - frozen=True, -) -class JavascriptHTMLInputElement: - """Interface for a Javascript HTMLInputElement https://developer.mozilla.org/en-US/docs/Web/API/HTMLInputElement.""" - - value: str = "" - checked: bool = False - - -@dataclasses.dataclass( - init=True, - frozen=True, -) -class JavascriptInputEvent: - """Interface for a Javascript InputEvent https://developer.mozilla.org/en-US/docs/Web/API/InputEvent.""" - - target: JavascriptHTMLInputElement = JavascriptHTMLInputElement() - - -@dataclasses.dataclass( - init=True, - frozen=True, -) -class JavascriptKeyboardEvent: - """Interface for a Javascript KeyboardEvent https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent.""" - - key: str = "" - altKey: bool = False # noqa: N815 - ctrlKey: bool = False # noqa: N815 - metaKey: bool = False # noqa: N815 - shiftKey: bool = False # noqa: N815 - - -def input_event(e: ObjectVar[JavascriptInputEvent]) -> tuple[Var[str]]: - """Get the value from an input event. - - Args: - e: The input event. - - Returns: - The value from the input event. - """ - return (e.target.value,) - - -def int_input_event(e: ObjectVar[JavascriptInputEvent]) -> tuple[Var[int]]: - """Get the value from an input event as an int. - - Args: - e: The input event. - - Returns: - The value from the input event as an int. - """ - return (Var("Number").to(FunctionVar).call(e.target.value).to(int),) - - -def float_input_event(e: ObjectVar[JavascriptInputEvent]) -> tuple[Var[float]]: - """Get the value from an input event as a float. - - Args: - e: The input event. - - Returns: - The value from the input event as a float. - """ - return (Var("Number").to(FunctionVar).call(e.target.value).to(float),) - - -def checked_input_event(e: ObjectVar[JavascriptInputEvent]) -> tuple[Var[bool]]: - """Get the checked state from an input event. - - Args: - e: The input event. - - Returns: - The checked state from the input event. - """ - return (e.target.checked,) - - -FORM_DATA = Var(_js_expr="form_data") - - -def on_submit_event() -> tuple[Var[dict[str, Any]]]: - """Event handler spec for the on_submit event. - - Returns: - The event handler spec. - """ - return (FORM_DATA,) - - -def on_submit_string_event() -> tuple[Var[dict[str, str]]]: - """Event handler spec for the on_submit event. - - Returns: - The event handler spec. - """ - return (FORM_DATA,) - - -class KeyInputInfo(TypedDict): - """Information about a key input event.""" - - alt_key: bool - ctrl_key: bool - meta_key: bool - shift_key: bool - - -def key_event( - e: ObjectVar[JavascriptKeyboardEvent], -) -> tuple[Var[str], Var[KeyInputInfo]]: - """Get the key from a keyboard event. - - Args: - e: The keyboard event. - - Returns: - The key from the keyboard event. - """ - return ( - e.key.to(str), - Var.create( - { - "alt_key": e.altKey, - "ctrl_key": e.ctrlKey, - "meta_key": e.metaKey, - "shift_key": e.shiftKey, - }, - ).to(KeyInputInfo), - ) - - -@dataclasses.dataclass( - init=True, - frozen=True, -) -class JavascriptMouseEvent: - """Interface for a Javascript MouseEvent https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent.""" - - button: int = 0 - buttons: list[int] = dataclasses.field(default_factory=list) - clientX: int = 0 # noqa: N815 - clientY: int = 0 # noqa: N815 - altKey: bool = False # noqa: N815 - ctrlKey: bool = False # noqa: N815 - metaKey: bool = False # noqa: N815 - shiftKey: bool = False # noqa: N815 - - -class JavascriptPointerEvent(JavascriptMouseEvent): - """Interface for a Javascript PointerEvent https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent. - - Inherits from JavascriptMouseEvent. - """ - - -class MouseEventInfo(TypedDict): - """Information about a mouse event.""" - - button: int - buttons: int - client_x: int - client_y: int - alt_key: bool - ctrl_key: bool - meta_key: bool - shift_key: bool - - -class PointerEventInfo(MouseEventInfo): - """Information about a pointer event.""" - - -def pointer_event_spec( - e: ObjectVar[JavascriptPointerEvent], -) -> tuple[Var[PointerEventInfo]]: - """Get the pointer event information. - - Args: - e: The pointer event. - - Returns: - The pointer event information. - """ - return ( - Var.create( - { - "button": e.button, - "buttons": e.buttons, - "client_x": e.clientX, - "client_y": e.clientY, - "alt_key": e.altKey, - "ctrl_key": e.ctrlKey, - "meta_key": e.metaKey, - "shift_key": e.shiftKey, - }, - ).to(PointerEventInfo), - ) - - -def no_args_event_spec() -> tuple[()]: - """Empty event handler. - - Returns: - An empty tuple. - """ - return () - - -T = TypeVar("T") -U = TypeVar("U") - - -class IdentityEventReturn(Generic[T], Protocol): - """Protocol for an identity event return.""" - - def __call__(self, *values: Var[T]) -> tuple[Var[T], ...]: - """Return the input values. - - Args: - *values: The values to return. - - Returns: - The input values. - """ - return values - - -@overload -def passthrough_event_spec( # pyright: ignore [reportOverlappingOverload] - event_type: type[T], / -) -> Callable[[Var[T]], tuple[Var[T]]]: ... - - -@overload -def passthrough_event_spec( - event_type_1: type[T], event_type2: type[U], / -) -> Callable[[Var[T], Var[U]], tuple[Var[T], Var[U]]]: ... - - -@overload -def passthrough_event_spec(*event_types: type[T]) -> IdentityEventReturn[T]: ... - - -def passthrough_event_spec(*event_types: type[T]) -> IdentityEventReturn[T]: # pyright: ignore [reportInconsistentOverload] - """A helper function that returns the input event as output. - - Args: - *event_types: The types of the events. - - Returns: - A function that returns the input event as output. - """ - - def inner(*values: Var[T]) -> tuple[Var[T], ...]: - return values - - inner_type = tuple(Var[event_type] for event_type in event_types) - return_annotation = tuple[inner_type] - - inner.__signature__ = inspect.signature(inner).replace( # pyright: ignore [reportFunctionMemberAccess] - parameters=[ - inspect.Parameter( - f"ev_{i}", - kind=inspect.Parameter.POSITIONAL_OR_KEYWORD, - annotation=Var[event_type], - ) - for i, event_type in enumerate(event_types) - ], - return_annotation=return_annotation, - ) - for i, event_type in enumerate(event_types): - inner.__annotations__[f"ev_{i}"] = Var[event_type] - inner.__annotations__["return"] = return_annotation - - return inner - - -@dataclasses.dataclass( - init=True, - frozen=True, -) -class FileUpload: - """Class to represent a file upload.""" - - upload_id: str | None = None - on_upload_progress: EventHandler | Callable | None = None - extra_headers: dict[str, str] | None = None - - @staticmethod - def on_upload_progress_args_spec(_prog: Var[dict[str, int | float | bool]]): - """Args spec for on_upload_progress event handler. - - Returns: - The arg mapping passed to backend event handler - """ - return [_prog] - - def _as_event_spec( - self, - handler: EventHandler, - *, - client_handler_name: str, - upload_param_name: str, - ) -> EventSpec: - """Create an upload EventSpec. - - Args: - handler: The event handler. - client_handler_name: The client handler name. - upload_param_name: The upload argument name in the event handler. - - Returns: - The upload EventSpec. - - Raises: - ValueError: If the on_upload_progress is not a valid event handler. - """ - from reflex_components_core.core.upload import ( - DEFAULT_UPLOAD_ID, - upload_files_context_var_data, - ) - - upload_id = self.upload_id if self.upload_id is not None else DEFAULT_UPLOAD_ID - upload_files_var = Var( - _js_expr="filesById", - _var_type=dict[str, Any], - _var_data=VarData.merge(upload_files_context_var_data), - ).to(ObjectVar)[LiteralVar.create(upload_id)] - spec_args = [ - ( - Var(_js_expr="files"), - upload_files_var, - ), - ( - Var(_js_expr="upload_param_name"), - LiteralVar.create(upload_param_name), - ), - ( - Var(_js_expr="upload_id"), - LiteralVar.create(upload_id), - ), - ( - Var(_js_expr="extra_headers"), - LiteralVar.create( - self.extra_headers if self.extra_headers is not None else {} - ), - ), - ] - if upload_param_name != "files": - spec_args.insert( - 1, - ( - Var(_js_expr=upload_param_name), - upload_files_var, - ), - ) - if self.on_upload_progress is not None: - on_upload_progress = self.on_upload_progress - if isinstance(on_upload_progress, EventHandler): - events = [ - call_event_handler( - on_upload_progress, - self.on_upload_progress_args_spec, - ), - ] - elif isinstance(on_upload_progress, Callable): - # Call the lambda to get the event chain. - events = call_event_fn( - on_upload_progress, self.on_upload_progress_args_spec - ) - else: - msg = f"{on_upload_progress} is not a valid event handler." - raise ValueError(msg) - if isinstance(events, Var): - msg = f"{on_upload_progress} cannot return a var {events}." - raise ValueError(msg) - on_upload_progress_chain = EventChain( - events=[*events], - args_spec=self.on_upload_progress_args_spec, - ) - formatted_chain = str(format.format_prop(on_upload_progress_chain)) - spec_args.append( - ( - Var(_js_expr="on_upload_progress"), - FunctionStringVar( - formatted_chain.strip("{}"), - ).to(FunctionVar, EventChain), - ), - ) - return EventSpec( - handler=handler, - client_handler_name=client_handler_name, - args=tuple(spec_args), - event_actions=handler.event_actions.copy(), - ) - - def as_event_spec(self, handler: EventHandler) -> EventSpec: - """Get the EventSpec for the file upload. - - Args: - handler: The event handler. - - Returns: - The event spec for the handler. - """ - from reflex.utils.exceptions import UploadValueError - - try: - upload_param_name, _annotation = resolve_upload_handler_param(handler) - except UploadValueError: - upload_param_name = "files" - return self._as_event_spec( - handler, - client_handler_name=UPLOAD_FILES_CLIENT_HANDLER, - upload_param_name=upload_param_name, - ) - - -# Alias for rx.upload_files -upload_files = FileUpload - - -@dataclasses.dataclass( - init=True, - frozen=True, -) -class UploadFilesChunk(FileUpload): - """Class to represent a streaming file upload.""" - - def as_event_spec(self, handler: EventHandler) -> EventSpec: - """Get the EventSpec for the streaming file upload. - - Args: - handler: The event handler. - - Returns: - The event spec for the handler. - """ - upload_param_name, _annotation = resolve_upload_chunk_handler_param(handler) - return self._as_event_spec( - handler, - client_handler_name=UPLOAD_FILES_CLIENT_HANDLER, - upload_param_name=upload_param_name, - ) - - -# Alias for rx.upload_files_chunk -upload_files_chunk = UploadFilesChunk - - -# Special server-side events. -def server_side(name: str, sig: inspect.Signature, **kwargs) -> EventSpec: - """A server-side event. - - Args: - name: The name of the event. - sig: The function signature of the event. - **kwargs: The arguments to pass to the event. - - Returns: - An event spec for a server-side event. - """ - - def fn(): - return None - - fn.__qualname__ = name - fn.__signature__ = sig # pyright: ignore [reportFunctionMemberAccess] - return EventSpec( - handler=EventHandler(fn=fn, state_full_name=FRONTEND_EVENT_STATE), - args=tuple( - ( - Var(_js_expr=k), - LiteralVar.create(v), - ) - for k, v in kwargs.items() - ), - ) - - -@overload -def redirect( - path: str | Var[str], - *, - is_external: Literal[False] = False, - replace: bool = False, -) -> EventSpec: ... - - -@overload -def redirect( - path: str | Var[str], - *, - is_external: Literal[True], - popup: bool = False, -) -> EventSpec: ... - - -def redirect( - path: str | Var[str], - *, - is_external: bool = False, - popup: bool = False, - replace: bool = False, -) -> EventSpec: - """Redirect to a new path. - - Args: - path: The path to redirect to. - is_external: Whether to open in new tab or not. - popup: Whether to open in a new window or not. - replace: If True, the current page will not create a new history entry. - - Returns: - An event to redirect to the path. - """ - return server_side( - "_redirect", - get_fn_signature(redirect), - path=path, - external=is_external, - popup=popup, - replace=replace, - ) - - -def console_log(message: str | Var[str]) -> EventSpec: - """Do a console.log on the browser. - - Args: - message: The message to log. - - Returns: - An event to log the message. - """ - return run_script(Var("console").to(dict).log.to(FunctionVar).call(message)) - - -@once -def noop() -> EventSpec: - """Do nothing. - - Returns: - An event to do nothing. - """ - return run_script(Var.create(None)) - - -def back() -> EventSpec: - """Do a history.back on the browser. - - Returns: - An event to go back one page. - """ - return run_script( - Var("window").to(dict).history.to(dict).back.to(FunctionVar).call() - ) - - -def window_alert(message: str | Var[str]) -> EventSpec: - """Create a window alert on the browser. - - Args: - message: The message to alert. - - Returns: - An event to alert the message. - """ - return run_script(Var("window").to(dict).alert.to(FunctionVar).call(message)) - - -def set_focus(ref: str) -> EventSpec: - """Set focus to specified ref. - - Args: - ref: The ref. - - Returns: - An event to set focus on the ref - """ - return server_side( - "_set_focus", - get_fn_signature(set_focus), - ref=LiteralVar.create(format.format_ref(ref)), - ) - - -def blur_focus(ref: str) -> EventSpec: - """Blur focus of specified ref. - - Args: - ref: The ref. - - Returns: - An event to blur focus on the ref - """ - return server_side( - "_blur_focus", - get_fn_signature(blur_focus), - ref=LiteralVar.create(format.format_ref(ref)), - ) - - -def scroll_to(elem_id: str, align_to_top: bool | Var[bool] = True) -> EventSpec: - """Select the id of a html element for scrolling into view. - - Args: - elem_id: The id of the element to scroll to. - align_to_top: Whether to scroll to the top (True) or bottom (False) of the element. - - Returns: - An EventSpec to scroll the page to the selected element. - """ - get_element_by_id = FunctionStringVar.create("document.getElementById") - - return run_script( - get_element_by_id - .call(elem_id) - .to(ObjectVar) - .scrollIntoView.to(FunctionVar) - .call(align_to_top), - ) - - -def set_value(ref: str, value: Any) -> EventSpec: - """Set the value of a ref. - - Args: - ref: The ref. - value: The value to set. - - Returns: - An event to set the ref. - """ - return server_side( - "_set_value", - get_fn_signature(set_value), - ref=LiteralVar.create(format.format_ref(ref)), - value=value, - ) - - -def remove_cookie(key: str, options: dict[str, Any] | None = None) -> EventSpec: - """Remove a cookie on the frontend. - - Args: - key: The key identifying the cookie to be removed. - options: Support all the cookie options from RFC 6265 - - Returns: - EventSpec: An event to remove a cookie. - """ - options = options or {} - options["path"] = options.get("path", "/") - return server_side( - "_remove_cookie", - get_fn_signature(remove_cookie), - key=key, - options=options, - ) - - -def clear_local_storage() -> EventSpec: - """Set a value in the local storage on the frontend. - - Returns: - EventSpec: An event to clear the local storage. - """ - return server_side( - "_clear_local_storage", - get_fn_signature(clear_local_storage), - ) - - -def remove_local_storage(key: str) -> EventSpec: - """Set a value in the local storage on the frontend. - - Args: - key: The key identifying the variable in the local storage to remove. - - Returns: - EventSpec: An event to remove an item based on the provided key in local storage. - """ - return server_side( - "_remove_local_storage", - get_fn_signature(remove_local_storage), - key=key, - ) - - -def clear_session_storage() -> EventSpec: - """Set a value in the session storage on the frontend. - - Returns: - EventSpec: An event to clear the session storage. - """ - return server_side( - "_clear_session_storage", - get_fn_signature(clear_session_storage), - ) - - -def remove_session_storage(key: str) -> EventSpec: - """Set a value in the session storage on the frontend. - - Args: - key: The key identifying the variable in the session storage to remove. - - Returns: - EventSpec: An event to remove an item based on the provided key in session storage. - """ - return server_side( - "_remove_session_storage", - get_fn_signature(remove_session_storage), - key=key, - ) - - -def set_clipboard(content: str | Var[str]) -> EventSpec: - """Set the text in content in the clipboard. - - Args: - content: The text to add to clipboard. - - Returns: - EventSpec: An event to set some content in the clipboard. - """ - return run_script( - Var("navigator") - .to(dict) - .clipboard.to(dict) - .writeText.to(FunctionVar) - .call(content) - ) - - -def download( - url: str | Var | None = None, - filename: str | Var | None = None, - data: str | bytes | Var | None = None, - mime_type: str | Var | None = None, -) -> EventSpec: - """Download the file at a given path or with the specified data. - - Args: - url: The URL to the file to download. - filename: The name that the file should be saved as after download. - data: The data to download. - mime_type: The mime type of the data to download. - - Returns: - EventSpec: An event to download the associated file. - - Raises: - ValueError: If the URL provided is invalid, both URL and data are provided, - or the data is not an expected type. - """ - from reflex_components_core.core.cond import cond - - if isinstance(url, str): - if not url.startswith("/"): - msg = "The URL argument should start with a /" - raise ValueError(msg) - - # if filename is not provided, infer it from url - if filename is None: - filename = url.rpartition("/")[-1] - - if filename is None: - filename = "" - - if data is not None: - if url is not None: - msg = "Cannot provide both URL and data to download." - raise ValueError(msg) - - if isinstance(data, str): - if mime_type is None: - mime_type = "text/plain" - # Caller provided a plain text string to download. - url = f"data:{mime_type};base64," + b64encode(data.encode("utf-8")).decode( - "utf-8" - ) - elif isinstance(data, Var): - if mime_type is None: - mime_type = "text/plain" - # Need to check on the frontend if the Var already looks like a data: URI. - - is_data_url = (data.js_type() == "string") & ( - data.to(str).startswith("data:") - ) - - # If it's a data: URI, use it as is, otherwise convert the Var to JSON in a data: URI. - url = cond( - is_data_url, - data.to(str), - f"data:{mime_type}," + data.to_string(), - ) - elif isinstance(data, bytes): - if mime_type is None: - mime_type = "application/octet-stream" - # Caller provided bytes, so base64 encode it as a data: URI. - b64_data = b64encode(data).decode("utf-8") - url = f"data:{mime_type};base64," + b64_data - else: - msg = f"Invalid data type {type(data)} for download. Use `str` or `bytes`." - raise ValueError(msg) - - return server_side( - "_download", - get_fn_signature(download), - url=url, - filename=filename, - ) - - -def call_script( - javascript_code: str | Var[str], - callback: EventType[Any] | None = None, -) -> EventSpec: - """Create an event handler that executes arbitrary javascript code. - - Args: - javascript_code: The code to execute. - callback: EventHandler that will receive the result of evaluating the javascript code. - - Returns: - EventSpec: An event that will execute the client side javascript. - """ - callback_kwargs = {"callback": None} - if callback is not None: - callback_kwargs = { - "callback": str( - format.format_queue_events( - callback, - args_spec=lambda result: [result], - ) - ), - } - if isinstance(javascript_code, str): - # When there is VarData, include it and eval the JS code inline on the client. - javascript_code, original_code = ( - LiteralVar.create(javascript_code), - javascript_code, - ) - if not javascript_code._get_all_var_data(): - # Without VarData, cast to string and eval the code in the event loop. - javascript_code = str(Var(_js_expr=original_code)) - - return server_side( - "_call_script", - get_fn_signature(call_script), - javascript_code=javascript_code, - **callback_kwargs, - ) - - -def call_function( - javascript_code: str | Var, - callback: EventType[Any] | None = None, -) -> EventSpec: - """Create an event handler that executes arbitrary javascript code. - - Args: - javascript_code: The code to execute. - callback: EventHandler that will receive the result of evaluating the javascript code. - - Returns: - EventSpec: An event that will execute the client side javascript. - """ - callback_kwargs = {"callback": None} - if callback is not None: - callback_kwargs = { - "callback": str( - format.format_queue_events( - callback, - args_spec=lambda result: [result], - ), - ) - } - - javascript_code = ( - Var(javascript_code) if isinstance(javascript_code, str) else javascript_code - ) - - return server_side( - "_call_function", - get_fn_signature(call_function), - function=javascript_code, - **callback_kwargs, - ) - - -def run_script( - javascript_code: str | Var, - callback: EventType[Any] | None = None, -) -> EventSpec: - """Create an event handler that executes arbitrary javascript code. - - Args: - javascript_code: The code to execute. - callback: EventHandler that will receive the result of evaluating the javascript code. - - Returns: - EventSpec: An event that will execute the client side javascript. - """ - javascript_code = ( - Var(javascript_code) if isinstance(javascript_code, str) else javascript_code - ) - - return call_function(ArgsFunctionOperation.create((), javascript_code), callback) - - -def get_event(state: BaseState, event: str): - """Get the event from the given state. - - Args: - state: The state. - event: The event. - - Returns: - The event. - """ - return f"{state.get_name()}.{event}" - - -def get_hydrate_event(state: BaseState) -> str: - """Get the name of the hydrate event for the state. - - Args: - state: The state. - - Returns: - The name of the hydrate event. - """ - return get_event(state, constants.CompileVars.HYDRATE) - - -def _values_returned_from_event(event_spec_annotations: list[Any]) -> list[Any]: - return [ - event_spec_return_type - for event_spec_annotation in event_spec_annotations - if (event_spec_return_type := event_spec_annotation.get("return")) is not None - and get_origin(event_spec_return_type) is tuple - ] - - -def _check_event_args_subclass_of_callback( - callback_params_names: list[str], - provided_event_types: list[Any], - callback_param_name_to_type: dict[str, Any], - callback_name: str = "", - key: str = "", -): - """Check if the event handler arguments are subclass of the callback. - - Args: - callback_params_names: The names of the callback parameters. - provided_event_types: The event types. - callback_param_name_to_type: The callback parameter name to type mapping. - callback_name: The name of the callback. - key: The key. - - Raises: - TypeError: If the event handler arguments are invalid. - EventHandlerArgTypeMismatchError: If the event handler arguments do not match the callback. - - # noqa: DAR401 delayed_exceptions[] - # noqa: DAR402 EventHandlerArgTypeMismatchError - """ - from reflex.utils import console - - type_match_found: dict[str, bool] = {} - delayed_exceptions: list[EventHandlerArgTypeMismatchError] = [] - - for event_spec_index, event_spec_return_type in enumerate(provided_event_types): - args = get_args(event_spec_return_type) - - args_types_without_vars = [ - arg if get_origin(arg) is not Var else get_args(arg)[0] for arg in args - ] - - # check that args of event handler are matching the spec if type hints are provided - for i, arg in enumerate(callback_params_names[: len(args_types_without_vars)]): - if arg not in callback_param_name_to_type: - continue - - type_match_found.setdefault(arg, False) - - try: - compare_result = typehint_issubclass( - args_types_without_vars[i], callback_param_name_to_type[arg] - ) - except TypeError as te: - callback_name_context = f" of {callback_name}" if callback_name else "" - key_context = f" for {key}" if key else "" - msg = f"Could not compare types {args_types_without_vars[i]} and {callback_param_name_to_type[arg]} for argument {arg}{callback_name_context}{key_context}." - raise TypeError(msg) from te - - if compare_result: - type_match_found[arg] = True - continue - type_match_found[arg] = False - as_annotated_in = ( - f" as annotated in {callback_name}" if callback_name else "" - ) - delayed_exceptions.append( - EventHandlerArgTypeMismatchError( - f"Event handler {key} expects {args_types_without_vars[i]} for argument {arg} but got {callback_param_name_to_type[arg]}{as_annotated_in} instead." - ) - ) - - if all(type_match_found.values()): - delayed_exceptions.clear() - if event_spec_index: - args = get_args(provided_event_types[0]) - - args_types_without_vars = [ - arg if get_origin(arg) is not Var else get_args(arg)[0] - for arg in args - ] - - expect_string = ", ".join( - repr(arg) for arg in args_types_without_vars - ).replace("[", "\\[") - - given_string = ", ".join( - repr(callback_param_name_to_type.get(arg, Any)) - for arg in callback_params_names - ).replace("[", "\\[") - - as_annotated_in = ( - f" as annotated in {callback_name}" if callback_name else "" - ) - - console.warn( - f"Event handler {key} expects ({expect_string}) -> () but got ({given_string}) -> (){as_annotated_in} instead. " - f"This may lead to unexpected behavior but is intentionally ignored for {key}." - ) - break - - if delayed_exceptions: - raise delayed_exceptions[0] - - -def call_event_handler( - event_callback: EventHandler | EventSpec, - event_spec: ArgsSpec | Sequence[ArgsSpec], - key: str | None = None, -) -> EventSpec: - """Call an event handler to get the event spec. - - This function will inspect the function signature of the event handler. - If it takes in an arg, the arg will be passed to the event handler. - Otherwise, the event handler will be called with no args. - - Args: - event_callback: The event handler. - event_spec: The lambda that define the argument(s) to pass to the event handler. - key: The key to pass to the event handler. - - Returns: - The event spec from calling the event handler. - """ - event_spec_args, event_annotations = parse_args_spec(event_spec) - - event_spec_return_types = _values_returned_from_event(event_annotations) - - if isinstance(event_callback, EventSpec): - parameters = event_callback.handler._parameters - - check_fn_match_arg_spec( - event_callback.handler.fn, - parameters, - event_spec_args, - key, - bool(event_callback.handler.state_full_name) + len(event_callback.args), - event_callback.handler.fn.__qualname__, - ) - - event_callback_spec_args = list(parameters) - - try: - type_hints_of_provided_callback = get_type_hints(event_callback.handler.fn) - except NameError: - type_hints_of_provided_callback = {} - - argument_names = [str(arg) for arg, value in event_callback.args] - - _check_event_args_subclass_of_callback( - [ - arg - for arg in event_callback_spec_args[ - bool(event_callback.handler.state_full_name) : - ] - if arg not in argument_names - ], - event_spec_return_types, - type_hints_of_provided_callback, - event_callback.handler.fn.__qualname__, - key or "", - ) - - # Handle partial application of EventSpec args - return event_callback.add_args(*event_spec_args) - - parameters = event_callback._parameters - - check_fn_match_arg_spec( - event_callback.fn, - parameters, - event_spec_args, - key, - bool(event_callback.state_full_name), - event_callback.fn.__qualname__, - ) - - if event_spec_return_types: - event_callback_spec_args = list(parameters) - - try: - type_hints_of_provided_callback = get_type_hints(event_callback.fn) - except NameError: - type_hints_of_provided_callback = {} - - _check_event_args_subclass_of_callback( - event_callback_spec_args[1:], - event_spec_return_types, - type_hints_of_provided_callback, - event_callback.fn.__qualname__, - key or "", - ) - - return event_callback(*event_spec_args) - - -def unwrap_var_annotation(annotation: GenericType): - """Unwrap a Var annotation or return it as is if it's not Var[X]. - - Args: - annotation: The annotation to unwrap. - - Returns: - The unwrapped annotation. - """ - if get_origin(annotation) in (Var, ObjectVar) and (args := get_args(annotation)): - return args[0] - return annotation - - -def resolve_annotation(annotations: dict[str, Any], arg_name: str, spec: ArgsSpec): - """Resolve the annotation for the given argument name. - - Args: - annotations: The annotations. - arg_name: The argument name. - spec: The specs which the annotations come from. - - Returns: - The resolved annotation. - - Raises: - MissingAnnotationError: If the annotation is missing for non-lambda methods. - """ - annotation = annotations.get(arg_name) - if annotation is None: - if not isinstance(spec, types.LambdaType): - raise MissingAnnotationError(var_name=arg_name) - return dict[str, dict] - return annotation - - -@lru_cache -def parse_args_spec(arg_spec: ArgsSpec | Sequence[ArgsSpec]): - """Parse the args provided in the ArgsSpec of an event trigger. - - Args: - arg_spec: The spec of the args. - - Returns: - The parsed args. - """ - # if there's multiple, the first is the default - if isinstance(arg_spec, Sequence): - annotations = [get_type_hints(one_arg_spec) for one_arg_spec in arg_spec] - arg_spec = arg_spec[0] - else: - annotations = [get_type_hints(arg_spec)] - - spec = inspect.getfullargspec(arg_spec) - - return list( - arg_spec(*[ - Var(f"_{l_arg}").to( - unwrap_var_annotation( - resolve_annotation(annotations[0], l_arg, spec=arg_spec) - ) - ) - for l_arg in spec.args - ]) - ), annotations - - -def args_specs_from_fields( - fields_dict: Mapping[str, BaseField], -) -> dict[str, ArgsSpec | Sequence[ArgsSpec]]: - """Get the event triggers and arg specs from the given fields. - - Args: - fields_dict: The fields, keyed by name - - Returns: - The args spec for any field annotated as EventHandler. - """ - return { - name: ( - metadata[0] - if ( - (metadata := getattr(field.annotated_type, "__metadata__", None)) - is not None - ) - else no_args_event_spec - ) - for name, field in fields_dict.items() - if field.type_origin is EventHandler - } - - -def check_fn_match_arg_spec( - user_func: Callable, - user_func_parameters: Mapping[str, inspect.Parameter], - event_spec_args: Sequence[Var], - key: str | None = None, - number_of_bound_args: int = 0, - func_name: str | None = None, -): - """Ensures that the function signature matches the passed argument specification - or raises an EventFnArgMismatchError if they do not. - - Args: - user_func: The function to be validated. - user_func_parameters: The parameters of the function to be validated. - event_spec_args: The argument specification for the event trigger. - key: The key of the event trigger. - number_of_bound_args: The number of bound arguments to the function. - func_name: The name of the function to be validated. - - Raises: - EventFnArgMismatchError: Raised if the number of mandatory arguments do not match - """ - user_args = list(user_func_parameters) - # Drop the first argument if it's a bound method - if inspect.ismethod(user_func) and user_func.__self__ is not None: - user_args = user_args[1:] - - user_default_args = [ - p.default - for p in user_func_parameters.values() - if p.default is not inspect.Parameter.empty - ] - number_of_user_args = len(user_args) - number_of_bound_args - number_of_user_default_args = len(user_default_args) if user_default_args else 0 - - number_of_event_args = len(event_spec_args) - - if number_of_user_args - number_of_user_default_args > number_of_event_args: - msg = ( - f"Event {key} only provides {number_of_event_args} arguments, but " - f"{func_name or user_func} requires at least {number_of_user_args - number_of_user_default_args} " - "arguments to be passed to the event handler.\n" - "See https://reflex.dev/docs/events/event-arguments/" - ) - raise EventFnArgMismatchError(msg) - - -def call_event_fn( - fn: Callable, - arg_spec: ArgsSpec | Sequence[ArgsSpec], - key: str | None = None, -) -> list[EventSpec | FunctionVar | EventVar]: - """Call a function to a list of event specs. - - The function should return a single event-like value or a heterogeneous - sequence of event-like values. - - Args: - fn: The function to call. - arg_spec: The argument spec for the event trigger. - key: The key to pass to the event handler. - - Returns: - The event-like values from calling the function. - - Raises: - EventHandlerValueError: If the lambda returns an unusable value. - """ - # Import here to avoid circular imports. - from reflex.event import EventHandler, EventSpec - from reflex.utils.exceptions import EventHandlerValueError - - parsed_args, _ = parse_args_spec(arg_spec) - - parameters = inspect.signature(fn).parameters - - # Check that fn signature matches arg_spec - check_fn_match_arg_spec(fn, parameters, parsed_args, key=key) - - number_of_fn_args = len(parameters) - - # Call the function with the parsed args. - out = fn(*[*parsed_args][:number_of_fn_args]) - - # Normalize common heterogeneous event collections into individual events - # while keeping other scalar values for validation below. - out = list(out) if isinstance(out, (list, tuple)) else [out] - - # Convert any event specs to event specs. - events = [] - for e in out: - if isinstance(e, EventHandler): - # An un-called EventHandler gets all of the args of the event trigger. - e = call_event_handler(e, arg_spec, key=key) - - if isinstance(e, EventChain): - # Nested EventChain is treated like a FunctionVar. - e = Var.create(e) - - # Make sure the event spec is valid. - if not isinstance(e, (EventSpec, FunctionVar, EventVar)): - hint = "" - if isinstance(e, VarOperationCall): - hint = " Hint: use `fn.partial(...)` instead of calling the FunctionVar directly." - msg = ( - f"Invalid event chain for {key}: {fn} -> {e}: A lambda inside an EventChain " - "list must return `EventSpec | EventHandler | EventChain | EventVar | FunctionVar` " - "or a heterogeneous sequence of these types. " - f"Got: {type(e)}.{hint}" - ) - raise EventHandlerValueError(msg) - - # Add the event spec to the chain. - events.append(e) - - # Return the events. - return events - - -def get_handler_args( - event_spec: EventSpec, -) -> tuple[tuple[Var, Var], ...]: - """Get the handler args for the given event spec. - - Args: - event_spec: The event spec. - - Returns: - The handler args. - """ - args = event_spec.handler._parameters - - return event_spec.args if len(args) > 1 else () - - -def fix_events( - events: list[EventSpec | EventHandler] | None, - token: str, - router_data: dict[str, Any] | None = None, -) -> list[Event]: - """Fix a list of events returned by an event handler. - - Args: - events: The events to fix. - token: The user token. - router_data: The optional router data to set in the event. - - Returns: - The fixed events. - - Raises: - ValueError: If the event type is not what was expected. - """ - # If the event handler returns nothing, return an empty list. - if events is None: - return [] - - # If the handler returns a single event, wrap it in a list. - if not isinstance(events, list): - events = [events] - - # Fix the events created by the handler. - out = [] - for e in events: - if callable(e) and getattr(e, "__name__", "") == "": - # A lambda was returned, assume the user wants to call it with no args. - e = e() - if isinstance(e, Event): - # If the event is already an event, append it to the list. - out.append(e) - continue - if not isinstance(e, (EventHandler, EventSpec)): - e = EventHandler(fn=e) - # Otherwise, create an event from the event spec. - if isinstance(e, EventHandler): - e = e() - if not isinstance(e, EventSpec): - msg = f"Unexpected event type, {type(e)}." - raise ValueError(msg) - name = format.format_event_handler(e.handler) - payload = {k._js_expr: v._decode() for k, v in e.args} - - # Filter router_data to reduce payload size - event_router_data = { - k: v - for k, v in (router_data or {}).items() - if k in constants.route.ROUTER_DATA_INCLUDE - } - # Create an event and append it to the list. - out.append( - Event( - token=token, - name=name, - payload=payload, - router_data=event_router_data, - ) - ) - - return out - - -def get_fn_signature(fn: Callable) -> inspect.Signature: - """Get the signature of a function. - - Args: - fn: The function. - - Returns: - The signature of the function. - """ - signature = inspect.signature(fn) - new_param = inspect.Parameter( - FRONTEND_EVENT_STATE, inspect.Parameter.POSITIONAL_OR_KEYWORD, annotation=Any - ) - return signature.replace(parameters=(new_param, *signature.parameters.values())) - - -# These chains can be used for their side effects when no other events are desired. -stop_propagation = noop().stop_propagation -prevent_default = noop().prevent_default - - -class EventVar(ObjectVar, python_types=(EventSpec, EventHandler)): - """Base class for event vars.""" - - def bool(self) -> NoReturn: - """Get the boolean value of the var. - - Raises: - TypeError: EventVar cannot be converted to a boolean. - """ - msg = f"Cannot convert {self._js_expr} of type {type(self).__name__} to bool." - raise TypeError(msg) - - -@dataclasses.dataclass( - eq=False, - frozen=True, - slots=True, -) -class LiteralEventVar(VarOperationCall, LiteralVar, EventVar): - """A literal event var.""" - - _var_value: EventSpec = dataclasses.field(default=None) # pyright: ignore [reportAssignmentType] - - def __hash__(self) -> int: - """Get the hash of the var. - - Returns: - The hash of the var. - """ - return hash((type(self).__name__, self._js_expr)) - - @classmethod - def create( - cls, - value: EventSpec | EventHandler, - _var_data: VarData | None = None, - ) -> LiteralEventVar: - """Create a new LiteralEventVar instance. - - Args: - value: The value of the var. - _var_data: The data of the var. - - Returns: - The created LiteralEventVar instance. - - Raises: - EventFnArgMismatchError: If the event handler takes arguments. - """ - if isinstance(value, EventHandler): - - def no_args(): - return () - - try: - value = call_event_handler(value, no_args) - except EventFnArgMismatchError: - msg = f"Event handler {value.fn.__qualname__} used inside of a rx.cond() must not take any arguments." - raise EventFnArgMismatchError(msg) from None - - return cls( - _js_expr="", - _var_type=EventSpec, - _var_data=_var_data, - _var_value=value, - _func=FunctionStringVar("ReflexEvent"), - _args=( - # event handler name - ".".join( - filter( - None, - format.get_event_handler_parts(value.handler), - ) - ), - # event handler args - {str(name): value for name, value in value.args}, - # event actions - value.event_actions, - # client handler name - *([value.client_handler_name] if value.client_handler_name else []), - ), - ) - - -class EventChainVar(BuilderFunctionVar, python_types=EventChain): - """Base class for event chain vars.""" - - def bool(self) -> NoReturn: - """Get the boolean value of the var. - - Raises: - TypeError: EventChainVar cannot be converted to a boolean. - """ - msg = f"Cannot convert {self._js_expr} of type {type(self).__name__} to bool." - raise TypeError(msg) - - -@dataclasses.dataclass( - eq=False, - frozen=True, - slots=True, -) -# Note: LiteralVar is second in the inheritance list allowing it act like a -# CachedVarOperation (ArgsFunctionOperation) and get the _js_expr from the -# _cached_var_name property. -class LiteralEventChainVar(ArgsFunctionOperationBuilder, LiteralVar, EventChainVar): - """A literal event chain var.""" - - _var_value: EventChain = dataclasses.field(default=None) # pyright: ignore [reportAssignmentType] - - def __hash__(self) -> int: - """Get the hash of the var. - - Returns: - The hash of the var. - """ - return hash((type(self).__name__, self._js_expr)) - - @classmethod - def create( - cls, - value: EventChain, - _var_data: VarData | None = None, - ) -> LiteralEventChainVar: - """Create a new LiteralEventChainVar instance. - - Args: - value: The value of the var. - _var_data: The data of the var. - - Returns: - The created LiteralEventChainVar instance. - - Raises: - ValueError: If the invocation is not a FunctionVar. - """ - arg_spec = ( - value.args_spec[0] - if isinstance(value.args_spec, Sequence) - else value.args_spec - ) - sig = inspect.signature(arg_spec) # pyright: ignore [reportArgumentType] - arg_vars = () - if sig.parameters: - arg_def = tuple(f"_{p}" for p in sig.parameters) - arg_vars = tuple(Var(_js_expr=arg) for arg in arg_def) - arg_def_expr = LiteralVar.create(list(arg_vars)) - else: - # add a default argument for addEvents if none were specified in value.args_spec - # used to trigger the preventDefault() on the event. - arg_def = ("...args",) - arg_def_expr = Var(_js_expr="args") - - if value.invocation is None: - invocation = FunctionStringVar.create( - CompileVars.ADD_EVENTS, - _var_data=VarData( - imports=Imports.EVENTS, - hooks={Hooks.EVENTS: None}, - ), - ) - else: - invocation = value.invocation - - if invocation is not None and not isinstance(invocation, FunctionVar): - msg = f"EventChain invocation must be a FunctionVar, got {invocation!s} of type {invocation._var_type!s}." - raise ValueError(msg) - assert invocation is not None - - call_args = arg_vars if sig.parameters else (Var(_js_expr="...args"),) - statements = [ - ( - event.call(*call_args) - if isinstance(event, FunctionVar) - else invocation.call( - LiteralVar.create([LiteralVar.create(event)]), - arg_def_expr, - _EMPTY_EVENT_ACTIONS, - ) - ) - for event in value.events - ] - - if not statements: - statements.append( - invocation.call( - _EMPTY_EVENTS, - arg_def_expr, - _EMPTY_EVENT_ACTIONS, - ) - ) - - if len(statements) == 1 and not value.event_actions: - return_expr = statements[0] - else: - statement_block = Var( - _js_expr=f"{{{''.join(f'{statement};' for statement in statements)}}}", - ) - if value.event_actions: - apply_event_actions = FunctionStringVar.create( - CompileVars.APPLY_EVENT_ACTIONS, - _var_data=VarData( - imports=Imports.EVENTS, - hooks={Hooks.EVENTS: None}, - ), - ) - return_expr = apply_event_actions.call( - ArgsFunctionOperation.create((), statement_block), - value.event_actions, - *call_args, - ) - else: - return_expr = statement_block - - return cls( - _js_expr="", - _var_type=EventChain, - _var_data=_var_data, - _args=FunctionArgs(arg_def), - _return_expr=return_expr, - _var_value=value, - ) - - -P = TypeVarTuple("P") -Q = TypeVarTuple("Q") -V = TypeVar("V") -V2 = TypeVar("V2") -V3 = TypeVar("V3") -V4 = TypeVar("V4") -V5 = TypeVar("V5") - - -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]): - """Initialize the descriptor with the function to be wrapped. - - Args: - func: The function to be wrapped. - """ - self.func = func - - @overload - def __call__( - self: EventCallback[Unpack[Q]], - ) -> EventCallback[Unpack[Q]]: ... - - @overload - def __call__( - self: EventCallback[V, Unpack[Q]], value: V | Var[V] - ) -> EventCallback[Unpack[Q]]: ... - - @overload - def __call__( - self: EventCallback[V, V2, Unpack[Q]], - value: V | Var[V], - value2: V2 | Var[V2], - ) -> EventCallback[Unpack[Q]]: ... - - @overload - def __call__( - self: EventCallback[V, V2, V3, Unpack[Q]], - value: V | Var[V], - value2: V2 | Var[V2], - value3: V3 | Var[V3], - ) -> EventCallback[Unpack[Q]]: ... - - @overload - def __call__( - self: EventCallback[V, V2, V3, V4, Unpack[Q]], - value: V | Var[V], - value2: V2 | Var[V2], - value3: V3 | Var[V3], - value4: V4 | Var[V4], - ) -> EventCallback[Unpack[Q]]: ... - - def __call__(self, *values) -> EventCallback: # pyright: ignore [reportInconsistentOverload] - """Call the function with the values. - - Args: - *values: The values to call the function with. - - Returns: - The function with the values. - """ - return self.func(*values) # pyright: ignore [reportArgumentType] - - @overload - def __get__( - self: EventCallback[Unpack[P]], instance: None, owner: Any - ) -> EventCallback[Unpack[P]]: ... - - @overload - def __get__(self, instance: Any, owner: Any) -> Callable[[Unpack[P]]]: ... - - def __get__(self, instance: Any, owner: Any) -> Callable: - """Get the function with the instance bound to it. - - Args: - instance: The instance to bind to the function. - owner: The owner of the function. - - Returns: - The function with the instance bound to it - """ - if instance is None: - return self.func - - return partial(self.func, instance) - - -class LambdaEventCallback(Protocol[Unpack[P]]): - """A protocol for a lambda event callback.""" - - __code__: types.CodeType - - @overload - def __call__(self: LambdaEventCallback[()]) -> Any: ... - - @overload - def __call__(self: LambdaEventCallback[V], value: Var[V], /) -> Any: ... - - @overload - def __call__( - self: LambdaEventCallback[V, V2], value: Var[V], value2: Var[V2], / - ) -> Any: ... - - @overload - def __call__( - self: LambdaEventCallback[V, V2, V3], - value: Var[V], - value2: Var[V2], - value3: Var[V3], - /, - ) -> Any: ... - - def __call__(self, *args: Var) -> Any: - """Call the lambda with the args. - - Args: - *args: The args to call the lambda with. - """ - - -ARGS = TypeVarTuple("ARGS") - - -LAMBDA_OR_STATE = TypeAliasType( - "LAMBDA_OR_STATE", - LambdaEventCallback[Unpack[ARGS]] | EventCallback[Unpack[ARGS]], - type_params=(ARGS,), -) - -ItemOrList = V | list[V] - -BASIC_EVENT_TYPES = TypeAliasType( - "BASIC_EVENT_TYPES", EventSpec | EventHandler | Var[Any], type_params=() -) - -IndividualEventType = TypeAliasType( - "IndividualEventType", - LAMBDA_OR_STATE[Unpack[ARGS]] | BASIC_EVENT_TYPES, - type_params=(ARGS,), -) - -EventType = TypeAliasType( - "EventType", - ItemOrList[LAMBDA_OR_STATE[Unpack[ARGS]] | BASIC_EVENT_TYPES], - type_params=(ARGS,), -) - - -if TYPE_CHECKING: - from reflex.state import BaseState - - BASE_STATE = TypeVar("BASE_STATE", bound=BaseState) -else: - BASE_STATE = TypeVar("BASE_STATE") - - -class EventNamespace: - """A namespace for event related classes.""" - - # Core Event Classes - Event = Event - EventActionsMixin = EventActionsMixin - EventHandler = EventHandler - EventSpec = EventSpec - CallableEventSpec = CallableEventSpec - EventChain = EventChain - EventVar = EventVar - LiteralEventVar = LiteralEventVar - EventChainVar = EventChainVar - LiteralEventChainVar = LiteralEventChainVar - EventCallback = EventCallback - LambdaEventCallback = LambdaEventCallback - - # Javascript Event Classes - JavascriptHTMLInputElement = JavascriptHTMLInputElement - JavascriptInputEvent = JavascriptInputEvent - JavascriptKeyboardEvent = JavascriptKeyboardEvent - JavascriptMouseEvent = JavascriptMouseEvent - JavascriptPointerEvent = JavascriptPointerEvent - - # Type Info Classes - KeyInputInfo = KeyInputInfo - MouseEventInfo = MouseEventInfo - PointerEventInfo = PointerEventInfo - IdentityEventReturn = IdentityEventReturn - - # File Upload - FileUpload = FileUpload - UploadFilesChunk = UploadFilesChunk - - # Type Aliases - EventType = EventType - LAMBDA_OR_STATE = LAMBDA_OR_STATE - BASIC_EVENT_TYPES = BASIC_EVENT_TYPES - IndividualEventType = IndividualEventType - - # Constants - BACKGROUND_TASK_MARKER = BACKGROUND_TASK_MARKER - EVENT_ACTIONS_MARKER = EVENT_ACTIONS_MARKER - _EVENT_FIELDS = _EVENT_FIELDS - FORM_DATA = FORM_DATA - upload_files = upload_files - upload_files_chunk = upload_files_chunk - stop_propagation = stop_propagation - prevent_default = prevent_default - - # Private/Internal Functions - resolve_upload_handler_param = staticmethod(resolve_upload_handler_param) - resolve_upload_chunk_handler_param = staticmethod( - resolve_upload_chunk_handler_param - ) - _values_returned_from_event = staticmethod(_values_returned_from_event) - _check_event_args_subclass_of_callback = staticmethod( - _check_event_args_subclass_of_callback - ) - - @overload - def __new__( - cls, - func: None = None, - *, - background: bool | None = None, - stop_propagation: bool | None = None, - prevent_default: bool | None = None, - throttle: int | None = None, - debounce: int | None = None, - temporal: bool | None = None, - ) -> Callable[ - [Callable[[BASE_STATE, Unpack[P]], Any]], EventCallback[Unpack[P]] # pyright: ignore [reportInvalidTypeVarUse] - ]: ... - - @overload - def __new__( - cls, - func: Callable[[BASE_STATE, Unpack[P]], Any], - *, - background: bool | None = None, - stop_propagation: bool | None = None, - prevent_default: bool | None = None, - throttle: int | None = None, - debounce: int | None = None, - temporal: bool | None = None, - ) -> EventCallback[Unpack[P]]: ... - - def __new__( - cls, - func: Callable[[BASE_STATE, Unpack[P]], Any] | None = None, - *, - background: bool | None = None, - stop_propagation: bool | None = None, - prevent_default: bool | None = None, - throttle: int | None = None, - debounce: int | None = None, - temporal: bool | None = None, - ) -> ( - EventCallback[Unpack[P]] - | Callable[[Callable[[BASE_STATE, Unpack[P]], Any]], EventCallback[Unpack[P]]] - ): - """Wrap a function to be used as an event. - - Args: - func: The function to wrap. - background: Whether the event should be run in the background. Defaults to False. - stop_propagation: Whether to stop the event from bubbling up the DOM tree. - prevent_default: Whether to prevent the default behavior of the event. - throttle: Throttle the event handler to limit calls (in milliseconds). - debounce: Debounce the event handler to delay calls (in milliseconds). - temporal: Whether the event should be dropped when the backend is down. - - Returns: - The wrapped function. - - Raises: - TypeError: If background is True and the function is not a coroutine or async generator. # noqa: DAR402 - """ - - def _build_event_actions(): - """Build event_actions dict from decorator parameters. - - Returns: - Dict of event actions to apply, or empty dict if none specified. - """ - if not any([ - stop_propagation, - prevent_default, - throttle, - debounce, - temporal, - ]): - return {} - - event_actions = {} - if stop_propagation is not None: - event_actions["stopPropagation"] = stop_propagation - if prevent_default is not None: - event_actions["preventDefault"] = prevent_default - if throttle is not None: - event_actions["throttle"] = throttle - if debounce is not None: - event_actions["debounce"] = debounce - if temporal is not None: - event_actions["temporal"] = temporal - return event_actions - - def wrapper( - func: Callable[[BASE_STATE, Unpack[P]], T], - ) -> EventCallback[Unpack[P]]: - if background is True: - if not inspect.iscoroutinefunction( - func - ) and not inspect.isasyncgenfunction(func): - msg = "Background task must be async function or generator." - raise TypeError(msg) - setattr(func, BACKGROUND_TASK_MARKER, True) - if getattr(func, "__name__", "").startswith("_"): - msg = "Event handlers cannot be private." - raise ValueError(msg) - - qualname: str | None = getattr(func, "__qualname__", None) - - if qualname and ( - len(func_path := qualname.split(".")) == 1 - or func_path[-2] == "" - ): - from reflex.state import BaseState - - types = get_type_hints(func) - state_arg_name = next(iter(inspect.signature(func).parameters), None) - state_cls = state_arg_name and types.get(state_arg_name) - if state_cls and issubclass(state_cls, BaseState): - name = ( - (func.__module__ + "." + qualname) - .replace(".", "_") - .replace("", "_") - .removeprefix("_") - ) - object.__setattr__(func, "__name__", name) - object.__setattr__(func, "__qualname__", name) - state_cls._add_event_handler(name, func) - event_callback = getattr(state_cls, name) - - # Apply decorator event actions - event_actions = _build_event_actions() - if event_actions: - # Create new EventCallback with updated event_actions - event_callback = dataclasses.replace( - event_callback, event_actions=event_actions - ) - - return event_callback - - # Store decorator event actions on the function for later processing - event_actions = _build_event_actions() - if event_actions: - setattr(func, EVENT_ACTIONS_MARKER, event_actions) - return func # pyright: ignore [reportReturnType] - - if func is not None: - return wrapper(func) - return wrapper - - get_event = staticmethod(get_event) - get_hydrate_event = staticmethod(get_hydrate_event) - fix_events = staticmethod(fix_events) - call_event_handler = staticmethod(call_event_handler) - call_event_fn = staticmethod(call_event_fn) - get_handler_args = staticmethod(get_handler_args) - check_fn_match_arg_spec = staticmethod(check_fn_match_arg_spec) - resolve_annotation = staticmethod(resolve_annotation) - parse_args_spec = staticmethod(parse_args_spec) - args_specs_from_fields = staticmethod(args_specs_from_fields) - unwrap_var_annotation = staticmethod(unwrap_var_annotation) - get_fn_signature = staticmethod(get_fn_signature) - - # Event Spec Functions - passthrough_event_spec = staticmethod(passthrough_event_spec) - input_event = staticmethod(input_event) - int_input_event = staticmethod(int_input_event) - float_input_event = staticmethod(float_input_event) - checked_input_event = staticmethod(checked_input_event) - key_event = staticmethod(key_event) - pointer_event_spec = staticmethod(pointer_event_spec) - no_args_event_spec = staticmethod(no_args_event_spec) - on_submit_event = staticmethod(on_submit_event) - on_submit_string_event = staticmethod(on_submit_string_event) - - # Server Side Events - server_side = staticmethod(server_side) - redirect = staticmethod(redirect) - console_log = staticmethod(console_log) - noop = staticmethod(noop) - back = staticmethod(back) - window_alert = staticmethod(window_alert) - set_focus = staticmethod(set_focus) - blur_focus = staticmethod(blur_focus) - scroll_to = staticmethod(scroll_to) - set_value = staticmethod(set_value) - remove_cookie = staticmethod(remove_cookie) - clear_local_storage = staticmethod(clear_local_storage) - remove_local_storage = staticmethod(remove_local_storage) - clear_session_storage = staticmethod(clear_session_storage) - remove_session_storage = staticmethod(remove_session_storage) - set_clipboard = staticmethod(set_clipboard) - download = staticmethod(download) - call_script = staticmethod(call_script) - call_function = staticmethod(call_function) - run_script = staticmethod(run_script) - __file__ = __file__ - - -event = EventNamespace -event.event = event # pyright: ignore[reportAttributeAccessIssue] -sys.modules[__name__] = event # pyright: ignore[reportArgumentType] +from reflex_core.event import * # noqa: F401, F403 From bd3b3b954ee4226d0fbb45fe96a16cf138625cc3 Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Thu, 26 Mar 2026 13:45:01 -0700 Subject: [PATCH 041/113] move more files into core --- .../core => reflex-core/src/reflex_core}/breakpoints.py | 0 .../reflex-core/src/reflex_core}/compiler/templates.py | 0 .../reflex-core/src/reflex_core}/components/dynamic.py | 0 .../reflex-core/src/reflex_core}/components/tags/__init__.py | 0 .../reflex-core/src/reflex_core}/components/tags/cond_tag.py | 0 .../reflex-core/src/reflex_core}/components/tags/iter_tag.py | 0 .../reflex-core/src/reflex_core}/components/tags/match_tag.py | 0 .../reflex-core/src/reflex_core}/components/tags/tag.py | 0 .../reflex-core/src/reflex_core}/components/tags/tagless.py | 0 {reflex => packages/reflex-core/src/reflex_core}/style.py | 0 10 files changed, 0 insertions(+), 0 deletions(-) rename packages/{reflex-components-core/src/reflex_components_core/core => reflex-core/src/reflex_core}/breakpoints.py (100%) rename {reflex => packages/reflex-core/src/reflex_core}/compiler/templates.py (100%) rename {reflex => packages/reflex-core/src/reflex_core}/components/dynamic.py (100%) rename {reflex => packages/reflex-core/src/reflex_core}/components/tags/__init__.py (100%) rename {reflex => packages/reflex-core/src/reflex_core}/components/tags/cond_tag.py (100%) rename {reflex => packages/reflex-core/src/reflex_core}/components/tags/iter_tag.py (100%) rename {reflex => packages/reflex-core/src/reflex_core}/components/tags/match_tag.py (100%) rename {reflex => packages/reflex-core/src/reflex_core}/components/tags/tag.py (100%) rename {reflex => packages/reflex-core/src/reflex_core}/components/tags/tagless.py (100%) rename {reflex => packages/reflex-core/src/reflex_core}/style.py (100%) diff --git a/packages/reflex-components-core/src/reflex_components_core/core/breakpoints.py b/packages/reflex-core/src/reflex_core/breakpoints.py similarity index 100% rename from packages/reflex-components-core/src/reflex_components_core/core/breakpoints.py rename to packages/reflex-core/src/reflex_core/breakpoints.py diff --git a/reflex/compiler/templates.py b/packages/reflex-core/src/reflex_core/compiler/templates.py similarity index 100% rename from reflex/compiler/templates.py rename to packages/reflex-core/src/reflex_core/compiler/templates.py diff --git a/reflex/components/dynamic.py b/packages/reflex-core/src/reflex_core/components/dynamic.py similarity index 100% rename from reflex/components/dynamic.py rename to packages/reflex-core/src/reflex_core/components/dynamic.py diff --git a/reflex/components/tags/__init__.py b/packages/reflex-core/src/reflex_core/components/tags/__init__.py similarity index 100% rename from reflex/components/tags/__init__.py rename to packages/reflex-core/src/reflex_core/components/tags/__init__.py diff --git a/reflex/components/tags/cond_tag.py b/packages/reflex-core/src/reflex_core/components/tags/cond_tag.py similarity index 100% rename from reflex/components/tags/cond_tag.py rename to packages/reflex-core/src/reflex_core/components/tags/cond_tag.py diff --git a/reflex/components/tags/iter_tag.py b/packages/reflex-core/src/reflex_core/components/tags/iter_tag.py similarity index 100% rename from reflex/components/tags/iter_tag.py rename to packages/reflex-core/src/reflex_core/components/tags/iter_tag.py diff --git a/reflex/components/tags/match_tag.py b/packages/reflex-core/src/reflex_core/components/tags/match_tag.py similarity index 100% rename from reflex/components/tags/match_tag.py rename to packages/reflex-core/src/reflex_core/components/tags/match_tag.py diff --git a/reflex/components/tags/tag.py b/packages/reflex-core/src/reflex_core/components/tags/tag.py similarity index 100% rename from reflex/components/tags/tag.py rename to packages/reflex-core/src/reflex_core/components/tags/tag.py diff --git a/reflex/components/tags/tagless.py b/packages/reflex-core/src/reflex_core/components/tags/tagless.py similarity index 100% rename from reflex/components/tags/tagless.py rename to packages/reflex-core/src/reflex_core/components/tags/tagless.py diff --git a/reflex/style.py b/packages/reflex-core/src/reflex_core/style.py similarity index 100% rename from reflex/style.py rename to packages/reflex-core/src/reflex_core/style.py From 8f64add38d44afd8b9b25cdfb08542d4cb2917d1 Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Thu, 26 Mar 2026 13:45:05 -0700 Subject: [PATCH 042/113] cleanups --- .../core/breakpoints.py | 3 +++ .../src/reflex_core/compiler/__init__.py | 1 + .../src/reflex_core/compiler/templates.py | 6 ++--- .../src/reflex_core/components/dynamic.py | 12 +++++----- .../reflex_core/components/tags/cond_tag.py | 2 +- .../reflex_core/components/tags/iter_tag.py | 8 +++---- .../reflex_core/components/tags/match_tag.py | 2 +- .../src/reflex_core/components/tags/tag.py | 6 ++--- .../reflex_core/components/tags/tagless.py | 4 ++-- packages/reflex-core/src/reflex_core/style.py | 24 +++++++++---------- reflex/compiler/templates.py | 3 +++ reflex/components/dynamic.py | 3 +++ reflex/components/tags/__init__.py | 3 +++ reflex/components/tags/cond_tag.py | 3 +++ reflex/components/tags/iter_tag.py | 3 +++ reflex/components/tags/match_tag.py | 3 +++ reflex/components/tags/tag.py | 3 +++ reflex/components/tags/tagless.py | 3 +++ reflex/style.py | 3 +++ 19 files changed, 63 insertions(+), 32 deletions(-) create mode 100644 packages/reflex-components-core/src/reflex_components_core/core/breakpoints.py create mode 100644 packages/reflex-core/src/reflex_core/compiler/__init__.py create mode 100644 reflex/compiler/templates.py create mode 100644 reflex/components/dynamic.py create mode 100644 reflex/components/tags/__init__.py create mode 100644 reflex/components/tags/cond_tag.py create mode 100644 reflex/components/tags/iter_tag.py create mode 100644 reflex/components/tags/match_tag.py create mode 100644 reflex/components/tags/tag.py create mode 100644 reflex/components/tags/tagless.py create mode 100644 reflex/style.py diff --git a/packages/reflex-components-core/src/reflex_components_core/core/breakpoints.py b/packages/reflex-components-core/src/reflex_components_core/core/breakpoints.py new file mode 100644 index 00000000000..c3418265649 --- /dev/null +++ b/packages/reflex-components-core/src/reflex_components_core/core/breakpoints.py @@ -0,0 +1,3 @@ +"""Re-export from reflex_core.""" + +from reflex_core.breakpoints import * # noqa: F401, F403 diff --git a/packages/reflex-core/src/reflex_core/compiler/__init__.py b/packages/reflex-core/src/reflex_core/compiler/__init__.py new file mode 100644 index 00000000000..0002dd54831 --- /dev/null +++ b/packages/reflex-core/src/reflex_core/compiler/__init__.py @@ -0,0 +1 @@ +"""Reflex core compiler.""" diff --git a/packages/reflex-core/src/reflex_core/compiler/templates.py b/packages/reflex-core/src/reflex_core/compiler/templates.py index 1bf6c8ce061..4c4f4bc7c50 100644 --- a/packages/reflex-core/src/reflex_core/compiler/templates.py +++ b/packages/reflex-core/src/reflex_core/compiler/templates.py @@ -7,9 +7,9 @@ from typing import TYPE_CHECKING, Any, Literal from reflex import constants -from reflex.constants import Hooks -from reflex.utils.format import format_state_name, json_dumps -from reflex.vars.base import VarData +from reflex_core.constants import Hooks +from reflex_core.utils.format import format_state_name, json_dumps +from reflex_core.vars.base import VarData if TYPE_CHECKING: from reflex.compiler.utils import _ImportDict diff --git a/packages/reflex-core/src/reflex_core/components/dynamic.py b/packages/reflex-core/src/reflex_core/components/dynamic.py index c6d4ff9ad97..020415b2fdb 100644 --- a/packages/reflex-core/src/reflex_core/components/dynamic.py +++ b/packages/reflex-core/src/reflex_core/components/dynamic.py @@ -3,12 +3,12 @@ from typing import TYPE_CHECKING, Union from reflex import constants -from reflex.utils import imports -from reflex.utils.exceptions import DynamicComponentMissingLibraryError -from reflex.utils.format import format_library_name -from reflex.utils.serializers import serializer -from reflex.vars import Var, get_unique_variable_name -from reflex.vars.base import VarData, transform +from reflex_core.utils import imports +from reflex_core.utils.exceptions import DynamicComponentMissingLibraryError +from reflex_core.utils.format import format_library_name +from reflex_core.utils.serializers import serializer +from reflex_core.vars import Var, get_unique_variable_name +from reflex_core.vars.base import VarData, transform if TYPE_CHECKING: from reflex.components.component import Component diff --git a/packages/reflex-core/src/reflex_core/components/tags/cond_tag.py b/packages/reflex-core/src/reflex_core/components/tags/cond_tag.py index 297f1c0c461..b101871e9e3 100644 --- a/packages/reflex-core/src/reflex_core/components/tags/cond_tag.py +++ b/packages/reflex-core/src/reflex_core/components/tags/cond_tag.py @@ -4,7 +4,7 @@ from collections.abc import Iterator, Mapping from typing import Any -from reflex.components.tags.tag import Tag +from reflex_core.components.tags.tag import Tag @dataclasses.dataclass(frozen=True, kw_only=True) diff --git a/packages/reflex-core/src/reflex_core/components/tags/iter_tag.py b/packages/reflex-core/src/reflex_core/components/tags/iter_tag.py index d268636e74f..c395c42265e 100644 --- a/packages/reflex-core/src/reflex_core/components/tags/iter_tag.py +++ b/packages/reflex-core/src/reflex_core/components/tags/iter_tag.py @@ -7,10 +7,10 @@ from collections.abc import Callable, Iterable from typing import TYPE_CHECKING -from reflex.components.tags.tag import Tag -from reflex.utils.types import GenericType -from reflex.vars import LiteralArrayVar, Var, get_unique_variable_name -from reflex.vars.sequence import _determine_value_of_array_index +from reflex_core.components.tags.tag import Tag +from reflex_core.utils.types import GenericType +from reflex_core.vars import LiteralArrayVar, Var, get_unique_variable_name +from reflex_core.vars.sequence import _determine_value_of_array_index if TYPE_CHECKING: from reflex.components.component import Component diff --git a/packages/reflex-core/src/reflex_core/components/tags/match_tag.py b/packages/reflex-core/src/reflex_core/components/tags/match_tag.py index 723654fd487..3f49882ae1b 100644 --- a/packages/reflex-core/src/reflex_core/components/tags/match_tag.py +++ b/packages/reflex-core/src/reflex_core/components/tags/match_tag.py @@ -4,7 +4,7 @@ from collections.abc import Iterator, Mapping, Sequence from typing import Any -from reflex.components.tags.tag import Tag +from reflex_core.components.tags.tag import Tag @dataclasses.dataclass(frozen=True, kw_only=True) diff --git a/packages/reflex-core/src/reflex_core/components/tags/tag.py b/packages/reflex-core/src/reflex_core/components/tags/tag.py index f73de5860cf..0efab023f40 100644 --- a/packages/reflex-core/src/reflex_core/components/tags/tag.py +++ b/packages/reflex-core/src/reflex_core/components/tags/tag.py @@ -6,9 +6,9 @@ from collections.abc import Iterator, Mapping, Sequence from typing import Any -from reflex.event import EventChain -from reflex.utils import format -from reflex.vars.base import LiteralVar, Var +from reflex_core.event import EventChain +from reflex_core.utils import format +from reflex_core.vars.base import LiteralVar, Var def render_prop(value: Any) -> Any: diff --git a/packages/reflex-core/src/reflex_core/components/tags/tagless.py b/packages/reflex-core/src/reflex_core/components/tags/tagless.py index 3d8db827d88..43978f5eeb9 100644 --- a/packages/reflex-core/src/reflex_core/components/tags/tagless.py +++ b/packages/reflex-core/src/reflex_core/components/tags/tagless.py @@ -2,8 +2,8 @@ import dataclasses -from reflex.components.tags import Tag -from reflex.utils import format +from reflex_core.components.tags import Tag +from reflex_core.utils import format @dataclasses.dataclass(frozen=True, kw_only=True) diff --git a/packages/reflex-core/src/reflex_core/style.py b/packages/reflex-core/src/reflex_core/style.py index d25b059f181..0fac9029fba 100644 --- a/packages/reflex-core/src/reflex_core/style.py +++ b/packages/reflex-core/src/reflex_core/style.py @@ -5,18 +5,18 @@ from collections.abc import Mapping from typing import Any, Literal -from reflex_components_core.core.breakpoints import Breakpoints, breakpoints_values - -from reflex import constants -from reflex.event import EventChain, EventHandler, EventSpec, run_script -from reflex.utils import format -from reflex.utils.exceptions import ReflexError -from reflex.utils.imports import ImportVar -from reflex.utils.types import typehint_issubclass -from reflex.vars import VarData -from reflex.vars.base import LiteralVar, Var -from reflex.vars.function import FunctionVar -from reflex.vars.object import ObjectVar +from reflex_core.breakpoints import Breakpoints, breakpoints_values + +from reflex_core import constants +from reflex_core.event import EventChain, EventHandler, EventSpec, run_script +from reflex_core.utils import format +from reflex_core.utils.exceptions import ReflexError +from reflex_core.utils.imports import ImportVar +from reflex_core.utils.types import typehint_issubclass +from reflex_core.vars import VarData +from reflex_core.vars.base import LiteralVar, Var +from reflex_core.vars.function import FunctionVar +from reflex_core.vars.object import ObjectVar SYSTEM_COLOR_MODE: str = "system" LIGHT_COLOR_MODE: str = "light" diff --git a/reflex/compiler/templates.py b/reflex/compiler/templates.py new file mode 100644 index 00000000000..3ee59558de7 --- /dev/null +++ b/reflex/compiler/templates.py @@ -0,0 +1,3 @@ +"""Re-export from reflex_core.""" + +from reflex_core.compiler.templates import * # noqa: F401, F403 diff --git a/reflex/components/dynamic.py b/reflex/components/dynamic.py new file mode 100644 index 00000000000..3cc1f291d5f --- /dev/null +++ b/reflex/components/dynamic.py @@ -0,0 +1,3 @@ +"""Re-export from reflex_core.""" + +from reflex_core.components.dynamic import * # noqa: F401, F403 diff --git a/reflex/components/tags/__init__.py b/reflex/components/tags/__init__.py new file mode 100644 index 00000000000..aad7b918ccc --- /dev/null +++ b/reflex/components/tags/__init__.py @@ -0,0 +1,3 @@ +"""Re-export from reflex_core.""" + +from reflex_core.components.tags import * # noqa: F401, F403 diff --git a/reflex/components/tags/cond_tag.py b/reflex/components/tags/cond_tag.py new file mode 100644 index 00000000000..4217bcc58bc --- /dev/null +++ b/reflex/components/tags/cond_tag.py @@ -0,0 +1,3 @@ +"""Re-export from reflex_core.""" + +from reflex_core.components.tags.cond_tag import * # noqa: F401, F403 diff --git a/reflex/components/tags/iter_tag.py b/reflex/components/tags/iter_tag.py new file mode 100644 index 00000000000..5850484b989 --- /dev/null +++ b/reflex/components/tags/iter_tag.py @@ -0,0 +1,3 @@ +"""Re-export from reflex_core.""" + +from reflex_core.components.tags.iter_tag import * # noqa: F401, F403 diff --git a/reflex/components/tags/match_tag.py b/reflex/components/tags/match_tag.py new file mode 100644 index 00000000000..0d079d270fd --- /dev/null +++ b/reflex/components/tags/match_tag.py @@ -0,0 +1,3 @@ +"""Re-export from reflex_core.""" + +from reflex_core.components.tags.match_tag import * # noqa: F401, F403 diff --git a/reflex/components/tags/tag.py b/reflex/components/tags/tag.py new file mode 100644 index 00000000000..b2592212198 --- /dev/null +++ b/reflex/components/tags/tag.py @@ -0,0 +1,3 @@ +"""Re-export from reflex_core.""" + +from reflex_core.components.tags.tag import * # noqa: F401, F403 diff --git a/reflex/components/tags/tagless.py b/reflex/components/tags/tagless.py new file mode 100644 index 00000000000..6eaa8e85fee --- /dev/null +++ b/reflex/components/tags/tagless.py @@ -0,0 +1,3 @@ +"""Re-export from reflex_core.""" + +from reflex_core.components.tags.tagless import * # noqa: F401, F403 diff --git a/reflex/style.py b/reflex/style.py new file mode 100644 index 00000000000..6e7065e876f --- /dev/null +++ b/reflex/style.py @@ -0,0 +1,3 @@ +"""Re-export from reflex_core.""" + +from reflex_core.style import * # noqa: F401, F403 From 259e29a360c9c73d4cbb32bf9540071915032512 Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Thu, 26 Mar 2026 13:49:38 -0700 Subject: [PATCH 043/113] move component --- .../reflex-core/src/reflex_core}/components/component.py | 0 .../reflex-core/src/reflex_core}/constants/event.py | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename {reflex => packages/reflex-core/src/reflex_core}/components/component.py (100%) rename {reflex => packages/reflex-core/src/reflex_core}/constants/event.py (100%) diff --git a/reflex/components/component.py b/packages/reflex-core/src/reflex_core/components/component.py similarity index 100% rename from reflex/components/component.py rename to packages/reflex-core/src/reflex_core/components/component.py diff --git a/reflex/constants/event.py b/packages/reflex-core/src/reflex_core/constants/event.py similarity index 100% rename from reflex/constants/event.py rename to packages/reflex-core/src/reflex_core/constants/event.py From bec35efbb53580bb53e4d32c8a87ea6b7e699d84 Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Thu, 26 Mar 2026 13:50:16 -0700 Subject: [PATCH 044/113] update imports --- .../src/reflex_core/compiler/templates.py | 2 +- .../src/reflex_core/components/component.py | 44 ++++++++++--------- .../src/reflex_core/components/dynamic.py | 4 +- .../reflex_core/components/tags/iter_tag.py | 2 +- .../src/reflex_core/components/tags/tag.py | 2 +- .../src/reflex_core/constants/__init__.py | 2 + packages/reflex-core/src/reflex_core/style.py | 2 +- .../src/reflex_core/utils/format.py | 2 +- .../src/reflex_core/utils/pyi_generator.py | 2 +- .../reflex-core/src/reflex_core/vars/base.py | 2 +- reflex/components/component.py | 3 ++ reflex/constants/event.py | 3 ++ 12 files changed, 40 insertions(+), 30 deletions(-) create mode 100644 reflex/components/component.py create mode 100644 reflex/constants/event.py diff --git a/packages/reflex-core/src/reflex_core/compiler/templates.py b/packages/reflex-core/src/reflex_core/compiler/templates.py index 4c4f4bc7c50..c6956e97276 100644 --- a/packages/reflex-core/src/reflex_core/compiler/templates.py +++ b/packages/reflex-core/src/reflex_core/compiler/templates.py @@ -13,7 +13,7 @@ if TYPE_CHECKING: from reflex.compiler.utils import _ImportDict - from reflex.components.component import Component, StatefulComponent + from reflex_core.components.component import Component, StatefulComponent def _sort_hooks( diff --git a/packages/reflex-core/src/reflex_core/components/component.py b/packages/reflex-core/src/reflex_core/components/component.py index 673a5cb012a..804984a36c2 100644 --- a/packages/reflex-core/src/reflex_core/components/component.py +++ b/packages/reflex-core/src/reflex_core/components/component.py @@ -17,17 +17,16 @@ from types import SimpleNamespace from typing import TYPE_CHECKING, Any, ClassVar, TypeVar, cast, get_args, get_origin -from reflex_components_core.core.breakpoints import Breakpoints +from reflex_core.breakpoints import Breakpoints from rich.markup import escape from typing_extensions import dataclass_transform -import reflex.state -from reflex import constants -from reflex.compiler.templates import stateful_component_template -from reflex.components.dynamic import load_dynamic_serializer -from reflex.components.field import BaseField, FieldBasedMeta -from reflex.components.tags import Tag -from reflex.constants import ( +from reflex_core import constants +from reflex_core.compiler.templates import stateful_component_template +from reflex_core.components.dynamic import load_dynamic_serializer +from reflex_core.components.field import BaseField, FieldBasedMeta +from reflex_core.components.tags import Tag +from reflex_core.constants import ( Dirs, EventTriggers, Hooks, @@ -36,9 +35,9 @@ MemoizationMode, PageNames, ) -from reflex.constants.compiler import SpecialAttributes -from reflex.constants.state import CAMEL_CASE_MEMO_MARKER, FRONTEND_EVENT_STATE -from reflex.event import ( +from reflex_core.constants.compiler import SpecialAttributes +from reflex_core.constants.state import CAMEL_CASE_MEMO_MARKER, FRONTEND_EVENT_STATE +from reflex_core.event import ( EventCallback, EventChain, EventHandler, @@ -50,21 +49,24 @@ run_script, unwrap_var_annotation, ) -from reflex.style import Style, format_as_emotion -from reflex.utils import console, format, imports, types -from reflex.utils.imports import ImportDict, ImportVar, ParsedImportDict -from reflex.vars import VarData -from reflex.vars.base import ( +from reflex_core.style import Style, format_as_emotion +from reflex_core.utils import console, format, imports, types +from reflex_core.utils.imports import ImportDict, ImportVar, ParsedImportDict +from reflex_core.vars import VarData +from reflex_core.vars.base import ( CachedVarOperation, LiteralNoneVar, LiteralVar, Var, cached_property_no_lock, ) -from reflex.vars.function import ArgsFunctionOperation, FunctionStringVar, FunctionVar -from reflex.vars.number import ternary_operation -from reflex.vars.object import ObjectVar -from reflex.vars.sequence import LiteralArrayVar, LiteralStringVar, StringVar +from reflex_core.vars.function import ArgsFunctionOperation, FunctionStringVar, FunctionVar +from reflex_core.vars.number import ternary_operation +from reflex_core.vars.object import ObjectVar +from reflex_core.vars.sequence import LiteralArrayVar, LiteralStringVar, StringVar + +if TYPE_CHECKING: + import reflex.state FIELD_TYPE = TypeVar("FIELD_TYPE") @@ -1118,7 +1120,7 @@ def _get_components_in_props(self) -> Sequence[BaseComponent]: @classmethod def _validate_children(cls, children: tuple | list): - from reflex.utils.exceptions import ChildrenTypeError + from reflex_core.utils.exceptions import ChildrenTypeError for child in children: if isinstance(child, (tuple, list)): diff --git a/packages/reflex-core/src/reflex_core/components/dynamic.py b/packages/reflex-core/src/reflex_core/components/dynamic.py index 020415b2fdb..3a716be02d2 100644 --- a/packages/reflex-core/src/reflex_core/components/dynamic.py +++ b/packages/reflex-core/src/reflex_core/components/dynamic.py @@ -11,7 +11,7 @@ from reflex_core.vars.base import VarData, transform if TYPE_CHECKING: - from reflex.components.component import Component + from reflex_core.components.component import Component def get_cdn_url(lib: str) -> str: @@ -57,7 +57,7 @@ def bundle_library(component: Union["Component", str]): def load_dynamic_serializer(): """Load the serializer for dynamic components.""" # Causes a circular import, so we import here. - from reflex.components.component import Component + from reflex_core.components.component import Component @serializer def make_component(component: Component) -> str: diff --git a/packages/reflex-core/src/reflex_core/components/tags/iter_tag.py b/packages/reflex-core/src/reflex_core/components/tags/iter_tag.py index c395c42265e..fad8ac6fb51 100644 --- a/packages/reflex-core/src/reflex_core/components/tags/iter_tag.py +++ b/packages/reflex-core/src/reflex_core/components/tags/iter_tag.py @@ -13,7 +13,7 @@ from reflex_core.vars.sequence import _determine_value_of_array_index if TYPE_CHECKING: - from reflex.components.component import Component + from reflex_core.components.component import Component @dataclasses.dataclass(frozen=True) diff --git a/packages/reflex-core/src/reflex_core/components/tags/tag.py b/packages/reflex-core/src/reflex_core/components/tags/tag.py index 0efab023f40..3d9312c6ad4 100644 --- a/packages/reflex-core/src/reflex_core/components/tags/tag.py +++ b/packages/reflex-core/src/reflex_core/components/tags/tag.py @@ -20,7 +20,7 @@ def render_prop(value: Any) -> Any: Returns: The rendered value. """ - from reflex.components.component import BaseComponent + from reflex_core.components.component import BaseComponent if isinstance(value, BaseComponent): return value.render() diff --git a/packages/reflex-core/src/reflex_core/constants/__init__.py b/packages/reflex-core/src/reflex_core/constants/__init__.py index 6a6e2a55f1f..bd9ae03f1ee 100644 --- a/packages/reflex-core/src/reflex_core/constants/__init__.py +++ b/packages/reflex-core/src/reflex_core/constants/__init__.py @@ -14,6 +14,8 @@ Imports, MemoizationDisposition, MemoizationMode, + PageNames, SETTER_PREFIX, ) +from .event import EventTriggers from .state import StateManagerMode diff --git a/packages/reflex-core/src/reflex_core/style.py b/packages/reflex-core/src/reflex_core/style.py index 0fac9029fba..72f1ac9664c 100644 --- a/packages/reflex-core/src/reflex_core/style.py +++ b/packages/reflex-core/src/reflex_core/style.py @@ -121,7 +121,7 @@ def convert_item( Raises: ReflexError: If an EventHandler is used as a style value """ - from reflex.components.component import BaseComponent + from reflex_core.components.component import BaseComponent if isinstance(style_item, (EventHandler, BaseComponent)): msg = ( diff --git a/packages/reflex-core/src/reflex_core/utils/format.py b/packages/reflex-core/src/reflex_core/utils/format.py index 5f50474fc5c..3a06a574256 100644 --- a/packages/reflex-core/src/reflex_core/utils/format.py +++ b/packages/reflex-core/src/reflex_core/utils/format.py @@ -13,7 +13,7 @@ from reflex_core.utils import exceptions if TYPE_CHECKING: - from reflex.components.component import ComponentStyle + from reflex_core.components.component import ComponentStyle from reflex_core.event import ArgsSpec, EventChain, EventHandler, EventSpec, EventType WRAP_MAP = { diff --git a/packages/reflex-core/src/reflex_core/utils/pyi_generator.py b/packages/reflex-core/src/reflex_core/utils/pyi_generator.py index 562bd742e3b..e54551abf70 100644 --- a/packages/reflex-core/src/reflex_core/utils/pyi_generator.py +++ b/packages/reflex-core/src/reflex_core/utils/pyi_generator.py @@ -23,7 +23,7 @@ from types import MappingProxyType, ModuleType, SimpleNamespace, UnionType from typing import Any, get_args, get_origin -from reflex.components.component import Component +from reflex_core.components.component import Component from reflex_core.vars.base import Var diff --git a/packages/reflex-core/src/reflex_core/vars/base.py b/packages/reflex-core/src/reflex_core/vars/base.py index 9034ffa588d..80446dbde83 100644 --- a/packages/reflex-core/src/reflex_core/vars/base.py +++ b/packages/reflex-core/src/reflex_core/vars/base.py @@ -72,7 +72,7 @@ ) if TYPE_CHECKING: - from reflex.components.component import BaseComponent + from reflex_core.components.component import BaseComponent from reflex.state import BaseState from reflex_core.constants.colors import Color diff --git a/reflex/components/component.py b/reflex/components/component.py new file mode 100644 index 00000000000..18c4ba767de --- /dev/null +++ b/reflex/components/component.py @@ -0,0 +1,3 @@ +"""Re-export from reflex_core.""" + +from reflex_core.components.component import * # noqa: F401, F403 diff --git a/reflex/constants/event.py b/reflex/constants/event.py new file mode 100644 index 00000000000..12ae3c55c1d --- /dev/null +++ b/reflex/constants/event.py @@ -0,0 +1,3 @@ +"""Re-export from reflex_core.""" + +from reflex_core.constants.event import * # noqa: F401, F403 From 2c907cbe1b356d61ce60874239a0070259e5fe19 Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Thu, 26 Mar 2026 13:57:45 -0700 Subject: [PATCH 045/113] fix imports --- .../src/reflex_components_code/code.py | 2 +- .../shiki_code_block.py | 6 +- .../reflex_components_core/base/app_wrap.py | 3 +- .../src/reflex_components_core/base/bare.py | 7 +- .../base/error_boundary.py | 7 +- .../src/reflex_components_core/base/link.py | 3 +- .../src/reflex_components_core/base/meta.py | 3 +- .../core/auto_scroll.py | 3 +- .../src/reflex_components_core/core/banner.py | 10 +- .../core/breakpoints.py | 2 +- .../reflex_components_core/core/clipboard.py | 5 +- .../src/reflex_components_core/core/colors.py | 3 +- .../src/reflex_components_core/core/cond.py | 9 +- .../reflex_components_core/core/debounce.py | 5 +- .../reflex_components_core/core/foreach.py | 5 +- .../src/reflex_components_core/core/html.py | 3 +- .../core/markdown_component_map.py | 4 +- .../src/reflex_components_core/core/match.py | 5 +- .../src/reflex_components_core/core/upload.py | 10 +- .../core/window_events.py | 5 +- .../el/elements/base.py | 3 +- .../el/elements/forms.py | 7 +- .../el/elements/inline.py | 3 +- .../el/elements/media.py | 3 +- .../el/elements/metadata.py | 3 +- .../el/elements/other.py | 3 +- .../el/elements/scripts.py | 3 +- .../el/elements/tables.py | 3 +- .../el/elements/typography.py | 3 +- .../dataeditor.py | 9 +- .../src/reflex_components_gridjs/datatable.py | 3 +- .../src/reflex_components_lucide/icon.py | 5 +- .../reflex_components_markdown/markdown.py | 8 +- .../src/reflex_components_moment/moment.py | 3 +- .../src/reflex_components_plotly/plotly.py | 2 +- .../primitives/accordion.py | 4 +- .../primitives/base.py | 3 +- .../primitives/dialog.py | 2 +- .../primitives/drawer.py | 3 +- .../primitives/form.py | 2 +- .../primitives/progress.py | 2 +- .../primitives/slider.py | 3 +- .../reflex_components_radix/themes/base.py | 2 +- .../themes/color_mode.py | 4 +- .../themes/components/alert_dialog.py | 2 +- .../themes/components/aspect_ratio.py | 3 +- .../themes/components/avatar.py | 2 +- .../themes/components/badge.py | 2 +- .../themes/components/button.py | 2 +- .../themes/components/callout.py | 2 +- .../themes/components/card.py | 2 +- .../themes/components/checkbox.py | 2 +- .../themes/components/checkbox_cards.py | 2 +- .../themes/components/checkbox_group.py | 2 +- .../themes/components/context_menu.py | 2 +- .../themes/components/data_list.py | 2 +- .../themes/components/dialog.py | 2 +- .../themes/components/dropdown_menu.py | 2 +- .../themes/components/hover_card.py | 2 +- .../themes/components/icon_button.py | 2 +- .../themes/components/inset.py | 2 +- .../themes/components/popover.py | 2 +- .../themes/components/progress.py | 2 +- .../themes/components/radio.py | 2 +- .../themes/components/radio_cards.py | 2 +- .../themes/components/radio_group.py | 4 +- .../themes/components/scroll_area.py | 3 +- .../themes/components/segmented_control.py | 2 +- .../themes/components/select.py | 2 +- .../themes/components/separator.py | 2 +- .../themes/components/skeleton.py | 2 +- .../themes/components/slider.py | 2 +- .../themes/components/spinner.py | 2 +- .../themes/components/switch.py | 2 +- .../themes/components/table.py | 2 +- .../themes/components/tabs.py | 2 +- .../themes/components/text_area.py | 2 +- .../themes/components/text_field.py | 4 +- .../themes/components/tooltip.py | 3 +- .../themes/layout/base.py | 2 +- .../themes/layout/container.py | 2 +- .../themes/layout/flex.py | 2 +- .../themes/layout/grid.py | 2 +- .../themes/layout/list.py | 2 +- .../themes/layout/section.py | 2 +- .../themes/layout/stack.py | 2 +- .../themes/typography/blockquote.py | 2 +- .../themes/typography/code.py | 2 +- .../themes/typography/heading.py | 2 +- .../themes/typography/link.py | 2 +- .../themes/typography/text.py | 2 +- .../react_player.py | 4 +- .../src/reflex_components_react_router/dom.py | 2 +- .../reflex_components_recharts/cartesian.py | 3 +- .../src/reflex_components_recharts/charts.py | 3 +- .../src/reflex_components_recharts/general.py | 3 +- .../src/reflex_components_recharts/polar.py | 3 +- .../src/reflex_components_sonner/toast.py | 10 +- .../src/reflex_core/components/component.py | 8 +- .../src/reflex_core/constants/__init__.py | 2 +- packages/reflex-core/src/reflex_core/style.py | 3 +- .../src/reflex_core/utils/format.py | 8 +- .../src/reflex_core/utils/pyi_generator.py | 4 +- .../reflex-core/src/reflex_core/vars/base.py | 2 +- .../reflex-docgen/src/reflex_docgen/_class.py | 3 +- pyi_hashes.json | 211 +++++++++--------- pyproject.toml | 4 +- reflex/app.py | 23 +- reflex/compiler/compiler.py | 2 +- reflex/compiler/templates.py | 2 +- reflex/compiler/utils.py | 4 +- reflex/components/component.py | 2 +- reflex/components/dynamic.py | 2 +- reflex/components/field.py | 2 +- reflex/components/props.py | 2 +- reflex/components/tags/__init__.py | 2 +- reflex/components/tags/cond_tag.py | 2 +- reflex/components/tags/iter_tag.py | 2 +- reflex/components/tags/match_tag.py | 2 +- reflex/components/tags/tag.py | 2 +- reflex/components/tags/tagless.py | 2 +- reflex/constants/base.py | 2 +- reflex/constants/colors.py | 2 +- reflex/constants/compiler.py | 2 +- reflex/constants/event.py | 2 +- reflex/constants/state.py | 2 +- reflex/event.py | 2 +- reflex/experimental/client_state.py | 7 +- reflex/experimental/hooks.py | 5 +- reflex/experimental/memo.py | 20 +- reflex/istate/proxy.py | 2 +- reflex/state.py | 22 +- reflex/style.py | 2 +- reflex/utils/compat.py | 2 +- reflex/utils/console.py | 2 +- reflex/utils/decorator.py | 2 +- reflex/utils/exceptions.py | 2 +- reflex/utils/exec.py | 4 +- reflex/utils/export.py | 4 +- reflex/utils/format.py | 2 +- reflex/utils/imports.py | 2 +- reflex/utils/pyi_generator.py | 2 +- reflex/utils/serializers.py | 2 +- reflex/utils/types.py | 2 +- reflex/vars/__init__.py | 2 +- reflex/vars/base.py | 2 +- reflex/vars/color.py | 2 +- reflex/vars/datetime.py | 2 +- reflex/vars/dep_tracking.py | 2 +- reflex/vars/function.py | 2 +- reflex/vars/number.py | 2 +- reflex/vars/object.py | 2 +- reflex/vars/sequence.py | 2 +- scripts/make_pyi.py | 2 +- tests/integration/test_var_operations.py | 4 +- tests/units/compiler/test_compiler.py | 4 +- tests/units/components/base/test_bare.py | 3 +- tests/units/components/core/test_colors.py | 2 +- tests/units/components/core/test_cond.py | 2 +- tests/units/components/core/test_debounce.py | 2 +- tests/units/components/core/test_foreach.py | 4 +- tests/units/components/core/test_match.py | 6 +- tests/units/components/core/test_upload.py | 2 +- .../components/datadisplay/test_shiki_code.py | 4 +- tests/units/components/forms/test_form.py | 2 +- .../components/markdown/test_markdown.py | 2 +- .../components/radix/test_icon_button.py | 2 +- tests/units/components/test_component.py | 6 +- tests/units/components/test_tag.py | 2 +- tests/units/experimental/test_memo.py | 6 +- tests/units/test_app.py | 2 +- tests/units/test_event.py | 2 +- tests/units/test_state.py | 2 +- tests/units/test_style.py | 4 +- tests/units/test_var.py | 36 +-- tests/units/utils/test_format.py | 8 +- tests/units/utils/test_serializers.py | 2 +- tests/units/utils/test_types.py | 5 +- tests/units/utils/test_utils.py | 2 +- tests/units/vars/test_base.py | 2 +- tests/units/vars/test_dep_tracking.py | 10 +- tests/units/vars/test_object.py | 6 +- 182 files changed, 454 insertions(+), 407 deletions(-) diff --git a/packages/reflex-components-code/src/reflex_components_code/code.py b/packages/reflex-components-code/src/reflex_components_code/code.py index 529c2c43c0d..8bc700ab3e4 100644 --- a/packages/reflex-components-code/src/reflex_components_code/code.py +++ b/packages/reflex-components-code/src/reflex_components_code/code.py @@ -10,6 +10,7 @@ from reflex_components_lucide.icon import Icon from reflex_components_radix.themes.components.button import Button from reflex_components_radix.themes.layout.box import Box +from reflex_core.vars.base import LiteralVar, Var, VarData from reflex.components.component import Component, ComponentNamespace, field from reflex.constants.colors import Color @@ -17,7 +18,6 @@ from reflex.style import Style from reflex.utils import format from reflex.utils.imports import ImportVar -from reflex.vars.base import LiteralVar, Var, VarData LiteralCodeLanguage = Literal[ "abap", diff --git a/packages/reflex-components-code/src/reflex_components_code/shiki_code_block.py b/packages/reflex-components-code/src/reflex_components_code/shiki_code_block.py index 6cc2aeed7ea..c3a4ccadd3c 100644 --- a/packages/reflex-components-code/src/reflex_components_code/shiki_code_block.py +++ b/packages/reflex-components-code/src/reflex_components_code/shiki_code_block.py @@ -14,6 +14,9 @@ from reflex_components_core.el.elements.forms import Button from reflex_components_lucide.icon import Icon from reflex_components_radix.themes.layout.box import Box +from reflex_core.vars.base import LiteralVar, Var +from reflex_core.vars.function import FunctionStringVar +from reflex_core.vars.sequence import StringVar, string_replace_operation from reflex.components.component import Component, ComponentNamespace, field from reflex.components.props import NoExtrasAllowedProps @@ -21,9 +24,6 @@ from reflex.style import Style from reflex.utils.exceptions import VarTypeError from reflex.utils.imports import ImportVar -from reflex.vars.base import LiteralVar, Var -from reflex.vars.function import FunctionStringVar -from reflex.vars.sequence import StringVar, string_replace_operation def copy_script() -> Any: diff --git a/packages/reflex-components-core/src/reflex_components_core/base/app_wrap.py b/packages/reflex-components-core/src/reflex_components_core/base/app_wrap.py index 9b244ef23eb..7736b49399e 100644 --- a/packages/reflex-components-core/src/reflex_components_core/base/app_wrap.py +++ b/packages/reflex-components-core/src/reflex_components_core/base/app_wrap.py @@ -1,7 +1,8 @@ """Top-level component that wraps the entire app.""" +from reflex_core.vars.base import Var + from reflex.components.component import Component -from reflex.vars.base import Var from reflex_components_core.base.fragment import Fragment diff --git a/packages/reflex-components-core/src/reflex_components_core/base/bare.py b/packages/reflex-components-core/src/reflex_components_core/base/bare.py index 4182d0666ba..9679e54ec3f 100644 --- a/packages/reflex-components-core/src/reflex_components_core/base/bare.py +++ b/packages/reflex-components-core/src/reflex_components_core/base/bare.py @@ -5,6 +5,10 @@ from collections.abc import Iterator, Sequence from typing import Any +from reflex_core.vars import BooleanVar, ObjectVar, Var +from reflex_core.vars.base import GLOBAL_CACHE, VarData +from reflex_core.vars.sequence import LiteralStringVar + from reflex.components.component import BaseComponent, Component, ComponentStyle from reflex.components.tags import Tag from reflex.components.tags.tagless import Tagless @@ -12,9 +16,6 @@ from reflex.utils import console from reflex.utils.decorator import once from reflex.utils.imports import ParsedImportDict -from reflex.vars import BooleanVar, ObjectVar, Var -from reflex.vars.base import GLOBAL_CACHE, VarData -from reflex.vars.sequence import LiteralStringVar @once diff --git a/packages/reflex-components-core/src/reflex_components_core/base/error_boundary.py b/packages/reflex-components-core/src/reflex_components_core/base/error_boundary.py index dcf3867aa2c..924a9cc80f6 100644 --- a/packages/reflex-components-core/src/reflex_components_core/base/error_boundary.py +++ b/packages/reflex-components-core/src/reflex_components_core/base/error_boundary.py @@ -2,12 +2,13 @@ from __future__ import annotations +from reflex_core.vars.base import Var +from reflex_core.vars.function import ArgsFunctionOperation +from reflex_core.vars.object import ObjectVar + from reflex.components.component import Component, field from reflex.event import EventHandler, set_clipboard from reflex.state import FrontendEventExceptionState -from reflex.vars.base import Var -from reflex.vars.function import ArgsFunctionOperation -from reflex.vars.object import ObjectVar from reflex_components_core.datadisplay.logo import svg_logo from reflex_components_core.el import a, button, div, h2, hr, p, pre, svg diff --git a/packages/reflex-components-core/src/reflex_components_core/base/link.py b/packages/reflex-components-core/src/reflex_components_core/base/link.py index 4df4deeebb6..2e44a2f401e 100644 --- a/packages/reflex-components-core/src/reflex_components_core/base/link.py +++ b/packages/reflex-components-core/src/reflex_components_core/base/link.py @@ -1,7 +1,8 @@ """Display the title of the current page.""" +from reflex_core.vars.base import Var + from reflex.components.component import field -from reflex.vars.base import Var from reflex_components_core.el.elements.base import BaseHTML diff --git a/packages/reflex-components-core/src/reflex_components_core/base/meta.py b/packages/reflex-components-core/src/reflex_components_core/base/meta.py index 947e68f4e53..ded1ab0a4d4 100644 --- a/packages/reflex-components-core/src/reflex_components_core/base/meta.py +++ b/packages/reflex-components-core/src/reflex_components_core/base/meta.py @@ -2,8 +2,9 @@ from __future__ import annotations +from reflex_core.vars.base import Var + from reflex.components.component import field -from reflex.vars.base import Var from reflex_components_core.base.bare import Bare from reflex_components_core.el import elements from reflex_components_core.el.elements.metadata import ( diff --git a/packages/reflex-components-core/src/reflex_components_core/core/auto_scroll.py b/packages/reflex-components-core/src/reflex_components_core/core/auto_scroll.py index 0ef2fcdb41e..e97f8d5a7a0 100644 --- a/packages/reflex-components-core/src/reflex_components_core/core/auto_scroll.py +++ b/packages/reflex-components-core/src/reflex_components_core/core/auto_scroll.py @@ -4,9 +4,10 @@ import dataclasses +from reflex_core.vars.base import Var, get_unique_variable_name + from reflex.constants.compiler import MemoizationDisposition, MemoizationMode from reflex.utils.imports import ImportDict -from reflex.vars.base import Var, get_unique_variable_name from reflex_components_core.el.elements.typography import Div diff --git a/packages/reflex-components-core/src/reflex_components_core/core/banner.py b/packages/reflex-components-core/src/reflex_components_core/core/banner.py index 7af7981d69a..151a6cc1a32 100644 --- a/packages/reflex-components-core/src/reflex_components_core/core/banner.py +++ b/packages/reflex-components-core/src/reflex_components_core/core/banner.py @@ -4,6 +4,11 @@ from reflex_components_lucide.icon import Icon from reflex_components_sonner.toast import ToastProps, toast_ref +from reflex_core.vars import VarData +from reflex_core.vars.base import LiteralVar, Var +from reflex_core.vars.function import FunctionStringVar +from reflex_core.vars.number import BooleanVar +from reflex_core.vars.sequence import LiteralArrayVar from reflex import constants from reflex.components.component import Component @@ -11,11 +16,6 @@ from reflex.constants.compiler import CompileVars from reflex.environment import environment from reflex.utils.imports import ImportVar -from reflex.vars import VarData -from reflex.vars.base import LiteralVar, Var -from reflex.vars.function import FunctionStringVar -from reflex.vars.number import BooleanVar -from reflex.vars.sequence import LiteralArrayVar from reflex_components_core.base.fragment import Fragment from reflex_components_core.core.cond import cond from reflex_components_core.el.elements.inline import Span diff --git a/packages/reflex-components-core/src/reflex_components_core/core/breakpoints.py b/packages/reflex-components-core/src/reflex_components_core/core/breakpoints.py index c3418265649..f7779aaab09 100644 --- a/packages/reflex-components-core/src/reflex_components_core/core/breakpoints.py +++ b/packages/reflex-components-core/src/reflex_components_core/core/breakpoints.py @@ -1,3 +1,3 @@ """Re-export from reflex_core.""" -from reflex_core.breakpoints import * # noqa: F401, F403 +from reflex_core.breakpoints import * diff --git a/packages/reflex-components-core/src/reflex_components_core/core/clipboard.py b/packages/reflex-components-core/src/reflex_components_core/core/clipboard.py index e60fc8572ab..02e8c249733 100644 --- a/packages/reflex-components-core/src/reflex_components_core/core/clipboard.py +++ b/packages/reflex-components-core/src/reflex_components_core/core/clipboard.py @@ -4,14 +4,15 @@ from collections.abc import Sequence +from reflex_core.vars import get_unique_variable_name +from reflex_core.vars.base import Var, VarData + from reflex.components.component import field from reflex.components.tags.tag import Tag from reflex.constants.compiler import Hooks from reflex.event import EventChain, EventHandler, passthrough_event_spec from reflex.utils.format import format_prop, wrap from reflex.utils.imports import ImportVar -from reflex.vars import get_unique_variable_name -from reflex.vars.base import Var, VarData from reflex_components_core.base.fragment import Fragment diff --git a/packages/reflex-components-core/src/reflex_components_core/core/colors.py b/packages/reflex-components-core/src/reflex_components_core/core/colors.py index 5556559eedd..65ec006041b 100644 --- a/packages/reflex-components-core/src/reflex_components_core/core/colors.py +++ b/packages/reflex-components-core/src/reflex_components_core/core/colors.py @@ -1,5 +1,7 @@ """The colors used in Reflex are a wrapper around https://www.radix-ui.com/colors.""" +from reflex_core.vars.base import Var + from reflex.constants.base import REFLEX_VAR_OPENING_TAG from reflex.constants.colors import ( COLORS, @@ -9,7 +11,6 @@ ColorType, ShadeType, ) -from reflex.vars.base import Var def color( diff --git a/packages/reflex-components-core/src/reflex_components_core/core/cond.py b/packages/reflex-components-core/src/reflex_components_core/core/cond.py index aaf853cff62..c8fbfa2e47d 100644 --- a/packages/reflex-components-core/src/reflex_components_core/core/cond.py +++ b/packages/reflex-components-core/src/reflex_components_core/core/cond.py @@ -4,15 +4,16 @@ from typing import Any, overload +from reflex_core.utils import types +from reflex_core.vars import VarData +from reflex_core.vars.base import LiteralVar, Var +from reflex_core.vars.number import ternary_operation + from reflex.components.component import BaseComponent, Component, field from reflex.components.tags import CondTag, Tag from reflex.constants import Dirs from reflex.style import LIGHT_COLOR_MODE, resolved_color_mode -from reflex.utils import types from reflex.utils.imports import ImportDict, ImportVar -from reflex.vars import VarData -from reflex.vars.base import LiteralVar, Var -from reflex.vars.number import ternary_operation from reflex_components_core.base.fragment import Fragment _IS_TRUE_IMPORT: ImportDict = { diff --git a/packages/reflex-components-core/src/reflex_components_core/core/debounce.py b/packages/reflex-components-core/src/reflex_components_core/core/debounce.py index 4b8d6a1aa88..0905b85d932 100644 --- a/packages/reflex-components-core/src/reflex_components_core/core/debounce.py +++ b/packages/reflex-components-core/src/reflex_components_core/core/debounce.py @@ -4,11 +4,12 @@ from typing import Any +from reflex_core.vars import VarData +from reflex_core.vars.base import Var + from reflex.components.component import Component, field from reflex.constants import EventTriggers from reflex.event import EventHandler, no_args_event_spec -from reflex.vars import VarData -from reflex.vars.base import Var DEFAULT_DEBOUNCE_TIMEOUT = 300 diff --git a/packages/reflex-components-core/src/reflex_components_core/core/foreach.py b/packages/reflex-components-core/src/reflex_components_core/core/foreach.py index 731a3471475..cd355fce079 100644 --- a/packages/reflex-components-core/src/reflex_components_core/core/foreach.py +++ b/packages/reflex-components-core/src/reflex_components_core/core/foreach.py @@ -8,6 +8,8 @@ from hashlib import md5 from typing import Any +from reflex_core.vars.base import LiteralVar, Var + from reflex.components.component import Component, field from reflex.components.tags import IterTag from reflex.constants import MemoizationMode @@ -15,7 +17,6 @@ from reflex.state import ComponentState from reflex.utils import types from reflex.utils.exceptions import UntypedVarError -from reflex.vars.base import LiteralVar, Var from reflex_components_core.base.fragment import Fragment from reflex_components_core.core.cond import cond @@ -64,7 +65,7 @@ def create( # noqa: DAR401 with_traceback # noqa: DAR402 UntypedVarError """ - from reflex.vars import ArrayVar, ObjectVar, StringVar + from reflex_core.vars import ArrayVar, ObjectVar, StringVar iterable = ( LiteralVar.create(iterable).guess_type() diff --git a/packages/reflex-components-core/src/reflex_components_core/core/html.py b/packages/reflex-components-core/src/reflex_components_core/core/html.py index 59455f25b1b..c842dc0cf2c 100644 --- a/packages/reflex-components-core/src/reflex_components_core/core/html.py +++ b/packages/reflex-components-core/src/reflex_components_core/core/html.py @@ -1,7 +1,8 @@ """A html component.""" +from reflex_core.vars.base import Var + from reflex.components.component import field -from reflex.vars.base import Var from reflex_components_core.el.elements.typography import Div diff --git a/packages/reflex-components-core/src/reflex_components_core/core/markdown_component_map.py b/packages/reflex-components-core/src/reflex_components_core/core/markdown_component_map.py index 97e805896e2..ca1b99d90b0 100644 --- a/packages/reflex-components-core/src/reflex_components_core/core/markdown_component_map.py +++ b/packages/reflex-components-core/src/reflex_components_core/core/markdown_component_map.py @@ -5,8 +5,8 @@ import dataclasses from collections.abc import Sequence -from reflex.vars.base import Var, VarData -from reflex.vars.function import ArgsFunctionOperation, DestructuredArg +from reflex_core.vars.base import Var, VarData +from reflex_core.vars.function import ArgsFunctionOperation, DestructuredArg # Special vars used in the component map. _CHILDREN = Var(_js_expr="children", _var_type=str) diff --git a/packages/reflex-components-core/src/reflex_components_core/core/match.py b/packages/reflex-components-core/src/reflex_components_core/core/match.py index 999a585006d..41e592c11b4 100644 --- a/packages/reflex-components-core/src/reflex_components_core/core/match.py +++ b/packages/reflex-components-core/src/reflex_components_core/core/match.py @@ -3,6 +3,9 @@ import textwrap from typing import Any, cast +from reflex_core.vars import VarData +from reflex_core.vars.base import LiteralVar, Var + from reflex.components.component import BaseComponent, Component, MemoizationLeaf, field from reflex.components.tags import Tag from reflex.components.tags.match_tag import MatchTag @@ -10,8 +13,6 @@ from reflex.utils import format from reflex.utils.exceptions import MatchTypeError from reflex.utils.imports import ImportDict -from reflex.vars import VarData -from reflex.vars.base import LiteralVar, Var from reflex_components_core.base import Fragment diff --git a/packages/reflex-components-core/src/reflex_components_core/core/upload.py b/packages/reflex-components-core/src/reflex_components_core/core/upload.py index f0284955bd5..556b47d056d 100644 --- a/packages/reflex-components-core/src/reflex_components_core/core/upload.py +++ b/packages/reflex-components-core/src/reflex_components_core/core/upload.py @@ -7,6 +7,11 @@ from typing import Any, ClassVar from reflex_components_sonner.toast import toast +from reflex_core.vars import VarData +from reflex_core.vars.base import Var, get_unique_variable_name +from reflex_core.vars.function import FunctionVar +from reflex_core.vars.object import ObjectVar +from reflex_core.vars.sequence import ArrayVar, LiteralStringVar from reflex._upload import UploadChunkIterator, UploadFile from reflex.components.component import ( @@ -34,11 +39,6 @@ from reflex.style import Style from reflex.utils import format from reflex.utils.imports import ImportVar -from reflex.vars import VarData -from reflex.vars.base import Var, get_unique_variable_name -from reflex.vars.function import FunctionVar -from reflex.vars.object import ObjectVar -from reflex.vars.sequence import ArrayVar, LiteralStringVar from reflex_components_core.base.fragment import Fragment from reflex_components_core.core.cond import cond from reflex_components_core.el.elements.forms import Input diff --git a/packages/reflex-components-core/src/reflex_components_core/core/window_events.py b/packages/reflex-components-core/src/reflex_components_core/core/window_events.py index 4379ff89cc7..ffff72f9ec9 100644 --- a/packages/reflex-components-core/src/reflex_components_core/core/window_events.py +++ b/packages/reflex-components-core/src/reflex_components_core/core/window_events.py @@ -4,12 +4,13 @@ from typing import Any, cast +from reflex_core.vars.base import Var, VarData +from reflex_core.vars.object import ObjectVar + import reflex as rx from reflex.components.component import StatefulComponent, field from reflex.constants.compiler import Hooks from reflex.event import key_event, no_args_event_spec -from reflex.vars.base import Var, VarData -from reflex.vars.object import ObjectVar from reflex_components_core.base.fragment import Fragment diff --git a/packages/reflex-components-core/src/reflex_components_core/el/elements/base.py b/packages/reflex-components-core/src/reflex_components_core/el/elements/base.py index 873972ea800..f4b7941b041 100644 --- a/packages/reflex-components-core/src/reflex_components_core/el/elements/base.py +++ b/packages/reflex-components-core/src/reflex_components_core/el/elements/base.py @@ -2,8 +2,9 @@ from typing import Literal +from reflex_core.vars.base import Var + from reflex.components.component import field -from reflex.vars.base import Var from reflex_components_core.el.element import Element AutoCapitalize = Literal["off", "none", "on", "sentences", "words", "characters"] diff --git a/packages/reflex-components-core/src/reflex_components_core/el/elements/forms.py b/packages/reflex-components-core/src/reflex_components_core/el/elements/forms.py index 24e331e484a..f3f2283b290 100644 --- a/packages/reflex-components-core/src/reflex_components_core/el/elements/forms.py +++ b/packages/reflex-components-core/src/reflex_components_core/el/elements/forms.py @@ -6,6 +6,10 @@ from hashlib import md5 from typing import Any, ClassVar, Literal +from reflex_core.vars import VarData +from reflex_core.vars.base import LiteralVar, Var +from reflex_core.vars.number import ternary_operation + from reflex.components.component import field from reflex.components.tags.tag import Tag from reflex.constants import Dirs, EventTriggers @@ -23,9 +27,6 @@ prevent_default, ) from reflex.utils.imports import ImportDict -from reflex.vars import VarData -from reflex.vars.base import LiteralVar, Var -from reflex.vars.number import ternary_operation from reflex_components_core.el.element import Element from .base import BaseHTML diff --git a/packages/reflex-components-core/src/reflex_components_core/el/elements/inline.py b/packages/reflex-components-core/src/reflex_components_core/el/elements/inline.py index c9e8fa6bbc8..8cff0b35045 100644 --- a/packages/reflex-components-core/src/reflex_components_core/el/elements/inline.py +++ b/packages/reflex-components-core/src/reflex_components_core/el/elements/inline.py @@ -2,8 +2,9 @@ from typing import ClassVar, Literal +from reflex_core.vars.base import Var + from reflex.components.component import field -from reflex.vars.base import Var from .base import BaseHTML diff --git a/packages/reflex-components-core/src/reflex_components_core/el/elements/media.py b/packages/reflex-components-core/src/reflex_components_core/el/elements/media.py index 0e45b68e6e5..c66f547fa41 100644 --- a/packages/reflex-components-core/src/reflex_components_core/el/elements/media.py +++ b/packages/reflex-components-core/src/reflex_components_core/el/elements/media.py @@ -2,10 +2,11 @@ from typing import Any, Literal +from reflex_core.vars.base import Var + from reflex import Component, ComponentNamespace from reflex.components.component import field from reflex.constants.colors import Color -from reflex.vars.base import Var from reflex_components_core.el.elements.inline import ReferrerPolicy from .base import BaseHTML diff --git a/packages/reflex-components-core/src/reflex_components_core/el/elements/metadata.py b/packages/reflex-components-core/src/reflex_components_core/el/elements/metadata.py index ebc6574fc06..9c12154528d 100644 --- a/packages/reflex-components-core/src/reflex_components_core/el/elements/metadata.py +++ b/packages/reflex-components-core/src/reflex_components_core/el/elements/metadata.py @@ -1,7 +1,8 @@ """Metadata classes.""" +from reflex_core.vars.base import Var + from reflex.components.component import field -from reflex.vars.base import Var from reflex_components_core.el.element import Element from reflex_components_core.el.elements.inline import ReferrerPolicy from reflex_components_core.el.elements.media import CrossOrigin diff --git a/packages/reflex-components-core/src/reflex_components_core/el/elements/other.py b/packages/reflex-components-core/src/reflex_components_core/el/elements/other.py index b73e75c2d63..23f0052a16d 100644 --- a/packages/reflex-components-core/src/reflex_components_core/el/elements/other.py +++ b/packages/reflex-components-core/src/reflex_components_core/el/elements/other.py @@ -1,7 +1,8 @@ """Other classes.""" +from reflex_core.vars.base import Var + from reflex.components.component import field -from reflex.vars.base import Var from .base import BaseHTML diff --git a/packages/reflex-components-core/src/reflex_components_core/el/elements/scripts.py b/packages/reflex-components-core/src/reflex_components_core/el/elements/scripts.py index 0c61f9cad09..9bbdd329e12 100644 --- a/packages/reflex-components-core/src/reflex_components_core/el/elements/scripts.py +++ b/packages/reflex-components-core/src/reflex_components_core/el/elements/scripts.py @@ -1,7 +1,8 @@ """Scripts classes.""" +from reflex_core.vars.base import Var + from reflex.components.component import field -from reflex.vars.base import Var from reflex_components_core.el.elements.inline import ReferrerPolicy from reflex_components_core.el.elements.media import CrossOrigin diff --git a/packages/reflex-components-core/src/reflex_components_core/el/elements/tables.py b/packages/reflex-components-core/src/reflex_components_core/el/elements/tables.py index d48a787812d..0321c5c5a17 100644 --- a/packages/reflex-components-core/src/reflex_components_core/el/elements/tables.py +++ b/packages/reflex-components-core/src/reflex_components_core/el/elements/tables.py @@ -2,8 +2,9 @@ from typing import Literal +from reflex_core.vars.base import Var + from reflex.components.component import field -from reflex.vars.base import Var from .base import BaseHTML diff --git a/packages/reflex-components-core/src/reflex_components_core/el/elements/typography.py b/packages/reflex-components-core/src/reflex_components_core/el/elements/typography.py index 50ee10b1f5d..3e53dfcc19b 100644 --- a/packages/reflex-components-core/src/reflex_components_core/el/elements/typography.py +++ b/packages/reflex-components-core/src/reflex_components_core/el/elements/typography.py @@ -2,8 +2,9 @@ from typing import ClassVar, Literal +from reflex_core.vars.base import Var + from reflex.components.component import field -from reflex.vars.base import Var from .base import BaseHTML diff --git a/packages/reflex-components-dataeditor/src/reflex_components_dataeditor/dataeditor.py b/packages/reflex-components-dataeditor/src/reflex_components_dataeditor/dataeditor.py index 5b116e468d1..fc514073dfd 100644 --- a/packages/reflex-components-dataeditor/src/reflex_components_dataeditor/dataeditor.py +++ b/packages/reflex-components-dataeditor/src/reflex_components_dataeditor/dataeditor.py @@ -7,16 +7,17 @@ from enum import Enum from typing import Any, Literal, TypedDict +from reflex_core.vars import get_unique_variable_name +from reflex_core.vars.base import Var +from reflex_core.vars.function import FunctionStringVar +from reflex_core.vars.sequence import ArrayVar + from reflex.components.component import Component, NoSSRComponent, field from reflex.components.literals import LiteralRowMarker from reflex.event import EventHandler, no_args_event_spec, passthrough_event_spec from reflex.utils import console, format, types from reflex.utils.imports import ImportDict, ImportVar from reflex.utils.serializers import serializer -from reflex.vars import get_unique_variable_name -from reflex.vars.base import Var -from reflex.vars.function import FunctionStringVar -from reflex.vars.sequence import ArrayVar # TODO: Fix the serialization issue for custom types. diff --git a/packages/reflex-components-gridjs/src/reflex_components_gridjs/datatable.py b/packages/reflex-components-gridjs/src/reflex_components_gridjs/datatable.py index 4ffc8db52fb..d8e67a87c25 100644 --- a/packages/reflex-components-gridjs/src/reflex_components_gridjs/datatable.py +++ b/packages/reflex-components-gridjs/src/reflex_components_gridjs/datatable.py @@ -5,12 +5,13 @@ from collections.abc import Sequence from typing import Any +from reflex_core.vars.base import LiteralVar, Var, is_computed_var + from reflex.components.component import NoSSRComponent, field from reflex.components.tags import Tag from reflex.utils import types from reflex.utils.imports import ImportDict from reflex.utils.serializers import serialize -from reflex.vars.base import LiteralVar, Var, is_computed_var class Gridjs(NoSSRComponent): diff --git a/packages/reflex-components-lucide/src/reflex_components_lucide/icon.py b/packages/reflex-components-lucide/src/reflex_components_lucide/icon.py index 1eaea18bca6..a63fc87fe28 100644 --- a/packages/reflex-components-lucide/src/reflex_components_lucide/icon.py +++ b/packages/reflex-components-lucide/src/reflex_components_lucide/icon.py @@ -1,10 +1,11 @@ """Lucide Icon component.""" +from reflex_core.vars.base import LiteralVar, Var +from reflex_core.vars.sequence import LiteralStringVar, StringVar + from reflex.components.component import Component, field from reflex.utils import console, format from reflex.utils.imports import ImportVar -from reflex.vars.base import LiteralVar, Var -from reflex.vars.sequence import LiteralStringVar, StringVar LUCIDE_LIBRARY = "lucide-react@0.577.0" diff --git a/packages/reflex-components-markdown/src/reflex_components_markdown/markdown.py b/packages/reflex-components-markdown/src/reflex_components_markdown/markdown.py index a6f4f181557..07c6eece206 100644 --- a/packages/reflex-components-markdown/src/reflex_components_markdown/markdown.py +++ b/packages/reflex-components-markdown/src/reflex_components_markdown/markdown.py @@ -11,6 +11,9 @@ from reflex_components_core.core.markdown_component_map import MarkdownComponentMap from reflex_components_core.el.elements.typography import Div +from reflex_core.vars.base import LiteralVar, Var, VarData +from reflex_core.vars.number import ternary_operation +from reflex_core.vars.sequence import LiteralArrayVar from reflex.components.component import ( BaseComponent, @@ -22,9 +25,6 @@ from reflex.components.tags.tag import Tag from reflex.utils import console from reflex.utils.imports import ImportDict, ImportTypes, ImportVar -from reflex.vars.base import LiteralVar, Var, VarData -from reflex.vars.number import ternary_operation -from reflex.vars.sequence import LiteralArrayVar # Special vars used in the component map. _CHILDREN = Var(_js_expr="children", _var_type=str) @@ -450,7 +450,7 @@ def _get_component_map_name(self) -> str: def _get_custom_code(self) -> str | None: hooks = {} - from reflex.compiler.templates import _render_hooks + from reflex_core.compiler.templates import _render_hooks for component_factory in self.component_map.values(): comp = component_factory(_MOCK_ARG) diff --git a/packages/reflex-components-moment/src/reflex_components_moment/moment.py b/packages/reflex-components-moment/src/reflex_components_moment/moment.py index a7c40bd074d..c67c4495c9d 100644 --- a/packages/reflex-components-moment/src/reflex_components_moment/moment.py +++ b/packages/reflex-components-moment/src/reflex_components_moment/moment.py @@ -5,10 +5,11 @@ import dataclasses from datetime import date, datetime, time, timedelta +from reflex_core.vars.base import LiteralVar, Var + from reflex.components.component import NoSSRComponent, field from reflex.event import EventHandler, passthrough_event_spec from reflex.utils.imports import ImportDict -from reflex.vars.base import LiteralVar, Var @dataclasses.dataclass(frozen=True) diff --git a/packages/reflex-components-plotly/src/reflex_components_plotly/plotly.py b/packages/reflex-components-plotly/src/reflex_components_plotly/plotly.py index a4bb2dcba78..4598b5f42c3 100644 --- a/packages/reflex-components-plotly/src/reflex_components_plotly/plotly.py +++ b/packages/reflex-components-plotly/src/reflex_components_plotly/plotly.py @@ -5,12 +5,12 @@ from typing import TYPE_CHECKING, Any, TypedDict, TypeVar from reflex_components_core.core.cond import color_mode_cond +from reflex_core.vars.base import LiteralVar, Var from reflex.components.component import Component, NoSSRComponent, field 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: from plotly.graph_objs import Figure diff --git a/packages/reflex-components-radix/src/reflex_components_radix/primitives/accordion.py b/packages/reflex-components-radix/src/reflex_components_radix/primitives/accordion.py index 71fdc8a3f19..7e85ec70cee 100644 --- a/packages/reflex-components-radix/src/reflex_components_radix/primitives/accordion.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/primitives/accordion.py @@ -8,13 +8,13 @@ from reflex_components_core.core.colors import color from reflex_components_core.core.cond import cond from reflex_components_lucide.icon import Icon +from reflex_core.vars import get_uuid_string_var +from reflex_core.vars.base import LiteralVar, Var from reflex.components.component import Component, ComponentNamespace, field from reflex.constants.compiler import MemoizationMode from reflex.event import EventHandler from reflex.style import Style -from reflex.vars import get_uuid_string_var -from reflex.vars.base import LiteralVar, Var from reflex_components_radix.primitives.base import RadixPrimitiveComponent from reflex_components_radix.themes.base import LiteralAccentColor, LiteralRadius diff --git a/packages/reflex-components-radix/src/reflex_components_radix/primitives/base.py b/packages/reflex-components-radix/src/reflex_components_radix/primitives/base.py index 6fe5112d884..bd65bb04c60 100644 --- a/packages/reflex-components-radix/src/reflex_components_radix/primitives/base.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/primitives/base.py @@ -2,10 +2,11 @@ from typing import Any +from reflex_core.vars.base import Var + from reflex.components.component import Component, field from reflex.components.tags.tag import Tag from reflex.utils import format -from reflex.vars.base import Var class RadixPrimitiveComponent(Component): diff --git a/packages/reflex-components-radix/src/reflex_components_radix/primitives/dialog.py b/packages/reflex-components-radix/src/reflex_components_radix/primitives/dialog.py index 987ef6f3a61..ec076ba6a1f 100644 --- a/packages/reflex-components-radix/src/reflex_components_radix/primitives/dialog.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/primitives/dialog.py @@ -3,11 +3,11 @@ from typing import Any, ClassVar from reflex_components_core.el import elements +from reflex_core.vars.base import Var from reflex.components.component import ComponentNamespace, field from reflex.constants.compiler import MemoizationMode from reflex.event import EventHandler, no_args_event_spec, passthrough_event_spec -from reflex.vars.base import Var from .base import RadixPrimitiveComponent, RadixPrimitiveTriggerComponent diff --git a/packages/reflex-components-radix/src/reflex_components_radix/primitives/drawer.py b/packages/reflex-components-radix/src/reflex_components_radix/primitives/drawer.py index a2ceb2b79de..aab8ebc729e 100644 --- a/packages/reflex-components-radix/src/reflex_components_radix/primitives/drawer.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/primitives/drawer.py @@ -7,10 +7,11 @@ from collections.abc import Sequence from typing import Any, Literal +from reflex_core.vars.base import Var + from reflex.components.component import Component, ComponentNamespace, field from reflex.constants.compiler import MemoizationMode from reflex.event import EventHandler, no_args_event_spec, passthrough_event_spec -from reflex.vars.base import Var from reflex_components_radix.primitives.base import RadixPrimitiveComponent from reflex_components_radix.themes.base import Theme from reflex_components_radix.themes.layout.flex import Flex diff --git a/packages/reflex-components-radix/src/reflex_components_radix/primitives/form.py b/packages/reflex-components-radix/src/reflex_components_radix/primitives/form.py index 73d5f4d78ed..1e95e9ba668 100644 --- a/packages/reflex-components-radix/src/reflex_components_radix/primitives/form.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/primitives/form.py @@ -6,10 +6,10 @@ from reflex_components_core.core.debounce import DebounceInput from reflex_components_core.el.elements.forms import Form as HTMLForm +from reflex_core.vars.base import Var from reflex.components.component import ComponentNamespace, field from reflex.event import EventHandler, no_args_event_spec -from reflex.vars.base import Var from reflex_components_radix.themes.components.text_field import TextFieldRoot from .base import RadixPrimitiveComponentWithClassName diff --git a/packages/reflex-components-radix/src/reflex_components_radix/primitives/progress.py b/packages/reflex-components-radix/src/reflex_components_radix/primitives/progress.py index 8dea7cdf9f7..ea74b342b46 100644 --- a/packages/reflex-components-radix/src/reflex_components_radix/primitives/progress.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/primitives/progress.py @@ -5,9 +5,9 @@ from typing import Any from reflex_components_core.core.colors import color +from reflex_core.vars.base import Var from reflex.components.component import Component, ComponentNamespace, field -from reflex.vars.base import Var from reflex_components_radix.primitives.accordion import DEFAULT_ANIMATION_DURATION from reflex_components_radix.primitives.base import RadixPrimitiveComponentWithClassName from reflex_components_radix.themes.base import LiteralAccentColor, LiteralRadius diff --git a/packages/reflex-components-radix/src/reflex_components_radix/primitives/slider.py b/packages/reflex-components-radix/src/reflex_components_radix/primitives/slider.py index 3d132cd05b0..3e16686fccd 100644 --- a/packages/reflex-components-radix/src/reflex_components_radix/primitives/slider.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/primitives/slider.py @@ -5,9 +5,10 @@ from collections.abc import Sequence from typing import Any, Literal +from reflex_core.vars.base import Var + from reflex.components.component import Component, ComponentNamespace, field from reflex.event import EventHandler, passthrough_event_spec -from reflex.vars.base import Var from reflex_components_radix.primitives.base import RadixPrimitiveComponentWithClassName LiteralSliderOrientation = Literal["horizontal", "vertical"] diff --git a/packages/reflex-components-radix/src/reflex_components_radix/themes/base.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/base.py index aa0a39c9d1c..4c3256af234 100644 --- a/packages/reflex-components-radix/src/reflex_components_radix/themes/base.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/base.py @@ -5,12 +5,12 @@ from typing import Any, ClassVar, Literal from reflex_components_core.core.breakpoints import Responsive +from reflex_core.vars.base import Var from reflex.components import Component from reflex.components.component import field from reflex.components.tags import Tag from reflex.utils.imports import ImportDict, ImportVar -from reflex.vars.base import Var LiteralAlign = Literal["start", "center", "end", "baseline", "stretch"] LiteralJustify = Literal["start", "center", "end", "between"] diff --git a/packages/reflex-components-radix/src/reflex_components_radix/themes/color_mode.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/color_mode.py index 4b5bccef52c..3a74843bafc 100644 --- a/packages/reflex-components-radix/src/reflex_components_radix/themes/color_mode.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/color_mode.py @@ -21,6 +21,8 @@ from reflex_components_core.core.cond import Cond, color_mode_cond, cond from reflex_components_lucide.icon import Icon +from reflex_core.vars.base import Var +from reflex_core.vars.sequence import LiteralArrayVar from reflex.components.component import BaseComponent, field from reflex.style import ( @@ -30,8 +32,6 @@ set_color_mode, toggle_color_mode, ) -from reflex.vars.base import Var -from reflex.vars.sequence import LiteralArrayVar from reflex_components_radix.themes.components.dropdown_menu import dropdown_menu from reflex_components_radix.themes.components.switch import Switch diff --git a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/alert_dialog.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/alert_dialog.py index 500924d51a8..8415a479ea3 100644 --- a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/alert_dialog.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/alert_dialog.py @@ -4,11 +4,11 @@ from reflex_components_core.core.breakpoints import Responsive from reflex_components_core.el import elements +from reflex_core.vars.base import Var from reflex.components.component import ComponentNamespace, field from reflex.constants.compiler import MemoizationMode from reflex.event import EventHandler, no_args_event_spec, passthrough_event_spec -from reflex.vars.base import Var from reflex_components_radix.themes.base import ( RadixThemesComponent, RadixThemesTriggerComponent, diff --git a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/aspect_ratio.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/aspect_ratio.py index 805fb0ed194..69a39d4e0d2 100644 --- a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/aspect_ratio.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/aspect_ratio.py @@ -1,7 +1,8 @@ """Interactive components provided by @radix-ui/themes.""" +from reflex_core.vars.base import Var + from reflex.components.component import field -from reflex.vars.base import Var from reflex_components_radix.themes.base import RadixThemesComponent diff --git a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/avatar.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/avatar.py index 13fc8f27242..ecf32e217f2 100644 --- a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/avatar.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/avatar.py @@ -3,9 +3,9 @@ from typing import Literal from reflex_components_core.core.breakpoints import Responsive +from reflex_core.vars.base import Var from reflex.components.component import field -from reflex.vars.base import Var from reflex_components_radix.themes.base import ( LiteralAccentColor, LiteralRadius, diff --git a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/badge.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/badge.py index 66dbd6bb9ad..5391bd6458a 100644 --- a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/badge.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/badge.py @@ -4,9 +4,9 @@ from reflex_components_core.core.breakpoints import Responsive from reflex_components_core.el import elements +from reflex_core.vars.base import Var from reflex.components.component import field -from reflex.vars.base import Var from reflex_components_radix.themes.base import ( LiteralAccentColor, LiteralRadius, diff --git a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/button.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/button.py index 94032f3b480..e70912d7fc5 100644 --- a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/button.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/button.py @@ -4,9 +4,9 @@ from reflex_components_core.core.breakpoints import Responsive from reflex_components_core.el import elements +from reflex_core.vars.base import Var from reflex.components.component import field -from reflex.vars.base import Var from reflex_components_radix.themes.base import ( LiteralAccentColor, LiteralRadius, diff --git a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/callout.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/callout.py index 4b01e051400..9ea65b0b60d 100644 --- a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/callout.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/callout.py @@ -5,10 +5,10 @@ from reflex_components_core.core.breakpoints import Responsive from reflex_components_core.el import elements from reflex_components_lucide.icon import Icon +from reflex_core.vars.base import Var import reflex as rx from reflex.components.component import Component, ComponentNamespace, field -from reflex.vars.base import Var from reflex_components_radix.themes.base import LiteralAccentColor, RadixThemesComponent CalloutVariant = Literal["soft", "surface", "outline"] diff --git a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/card.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/card.py index fa0655cc583..8e96af1617e 100644 --- a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/card.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/card.py @@ -4,9 +4,9 @@ from reflex_components_core.core.breakpoints import Responsive from reflex_components_core.el import elements +from reflex_core.vars.base import Var from reflex.components.component import field -from reflex.vars.base import Var from reflex_components_radix.themes.base import RadixThemesComponent diff --git a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/checkbox.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/checkbox.py index 6da370ac347..43025f48722 100644 --- a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/checkbox.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/checkbox.py @@ -3,10 +3,10 @@ from typing import Literal from reflex_components_core.core.breakpoints import Responsive +from reflex_core.vars.base import Var from reflex.components.component import Component, ComponentNamespace, field from reflex.event import EventHandler, passthrough_event_spec -from reflex.vars.base import Var from reflex_components_radix.themes.base import ( LiteralAccentColor, LiteralSpacing, diff --git a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/checkbox_cards.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/checkbox_cards.py index a7eccd91248..8dcfed838ea 100644 --- a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/checkbox_cards.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/checkbox_cards.py @@ -4,9 +4,9 @@ from typing import Literal from reflex_components_core.core.breakpoints import Responsive +from reflex_core.vars.base import Var from reflex.components.component import field -from reflex.vars.base import Var from reflex_components_radix.themes.base import LiteralAccentColor, RadixThemesComponent diff --git a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/checkbox_group.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/checkbox_group.py index 388d6b48afd..0b9813517c6 100644 --- a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/checkbox_group.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/checkbox_group.py @@ -5,9 +5,9 @@ from typing import Literal from reflex_components_core.core.breakpoints import Responsive +from reflex_core.vars.base import Var from reflex.components.component import field -from reflex.vars.base import Var from reflex_components_radix.themes.base import LiteralAccentColor, RadixThemesComponent diff --git a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/context_menu.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/context_menu.py index b7ad94ff799..f2a42bb8502 100644 --- a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/context_menu.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/context_menu.py @@ -3,11 +3,11 @@ from typing import ClassVar, Literal from reflex_components_core.core.breakpoints import Responsive +from reflex_core.vars.base import Var from reflex.components.component import ComponentNamespace, field from reflex.constants.compiler import MemoizationMode from reflex.event import EventHandler, no_args_event_spec, passthrough_event_spec -from reflex.vars.base import Var from reflex_components_radix.themes.base import LiteralAccentColor, RadixThemesComponent from .checkbox import Checkbox diff --git a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/data_list.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/data_list.py index a052bbcaaf1..0197f16a228 100644 --- a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/data_list.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/data_list.py @@ -4,9 +4,9 @@ from typing import Literal from reflex_components_core.core.breakpoints import Responsive +from reflex_core.vars.base import Var from reflex.components.component import field -from reflex.vars.base import Var from reflex_components_radix.themes.base import LiteralAccentColor, RadixThemesComponent diff --git a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/dialog.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/dialog.py index bc7ab03bed2..8ffbf20821f 100644 --- a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/dialog.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/dialog.py @@ -4,11 +4,11 @@ from reflex_components_core.core.breakpoints import Responsive from reflex_components_core.el import elements +from reflex_core.vars.base import Var from reflex.components.component import ComponentNamespace, field from reflex.constants.compiler import MemoizationMode from reflex.event import EventHandler, no_args_event_spec, passthrough_event_spec -from reflex.vars.base import Var from reflex_components_radix.themes.base import ( RadixThemesComponent, RadixThemesTriggerComponent, diff --git a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/dropdown_menu.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/dropdown_menu.py index b3817993dfa..bd2b8f0b381 100644 --- a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/dropdown_menu.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/dropdown_menu.py @@ -3,11 +3,11 @@ from typing import ClassVar, Literal from reflex_components_core.core.breakpoints import Responsive +from reflex_core.vars.base import Var from reflex.components.component import ComponentNamespace, field from reflex.constants.compiler import MemoizationMode from reflex.event import EventHandler, no_args_event_spec, passthrough_event_spec -from reflex.vars.base import Var from reflex_components_radix.themes.base import ( LiteralAccentColor, RadixThemesComponent, diff --git a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/hover_card.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/hover_card.py index 71c81063949..6d262952dfb 100644 --- a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/hover_card.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/hover_card.py @@ -4,11 +4,11 @@ from reflex_components_core.core.breakpoints import Responsive from reflex_components_core.el import elements +from reflex_core.vars.base import Var from reflex.components.component import ComponentNamespace, field from reflex.constants.compiler import MemoizationMode from reflex.event import EventHandler, passthrough_event_spec -from reflex.vars.base import Var from reflex_components_radix.themes.base import ( RadixThemesComponent, RadixThemesTriggerComponent, diff --git a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/icon_button.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/icon_button.py index 82f313c8012..a3e3bbb11db 100644 --- a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/icon_button.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/icon_button.py @@ -8,10 +8,10 @@ from reflex_components_core.core.match import Match from reflex_components_core.el import elements from reflex_components_lucide import Icon +from reflex_core.vars.base import Var from reflex.components.component import Component, field from reflex.style import Style -from reflex.vars.base import Var from reflex_components_radix.themes.base import ( LiteralAccentColor, LiteralRadius, diff --git a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/inset.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/inset.py index 90abd565187..f7174ce9db4 100644 --- a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/inset.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/inset.py @@ -4,9 +4,9 @@ from reflex_components_core.core.breakpoints import Responsive from reflex_components_core.el import elements +from reflex_core.vars.base import Var from reflex.components.component import field -from reflex.vars.base import Var from reflex_components_radix.themes.base import RadixThemesComponent LiteralButtonSize = Literal["1", "2", "3", "4"] diff --git a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/popover.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/popover.py index e61916d523b..6fd1f5b49c7 100644 --- a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/popover.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/popover.py @@ -4,11 +4,11 @@ from reflex_components_core.core.breakpoints import Responsive from reflex_components_core.el import elements +from reflex_core.vars.base import Var from reflex.components.component import ComponentNamespace, field from reflex.constants.compiler import MemoizationMode from reflex.event import EventHandler, no_args_event_spec, passthrough_event_spec -from reflex.vars.base import Var from reflex_components_radix.themes.base import ( RadixThemesComponent, RadixThemesTriggerComponent, diff --git a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/progress.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/progress.py index 4993a85bea1..c03546dee41 100644 --- a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/progress.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/progress.py @@ -3,10 +3,10 @@ from typing import Literal from reflex_components_core.core.breakpoints import Responsive +from reflex_core.vars.base import Var from reflex.components.component import Component, field from reflex.style import Style -from reflex.vars.base import Var from reflex_components_radix.themes.base import LiteralAccentColor, RadixThemesComponent diff --git a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/radio.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/radio.py index 5e92cc6041f..96e0aaf6e70 100644 --- a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/radio.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/radio.py @@ -3,9 +3,9 @@ from typing import Literal from reflex_components_core.core.breakpoints import Responsive +from reflex_core.vars.base import Var from reflex.components.component import field -from reflex.vars.base import Var from reflex_components_radix.themes.base import LiteralAccentColor, RadixThemesComponent diff --git a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/radio_cards.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/radio_cards.py index e1e9078d42b..b8de190074d 100644 --- a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/radio_cards.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/radio_cards.py @@ -4,10 +4,10 @@ from typing import ClassVar, Literal from reflex_components_core.core.breakpoints import Responsive +from reflex_core.vars.base import Var from reflex.components.component import field from reflex.event import EventHandler, passthrough_event_spec -from reflex.vars.base import Var from reflex_components_radix.themes.base import LiteralAccentColor, RadixThemesComponent diff --git a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/radio_group.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/radio_group.py index 7bffcd12bfe..04b1f87b244 100644 --- a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/radio_group.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/radio_group.py @@ -6,13 +6,13 @@ from typing import Literal from reflex_components_core.core.breakpoints import Responsive +from reflex_core.vars.base import LiteralVar, Var +from reflex_core.vars.sequence import StringVar import reflex as rx from reflex.components.component import Component, ComponentNamespace, field from reflex.event import EventHandler, passthrough_event_spec from reflex.utils import types -from reflex.vars.base import LiteralVar, Var -from reflex.vars.sequence import StringVar from reflex_components_radix.themes.base import ( LiteralAccentColor, LiteralSpacing, diff --git a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/scroll_area.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/scroll_area.py index afde09818bb..3b4a05a2ee8 100644 --- a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/scroll_area.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/scroll_area.py @@ -2,8 +2,9 @@ from typing import Literal +from reflex_core.vars.base import Var + from reflex.components.component import field -from reflex.vars.base import Var from reflex_components_radix.themes.base import RadixThemesComponent diff --git a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/segmented_control.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/segmented_control.py index 0d32b1484dd..2599d1e1d63 100644 --- a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/segmented_control.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/segmented_control.py @@ -7,10 +7,10 @@ from typing import ClassVar, Literal from reflex_components_core.core.breakpoints import Responsive +from reflex_core.vars.base import Var from reflex.components.component import field from reflex.event import EventHandler -from reflex.vars.base import Var from reflex_components_radix.themes.base import LiteralAccentColor, RadixThemesComponent diff --git a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/select.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/select.py index 69a1188520a..cf08538956a 100644 --- a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/select.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/select.py @@ -4,12 +4,12 @@ from typing import ClassVar, Literal from reflex_components_core.core.breakpoints import Responsive +from reflex_core.vars.base import Var import reflex as rx from reflex.components.component import Component, ComponentNamespace, field from reflex.constants.compiler import MemoizationMode from reflex.event import no_args_event_spec, passthrough_event_spec -from reflex.vars.base import Var from reflex_components_radix.themes.base import ( LiteralAccentColor, LiteralRadius, diff --git a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/separator.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/separator.py index 3d9db2a21a1..aa2fe3be7ad 100644 --- a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/separator.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/separator.py @@ -3,9 +3,9 @@ from typing import Literal from reflex_components_core.core.breakpoints import Responsive +from reflex_core.vars.base import LiteralVar, Var from reflex.components.component import field -from reflex.vars.base import LiteralVar, Var from reflex_components_radix.themes.base import LiteralAccentColor, RadixThemesComponent LiteralSeparatorSize = Literal["1", "2", "3", "4"] diff --git a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/skeleton.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/skeleton.py index fea674943dc..55f9e37349e 100644 --- a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/skeleton.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/skeleton.py @@ -1,10 +1,10 @@ """Skeleton theme from Radix components.""" from reflex_components_core.core.breakpoints import Responsive +from reflex_core.vars.base import Var from reflex.components.component import field from reflex.constants.compiler import MemoizationMode -from reflex.vars.base import Var from reflex_components_radix.themes.base import RadixLoadingProp, RadixThemesComponent diff --git a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/slider.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/slider.py index ff0e6499486..43e8aff9e3f 100644 --- a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/slider.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/slider.py @@ -6,11 +6,11 @@ from typing import Literal from reflex_components_core.core.breakpoints import Responsive +from reflex_core.vars.base import Var from reflex.components.component import Component, field from reflex.event import EventHandler, passthrough_event_spec from reflex.utils.types import typehint_issubclass -from reflex.vars.base import Var from reflex_components_radix.themes.base import LiteralAccentColor, RadixThemesComponent on_value_event_spec = ( diff --git a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/spinner.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/spinner.py index d9eccfb0fee..33a1a45b095 100644 --- a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/spinner.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/spinner.py @@ -3,9 +3,9 @@ from typing import Literal from reflex_components_core.core.breakpoints import Responsive +from reflex_core.vars.base import Var from reflex.components.component import field -from reflex.vars.base import Var from reflex_components_radix.themes.base import RadixLoadingProp, RadixThemesComponent LiteralSpinnerSize = Literal["1", "2", "3"] diff --git a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/switch.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/switch.py index eee57dac486..c2b9b28c6f9 100644 --- a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/switch.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/switch.py @@ -3,10 +3,10 @@ from typing import Literal from reflex_components_core.core.breakpoints import Responsive +from reflex_core.vars.base import Var from reflex.components.component import field from reflex.event import EventHandler, passthrough_event_spec -from reflex.vars.base import Var from reflex_components_radix.themes.base import LiteralAccentColor, RadixThemesComponent LiteralSwitchSize = Literal["1", "2", "3"] diff --git a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/table.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/table.py index f352e4a8820..01011d38628 100644 --- a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/table.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/table.py @@ -4,9 +4,9 @@ from reflex_components_core.core.breakpoints import Responsive from reflex_components_core.el import elements +from reflex_core.vars.base import Var from reflex.components.component import ComponentNamespace, field -from reflex.vars.base import Var from reflex_components_radix.themes.base import CommonPaddingProps, RadixThemesComponent diff --git a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/tabs.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/tabs.py index e6d2eb51feb..41c63f4a559 100644 --- a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/tabs.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/tabs.py @@ -6,11 +6,11 @@ from reflex_components_core.core.breakpoints import Responsive from reflex_components_core.core.colors import color +from reflex_core.vars.base import Var from reflex.components.component import Component, ComponentNamespace, field from reflex.constants.compiler import MemoizationMode from reflex.event import EventHandler, passthrough_event_spec -from reflex.vars.base import Var from reflex_components_radix.themes.base import LiteralAccentColor, RadixThemesComponent vertical_orientation_css = "&[data-orientation='vertical']" diff --git a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/text_area.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/text_area.py index 60b9f3e104f..d7a490f82db 100644 --- a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/text_area.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/text_area.py @@ -5,9 +5,9 @@ from reflex_components_core.core.breakpoints import Responsive from reflex_components_core.core.debounce import DebounceInput from reflex_components_core.el import elements +from reflex_core.vars.base import Var from reflex.components.component import Component, field -from reflex.vars.base import Var from reflex_components_radix.themes.base import ( LiteralAccentColor, LiteralRadius, diff --git a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/text_field.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/text_field.py index 24ff5d7612a..32b9e213996 100644 --- a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/text_field.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/text_field.py @@ -7,12 +7,12 @@ from reflex_components_core.core.breakpoints import Responsive from reflex_components_core.core.debounce import DebounceInput from reflex_components_core.el import elements +from reflex_core.vars.base import Var +from reflex_core.vars.number import ternary_operation from reflex.components.component import Component, ComponentNamespace, field from reflex.event import EventHandler, input_event, key_event from reflex.utils.types import is_optional -from reflex.vars.base import Var -from reflex.vars.number import ternary_operation from reflex_components_radix.themes.base import ( LiteralAccentColor, LiteralRadius, diff --git a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/tooltip.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/tooltip.py index 90c9b2a8a8a..4b8d4d4abe8 100644 --- a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/tooltip.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/tooltip.py @@ -2,11 +2,12 @@ from typing import Literal +from reflex_core.vars.base import Var + from reflex.components.component import Component, field from reflex.constants.compiler import MemoizationMode from reflex.event import EventHandler, no_args_event_spec, passthrough_event_spec from reflex.utils import format -from reflex.vars.base import Var from reflex_components_radix.themes.base import RadixThemesComponent LiteralSideType = Literal[ diff --git a/packages/reflex-components-radix/src/reflex_components_radix/themes/layout/base.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/layout/base.py index f90ad69d63e..72f096aa380 100644 --- a/packages/reflex-components-radix/src/reflex_components_radix/themes/layout/base.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/layout/base.py @@ -5,9 +5,9 @@ from typing import Literal from reflex_components_core.core.breakpoints import Responsive +from reflex_core.vars.base import Var from reflex.components.component import field -from reflex.vars.base import Var from reflex_components_radix.themes.base import ( CommonMarginProps, CommonPaddingProps, diff --git a/packages/reflex-components-radix/src/reflex_components_radix/themes/layout/container.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/layout/container.py index 9cdcc9080cf..f62592d2068 100644 --- a/packages/reflex-components-radix/src/reflex_components_radix/themes/layout/container.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/layout/container.py @@ -6,10 +6,10 @@ from reflex_components_core.core.breakpoints import Responsive from reflex_components_core.el import elements +from reflex_core.vars.base import LiteralVar, Var from reflex.components.component import field from reflex.style import STACK_CHILDREN_FULL_WIDTH -from reflex.vars.base import LiteralVar, Var from reflex_components_radix.themes.base import RadixThemesComponent LiteralContainerSize = Literal["1", "2", "3", "4"] diff --git a/packages/reflex-components-radix/src/reflex_components_radix/themes/layout/flex.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/layout/flex.py index 9b59dc4766a..e6e19b8c5a2 100644 --- a/packages/reflex-components-radix/src/reflex_components_radix/themes/layout/flex.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/layout/flex.py @@ -6,9 +6,9 @@ from reflex_components_core.core.breakpoints import Responsive from reflex_components_core.el import elements +from reflex_core.vars.base import Var from reflex.components.component import field -from reflex.vars.base import Var from reflex_components_radix.themes.base import ( LiteralAlign, LiteralJustify, diff --git a/packages/reflex-components-radix/src/reflex_components_radix/themes/layout/grid.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/layout/grid.py index 5e9298f8c5d..18e05d70538 100644 --- a/packages/reflex-components-radix/src/reflex_components_radix/themes/layout/grid.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/layout/grid.py @@ -6,9 +6,9 @@ from reflex_components_core.core.breakpoints import Responsive from reflex_components_core.el import elements +from reflex_core.vars.base import Var from reflex.components.component import field -from reflex.vars.base import Var from reflex_components_radix.themes.base import ( LiteralAlign, LiteralJustify, diff --git a/packages/reflex-components-radix/src/reflex_components_radix/themes/layout/list.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/layout/list.py index e09e474ac31..923ebba5b9b 100644 --- a/packages/reflex-components-radix/src/reflex_components_radix/themes/layout/list.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/layout/list.py @@ -10,9 +10,9 @@ from reflex_components_core.el.elements.base import BaseHTML from reflex_components_core.el.elements.typography import Li, Ol, Ul from reflex_components_lucide.icon import Icon +from reflex_core.vars.base import Var from reflex.components.component import ComponentNamespace, field -from reflex.vars.base import Var from reflex_components_radix.themes.typography.text import Text LiteralListStyleTypeUnordered = Literal[ diff --git a/packages/reflex-components-radix/src/reflex_components_radix/themes/layout/section.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/layout/section.py index b7ec569abbc..c2db752a286 100644 --- a/packages/reflex-components-radix/src/reflex_components_radix/themes/layout/section.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/layout/section.py @@ -6,9 +6,9 @@ from reflex_components_core.core.breakpoints import Responsive from reflex_components_core.el import elements +from reflex_core.vars.base import LiteralVar, Var from reflex.components.component import field -from reflex.vars.base import LiteralVar, Var from reflex_components_radix.themes.base import RadixThemesComponent LiteralSectionSize = Literal["1", "2", "3"] diff --git a/packages/reflex-components-radix/src/reflex_components_radix/themes/layout/stack.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/layout/stack.py index c7463875fb8..b0fb41e3f06 100644 --- a/packages/reflex-components-radix/src/reflex_components_radix/themes/layout/stack.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/layout/stack.py @@ -3,9 +3,9 @@ from __future__ import annotations from reflex_components_core.core.breakpoints import Responsive +from reflex_core.vars.base import Var from reflex.components.component import Component, field -from reflex.vars.base import Var from reflex_components_radix.themes.base import LiteralAlign, LiteralSpacing from .flex import Flex, LiteralFlexDirection diff --git a/packages/reflex-components-radix/src/reflex_components_radix/themes/typography/blockquote.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/typography/blockquote.py index 689af36f89e..48801e09123 100644 --- a/packages/reflex-components-radix/src/reflex_components_radix/themes/typography/blockquote.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/typography/blockquote.py @@ -7,9 +7,9 @@ from reflex_components_core.core.breakpoints import Responsive from reflex_components_core.el import elements +from reflex_core.vars.base import Var from reflex.components.component import field -from reflex.vars.base import Var from reflex_components_radix.themes.base import LiteralAccentColor, RadixThemesComponent from .base import LiteralTextSize, LiteralTextWeight diff --git a/packages/reflex-components-radix/src/reflex_components_radix/themes/typography/code.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/typography/code.py index 809d0160f20..f552232fe01 100644 --- a/packages/reflex-components-radix/src/reflex_components_radix/themes/typography/code.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/typography/code.py @@ -8,9 +8,9 @@ from reflex_components_core.core.breakpoints import Responsive from reflex_components_core.core.markdown_component_map import MarkdownComponentMap from reflex_components_core.el import elements +from reflex_core.vars.base import Var from reflex.components.component import field -from reflex.vars.base import Var from reflex_components_radix.themes.base import ( LiteralAccentColor, LiteralVariant, diff --git a/packages/reflex-components-radix/src/reflex_components_radix/themes/typography/heading.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/typography/heading.py index 8ff3c17ae4d..d957b7877a8 100644 --- a/packages/reflex-components-radix/src/reflex_components_radix/themes/typography/heading.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/typography/heading.py @@ -8,9 +8,9 @@ from reflex_components_core.core.breakpoints import Responsive from reflex_components_core.core.markdown_component_map import MarkdownComponentMap from reflex_components_core.el import elements +from reflex_core.vars.base import Var from reflex.components.component import field -from reflex.vars.base import Var from reflex_components_radix.themes.base import LiteralAccentColor, RadixThemesComponent from .base import LiteralTextAlign, LiteralTextSize, LiteralTextTrim, LiteralTextWeight diff --git a/packages/reflex-components-radix/src/reflex_components_radix/themes/typography/link.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/typography/link.py index b83952da9b9..f27803d0516 100644 --- a/packages/reflex-components-radix/src/reflex_components_radix/themes/typography/link.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/typography/link.py @@ -13,10 +13,10 @@ from reflex_components_core.core.markdown_component_map import MarkdownComponentMap from reflex_components_core.el.elements.inline import A from reflex_components_react_router.dom import ReactRouterLink +from reflex_core.vars.base import Var from reflex.components.component import Component, MemoizationLeaf, field from reflex.utils.imports import ImportDict, ImportVar -from reflex.vars.base import Var from reflex_components_radix.themes.base import LiteralAccentColor, RadixThemesComponent from .base import LiteralTextSize, LiteralTextTrim, LiteralTextWeight diff --git a/packages/reflex-components-radix/src/reflex_components_radix/themes/typography/text.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/typography/text.py index 64ab2a62757..2850a31af78 100644 --- a/packages/reflex-components-radix/src/reflex_components_radix/themes/typography/text.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/typography/text.py @@ -10,9 +10,9 @@ from reflex_components_core.core.breakpoints import Responsive from reflex_components_core.core.markdown_component_map import MarkdownComponentMap from reflex_components_core.el import elements +from reflex_core.vars.base import Var from reflex.components.component import ComponentNamespace, field -from reflex.vars.base import Var from reflex_components_radix.themes.base import LiteralAccentColor, RadixThemesComponent from .base import LiteralTextAlign, LiteralTextSize, LiteralTextTrim, LiteralTextWeight diff --git a/packages/reflex-components-react-player/src/reflex_components_react_player/react_player.py b/packages/reflex-components-react-player/src/reflex_components_react_player/react_player.py index af01d475c88..0ed2b7251f2 100644 --- a/packages/reflex-components-react-player/src/reflex_components_react_player/react_player.py +++ b/packages/reflex-components-react-player/src/reflex_components_react_player/react_player.py @@ -5,12 +5,12 @@ from typing import Any, TypedDict from reflex_components_core.core.cond import cond +from reflex_core.vars.base import Var +from reflex_core.vars.object import ObjectVar from reflex.components.component import Component, field from reflex.event import EventHandler, no_args_event_spec from reflex.utils import console -from reflex.vars.base import Var -from reflex.vars.object import ObjectVar ReactPlayerEvent = ObjectVar[dict[str, dict[str, dict[str, Any]]]] diff --git a/packages/reflex-components-react-router/src/reflex_components_react_router/dom.py b/packages/reflex-components-react-router/src/reflex_components_react_router/dom.py index 9f744773ee8..c4d8c3ac457 100644 --- a/packages/reflex-components-react-router/src/reflex_components_react_router/dom.py +++ b/packages/reflex-components-react-router/src/reflex_components_react_router/dom.py @@ -5,9 +5,9 @@ from typing import ClassVar, Literal, TypedDict from reflex_components_core.el.elements.inline import A +from reflex_core.vars.base import Var from reflex.components.component import field -from reflex.vars.base import Var LiteralLinkDiscover = Literal["none", "render"] diff --git a/packages/reflex-components-recharts/src/reflex_components_recharts/cartesian.py b/packages/reflex-components-recharts/src/reflex_components_recharts/cartesian.py index 314d6b91ce6..84c13ec6baa 100644 --- a/packages/reflex-components-recharts/src/reflex_components_recharts/cartesian.py +++ b/packages/reflex-components-recharts/src/reflex_components_recharts/cartesian.py @@ -5,11 +5,12 @@ from collections.abc import Sequence from typing import Any, ClassVar, TypedDict +from reflex_core.vars.base import LiteralVar, Var + from reflex.components.component import field from reflex.constants import EventTriggers from reflex.constants.colors import Color from reflex.event import EventHandler, no_args_event_spec -from reflex.vars.base import LiteralVar, Var from .recharts import ( ACTIVE_DOT_TYPE, diff --git a/packages/reflex-components-recharts/src/reflex_components_recharts/charts.py b/packages/reflex-components-recharts/src/reflex_components_recharts/charts.py index c9a9853c269..ee0132edcd1 100644 --- a/packages/reflex-components-recharts/src/reflex_components_recharts/charts.py +++ b/packages/reflex-components-recharts/src/reflex_components_recharts/charts.py @@ -5,11 +5,12 @@ from collections.abc import Sequence from typing import Any, ClassVar +from reflex_core.vars.base import Var + from reflex.components.component import Component, field from reflex.constants import EventTriggers from reflex.constants.colors import Color from reflex.event import EventHandler, no_args_event_spec -from reflex.vars.base import Var from reflex_components_recharts.general import ResponsiveContainer from .recharts import ( diff --git a/packages/reflex-components-recharts/src/reflex_components_recharts/general.py b/packages/reflex-components-recharts/src/reflex_components_recharts/general.py index aeb7fa98ecd..76431c272ee 100644 --- a/packages/reflex-components-recharts/src/reflex_components_recharts/general.py +++ b/packages/reflex-components-recharts/src/reflex_components_recharts/general.py @@ -5,10 +5,11 @@ from collections.abc import Sequence from typing import Any, ClassVar +from reflex_core.vars.base import LiteralVar, Var + from reflex.components.component import MemoizationLeaf, field from reflex.constants.colors import Color from reflex.event import EventHandler, no_args_event_spec -from reflex.vars.base import LiteralVar, Var from .recharts import ( LiteralAnimationEasing, diff --git a/packages/reflex-components-recharts/src/reflex_components_recharts/polar.py b/packages/reflex-components-recharts/src/reflex_components_recharts/polar.py index bcbd2d87a8d..c7962a5a9f0 100644 --- a/packages/reflex-components-recharts/src/reflex_components_recharts/polar.py +++ b/packages/reflex-components-recharts/src/reflex_components_recharts/polar.py @@ -5,11 +5,12 @@ from collections.abc import Sequence from typing import Any, ClassVar +from reflex_core.vars.base import LiteralVar, Var + from reflex.components.component import field from reflex.constants import EventTriggers from reflex.constants.colors import Color from reflex.event import EventHandler, no_args_event_spec -from reflex.vars.base import LiteralVar, Var from .recharts import ( ACTIVE_DOT_TYPE, diff --git a/packages/reflex-components-sonner/src/reflex_components_sonner/toast.py b/packages/reflex-components-sonner/src/reflex_components_sonner/toast.py index 5514d1a8c80..938235d58c0 100644 --- a/packages/reflex-components-sonner/src/reflex_components_sonner/toast.py +++ b/packages/reflex-components-sonner/src/reflex_components_sonner/toast.py @@ -6,6 +6,11 @@ from typing import Any, Literal from reflex_components_lucide.icon import Icon +from reflex_core.vars import VarData +from reflex_core.vars.base import LiteralVar, Var +from reflex_core.vars.function import FunctionVar +from reflex_core.vars.number import ternary_operation +from reflex_core.vars.object import ObjectVar from reflex.components.component import Component, ComponentNamespace, field from reflex.components.props import NoExtrasAllowedProps @@ -15,11 +20,6 @@ from reflex.utils import format from reflex.utils.imports import ImportVar from reflex.utils.serializers import serializer -from reflex.vars import VarData -from reflex.vars.base import LiteralVar, Var -from reflex.vars.function import FunctionVar -from reflex.vars.number import ternary_operation -from reflex.vars.object import ObjectVar LiteralPosition = Literal[ "top-left", diff --git a/packages/reflex-core/src/reflex_core/components/component.py b/packages/reflex-core/src/reflex_core/components/component.py index 804984a36c2..82d60203b22 100644 --- a/packages/reflex-core/src/reflex_core/components/component.py +++ b/packages/reflex-core/src/reflex_core/components/component.py @@ -17,11 +17,11 @@ from types import SimpleNamespace from typing import TYPE_CHECKING, Any, ClassVar, TypeVar, cast, get_args, get_origin -from reflex_core.breakpoints import Breakpoints from rich.markup import escape from typing_extensions import dataclass_transform from reflex_core import constants +from reflex_core.breakpoints import Breakpoints from reflex_core.compiler.templates import stateful_component_template from reflex_core.components.dynamic import load_dynamic_serializer from reflex_core.components.field import BaseField, FieldBasedMeta @@ -60,7 +60,11 @@ Var, cached_property_no_lock, ) -from reflex_core.vars.function import ArgsFunctionOperation, FunctionStringVar, FunctionVar +from reflex_core.vars.function import ( + ArgsFunctionOperation, + FunctionStringVar, + FunctionVar, +) from reflex_core.vars.number import ternary_operation from reflex_core.vars.object import ObjectVar from reflex_core.vars.sequence import LiteralArrayVar, LiteralStringVar, StringVar diff --git a/packages/reflex-core/src/reflex_core/constants/__init__.py b/packages/reflex-core/src/reflex_core/constants/__init__.py index bd9ae03f1ee..f9068c74cb5 100644 --- a/packages/reflex-core/src/reflex_core/constants/__init__.py +++ b/packages/reflex-core/src/reflex_core/constants/__init__.py @@ -10,12 +10,12 @@ Reflex, ) from .compiler import ( + SETTER_PREFIX, Hooks, Imports, MemoizationDisposition, MemoizationMode, PageNames, - SETTER_PREFIX, ) from .event import EventTriggers from .state import StateManagerMode diff --git a/packages/reflex-core/src/reflex_core/style.py b/packages/reflex-core/src/reflex_core/style.py index 72f1ac9664c..2aa2dc090e3 100644 --- a/packages/reflex-core/src/reflex_core/style.py +++ b/packages/reflex-core/src/reflex_core/style.py @@ -5,9 +5,8 @@ from collections.abc import Mapping from typing import Any, Literal -from reflex_core.breakpoints import Breakpoints, breakpoints_values - from reflex_core import constants +from reflex_core.breakpoints import Breakpoints, breakpoints_values from reflex_core.event import EventChain, EventHandler, EventSpec, run_script from reflex_core.utils import format from reflex_core.utils.exceptions import ReflexError diff --git a/packages/reflex-core/src/reflex_core/utils/format.py b/packages/reflex-core/src/reflex_core/utils/format.py index 3a06a574256..2f1eb20db46 100644 --- a/packages/reflex-core/src/reflex_core/utils/format.py +++ b/packages/reflex-core/src/reflex_core/utils/format.py @@ -14,7 +14,13 @@ if TYPE_CHECKING: from reflex_core.components.component import ComponentStyle - from reflex_core.event import ArgsSpec, EventChain, EventHandler, EventSpec, EventType + from reflex_core.event import ( + ArgsSpec, + EventChain, + EventHandler, + EventSpec, + EventType, + ) WRAP_MAP = { "{": "}", diff --git a/packages/reflex-core/src/reflex_core/utils/pyi_generator.py b/packages/reflex-core/src/reflex_core/utils/pyi_generator.py index e54551abf70..0f4fcb0f6a8 100644 --- a/packages/reflex-core/src/reflex_core/utils/pyi_generator.py +++ b/packages/reflex-core/src/reflex_core/utils/pyi_generator.py @@ -119,7 +119,7 @@ def _safe_issubclass(cls: Any, cls_check: Any | tuple[Any, ...]) -> bool: "PointerEventInfo", ], "reflex.style": ["Style"], - "reflex.vars.base": ["Var"], + "reflex_core.vars.base": ["Var"], } @@ -682,7 +682,7 @@ def _get_parent_imports(func: Callable) -> Mapping[str, tuple[str, ...]]: Returns: An immutable mapping of module names to imported symbol names. """ - imports_: dict[str, set[str]] = {"reflex.vars": {"Var"}} + imports_: dict[str, set[str]] = {"reflex_core.vars": {"Var"}} module_dir = set(dir(importlib.import_module(func.__module__))) for type_hint in inspect.get_annotations(func).values(): try: diff --git a/packages/reflex-core/src/reflex_core/vars/base.py b/packages/reflex-core/src/reflex_core/vars/base.py index 80446dbde83..10e8be87385 100644 --- a/packages/reflex-core/src/reflex_core/vars/base.py +++ b/packages/reflex-core/src/reflex_core/vars/base.py @@ -72,8 +72,8 @@ ) if TYPE_CHECKING: - from reflex_core.components.component import BaseComponent from reflex.state import BaseState + from reflex_core.components.component import BaseComponent from reflex_core.constants.colors import Color from .color import LiteralColorVar diff --git a/packages/reflex-docgen/src/reflex_docgen/_class.py b/packages/reflex-docgen/src/reflex_docgen/_class.py index 3526f6e8abc..005cce5152b 100644 --- a/packages/reflex-docgen/src/reflex_docgen/_class.py +++ b/packages/reflex-docgen/src/reflex_docgen/_class.py @@ -5,10 +5,9 @@ from dataclasses import dataclass from typing import Any, get_args, get_type_hints +from reflex_core.vars.base import BaseStateMeta from typing_inspection.introspection import AnnotationSource, inspect_annotation -from reflex.vars.base import BaseStateMeta - @dataclass(frozen=True, slots=True, kw_only=True) class FieldDocumentation: diff --git a/pyi_hashes.json b/pyi_hashes.json index 9d34f3ba46e..2723c365c94 100644 --- a/pyi_hashes.json +++ b/pyi_hashes.json @@ -1,124 +1,121 @@ { - "packages/reflex-components-code/src/reflex_components_code/code.pyi": "a2718f93b53790394297b3b7d5f8515e", - "packages/reflex-components-code/src/reflex_components_code/shiki_code_block.pyi": "332ef077e91dfd8d4ffd3ae0c68d95d6", + "packages/reflex-components-code/src/reflex_components_code/code.pyi": "2319e7886182f5ef039f0a1ed1d62317", + "packages/reflex-components-code/src/reflex_components_code/shiki_code_block.pyi": "45379e4c1fc1f214b7a5d66d90f76f1c", "packages/reflex-components-core/src/reflex_components_core/__init__.pyi": "6c3ceff429117483dd0035e7a21930f4", "packages/reflex-components-core/src/reflex_components_core/base/__init__.pyi": "4beb5ba739680b7974c37241f3f6791c", - "packages/reflex-components-core/src/reflex_components_core/base/app_wrap.pyi": "f8cb9cee04ffa9006d74db1b3500aadf", - "packages/reflex-components-core/src/reflex_components_core/base/body.pyi": "3ad765bf23f5da9cee280b44a80dbcc5", - "packages/reflex-components-core/src/reflex_components_core/base/document.pyi": "ca67174aeb6419d320b6c54a1cdd1955", - "packages/reflex-components-core/src/reflex_components_core/base/error_boundary.pyi": "19dcd8cba0603b6371d24d768eaf84f4", - "packages/reflex-components-core/src/reflex_components_core/base/fragment.pyi": "75a5f1a9ac628f3f211cd7d72c605aa2", - "packages/reflex-components-core/src/reflex_components_core/base/link.pyi": "8893f124131be7c2f60f086fa4beb255", - "packages/reflex-components-core/src/reflex_components_core/base/meta.pyi": "1a1904b9118c22fdae1b5e70e5006aa7", - "packages/reflex-components-core/src/reflex_components_core/base/script.pyi": "58bae1b5f9d6fabd284752e0adf8d868", - "packages/reflex-components-core/src/reflex_components_core/base/strict_mode.pyi": "a037c6fcb007d884915e52a6ab0046af", + "packages/reflex-components-core/src/reflex_components_core/base/app_wrap.pyi": "900f0927a8fbaea6d70a6c5f79ffe390", + "packages/reflex-components-core/src/reflex_components_core/base/body.pyi": "d05d36b0049a1ca9f48828de0a780264", + "packages/reflex-components-core/src/reflex_components_core/base/document.pyi": "abff8b40e875b1fd5a49adc602c0b3cb", + "packages/reflex-components-core/src/reflex_components_core/base/fragment.pyi": "203b060bc2ed6a1ee4f5139f3643a41b", + "packages/reflex-components-core/src/reflex_components_core/base/link.pyi": "560d2c9c4a21d5a99ecdaf7f220874d3", + "packages/reflex-components-core/src/reflex_components_core/base/meta.pyi": "d094461c8a1d56ba02102fce4940eff6", + "packages/reflex-components-core/src/reflex_components_core/base/script.pyi": "d6c683e3ad0d791b254c5961ff467ada", + "packages/reflex-components-core/src/reflex_components_core/base/strict_mode.pyi": "490676ec4319d738aee31c3975361591", "packages/reflex-components-core/src/reflex_components_core/core/__init__.pyi": "17f79762cef09c69acd9df227cf3bb35", - "packages/reflex-components-core/src/reflex_components_core/core/auto_scroll.pyi": "59a02f780bd1568c26f7991c99bf2110", - "packages/reflex-components-core/src/reflex_components_core/core/banner.pyi": "26dfe9462144c32280876d4f65b40507", - "packages/reflex-components-core/src/reflex_components_core/core/clipboard.pyi": "3bf314f1e3a1faf9c060cd34f8fa7079", - "packages/reflex-components-core/src/reflex_components_core/core/debounce.pyi": "dab0e776a0fbe8c2525ed8950bdf0293", - "packages/reflex-components-core/src/reflex_components_core/core/helmet.pyi": "9fa0dea0b63a975dafe2b4c06c5a008c", - "packages/reflex-components-core/src/reflex_components_core/core/html.pyi": "f3d8fe298d93bc20aee9d6461f7d5b17", - "packages/reflex-components-core/src/reflex_components_core/core/sticky.pyi": "472a83abd9c53f8e89755f095e9bb706", - "packages/reflex-components-core/src/reflex_components_core/core/upload.pyi": "ee6fa123b4726b17b581f5887984a65e", - "packages/reflex-components-core/src/reflex_components_core/core/window_events.pyi": "22dbc116b98a9e70a36cb1ef846adc62", + "packages/reflex-components-core/src/reflex_components_core/core/auto_scroll.pyi": "3bdf9e60d4b7aa9043143561eacdb911", + "packages/reflex-components-core/src/reflex_components_core/core/banner.pyi": "bd5825c02dafee1b9a85dc9511004bd0", + "packages/reflex-components-core/src/reflex_components_core/core/clipboard.pyi": "be619a31f5f16891f1ad561f7483eb77", + "packages/reflex-components-core/src/reflex_components_core/core/debounce.pyi": "a34556c3d3e3a1cd498be2deba1926d5", + "packages/reflex-components-core/src/reflex_components_core/core/helmet.pyi": "bb1b4d7468cb1496bb301ade46354d3e", + "packages/reflex-components-core/src/reflex_components_core/core/html.pyi": "690ef52cd0c56fcc397826156a79c1e7", + "packages/reflex-components-core/src/reflex_components_core/core/sticky.pyi": "9e817ab33107cb59402e3aebf12c50cc", + "packages/reflex-components-core/src/reflex_components_core/core/upload.pyi": "30a3d28d7e17045f5fd667a50a6731df", + "packages/reflex-components-core/src/reflex_components_core/core/window_events.pyi": "c05b5e617a5f1e534aaa15b6130006cc", "packages/reflex-components-core/src/reflex_components_core/datadisplay/__init__.pyi": "73e27e0bfd7e914c4baba7b166e75736", "packages/reflex-components-core/src/reflex_components_core/el/__init__.pyi": "a1d4d65ddc73b5fbba3726f4bcf6d6f0", - "packages/reflex-components-core/src/reflex_components_core/el/element.pyi": "0cb57beaf2a3dd03be9d3b008bb0a916", + "packages/reflex-components-core/src/reflex_components_core/el/element.pyi": "071bd8e46b6b65806c938d343ec739cd", "packages/reflex-components-core/src/reflex_components_core/el/elements/__init__.pyi": "fbe4c6f4960bcd811311b1f73987cdb1", - "packages/reflex-components-core/src/reflex_components_core/el/elements/base.pyi": "7220e4f079af9f3473491751a4cacef1", - "packages/reflex-components-core/src/reflex_components_core/el/elements/forms.pyi": "da68f5ced0afa890ad79e646aecc094a", - "packages/reflex-components-core/src/reflex_components_core/el/elements/inline.pyi": "d0d132e32abe141ca2940637609875bd", - "packages/reflex-components-core/src/reflex_components_core/el/elements/media.pyi": "77703a560c2aa2f013ae58e28c6b0c88", - "packages/reflex-components-core/src/reflex_components_core/el/elements/metadata.pyi": "cc4c5bd0128af7b98dc44923c37739b6", - "packages/reflex-components-core/src/reflex_components_core/el/elements/other.pyi": "8f775db675bb055cd8c75c4ea00892f9", - "packages/reflex-components-core/src/reflex_components_core/el/elements/scripts.pyi": "6c3925340de81d4d26d1de74c5ed24e9", - "packages/reflex-components-core/src/reflex_components_core/el/elements/sectioning.pyi": "919119139837bd01475e90173602c381", - "packages/reflex-components-core/src/reflex_components_core/el/elements/tables.pyi": "8b7933cb9f4c11734f49ec41f2b80553", - "packages/reflex-components-core/src/reflex_components_core/el/elements/typography.pyi": "f5cfe2996ae3aaf7bd630c852c0e9068", - "packages/reflex-components-dataeditor/src/reflex_components_dataeditor/dataeditor.pyi": "0f8ec6d513cc649caf10981cf29bcd38", - "packages/reflex-components-gridjs/src/reflex_components_gridjs/datatable.pyi": "3dcdc432507f1a455b97f719160118ac", - "packages/reflex-components-lucide/src/reflex_components_lucide/icon.pyi": "21ac24351c0e0de123dec46a7e90209b", - "packages/reflex-components-markdown/src/reflex_components_markdown/markdown.pyi": "726d0c3d7fd6ef38110e77555fa69380", - "packages/reflex-components-moment/src/reflex_components_moment/moment.pyi": "dffd48bde7241e80093b2f8143cfc30e", - "packages/reflex-components-plotly/src/reflex_components_plotly/plotly.pyi": "84b5f3f01d5fcb60067be9105a4a12ba", + "packages/reflex-components-core/src/reflex_components_core/el/elements/base.pyi": "b004e6bfbb7de0789a6581274959dbb3", + "packages/reflex-components-core/src/reflex_components_core/el/elements/forms.pyi": "9ef8d52bef8fd00746c7f28b955ca340", + "packages/reflex-components-core/src/reflex_components_core/el/elements/inline.pyi": "a7d7c110149e0df7fd5798d29789d194", + "packages/reflex-components-core/src/reflex_components_core/el/elements/media.pyi": "88731777d7a7157543a46d44d5d69855", + "packages/reflex-components-core/src/reflex_components_core/el/elements/metadata.pyi": "be4af13583cd210538a5bf401aee4653", + "packages/reflex-components-core/src/reflex_components_core/el/elements/other.pyi": "b38e41dbbc6ab30e9ee5fbb32cf675b9", + "packages/reflex-components-core/src/reflex_components_core/el/elements/scripts.pyi": "6391432c86072d5da87a1a54f4e861e9", + "packages/reflex-components-core/src/reflex_components_core/el/elements/sectioning.pyi": "6cd334cc7c5999ee4ae4c03455688313", + "packages/reflex-components-core/src/reflex_components_core/el/elements/tables.pyi": "28162491f97aeb8af88ec3b3d5faa275", + "packages/reflex-components-core/src/reflex_components_core/el/elements/typography.pyi": "80368be4e5e781a6ece17e452d37719b", + "packages/reflex-components-dataeditor/src/reflex_components_dataeditor/dataeditor.pyi": "d7183df23f15feb744bea0c3ae786a5e", + "packages/reflex-components-gridjs/src/reflex_components_gridjs/datatable.pyi": "ffe864c848a9d775809b4edbc1b731b6", + "packages/reflex-components-lucide/src/reflex_components_lucide/icon.pyi": "a3605c42f5b6b40e1f174dc299df2c9a", + "packages/reflex-components-markdown/src/reflex_components_markdown/markdown.pyi": "1bad32ee95b99fe53e9257186ddf62f5", + "packages/reflex-components-moment/src/reflex_components_moment/moment.pyi": "082cda5a15e08c64be7e2b9490af4828", + "packages/reflex-components-plotly/src/reflex_components_plotly/plotly.pyi": "ac6382621506be5298009616966f9897", "packages/reflex-components-radix/src/reflex_components_radix/__init__.pyi": "899d63be42eb21c187519920518bf32e", "packages/reflex-components-radix/src/reflex_components_radix/primitives/__init__.pyi": "11bc17db31334f11d7fcde315b29cae1", - "packages/reflex-components-radix/src/reflex_components_radix/primitives/accordion.pyi": "e3cbfe19cadd7e24e7a3556b898e9170", - "packages/reflex-components-radix/src/reflex_components_radix/primitives/base.pyi": "4ed7151c05f155a7140b608e3350b684", - "packages/reflex-components-radix/src/reflex_components_radix/primitives/dialog.pyi": "75539bdf369af2bab188335a0cad59f0", - "packages/reflex-components-radix/src/reflex_components_radix/primitives/drawer.pyi": "8eed776a75cc0583a5a27e12205fc22b", - "packages/reflex-components-radix/src/reflex_components_radix/primitives/form.pyi": "47f6c48ecd0733b6183af90a65797f45", - "packages/reflex-components-radix/src/reflex_components_radix/primitives/progress.pyi": "5181780f01dcb17a996d383a9fbda0e1", - "packages/reflex-components-radix/src/reflex_components_radix/primitives/slider.pyi": "dfeab92ef06442a0339eed625c8902f8", + "packages/reflex-components-radix/src/reflex_components_radix/primitives/accordion.pyi": "e9a06d65e8709de70231db48e5aca96a", + "packages/reflex-components-radix/src/reflex_components_radix/primitives/base.pyi": "c5d92880310ab703ea93d4b1a056eb86", + "packages/reflex-components-radix/src/reflex_components_radix/primitives/dialog.pyi": "a79fdb748d331b5987cecc6ee0a34ee6", + "packages/reflex-components-radix/src/reflex_components_radix/primitives/drawer.pyi": "a1c94022c183cae0528d144c0588d62a", + "packages/reflex-components-radix/src/reflex_components_radix/primitives/form.pyi": "a066a50b11c2b9507eb0544d0df3f668", + "packages/reflex-components-radix/src/reflex_components_radix/primitives/progress.pyi": "ac977b63b3640eeaa21204e6e9dcf40c", + "packages/reflex-components-radix/src/reflex_components_radix/primitives/slider.pyi": "a4d76b1220aeb40a53b5f6033f67ff04", "packages/reflex-components-radix/src/reflex_components_radix/themes/__init__.pyi": "a152450345e7da9124e954843224e936", - "packages/reflex-components-radix/src/reflex_components_radix/themes/base.pyi": "678774944ad675c1efad0dcd5ac26097", - "packages/reflex-components-radix/src/reflex_components_radix/themes/color_mode.pyi": "cdf329dd430dfb35bc0fc0c22903d2bf", + "packages/reflex-components-radix/src/reflex_components_radix/themes/base.pyi": "9ae9885a592496736170bf61d3869ee0", "packages/reflex-components-radix/src/reflex_components_radix/themes/components/__init__.pyi": "67d45aa96a97359ee556725641bb583f", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/alert_dialog.pyi": "4f332c3ca24a26d2349503689eb89c8c", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/aspect_ratio.pyi": "559909085f8c1cf37fad1c4ecd2502d7", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/avatar.pyi": "c8730ae9413528fc88caeb0a013e60ee", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/badge.pyi": "9227edf16541a8f0e413efb1d9cdca42", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/button.pyi": "6114e8e6330932100884e435544a3a52", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/callout.pyi": "d356e8555fa4a3141783827355f66524", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/card.pyi": "05e5b293b2d5388d619c5b4676609950", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/checkbox.pyi": "3d1439b90dbc2b1d4995e7ed91e040f3", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/checkbox_cards.pyi": "0f5736c128c8e6a7cf7a3c7eef1e7159", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/checkbox_group.pyi": "19042165e9ff53de6be5e8c18e61429f", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/context_menu.pyi": "0affc657216038379632808dcbbec1f5", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/data_list.pyi": "86fa8b592994d23e99d585d64056d0c1", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/dialog.pyi": "3cfc6ed392463054db7b41db012af862", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/dropdown_menu.pyi": "da1560563223edcb56cf8436ab1f5ef6", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/hover_card.pyi": "557e3cb9553c13fd3d2bae7c30371176", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/icon_button.pyi": "632ee951c76dc22bf63c904d2cbc74a7", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/inset.pyi": "a1e3374cda158ea64e180da6ce61d265", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/popover.pyi": "1442682c3b937f79ef84737ac5464245", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/progress.pyi": "2d064ffabbc2ed3a23e8f46d833b0277", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/radio.pyi": "a331ef9d0d869a997562d9f987e1814f", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/radio_cards.pyi": "4c236e3c3d1ed17ce062cd63a3eb613f", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/radio_group.pyi": "0355e29cf569e1405091bb29f525e86b", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/scroll_area.pyi": "5b7223cab1ac1ac42bdeeab6cd72bd8a", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/segmented_control.pyi": "d7451983f9591ff7b81f3bfbd72a2689", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/select.pyi": "791cb1c3c5c67f77bd581c6d0b99ab9b", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/separator.pyi": "00a858bbdc0d35f2220775eb06d2d733", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/skeleton.pyi": "fd31c794583ec744207312fa1a72c3d1", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/slider.pyi": "683128339dfe213d8a7401b75814953b", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/spinner.pyi": "713adb1481b8e3c525fd054d417239b0", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/switch.pyi": "7e038a571514d7ddedba423797498b4c", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/table.pyi": "1bdfc7f3f18894378a5629c3d16546fc", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/tabs.pyi": "079c79809feaea387c07716083184c94", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/text_area.pyi": "36d3ecbef7a6b28554f400fc41433522", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/text_field.pyi": "be36d108861eb1c5d486175637bd78d1", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/tooltip.pyi": "a68e09331535bbf6187d1efbd248baf1", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/alert_dialog.pyi": "a58ad421715ce6f80faecc371e6f51d4", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/aspect_ratio.pyi": "f6a1f6388abca0317671e336dcabfc4b", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/avatar.pyi": "99024032ec9f36c918174a3dd13d195e", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/badge.pyi": "04c9570befb04530ceb6f6a1de2dd581", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/button.pyi": "580e9f0b9ad5f5473e11adfdc6513166", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/callout.pyi": "a332c402424d159ab7e9f7a8a2145be1", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/card.pyi": "589ded8d2c453fbfd0c3c5a70f3f9826", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/checkbox.pyi": "6ca7329b87a775ff90cfceade116b28e", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/checkbox_cards.pyi": "aca084957873b350361996cd1e26ff28", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/checkbox_group.pyi": "4af3f42fd55c2a5810aad89f2077f03d", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/context_menu.pyi": "831a5c8aecf3fe8e3afcc863b2e6edbb", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/data_list.pyi": "d526eb5152b616f93214f68670a0f4a9", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/dialog.pyi": "bb0c627b1e07cd8d47ef7eb6218cbcd1", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/dropdown_menu.pyi": "ad62973010567ef84cb29dc485425216", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/hover_card.pyi": "e4e730069f4e3aa20ecbf1f62a2f2911", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/icon_button.pyi": "e357ea5ef2c2c6d59decf528676c768d", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/inset.pyi": "d8ea4c7d2e69dc6fd27d98c3697b95c2", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/popover.pyi": "93a612094af186d17ee2394fce24fb99", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/progress.pyi": "cef3d099f95eb9ac18745df3cb57e9f6", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/radio.pyi": "117da2df15b89b430cd2d6c693d7eb30", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/radio_cards.pyi": "17c13d2209eaf47f43baab0c63574785", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/radio_group.pyi": "4501692b53bf26a18ddf968d4e212b0c", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/scroll_area.pyi": "2e3084f01527cb2a6be0db0cbac27237", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/segmented_control.pyi": "e16843a5243ae03a69c3c13f88910b74", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/select.pyi": "7a9e52419a261ad12ce065443bef7d7c", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/separator.pyi": "445408b7ee359bceddd897f164656e9b", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/skeleton.pyi": "b1bbdcba9221cb264bc202e78ecf7ede", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/slider.pyi": "41a518be074ba740d995cbc0956ddf52", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/spinner.pyi": "4856ba4446ad21985a070d04dd6293d8", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/switch.pyi": "45fb1de0f43bf272ce5bfff9802839e8", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/table.pyi": "9aed7860e0541a82f69ae0ac37a44444", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/tabs.pyi": "7c4cafb3bf6ef7c6254e30f30f8744b0", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/text_area.pyi": "a7689a98975afd666422cf4eec3a3eed", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/text_field.pyi": "743552036a6ae8143d8ce8d23b0d6fc5", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/tooltip.pyi": "7fc4cecd0e2cc70ae25870908c142cb8", "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/__init__.pyi": "b7dbdecab770a7fc1435ad417d7f91c1", - "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/base.pyi": "a63c001cf714739c04a86556951e9bfc", - "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/box.pyi": "17f83beeb3c6c83b1571ea5d4887f76c", - "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/center.pyi": "123b2d713d9845f2715576d5cf9f2536", - "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/container.pyi": "fe41ccaa90e859f2f8a74b015fbde058", - "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/flex.pyi": "f615a989fcfbda790de3f1305c42605a", - "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/grid.pyi": "2a23d9b1d7f2098f3e05e6deeb7453fb", - "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/list.pyi": "d6c7fc4dc8422d9ca3d020f4005e48de", - "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/section.pyi": "edada2fc5d0139b61d035485ecaf5ebb", - "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/spacer.pyi": "a289f92e5da0ec4576e4c0bc1ee4986c", - "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/stack.pyi": "b857d2ed0549a217fa28e3cb86c52ffb", + "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/base.pyi": "b541c3e56e4b9afdb208db97b3c7846d", + "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/box.pyi": "744e6e83ab08c3f151715c5e459a03d2", + "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/center.pyi": "c80413d4051b478e37c10a5e6e132f25", + "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/container.pyi": "b86f337365de46582e71f32135609ad2", + "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/flex.pyi": "4d5128e75445be5cc15d73006abbb1ec", + "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/grid.pyi": "ab58ab7820e10612a3fd1fddb0729003", + "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/section.pyi": "295bf854ef0d0feadda6fbfdb7788244", + "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/spacer.pyi": "02275e5a87cf57b880f6ee0cdde9ac94", + "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/stack.pyi": "b8e21fbae89af6a5d72beec914e196c4", "packages/reflex-components-radix/src/reflex_components_radix/themes/typography/__init__.pyi": "15b13877fc52b82182fc42acd8adb027", - "packages/reflex-components-radix/src/reflex_components_radix/themes/typography/blockquote.pyi": "833c57bded556a13db59726769183230", - "packages/reflex-components-radix/src/reflex_components_radix/themes/typography/code.pyi": "66e385994fcfc53183d2cba39e101174", - "packages/reflex-components-radix/src/reflex_components_radix/themes/typography/heading.pyi": "25c23877aba76ab41c5347e127a4e279", - "packages/reflex-components-radix/src/reflex_components_radix/themes/typography/link.pyi": "3ab992394caf0da003641a6b9dccbdde", - "packages/reflex-components-radix/src/reflex_components_radix/themes/typography/text.pyi": "0d4675582c25f95ff2317c3342e2b8c6", - "packages/reflex-components-react-player/src/reflex_components_react_player/audio.pyi": "383e6c81567915da364dd7c02705dd7c", - "packages/reflex-components-react-player/src/reflex_components_react_player/react_player.pyi": "20deae7c420096252c8958f1d9194b9e", - "packages/reflex-components-react-player/src/reflex_components_react_player/video.pyi": "58e2a086560862b2f9ecd1f18840e893", - "packages/reflex-components-react-router/src/reflex_components_react_router/dom.pyi": "41c140176951d4536af9d51aa22da96e", + "packages/reflex-components-radix/src/reflex_components_radix/themes/typography/blockquote.pyi": "f03e588746d92248a8b2510cc19e43a6", + "packages/reflex-components-radix/src/reflex_components_radix/themes/typography/code.pyi": "ac3723aef4c9574578c67316e3ec7ebb", + "packages/reflex-components-radix/src/reflex_components_radix/themes/typography/heading.pyi": "b8961f85b0df2f70d345db45d138ea7b", + "packages/reflex-components-radix/src/reflex_components_radix/themes/typography/link.pyi": "2dbd09f648d921572aad7e064d3d2ce1", + "packages/reflex-components-radix/src/reflex_components_radix/themes/typography/text.pyi": "dea370cc7f7e4a75cb671d7ec0ccd949", + "packages/reflex-components-react-player/src/reflex_components_react_player/audio.pyi": "551894a3f295deac9fed3fffb3f171b7", + "packages/reflex-components-react-player/src/reflex_components_react_player/react_player.pyi": "4961725c725fec0538b9e3bdbeafbcfe", + "packages/reflex-components-react-player/src/reflex_components_react_player/video.pyi": "4ca94af43518e83e1b9ee5da55a4843b", + "packages/reflex-components-react-router/src/reflex_components_react_router/dom.pyi": "5c13be18f98b23ed957e53c3a26c6216", "packages/reflex-components-recharts/src/reflex_components_recharts/__init__.pyi": "1f04ab9f482261f18010fbf728d7b946", - "packages/reflex-components-recharts/src/reflex_components_recharts/cartesian.pyi": "cb57586301f693b6667e29e916a34427", - "packages/reflex-components-recharts/src/reflex_components_recharts/charts.pyi": "e6e69455c92b91a8cd37cec7d83b3589", - "packages/reflex-components-recharts/src/reflex_components_recharts/general.pyi": "6bdf04181af6b94f06ebc07e32649a13", - "packages/reflex-components-recharts/src/reflex_components_recharts/polar.pyi": "7af2bc6774ef1c5240165a6afff12633", - "packages/reflex-components-recharts/src/reflex_components_recharts/recharts.pyi": "83a840eceb0515ce7d27d52644ff60a5", - "packages/reflex-components-sonner/src/reflex_components_sonner/toast.pyi": "296cb0d27bc1774f3a330ca9f79fe2bd", + "packages/reflex-components-recharts/src/reflex_components_recharts/cartesian.pyi": "5229afe951d976abc280aa34f72ae8ac", + "packages/reflex-components-recharts/src/reflex_components_recharts/charts.pyi": "c1aef94f3a866c62bec8f82897bd85e8", + "packages/reflex-components-recharts/src/reflex_components_recharts/general.pyi": "e243056a5614ee9e1d8be66d94bba737", + "packages/reflex-components-recharts/src/reflex_components_recharts/polar.pyi": "e27277901bad6b5d2729a6eca8ac4895", + "packages/reflex-components-recharts/src/reflex_components_recharts/recharts.pyi": "fa3dfbba1e25242fdb9787e280b43d1d", + "packages/reflex-components-sonner/src/reflex_components_sonner/toast.pyi": "efd01b2494c53ede3668c8c16e7cdfe7", "reflex/__init__.pyi": "77b2efa084aa8473dce836f78fc4f61a", "reflex/components/__init__.pyi": "d620d52015f908cda828d231c1064236", - "reflex/experimental/memo.pyi": "72b2c75d02f165dde5a3229210d5545d" + "reflex/experimental/memo.pyi": "555f5495d3ddd30dc974eacbb046662a" } diff --git a/pyproject.toml b/pyproject.toml index 6f45b42a90e..33fb0b15602 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -200,8 +200,8 @@ lint.ignore = [ ] lint.pydocstyle.convention = "google" lint.flake8-bugbear.extend-immutable-calls = [ - "reflex.utils.types.Unset", - "reflex.vars.base.Var.create", + "reflex_core.utils.types.Unset", + "reflex_core.vars.base.Var.create", ] [tool.ruff.lint.per-file-ignores] diff --git a/reflex/app.py b/reflex/app.py index 2bfbbfdecb6..01cb8f536f6 100644 --- a/reflex/app.py +++ b/reflex/app.py @@ -43,6 +43,15 @@ from reflex_components_core.core.sticky import sticky from reflex_components_radix import themes from reflex_components_sonner.toast import toast +from reflex_core.event import ( + _EVENT_FIELDS, + Event, + EventSpec, + EventType, + IndividualEventType, + get_hydrate_event, + noop, +) from rich.progress import MofNCompleteColumn, Progress, TimeElapsedColumn from socketio import ASGIApp as EngineIOApp from socketio import AsyncNamespace, AsyncServer @@ -73,15 +82,6 @@ ) from reflex.config import get_config from reflex.environment import ExecutorType, environment -from reflex.event import ( - _EVENT_FIELDS, - Event, - EventSpec, - EventType, - IndividualEventType, - get_hydrate_event, - noop, -) from reflex.experimental.memo import EXPERIMENTAL_MEMOS from reflex.istate.manager import StateModificationContext from reflex.istate.proxy import StateProxy @@ -124,7 +124,7 @@ from reflex.utils.types import ASGIApp, Message, Receive, Scope, Send if TYPE_CHECKING: - from reflex.vars import Var + from reflex_core.vars import Var # Define custom types. ComponentCallable = Callable[[], Component | tuple[Component, ...] | str | Var] @@ -577,8 +577,9 @@ def __call__(self) -> ASGIApp: Raises: ValueError: If the app has not been initialized. """ + from reflex_core.vars.base import GLOBAL_CACHE + from reflex.assets import remove_stale_external_asset_symlinks - from reflex.vars.base import GLOBAL_CACHE # Clean up stale symlinks in assets/external/ before compiling, so that # rx.asset(shared=True) symlink re-creation doesn't trigger further reloads. diff --git a/reflex/compiler/compiler.py b/reflex/compiler/compiler.py index f834a6900a9..896643861e5 100644 --- a/reflex/compiler/compiler.py +++ b/reflex/compiler/compiler.py @@ -9,6 +9,7 @@ from typing import TYPE_CHECKING, Any from reflex_components_core.base.fragment import Fragment +from reflex_core.vars.base import LiteralVar, Var from reflex import constants from reflex.compiler import templates, utils @@ -36,7 +37,6 @@ from reflex.utils.format import to_title_case from reflex.utils.imports import ImportVar, ParsedImportDict from reflex.utils.prerequisites import get_web_dir -from reflex.vars.base import LiteralVar, Var def _apply_common_imports( diff --git a/reflex/compiler/templates.py b/reflex/compiler/templates.py index 3ee59558de7..42cea30e3b0 100644 --- a/reflex/compiler/templates.py +++ b/reflex/compiler/templates.py @@ -1,3 +1,3 @@ """Re-export from reflex_core.""" -from reflex_core.compiler.templates import * # noqa: F401, F403 +from reflex_core.compiler.templates import * diff --git a/reflex/compiler/utils.py b/reflex/compiler/utils.py index 437e6e2e99d..ddd2bf55433 100644 --- a/reflex/compiler/utils.py +++ b/reflex/compiler/utils.py @@ -19,6 +19,8 @@ from reflex_components_core.el.elements.metadata import Head, Link, Meta, Title from reflex_components_core.el.elements.other import Html from reflex_components_core.el.elements.sectioning import Body +from reflex_core.vars.base import Field, Var, VarData +from reflex_core.vars.function import DestructuredArg from reflex import constants from reflex.components.component import Component, ComponentStyle, CustomComponent @@ -33,8 +35,6 @@ from reflex.utils import format, imports, path_ops from reflex.utils.imports import ImportVar, ParsedImportDict from reflex.utils.prerequisites import get_web_dir -from reflex.vars.base import Field, Var, VarData -from reflex.vars.function import DestructuredArg # To re-export this function. merge_imports = imports.merge_imports diff --git a/reflex/components/component.py b/reflex/components/component.py index 18c4ba767de..5a82e9ec3eb 100644 --- a/reflex/components/component.py +++ b/reflex/components/component.py @@ -1,3 +1,3 @@ """Re-export from reflex_core.""" -from reflex_core.components.component import * # noqa: F401, F403 +from reflex_core.components.component import * diff --git a/reflex/components/dynamic.py b/reflex/components/dynamic.py index 3cc1f291d5f..951d21d14c2 100644 --- a/reflex/components/dynamic.py +++ b/reflex/components/dynamic.py @@ -1,3 +1,3 @@ """Re-export from reflex_core.""" -from reflex_core.components.dynamic import * # noqa: F401, F403 +from reflex_core.components.dynamic import * diff --git a/reflex/components/field.py b/reflex/components/field.py index a371bb2d55b..a58c45e230f 100644 --- a/reflex/components/field.py +++ b/reflex/components/field.py @@ -1,3 +1,3 @@ """Re-export from reflex_core.""" -from reflex_core.components.field import * # noqa: F401, F403 +from reflex_core.components.field import * diff --git a/reflex/components/props.py b/reflex/components/props.py index bfa03e50851..c1334231b4c 100644 --- a/reflex/components/props.py +++ b/reflex/components/props.py @@ -6,6 +6,7 @@ from dataclasses import _MISSING_TYPE, MISSING from typing import Any, TypeVar, get_args, get_origin +from reflex_core.vars.object import LiteralObjectVar from typing_extensions import dataclass_transform from reflex.components.field import BaseField, FieldBasedMeta @@ -14,7 +15,6 @@ from reflex.utils.exceptions import InvalidPropValueError from reflex.utils.serializers import serializer from reflex.utils.types import is_union -from reflex.vars.object import LiteralObjectVar PROPS_FIELD_TYPE = TypeVar("PROPS_FIELD_TYPE") diff --git a/reflex/components/tags/__init__.py b/reflex/components/tags/__init__.py index aad7b918ccc..7ac42b86898 100644 --- a/reflex/components/tags/__init__.py +++ b/reflex/components/tags/__init__.py @@ -1,3 +1,3 @@ """Re-export from reflex_core.""" -from reflex_core.components.tags import * # noqa: F401, F403 +from reflex_core.components.tags import * diff --git a/reflex/components/tags/cond_tag.py b/reflex/components/tags/cond_tag.py index 4217bcc58bc..af4b393cf7d 100644 --- a/reflex/components/tags/cond_tag.py +++ b/reflex/components/tags/cond_tag.py @@ -1,3 +1,3 @@ """Re-export from reflex_core.""" -from reflex_core.components.tags.cond_tag import * # noqa: F401, F403 +from reflex_core.components.tags.cond_tag import * diff --git a/reflex/components/tags/iter_tag.py b/reflex/components/tags/iter_tag.py index 5850484b989..c26ed69b3e3 100644 --- a/reflex/components/tags/iter_tag.py +++ b/reflex/components/tags/iter_tag.py @@ -1,3 +1,3 @@ """Re-export from reflex_core.""" -from reflex_core.components.tags.iter_tag import * # noqa: F401, F403 +from reflex_core.components.tags.iter_tag import * diff --git a/reflex/components/tags/match_tag.py b/reflex/components/tags/match_tag.py index 0d079d270fd..fc26bd1b38e 100644 --- a/reflex/components/tags/match_tag.py +++ b/reflex/components/tags/match_tag.py @@ -1,3 +1,3 @@ """Re-export from reflex_core.""" -from reflex_core.components.tags.match_tag import * # noqa: F401, F403 +from reflex_core.components.tags.match_tag import * diff --git a/reflex/components/tags/tag.py b/reflex/components/tags/tag.py index b2592212198..61ae1502fcb 100644 --- a/reflex/components/tags/tag.py +++ b/reflex/components/tags/tag.py @@ -1,3 +1,3 @@ """Re-export from reflex_core.""" -from reflex_core.components.tags.tag import * # noqa: F401, F403 +from reflex_core.components.tags.tag import * diff --git a/reflex/components/tags/tagless.py b/reflex/components/tags/tagless.py index 6eaa8e85fee..7e40f8e3ee0 100644 --- a/reflex/components/tags/tagless.py +++ b/reflex/components/tags/tagless.py @@ -1,3 +1,3 @@ """Re-export from reflex_core.""" -from reflex_core.components.tags.tagless import * # noqa: F401, F403 +from reflex_core.components.tags.tagless import * diff --git a/reflex/constants/base.py b/reflex/constants/base.py index efacb9974a7..cb4eb5d32ea 100644 --- a/reflex/constants/base.py +++ b/reflex/constants/base.py @@ -1,3 +1,3 @@ """Re-export from reflex_core.""" -from reflex_core.constants.base import * # noqa: F401, F403 +from reflex_core.constants.base import * diff --git a/reflex/constants/colors.py b/reflex/constants/colors.py index 007eacd6dd2..8810ebc5429 100644 --- a/reflex/constants/colors.py +++ b/reflex/constants/colors.py @@ -1,3 +1,3 @@ """Re-export from reflex_core.""" -from reflex_core.constants.colors import * # noqa: F401, F403 +from reflex_core.constants.colors import * diff --git a/reflex/constants/compiler.py b/reflex/constants/compiler.py index 2168d30749f..5b1103a6a31 100644 --- a/reflex/constants/compiler.py +++ b/reflex/constants/compiler.py @@ -1,3 +1,3 @@ """Re-export from reflex_core.""" -from reflex_core.constants.compiler import * # noqa: F401, F403 +from reflex_core.constants.compiler import * diff --git a/reflex/constants/event.py b/reflex/constants/event.py index 12ae3c55c1d..32dc8581e7f 100644 --- a/reflex/constants/event.py +++ b/reflex/constants/event.py @@ -1,3 +1,3 @@ """Re-export from reflex_core.""" -from reflex_core.constants.event import * # noqa: F401, F403 +from reflex_core.constants.event import * diff --git a/reflex/constants/state.py b/reflex/constants/state.py index 02ccba72343..9e688c8e8aa 100644 --- a/reflex/constants/state.py +++ b/reflex/constants/state.py @@ -1,3 +1,3 @@ """Re-export from reflex_core.""" -from reflex_core.constants.state import * # noqa: F401, F403 +from reflex_core.constants.state import * diff --git a/reflex/event.py b/reflex/event.py index e986be1d651..b1ed3b3db8a 100644 --- a/reflex/event.py +++ b/reflex/event.py @@ -1,3 +1,3 @@ """Re-export from reflex_core.""" -from reflex_core.event import * # noqa: F401, F403 +from reflex_core.event import * diff --git a/reflex/experimental/client_state.py b/reflex/experimental/client_state.py index 7e7063fbd97..952e3ad2d98 100644 --- a/reflex/experimental/client_state.py +++ b/reflex/experimental/client_state.py @@ -7,12 +7,13 @@ from collections.abc import Callable from typing import Any +from reflex_core.vars import VarData, get_unique_variable_name +from reflex_core.vars.base import LiteralVar, Var +from reflex_core.vars.function import ArgsFunctionOperationBuilder, FunctionVar + from reflex import constants from reflex.event import EventChain, EventHandler, EventSpec, run_script from reflex.utils.imports import ImportVar -from reflex.vars import VarData, get_unique_variable_name -from reflex.vars.base import LiteralVar, Var -from reflex.vars.function import ArgsFunctionOperationBuilder, FunctionVar NoValue = object() diff --git a/reflex/experimental/hooks.py b/reflex/experimental/hooks.py index c00dd3bf925..ef2eba3b0e2 100644 --- a/reflex/experimental/hooks.py +++ b/reflex/experimental/hooks.py @@ -2,9 +2,10 @@ from __future__ import annotations +from reflex_core.vars import VarData +from reflex_core.vars.base import Var + from reflex.utils.imports import ImportVar -from reflex.vars import VarData -from reflex.vars.base import Var def _compose_react_imports(tags: list[str]) -> dict[str, list[ImportVar]]: diff --git a/reflex/experimental/memo.py b/reflex/experimental/memo.py index 97c7ecb1bad..1c6297fe20f 100644 --- a/reflex/experimental/memo.py +++ b/reflex/experimental/memo.py @@ -10,6 +10,16 @@ from reflex_components_core.base.bare import Bare from reflex_components_core.base.fragment import Fragment +from reflex_core.vars import VarData +from reflex_core.vars.base import LiteralVar, Var +from reflex_core.vars.function import ( + ArgsFunctionOperation, + DestructuredArg, + FunctionStringVar, + FunctionVar, + ReflexCallable, +) +from reflex_core.vars.object import RestProp from reflex import constants from reflex.components.component import Component @@ -19,16 +29,6 @@ from reflex.utils import format from reflex.utils import types as type_utils from reflex.utils.imports import ImportVar -from reflex.vars import VarData -from reflex.vars.base import LiteralVar, Var -from reflex.vars.function import ( - ArgsFunctionOperation, - DestructuredArg, - FunctionStringVar, - FunctionVar, - ReflexCallable, -) -from reflex.vars.object import RestProp @dataclasses.dataclass(frozen=True, slots=True, kw_only=True) diff --git a/reflex/istate/proxy.py b/reflex/istate/proxy.py index 91074356ef3..89b7b2260ec 100644 --- a/reflex/istate/proxy.py +++ b/reflex/istate/proxy.py @@ -15,13 +15,13 @@ from typing import TYPE_CHECKING, Any, SupportsIndex, TypeVar import wrapt +from reflex_core.vars.base import Var from typing_extensions import Self from reflex.event import Event from reflex.utils import prerequisites from reflex.utils.exceptions import ImmutableStateError from reflex.utils.serializers import can_serialize, serialize, serializer -from reflex.vars.base import Var if TYPE_CHECKING: from reflex.state import BaseState, StateUpdate diff --git a/reflex/state.py b/reflex/state.py index 7062affeda3..185f91a6e2e 100644 --- a/reflex/state.py +++ b/reflex/state.py @@ -32,6 +32,17 @@ get_type_hints, ) +from reflex_core.utils.types import _isinstance, is_union, value_inside_optional +from reflex_core.vars import Field, VarData, field +from reflex_core.vars.base import ( + ComputedVar, + DynamicRouteVar, + EvenMoreBasicBaseState, + Var, + computed_var, + dispatch, + is_computed_var, +) from rich.markup import escape from typing_extensions import Self @@ -71,17 +82,6 @@ ) from reflex.utils.exceptions import ImmutableStateError as ImmutableStateError from reflex.utils.exec import is_testing_env -from reflex.utils.types import _isinstance, is_union, value_inside_optional -from reflex.vars import Field, VarData, field -from reflex.vars.base import ( - ComputedVar, - DynamicRouteVar, - EvenMoreBasicBaseState, - Var, - computed_var, - dispatch, - is_computed_var, -) if TYPE_CHECKING: from reflex.components.component import Component diff --git a/reflex/style.py b/reflex/style.py index 6e7065e876f..3caac782ebb 100644 --- a/reflex/style.py +++ b/reflex/style.py @@ -1,3 +1,3 @@ """Re-export from reflex_core.""" -from reflex_core.style import * # noqa: F401, F403 +from reflex_core.style import * diff --git a/reflex/utils/compat.py b/reflex/utils/compat.py index 1738102f45c..6a3570f0c0a 100644 --- a/reflex/utils/compat.py +++ b/reflex/utils/compat.py @@ -1,3 +1,3 @@ """Re-export from reflex_core.""" -from reflex_core.utils.compat import * # noqa: F401, F403 +from reflex_core.utils.compat import * diff --git a/reflex/utils/console.py b/reflex/utils/console.py index ec5e07eea88..dc03bd72c23 100644 --- a/reflex/utils/console.py +++ b/reflex/utils/console.py @@ -1,3 +1,3 @@ """Re-export from reflex_core.""" -from reflex_core.utils.console import * # noqa: F401, F403 +from reflex_core.utils.console import * diff --git a/reflex/utils/decorator.py b/reflex/utils/decorator.py index aaec02ebf02..908ddbc7d25 100644 --- a/reflex/utils/decorator.py +++ b/reflex/utils/decorator.py @@ -1,3 +1,3 @@ """Re-export from reflex_core.""" -from reflex_core.utils.decorator import * # noqa: F401, F403 +from reflex_core.utils.decorator import * diff --git a/reflex/utils/exceptions.py b/reflex/utils/exceptions.py index f2a63d36021..d0cc24d089e 100644 --- a/reflex/utils/exceptions.py +++ b/reflex/utils/exceptions.py @@ -1,3 +1,3 @@ """Re-export from reflex_core.""" -from reflex_core.utils.exceptions import * # noqa: F401, F403 +from reflex_core.utils.exceptions import * diff --git a/reflex/utils/exec.py b/reflex/utils/exec.py index c67976b23b2..28be5ee4171 100644 --- a/reflex/utils/exec.py +++ b/reflex/utils/exec.py @@ -15,11 +15,13 @@ from typing import Any, NamedTuple, TypedDict from urllib.parse import urljoin +from reflex_core.utils import console + from reflex import constants from reflex.config import get_config from reflex.constants.base import LogLevel from reflex.environment import environment -from reflex.utils import console, path_ops +from reflex.utils import path_ops from reflex.utils.decorator import once from reflex.utils.misc import get_module_path from reflex.utils.prerequisites import get_web_dir diff --git a/reflex/utils/export.py b/reflex/utils/export.py index cc58a659f25..c6c22c4fe19 100644 --- a/reflex/utils/export.py +++ b/reflex/utils/export.py @@ -2,10 +2,12 @@ from pathlib import Path +from reflex_core.utils import console + from reflex import constants from reflex.config import get_config from reflex.environment import environment -from reflex.utils import build, console, exec, prerequisites, telemetry +from reflex.utils import build, exec, prerequisites, telemetry def export( diff --git a/reflex/utils/format.py b/reflex/utils/format.py index 84a1d6f8039..b5bfc9d23b3 100644 --- a/reflex/utils/format.py +++ b/reflex/utils/format.py @@ -1,3 +1,3 @@ """Re-export from reflex_core.""" -from reflex_core.utils.format import * # noqa: F401, F403 +from reflex_core.utils.format import * diff --git a/reflex/utils/imports.py b/reflex/utils/imports.py index 8965fae2249..a40f3b6648b 100644 --- a/reflex/utils/imports.py +++ b/reflex/utils/imports.py @@ -1,3 +1,3 @@ """Re-export from reflex_core.""" -from reflex_core.utils.imports import * # noqa: F401, F403 +from reflex_core.utils.imports import * diff --git a/reflex/utils/pyi_generator.py b/reflex/utils/pyi_generator.py index c67d448628f..9a58d31e5ca 100644 --- a/reflex/utils/pyi_generator.py +++ b/reflex/utils/pyi_generator.py @@ -1,3 +1,3 @@ """Re-export from reflex_core.""" -from reflex_core.utils.pyi_generator import * # noqa: F401, F403 +from reflex_core.utils.pyi_generator import * diff --git a/reflex/utils/serializers.py b/reflex/utils/serializers.py index e4c0cfd5a6c..4ddf05bb29d 100644 --- a/reflex/utils/serializers.py +++ b/reflex/utils/serializers.py @@ -1,3 +1,3 @@ """Re-export from reflex_core.""" -from reflex_core.utils.serializers import * # noqa: F401, F403 +from reflex_core.utils.serializers import * diff --git a/reflex/utils/types.py b/reflex/utils/types.py index dc3ee548ab5..9345271874c 100644 --- a/reflex/utils/types.py +++ b/reflex/utils/types.py @@ -1,3 +1,3 @@ """Re-export from reflex_core.""" -from reflex_core.utils.types import * # noqa: F401, F403 +from reflex_core.utils.types import * diff --git a/reflex/vars/__init__.py b/reflex/vars/__init__.py index 04e3fd83f76..6aa4b67a6c1 100644 --- a/reflex/vars/__init__.py +++ b/reflex/vars/__init__.py @@ -1,4 +1,4 @@ """Immutable-Based Var System.""" -from reflex_core.vars import * # noqa: F401, F403 +from reflex_core.vars import * from reflex_core.vars import __all__ diff --git a/reflex/vars/base.py b/reflex/vars/base.py index 68b81bf9358..45dd044f98a 100644 --- a/reflex/vars/base.py +++ b/reflex/vars/base.py @@ -1,3 +1,3 @@ """Re-export from reflex_core.""" -from reflex_core.vars.base import * # noqa: F401, F403 +from reflex_core.vars.base import * diff --git a/reflex/vars/color.py b/reflex/vars/color.py index 37494d9b021..7fdf802e9b4 100644 --- a/reflex/vars/color.py +++ b/reflex/vars/color.py @@ -1,3 +1,3 @@ """Re-export from reflex_core.""" -from reflex_core.vars.color import * # noqa: F401, F403 +from reflex_core.vars.color import * diff --git a/reflex/vars/datetime.py b/reflex/vars/datetime.py index 58c176f8e7c..da48d048787 100644 --- a/reflex/vars/datetime.py +++ b/reflex/vars/datetime.py @@ -1,3 +1,3 @@ """Re-export from reflex_core.""" -from reflex_core.vars.datetime import * # noqa: F401, F403 +from reflex_core.vars.datetime import * diff --git a/reflex/vars/dep_tracking.py b/reflex/vars/dep_tracking.py index c0b91022d02..4d65fca8fe6 100644 --- a/reflex/vars/dep_tracking.py +++ b/reflex/vars/dep_tracking.py @@ -1,3 +1,3 @@ """Re-export from reflex_core.""" -from reflex_core.vars.dep_tracking import * # noqa: F401, F403 +from reflex_core.vars.dep_tracking import * diff --git a/reflex/vars/function.py b/reflex/vars/function.py index 547e61efbd6..4343403cbc4 100644 --- a/reflex/vars/function.py +++ b/reflex/vars/function.py @@ -1,3 +1,3 @@ """Re-export from reflex_core.""" -from reflex_core.vars.function import * # noqa: F401, F403 +from reflex_core.vars.function import * diff --git a/reflex/vars/number.py b/reflex/vars/number.py index 555916bce88..72565a5c469 100644 --- a/reflex/vars/number.py +++ b/reflex/vars/number.py @@ -1,3 +1,3 @@ """Re-export from reflex_core.""" -from reflex_core.vars.number import * # noqa: F401, F403 +from reflex_core.vars.number import * diff --git a/reflex/vars/object.py b/reflex/vars/object.py index 71e691f4c6d..bfadb1e4ee0 100644 --- a/reflex/vars/object.py +++ b/reflex/vars/object.py @@ -1,3 +1,3 @@ """Re-export from reflex_core.""" -from reflex_core.vars.object import * # noqa: F401, F403 +from reflex_core.vars.object import * diff --git a/reflex/vars/sequence.py b/reflex/vars/sequence.py index a6bce968e9e..0a26e20d514 100644 --- a/reflex/vars/sequence.py +++ b/reflex/vars/sequence.py @@ -1,3 +1,3 @@ """Re-export from reflex_core.""" -from reflex_core.vars.sequence import * # noqa: F401, F403 +from reflex_core.vars.sequence import * diff --git a/scripts/make_pyi.py b/scripts/make_pyi.py index 6ee5d0c125c..81c20bb4ac5 100644 --- a/scripts/make_pyi.py +++ b/scripts/make_pyi.py @@ -5,7 +5,7 @@ import sys from pathlib import Path -from reflex.utils.pyi_generator import PyiGenerator, _relative_to_pwd +from reflex_core.utils.pyi_generator import PyiGenerator, _relative_to_pwd logger = logging.getLogger("pyi_generator") diff --git a/tests/integration/test_var_operations.py b/tests/integration/test_var_operations.py index 4988e2e7dc1..7de76586e97 100644 --- a/tests/integration/test_var_operations.py +++ b/tests/integration/test_var_operations.py @@ -13,10 +13,10 @@ def VarOperations(): from typing import TypedDict import pydantic + from reflex_core.vars.base import LiteralVar + from reflex_core.vars.sequence import ArrayVar import reflex as rx - from reflex.vars.base import LiteralVar - from reflex.vars.sequence import ArrayVar class Object(pydantic.BaseModel): name: str = "hello" diff --git a/tests/units/compiler/test_compiler.py b/tests/units/compiler/test_compiler.py index 2cac70f49da..c112e6e9267 100644 --- a/tests/units/compiler/test_compiler.py +++ b/tests/units/compiler/test_compiler.py @@ -6,13 +6,13 @@ from pytest_mock import MockerFixture from reflex_components_core.base import document from reflex_components_core.el.elements.metadata import Link +from reflex_core.vars.base import Var +from reflex_core.vars.sequence import LiteralStringVar from reflex import constants from reflex.compiler import compiler, utils from reflex.constants.compiler import PageNames from reflex.utils.imports import ImportVar, ParsedImportDict -from reflex.vars.base import Var -from reflex.vars.sequence import LiteralStringVar @pytest.mark.parametrize( diff --git a/tests/units/components/base/test_bare.py b/tests/units/components/base/test_bare.py index 91e3c58fcf4..52fd2b358e4 100644 --- a/tests/units/components/base/test_bare.py +++ b/tests/units/components/base/test_bare.py @@ -1,7 +1,6 @@ import pytest from reflex_components_core.base.bare import Bare - -from reflex.vars.base import Var +from reflex_core.vars.base import Var STATE_VAR = Var(_js_expr="default_state.name") diff --git a/tests/units/components/core/test_colors.py b/tests/units/components/core/test_colors.py index 5e3f621ec61..17ece7305c9 100644 --- a/tests/units/components/core/test_colors.py +++ b/tests/units/components/core/test_colors.py @@ -1,10 +1,10 @@ import pytest from reflex_components_code.code import CodeBlock +from reflex_core.vars.base import LiteralVar import reflex as rx from reflex.constants.colors import Color from reflex.constants.state import FIELD_MARKER -from reflex.vars.base import LiteralVar class ColorState(rx.State): diff --git a/tests/units/components/core/test_cond.py b/tests/units/components/core/test_cond.py index 9175ded0974..45eb760d680 100644 --- a/tests/units/components/core/test_cond.py +++ b/tests/units/components/core/test_cond.py @@ -5,11 +5,11 @@ from reflex_components_core.base.fragment import Fragment from reflex_components_core.core.cond import Cond, cond from reflex_components_radix.themes.typography.text import Text +from reflex_core.vars.base import LiteralVar, Var, computed_var from reflex.constants.state import FIELD_MARKER from reflex.state import BaseState from reflex.utils.format import format_state_name -from reflex.vars.base import LiteralVar, Var, computed_var @pytest.fixture diff --git a/tests/units/components/core/test_debounce.py b/tests/units/components/core/test_debounce.py index 44aacbaf122..57e91763fad 100644 --- a/tests/units/components/core/test_debounce.py +++ b/tests/units/components/core/test_debounce.py @@ -2,10 +2,10 @@ import pytest from reflex_components_core.core.debounce import DEFAULT_DEBOUNCE_TIMEOUT +from reflex_core.vars.base import LiteralVar, Var import reflex as rx from reflex.state import BaseState -from reflex.vars.base import LiteralVar, Var def test_create_no_child(): diff --git a/tests/units/components/core/test_foreach.py b/tests/units/components/core/test_foreach.py index af070f6d1ad..11b16c51314 100644 --- a/tests/units/components/core/test_foreach.py +++ b/tests/units/components/core/test_foreach.py @@ -8,14 +8,14 @@ ) from reflex_components_radix.themes.layout.box import box from reflex_components_radix.themes.typography.text import text +from reflex_core.vars.number import NumberVar +from reflex_core.vars.sequence import ArrayVar import reflex as rx from reflex import el from reflex.components.component import Component from reflex.constants.state import FIELD_MARKER from reflex.state import BaseState, ComponentState -from reflex.vars.number import NumberVar -from reflex.vars.sequence import ArrayVar class ForEachTag(pydantic.BaseModel): diff --git a/tests/units/components/core/test_match.py b/tests/units/components/core/test_match.py index 310f9d41cb2..d9fa318113b 100644 --- a/tests/units/components/core/test_match.py +++ b/tests/units/components/core/test_match.py @@ -2,13 +2,13 @@ import pytest from reflex_components_core.core.match import Match +from reflex_core.vars.base import Var import reflex as rx from reflex.components.component import Component from reflex.constants.state import FIELD_MARKER from reflex.state import BaseState from reflex.utils.exceptions import MatchTypeError -from reflex.vars.base import Var class MatchState(BaseState): @@ -249,7 +249,7 @@ def test_match_case_tuple_elements(match_case): ), ( 'Match cases should have the same return types. Case 3 with return value `"red"` of type ' - " is not " + " is not " ), ), ( @@ -264,7 +264,7 @@ def test_match_case_tuple_elements(match_case): ), ( 'Match cases should have the same return types. Case 3 with return value `jsx(RadixThemesText,{as:"p"},"first value")` ' - "of type is not " + "of type is not " ), ), ], diff --git a/tests/units/components/core/test_upload.py b/tests/units/components/core/test_upload.py index 82a03b03c24..b907697fc5d 100644 --- a/tests/units/components/core/test_upload.py +++ b/tests/units/components/core/test_upload.py @@ -9,12 +9,12 @@ cancel_upload, get_upload_url, ) +from reflex_core.vars.base import LiteralVar, Var import reflex as rx from reflex import event from reflex.event import EventChain, EventHandler, EventSpec from reflex.state import State -from reflex.vars.base import LiteralVar, Var class UploadStateTest(State): diff --git a/tests/units/components/datadisplay/test_shiki_code.py b/tests/units/components/datadisplay/test_shiki_code.py index 5b33be816f7..5a7fa93c5ee 100644 --- a/tests/units/components/datadisplay/test_shiki_code.py +++ b/tests/units/components/datadisplay/test_shiki_code.py @@ -8,10 +8,10 @@ from reflex_components_core.el.elements.forms import Button from reflex_components_lucide.icon import Icon from reflex_components_radix.themes.layout.box import Box +from reflex_core.vars import Var +from reflex_core.vars.base import LiteralVar from reflex.style import Style -from reflex.vars import Var -from reflex.vars.base import LiteralVar @pytest.mark.parametrize( diff --git a/tests/units/components/forms/test_form.py b/tests/units/components/forms/test_form.py index ab80d2c9147..492f6ba3e62 100644 --- a/tests/units/components/forms/test_form.py +++ b/tests/units/components/forms/test_form.py @@ -1,7 +1,7 @@ from reflex_components_radix.primitives.form import Form +from reflex_core.vars.base import Var from reflex.event import EventChain, prevent_default -from reflex.vars.base import Var def test_render_on_submit(): diff --git a/tests/units/components/markdown/test_markdown.py b/tests/units/components/markdown/test_markdown.py index 3ec94cd6f0e..76100922a08 100644 --- a/tests/units/components/markdown/test_markdown.py +++ b/tests/units/components/markdown/test_markdown.py @@ -5,9 +5,9 @@ from reflex_components_markdown.markdown import Markdown from reflex_components_radix.themes.layout.box import Box from reflex_components_radix.themes.typography.heading import Heading +from reflex_core.vars.base import Var from reflex.components.component import Component, memo -from reflex.vars.base import Var class CustomMarkdownComponent(Component, MarkdownComponentMap): diff --git a/tests/units/components/radix/test_icon_button.py b/tests/units/components/radix/test_icon_button.py index 7314293b970..123c12db264 100644 --- a/tests/units/components/radix/test_icon_button.py +++ b/tests/units/components/radix/test_icon_button.py @@ -1,9 +1,9 @@ import pytest from reflex_components_lucide.icon import Icon from reflex_components_radix.themes.components.icon_button import IconButton +from reflex_core.vars.base import LiteralVar from reflex.style import Style -from reflex.vars.base import LiteralVar def test_icon_button(): diff --git a/tests/units/components/test_component.py b/tests/units/components/test_component.py index 97b189f7d51..b248b1fc92a 100644 --- a/tests/units/components/test_component.py +++ b/tests/units/components/test_component.py @@ -6,6 +6,9 @@ from reflex_components_core.base.bare import Bare from reflex_components_core.base.fragment import Fragment from reflex_components_radix.themes.layout.box import Box +from reflex_core.vars import VarData +from reflex_core.vars.base import LiteralVar, Var +from reflex_core.vars.object import ObjectVar import reflex as rx from reflex.compiler.utils import compile_custom_component @@ -36,9 +39,6 @@ EventHandlerArgTypeMismatchError, ) from reflex.utils.imports import ImportDict, ImportVar, ParsedImportDict, parse_imports -from reflex.vars import VarData -from reflex.vars.base import LiteralVar, Var -from reflex.vars.object import ObjectVar class TestState(BaseState): diff --git a/tests/units/components/test_tag.py b/tests/units/components/test_tag.py index 32ad2d7068f..303f0d21f01 100644 --- a/tests/units/components/test_tag.py +++ b/tests/units/components/test_tag.py @@ -1,7 +1,7 @@ import pytest +from reflex_core.vars.base import LiteralVar, Var from reflex.components.tags import CondTag, Tag, tagless -from reflex.vars.base import LiteralVar, Var @pytest.mark.parametrize( diff --git a/tests/units/experimental/test_memo.py b/tests/units/experimental/test_memo.py index 7de8bc012e9..3abfe75ae8f 100644 --- a/tests/units/experimental/test_memo.py +++ b/tests/units/experimental/test_memo.py @@ -6,6 +6,9 @@ from typing import Any import pytest +from reflex_core.vars import VarData +from reflex_core.vars.base import Var +from reflex_core.vars.function import FunctionVar import reflex as rx from reflex.compiler import compiler @@ -19,9 +22,6 @@ ) from reflex.style import Style from reflex.utils.imports import ImportVar -from reflex.vars import VarData -from reflex.vars.base import Var -from reflex.vars.function import FunctionVar @pytest.fixture(autouse=True) diff --git a/tests/units/test_app.py b/tests/units/test_app.py index 16f4c280a0c..32c2bf12a3e 100644 --- a/tests/units/test_app.py +++ b/tests/units/test_app.py @@ -20,6 +20,7 @@ from reflex_components_core.base.fragment import Fragment from reflex_components_core.core.cond import Cond from reflex_components_radix.themes.typography.text import Text +from reflex_core.vars.base import computed_var from starlette.applications import Starlette from starlette.datastructures import FormData, Headers, UploadFile from starlette.responses import StreamingResponse @@ -52,7 +53,6 @@ ) from reflex.style import Style from reflex.utils import console, exceptions, format -from reflex.vars.base import computed_var from .conftest import chdir from .states import GenState diff --git a/tests/units/test_event.py b/tests/units/test_event.py index 10123f1f9d3..e3185cbea2d 100644 --- a/tests/units/test_event.py +++ b/tests/units/test_event.py @@ -3,6 +3,7 @@ from typing import Any, cast import pytest +from reflex_core.vars.base import Field, LiteralVar, Var, VarData, field import reflex as rx from reflex.constants.compiler import Hooks, Imports @@ -20,7 +21,6 @@ ) from reflex.state import BaseState from reflex.utils import format -from reflex.vars.base import Field, LiteralVar, Var, VarData, field def make_var(value) -> Var: diff --git a/tests/units/test_state.py b/tests/units/test_state.py index 7be18207f6a..5caad311587 100644 --- a/tests/units/test_state.py +++ b/tests/units/test_state.py @@ -20,6 +20,7 @@ from plotly.graph_objects import Figure from pydantic import BaseModel as Base from pytest_mock import MockerFixture +from reflex_core.vars.base import Field, Var, computed_var, field import reflex as rx import reflex.config @@ -58,7 +59,6 @@ ) from reflex.utils.format import json_dumps from reflex.utils.token_manager import SocketRecord -from reflex.vars.base import Field, Var, computed_var, field from tests.units.mock_redis import mock_redis from .states import GenState diff --git a/tests/units/test_style.py b/tests/units/test_style.py index 8706d7bc2ca..193b7c74339 100644 --- a/tests/units/test_style.py +++ b/tests/units/test_style.py @@ -3,14 +3,14 @@ from typing import Any import pytest +from reflex_core.vars import VarData +from reflex_core.vars.base import LiteralVar, Var import reflex as rx from reflex import style from reflex.components.component import evaluate_style_namespaces from reflex.style import Style from reflex.utils.exceptions import ReflexError -from reflex.vars import VarData -from reflex.vars.base import LiteralVar, Var style_var = rx.Var.create({"height": "42px"}) diff --git a/tests/units/test_var.py b/tests/units/test_var.py index 96205cdb83a..78fd82c6545 100644 --- a/tests/units/test_var.py +++ b/tests/units/test_var.py @@ -9,20 +9,8 @@ from pandas import DataFrame from pydantic import BaseModel as Base from pytest_mock import MockerFixture - -import reflex as rx -from reflex.constants.base import REFLEX_VAR_CLOSING_TAG, REFLEX_VAR_OPENING_TAG -from reflex.constants.state import FIELD_MARKER -from reflex.environment import PerformanceMode -from reflex.state import BaseState -from reflex.utils.exceptions import ( - PrimitiveUnserializableToJSONError, - UntypedComputedVarError, -) -from reflex.utils.imports import ImportVar -from reflex.utils.types import get_default_value_for_type -from reflex.vars import VarData -from reflex.vars.base import ( +from reflex_core.vars import VarData +from reflex_core.vars.base import ( ComputedVar, LiteralVar, Var, @@ -30,20 +18,32 @@ var_operation, var_operation_return, ) -from reflex.vars.function import ( +from reflex_core.vars.function import ( ArgsFunctionOperation, DestructuredArg, FunctionStringVar, ) -from reflex.vars.number import LiteralBooleanVar, LiteralNumberVar, NumberVar -from reflex.vars.object import LiteralObjectVar, ObjectVar -from reflex.vars.sequence import ( +from reflex_core.vars.number import LiteralBooleanVar, LiteralNumberVar, NumberVar +from reflex_core.vars.object import LiteralObjectVar, ObjectVar +from reflex_core.vars.sequence import ( ArrayVar, ConcatVarOperation, LiteralArrayVar, LiteralStringVar, ) +import reflex as rx +from reflex.constants.base import REFLEX_VAR_CLOSING_TAG, REFLEX_VAR_OPENING_TAG +from reflex.constants.state import FIELD_MARKER +from reflex.environment import PerformanceMode +from reflex.state import BaseState +from reflex.utils.exceptions import ( + PrimitiveUnserializableToJSONError, + UntypedComputedVarError, +) +from reflex.utils.imports import ImportVar +from reflex.utils.types import get_default_value_for_type + test_vars = [ Var(_js_expr="prop1", _var_type=int), Var(_js_expr="key", _var_type=str), diff --git a/tests/units/utils/test_format.py b/tests/units/utils/test_format.py index 48db442223c..4174a2b7941 100644 --- a/tests/units/utils/test_format.py +++ b/tests/units/utils/test_format.py @@ -6,6 +6,10 @@ import plotly.graph_objects as go import pytest +from reflex_core.utils import format +from reflex_core.vars.base import LiteralVar, Var +from reflex_core.vars.function import FunctionStringVar +from reflex_core.vars.object import ObjectVar from reflex.components.tags.tag import Tag from reflex.constants.state import FIELD_MARKER @@ -17,11 +21,7 @@ no_args_event_spec, ) from reflex.style import Style -from reflex.utils import format from reflex.utils.serializers import serialize_figure -from reflex.vars.base import LiteralVar, Var -from reflex.vars.function import FunctionStringVar -from reflex.vars.object import ObjectVar pytest.importorskip("pydantic") diff --git a/tests/units/utils/test_serializers.py b/tests/units/utils/test_serializers.py index 1a39bcb1811..acf87261b23 100644 --- a/tests/units/utils/test_serializers.py +++ b/tests/units/utils/test_serializers.py @@ -8,10 +8,10 @@ import pytest from pydantic import BaseModel as Base from reflex_components_core.core.colors import Color +from reflex_core.vars.base import LiteralVar from reflex.utils import serializers from reflex.utils.format import json_dumps -from reflex.vars.base import LiteralVar pytest.importorskip("pydantic") diff --git a/tests/units/utils/test_types.py b/tests/units/utils/test_types.py index c92f1156c5f..c1f0841664d 100644 --- a/tests/units/utils/test_types.py +++ b/tests/units/utils/test_types.py @@ -1,9 +1,8 @@ from typing import Any, Literal, TypedDict import pytest - -from reflex.utils import types -from reflex.vars.base import Var +from reflex_core.utils import types +from reflex_core.vars.base import Var @pytest.mark.parametrize( diff --git a/tests/units/utils/test_utils.py b/tests/units/utils/test_utils.py index a5176bc4c7b..33a0b38e873 100644 --- a/tests/units/utils/test_utils.py +++ b/tests/units/utils/test_utils.py @@ -8,6 +8,7 @@ import pytest from packaging import version from pytest_mock import MockerFixture +from reflex_core.vars.base import Var from reflex import constants from reflex.environment import environment @@ -16,7 +17,6 @@ from reflex.utils import exec as utils_exec from reflex.utils import frontend_skeleton, js_runtimes, prerequisites, templates, types from reflex.utils.exceptions import ReflexError, SystemPackageMissingError -from reflex.vars.base import Var class ExampleTestState(BaseState): diff --git a/tests/units/vars/test_base.py b/tests/units/vars/test_base.py index 54b3d48182f..e7a6b07488e 100644 --- a/tests/units/vars/test_base.py +++ b/tests/units/vars/test_base.py @@ -1,9 +1,9 @@ from collections.abc import Mapping, Sequence import pytest +from reflex_core.vars.base import computed_var, figure_out_type from reflex.state import State -from reflex.vars.base import computed_var, figure_out_type class CustomDict(dict[str, str]): diff --git a/tests/units/vars/test_dep_tracking.py b/tests/units/vars/test_dep_tracking.py index 1acd5f864bc..0ab3d08677c 100644 --- a/tests/units/vars/test_dep_tracking.py +++ b/tests/units/vars/test_dep_tracking.py @@ -5,16 +5,16 @@ import sys import pytest +from reflex_core.vars.dep_tracking import ( + DependencyTracker, + UntrackedLocalVarError, + get_cell_value, +) import reflex as rx import tests.units.states.upload as tus_upload from reflex.state import State from reflex.utils.exceptions import VarValueError -from reflex.vars.dep_tracking import ( - DependencyTracker, - UntrackedLocalVarError, - get_cell_value, -) class DependencyTestState(State): diff --git a/tests/units/vars/test_object.py b/tests/units/vars/test_object.py index 40ee1f17e4a..acccebcf602 100644 --- a/tests/units/vars/test_object.py +++ b/tests/units/vars/test_object.py @@ -3,13 +3,13 @@ import pydantic import pytest +from reflex_core.vars.base import Var +from reflex_core.vars.object import LiteralObjectVar, ObjectVar +from reflex_core.vars.sequence import ArrayVar from typing_extensions import assert_type import reflex as rx from reflex.utils.types import GenericType -from reflex.vars.base import Var -from reflex.vars.object import LiteralObjectVar, ObjectVar -from reflex.vars.sequence import ArrayVar pytest.importorskip("sqlalchemy") pytest.importorskip("pydantic") From c020c31c8498fa6ea8377bec9286aca7a6ce75c6 Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Thu, 26 Mar 2026 13:58:55 -0700 Subject: [PATCH 046/113] fix precommit --- packages/reflex-core/src/reflex_core/utils/exceptions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/reflex-core/src/reflex_core/utils/exceptions.py b/packages/reflex-core/src/reflex_core/utils/exceptions.py index edfafd3dc55..4617560e45e 100644 --- a/packages/reflex-core/src/reflex_core/utils/exceptions.py +++ b/packages/reflex-core/src/reflex_core/utils/exceptions.py @@ -263,7 +263,7 @@ def __init__(self, package: str): Args: package: The missing package. """ - from reflex_core.constants import IS_MACOS + from reflex_core.constants.base import IS_MACOS extra = ( f" You can do so by running 'brew install {package}'." if IS_MACOS else "" From e7b022aa069fbd6efe334fff783d56cbc812ecc6 Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Thu, 26 Mar 2026 14:04:41 -0700 Subject: [PATCH 047/113] maybe? --- packages/hatch-reflex-pyi/pyproject.toml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/hatch-reflex-pyi/pyproject.toml b/packages/hatch-reflex-pyi/pyproject.toml index 68817a928f1..f5d159ce207 100644 --- a/packages/hatch-reflex-pyi/pyproject.toml +++ b/packages/hatch-reflex-pyi/pyproject.toml @@ -21,6 +21,9 @@ source = "uv-dynamic-versioning" pattern-prefix = "hatch-reflex-pyi-" fallback-version = "0.0.0dev0" +[tool.uv.sources] +reflex-core.workspace = true + [build-system] requires = ["hatchling", "uv-dynamic-versioning"] build-backend = "hatchling.build" From 97de5f019138d5ad7ed51e3bf56e98c900426f5b Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Thu, 26 Mar 2026 14:08:15 -0700 Subject: [PATCH 048/113] that didn't work --- packages/hatch-reflex-pyi/pyproject.toml | 3 --- packages/reflex-components-code/pyproject.toml | 2 +- packages/reflex-components-core/pyproject.toml | 2 +- packages/reflex-components-dataeditor/pyproject.toml | 2 +- packages/reflex-components-gridjs/pyproject.toml | 2 +- packages/reflex-components-lucide/pyproject.toml | 2 +- packages/reflex-components-markdown/pyproject.toml | 2 +- packages/reflex-components-moment/pyproject.toml | 2 +- packages/reflex-components-plotly/pyproject.toml | 2 +- packages/reflex-components-radix/pyproject.toml | 2 +- packages/reflex-components-react-player/pyproject.toml | 2 +- packages/reflex-components-react-router/pyproject.toml | 2 +- packages/reflex-components-recharts/pyproject.toml | 2 +- packages/reflex-components-sonner/pyproject.toml | 2 +- 14 files changed, 13 insertions(+), 16 deletions(-) diff --git a/packages/hatch-reflex-pyi/pyproject.toml b/packages/hatch-reflex-pyi/pyproject.toml index f5d159ce207..68817a928f1 100644 --- a/packages/hatch-reflex-pyi/pyproject.toml +++ b/packages/hatch-reflex-pyi/pyproject.toml @@ -21,9 +21,6 @@ source = "uv-dynamic-versioning" pattern-prefix = "hatch-reflex-pyi-" fallback-version = "0.0.0dev0" -[tool.uv.sources] -reflex-core.workspace = true - [build-system] requires = ["hatchling", "uv-dynamic-versioning"] build-backend = "hatchling.build" diff --git a/packages/reflex-components-code/pyproject.toml b/packages/reflex-components-code/pyproject.toml index 9d5cef6f8e3..44c6b05beb5 100644 --- a/packages/reflex-components-code/pyproject.toml +++ b/packages/reflex-components-code/pyproject.toml @@ -24,7 +24,7 @@ targets.sdist.artifacts = ["*.pyi"] targets.wheel.artifacts = ["*.pyi"] [tool.hatch.build.hooks.reflex-pyi] -dependencies = ["ruff"] +dependencies = ["ruff", "reflex-core"] [build-system] requires = ["hatchling", "uv-dynamic-versioning", "hatch-reflex-pyi"] diff --git a/packages/reflex-components-core/pyproject.toml b/packages/reflex-components-core/pyproject.toml index 1b8c8a188ea..407bc59919e 100644 --- a/packages/reflex-components-core/pyproject.toml +++ b/packages/reflex-components-core/pyproject.toml @@ -23,7 +23,7 @@ targets.sdist.artifacts = ["*.pyi"] targets.wheel.artifacts = ["*.pyi"] [tool.hatch.build.hooks.reflex-pyi] -dependencies = ["ruff"] +dependencies = ["ruff", "reflex-core"] [build-system] requires = ["hatchling", "uv-dynamic-versioning", "hatch-reflex-pyi"] diff --git a/packages/reflex-components-dataeditor/pyproject.toml b/packages/reflex-components-dataeditor/pyproject.toml index 157363d72b4..fa2614256b2 100644 --- a/packages/reflex-components-dataeditor/pyproject.toml +++ b/packages/reflex-components-dataeditor/pyproject.toml @@ -22,7 +22,7 @@ targets.sdist.artifacts = ["*.pyi"] targets.wheel.artifacts = ["*.pyi"] [tool.hatch.build.hooks.reflex-pyi] -dependencies = ["ruff"] +dependencies = ["ruff", "reflex-core"] [build-system] requires = ["hatchling", "uv-dynamic-versioning", "hatch-reflex-pyi"] diff --git a/packages/reflex-components-gridjs/pyproject.toml b/packages/reflex-components-gridjs/pyproject.toml index 4be9625dd1e..631d694dbd3 100644 --- a/packages/reflex-components-gridjs/pyproject.toml +++ b/packages/reflex-components-gridjs/pyproject.toml @@ -20,7 +20,7 @@ targets.sdist.artifacts = ["*.pyi"] targets.wheel.artifacts = ["*.pyi"] [tool.hatch.build.hooks.reflex-pyi] -dependencies = ["ruff"] +dependencies = ["ruff", "reflex-core"] [build-system] requires = ["hatchling", "uv-dynamic-versioning", "hatch-reflex-pyi"] diff --git a/packages/reflex-components-lucide/pyproject.toml b/packages/reflex-components-lucide/pyproject.toml index cb85be39b9e..27b6c6821d0 100644 --- a/packages/reflex-components-lucide/pyproject.toml +++ b/packages/reflex-components-lucide/pyproject.toml @@ -20,7 +20,7 @@ targets.sdist.artifacts = ["*.pyi"] targets.wheel.artifacts = ["*.pyi"] [tool.hatch.build.hooks.reflex-pyi] -dependencies = ["ruff"] +dependencies = ["ruff", "reflex-core"] [build-system] requires = ["hatchling", "uv-dynamic-versioning", "hatch-reflex-pyi"] diff --git a/packages/reflex-components-markdown/pyproject.toml b/packages/reflex-components-markdown/pyproject.toml index ca7abf47b61..08a5078a9d5 100644 --- a/packages/reflex-components-markdown/pyproject.toml +++ b/packages/reflex-components-markdown/pyproject.toml @@ -24,7 +24,7 @@ targets.sdist.artifacts = ["*.pyi"] targets.wheel.artifacts = ["*.pyi"] [tool.hatch.build.hooks.reflex-pyi] -dependencies = ["ruff"] +dependencies = ["ruff", "reflex-core"] [build-system] requires = ["hatchling", "uv-dynamic-versioning", "hatch-reflex-pyi"] diff --git a/packages/reflex-components-moment/pyproject.toml b/packages/reflex-components-moment/pyproject.toml index 08657dbb885..70f3cf1a6f4 100644 --- a/packages/reflex-components-moment/pyproject.toml +++ b/packages/reflex-components-moment/pyproject.toml @@ -20,7 +20,7 @@ targets.sdist.artifacts = ["*.pyi"] targets.wheel.artifacts = ["*.pyi"] [tool.hatch.build.hooks.reflex-pyi] -dependencies = ["ruff"] +dependencies = ["ruff", "reflex-core"] [build-system] requires = ["hatchling", "uv-dynamic-versioning", "hatch-reflex-pyi"] diff --git a/packages/reflex-components-plotly/pyproject.toml b/packages/reflex-components-plotly/pyproject.toml index 991e56e377b..6fc5f4f8859 100644 --- a/packages/reflex-components-plotly/pyproject.toml +++ b/packages/reflex-components-plotly/pyproject.toml @@ -22,7 +22,7 @@ targets.sdist.artifacts = ["*.pyi"] targets.wheel.artifacts = ["*.pyi"] [tool.hatch.build.hooks.reflex-pyi] -dependencies = ["ruff"] +dependencies = ["ruff", "reflex-core"] [build-system] requires = ["hatchling", "uv-dynamic-versioning", "hatch-reflex-pyi"] diff --git a/packages/reflex-components-radix/pyproject.toml b/packages/reflex-components-radix/pyproject.toml index 0eff373a9a5..3d17aedd1b2 100644 --- a/packages/reflex-components-radix/pyproject.toml +++ b/packages/reflex-components-radix/pyproject.toml @@ -24,7 +24,7 @@ targets.sdist.artifacts = ["*.pyi"] targets.wheel.artifacts = ["*.pyi"] [tool.hatch.build.hooks.reflex-pyi] -dependencies = ["ruff"] +dependencies = ["ruff", "reflex-core"] [build-system] requires = ["hatchling", "uv-dynamic-versioning", "hatch-reflex-pyi"] diff --git a/packages/reflex-components-react-player/pyproject.toml b/packages/reflex-components-react-player/pyproject.toml index a356d5732d7..01f643e6831 100644 --- a/packages/reflex-components-react-player/pyproject.toml +++ b/packages/reflex-components-react-player/pyproject.toml @@ -22,7 +22,7 @@ targets.sdist.artifacts = ["*.pyi"] targets.wheel.artifacts = ["*.pyi"] [tool.hatch.build.hooks.reflex-pyi] -dependencies = ["ruff"] +dependencies = ["ruff", "reflex-core"] [build-system] requires = ["hatchling", "uv-dynamic-versioning", "hatch-reflex-pyi"] diff --git a/packages/reflex-components-react-router/pyproject.toml b/packages/reflex-components-react-router/pyproject.toml index 309472281b6..64d6b021862 100644 --- a/packages/reflex-components-react-router/pyproject.toml +++ b/packages/reflex-components-react-router/pyproject.toml @@ -22,7 +22,7 @@ targets.sdist.artifacts = ["*.pyi"] targets.wheel.artifacts = ["*.pyi"] [tool.hatch.build.hooks.reflex-pyi] -dependencies = ["ruff"] +dependencies = ["ruff", "reflex-core"] [build-system] requires = ["hatchling", "uv-dynamic-versioning", "hatch-reflex-pyi"] diff --git a/packages/reflex-components-recharts/pyproject.toml b/packages/reflex-components-recharts/pyproject.toml index 098082c7597..20c868bf7d9 100644 --- a/packages/reflex-components-recharts/pyproject.toml +++ b/packages/reflex-components-recharts/pyproject.toml @@ -20,7 +20,7 @@ targets.sdist.artifacts = ["*.pyi"] targets.wheel.artifacts = ["*.pyi"] [tool.hatch.build.hooks.reflex-pyi] -dependencies = ["ruff"] +dependencies = ["ruff", "reflex-core"] [build-system] requires = ["hatchling", "uv-dynamic-versioning", "hatch-reflex-pyi"] diff --git a/packages/reflex-components-sonner/pyproject.toml b/packages/reflex-components-sonner/pyproject.toml index 443afa0e5ec..8fae4b5992a 100644 --- a/packages/reflex-components-sonner/pyproject.toml +++ b/packages/reflex-components-sonner/pyproject.toml @@ -22,7 +22,7 @@ targets.sdist.artifacts = ["*.pyi"] targets.wheel.artifacts = ["*.pyi"] [tool.hatch.build.hooks.reflex-pyi] -dependencies = ["ruff"] +dependencies = ["ruff", "reflex-core"] [build-system] requires = ["hatchling", "uv-dynamic-versioning", "hatch-reflex-pyi"] From 62f7aaa0233feb2ca69a558e17b05785765bb898 Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Thu, 26 Mar 2026 14:09:18 -0700 Subject: [PATCH 049/113] ah --- packages/hatch-reflex-pyi/pyproject.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/hatch-reflex-pyi/pyproject.toml b/packages/hatch-reflex-pyi/pyproject.toml index 68817a928f1..a2754ad8a0c 100644 --- a/packages/hatch-reflex-pyi/pyproject.toml +++ b/packages/hatch-reflex-pyi/pyproject.toml @@ -8,7 +8,6 @@ maintainers = [{ name = "Khaleel Al-Adhami", email = "khaleel@reflex.dev" }] requires-python = ">=3.10" dependencies = [ "hatchling", - "reflex-core", ] [project.entry-points.hatch] From 5b558aee3c81164339c911c0b620ed6655ff61e7 Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Thu, 26 Mar 2026 14:13:47 -0700 Subject: [PATCH 050/113] fix import for event module --- reflex/event.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/reflex/event.py b/reflex/event.py index b1ed3b3db8a..cf383dbb67a 100644 --- a/reflex/event.py +++ b/reflex/event.py @@ -1,3 +1,8 @@ """Re-export from reflex_core.""" -from reflex_core.event import * +import sys + +from reflex_core.event import * # pyright: ignore[reportWildcardImportFromLibrary] +from reflex_core.event import event + +sys.modules[__name__] = event # pyright: ignore[reportArgumentType] From e5c5d78f4c878028bd2586b15cb7506cfdd22e87 Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Thu, 26 Mar 2026 14:15:58 -0700 Subject: [PATCH 051/113] uv sync and precommit --- pyi_hashes.json | 3 +++ uv.lock | 6 +----- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/pyi_hashes.json b/pyi_hashes.json index 2723c365c94..83aade50ba6 100644 --- a/pyi_hashes.json +++ b/pyi_hashes.json @@ -6,6 +6,7 @@ "packages/reflex-components-core/src/reflex_components_core/base/app_wrap.pyi": "900f0927a8fbaea6d70a6c5f79ffe390", "packages/reflex-components-core/src/reflex_components_core/base/body.pyi": "d05d36b0049a1ca9f48828de0a780264", "packages/reflex-components-core/src/reflex_components_core/base/document.pyi": "abff8b40e875b1fd5a49adc602c0b3cb", + "packages/reflex-components-core/src/reflex_components_core/base/error_boundary.pyi": "8c6128003fc030ae3cad6a3c603f9600", "packages/reflex-components-core/src/reflex_components_core/base/fragment.pyi": "203b060bc2ed6a1ee4f5139f3643a41b", "packages/reflex-components-core/src/reflex_components_core/base/link.pyi": "560d2c9c4a21d5a99ecdaf7f220874d3", "packages/reflex-components-core/src/reflex_components_core/base/meta.pyi": "d094461c8a1d56ba02102fce4940eff6", @@ -52,6 +53,7 @@ "packages/reflex-components-radix/src/reflex_components_radix/primitives/slider.pyi": "a4d76b1220aeb40a53b5f6033f67ff04", "packages/reflex-components-radix/src/reflex_components_radix/themes/__init__.pyi": "a152450345e7da9124e954843224e936", "packages/reflex-components-radix/src/reflex_components_radix/themes/base.pyi": "9ae9885a592496736170bf61d3869ee0", + "packages/reflex-components-radix/src/reflex_components_radix/themes/color_mode.pyi": "5f41e358cde825fbdf589bf798b0bf00", "packages/reflex-components-radix/src/reflex_components_radix/themes/components/__init__.pyi": "67d45aa96a97359ee556725641bb583f", "packages/reflex-components-radix/src/reflex_components_radix/themes/components/alert_dialog.pyi": "a58ad421715ce6f80faecc371e6f51d4", "packages/reflex-components-radix/src/reflex_components_radix/themes/components/aspect_ratio.pyi": "f6a1f6388abca0317671e336dcabfc4b", @@ -95,6 +97,7 @@ "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/container.pyi": "b86f337365de46582e71f32135609ad2", "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/flex.pyi": "4d5128e75445be5cc15d73006abbb1ec", "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/grid.pyi": "ab58ab7820e10612a3fd1fddb0729003", + "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/list.pyi": "ba6f514524b94cb5e58e3543ac87d4a8", "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/section.pyi": "295bf854ef0d0feadda6fbfdb7788244", "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/spacer.pyi": "02275e5a87cf57b880f6ee0cdde9ac94", "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/stack.pyi": "b8e21fbae89af6a5d72beec914e196c4", diff --git a/uv.lock b/uv.lock index 18c2475e6f7..294aa58dd83 100644 --- a/uv.lock +++ b/uv.lock @@ -709,14 +709,10 @@ name = "hatch-reflex-pyi" source = { editable = "packages/hatch-reflex-pyi" } dependencies = [ { name = "hatchling" }, - { name = "reflex-core" }, ] [package.metadata] -requires-dist = [ - { name = "hatchling" }, - { name = "reflex-core", editable = "packages/reflex-core" }, -] +requires-dist = [{ name = "hatchling" }] [[package]] name = "hatchling" From a0c10120b6f1a64c5f54c761e7a48458a292dcc6 Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Thu, 26 Mar 2026 14:22:29 -0700 Subject: [PATCH 052/113] move templates --- .../.templates/apps/blank/assets/favicon.ico | Bin .../.templates/apps/blank/code/__init__.py | 0 .../.templates}/.templates/apps/blank/code/blank.py | 0 .../.templates}/.templates/web/.gitignore | 0 .../.templates}/.templates/web/app/entry.client.js | 0 .../.templates}/.templates/web/app/routes.js | 0 .../reflex/radix_themes_color_mode_provider.js | 0 .../.templates/web/components/shiki/code.js | 0 .../.templates}/.templates/web/jsconfig.json | 0 .../.templates}/.templates/web/postcss.config.js | 0 .../.templates/web/react-router.config.js | 0 .../.templates/web/styles/__reflex_style_reset.css | 0 .../.templates/web/utils/helpers/dataeditor.js | 0 .../.templates/web/utils/helpers/debounce.js | 0 .../.templates/web/utils/helpers/paste.js | 0 .../.templates/web/utils/helpers/range.js | 0 .../.templates/web/utils/helpers/throttle.js | 0 .../.templates/web/utils/helpers/upload.js | 0 .../.templates}/.templates/web/utils/react-theme.js | 0 .../.templates}/.templates/web/utils/state.js | 0 .../.templates/web/vite-plugin-safari-cachebust.js | 0 21 files changed, 0 insertions(+), 0 deletions(-) rename {reflex => packages/reflex-core/src/reflex_core/.templates}/.templates/apps/blank/assets/favicon.ico (100%) rename {reflex => packages/reflex-core/src/reflex_core/.templates}/.templates/apps/blank/code/__init__.py (100%) rename {reflex => packages/reflex-core/src/reflex_core/.templates}/.templates/apps/blank/code/blank.py (100%) rename {reflex => packages/reflex-core/src/reflex_core/.templates}/.templates/web/.gitignore (100%) rename {reflex => packages/reflex-core/src/reflex_core/.templates}/.templates/web/app/entry.client.js (100%) rename {reflex => packages/reflex-core/src/reflex_core/.templates}/.templates/web/app/routes.js (100%) rename {reflex => packages/reflex-core/src/reflex_core/.templates}/.templates/web/components/reflex/radix_themes_color_mode_provider.js (100%) rename {reflex => packages/reflex-core/src/reflex_core/.templates}/.templates/web/components/shiki/code.js (100%) rename {reflex => packages/reflex-core/src/reflex_core/.templates}/.templates/web/jsconfig.json (100%) rename {reflex => packages/reflex-core/src/reflex_core/.templates}/.templates/web/postcss.config.js (100%) rename {reflex => packages/reflex-core/src/reflex_core/.templates}/.templates/web/react-router.config.js (100%) rename {reflex => packages/reflex-core/src/reflex_core/.templates}/.templates/web/styles/__reflex_style_reset.css (100%) rename {reflex => packages/reflex-core/src/reflex_core/.templates}/.templates/web/utils/helpers/dataeditor.js (100%) rename {reflex => packages/reflex-core/src/reflex_core/.templates}/.templates/web/utils/helpers/debounce.js (100%) rename {reflex => packages/reflex-core/src/reflex_core/.templates}/.templates/web/utils/helpers/paste.js (100%) rename {reflex => packages/reflex-core/src/reflex_core/.templates}/.templates/web/utils/helpers/range.js (100%) rename {reflex => packages/reflex-core/src/reflex_core/.templates}/.templates/web/utils/helpers/throttle.js (100%) rename {reflex => packages/reflex-core/src/reflex_core/.templates}/.templates/web/utils/helpers/upload.js (100%) rename {reflex => packages/reflex-core/src/reflex_core/.templates}/.templates/web/utils/react-theme.js (100%) rename {reflex => packages/reflex-core/src/reflex_core/.templates}/.templates/web/utils/state.js (100%) rename {reflex => packages/reflex-core/src/reflex_core/.templates}/.templates/web/vite-plugin-safari-cachebust.js (100%) diff --git a/reflex/.templates/apps/blank/assets/favicon.ico b/packages/reflex-core/src/reflex_core/.templates/.templates/apps/blank/assets/favicon.ico similarity index 100% rename from reflex/.templates/apps/blank/assets/favicon.ico rename to packages/reflex-core/src/reflex_core/.templates/.templates/apps/blank/assets/favicon.ico diff --git a/reflex/.templates/apps/blank/code/__init__.py b/packages/reflex-core/src/reflex_core/.templates/.templates/apps/blank/code/__init__.py similarity index 100% rename from reflex/.templates/apps/blank/code/__init__.py rename to packages/reflex-core/src/reflex_core/.templates/.templates/apps/blank/code/__init__.py diff --git a/reflex/.templates/apps/blank/code/blank.py b/packages/reflex-core/src/reflex_core/.templates/.templates/apps/blank/code/blank.py similarity index 100% rename from reflex/.templates/apps/blank/code/blank.py rename to packages/reflex-core/src/reflex_core/.templates/.templates/apps/blank/code/blank.py diff --git a/reflex/.templates/web/.gitignore b/packages/reflex-core/src/reflex_core/.templates/.templates/web/.gitignore similarity index 100% rename from reflex/.templates/web/.gitignore rename to packages/reflex-core/src/reflex_core/.templates/.templates/web/.gitignore diff --git a/reflex/.templates/web/app/entry.client.js b/packages/reflex-core/src/reflex_core/.templates/.templates/web/app/entry.client.js similarity index 100% rename from reflex/.templates/web/app/entry.client.js rename to packages/reflex-core/src/reflex_core/.templates/.templates/web/app/entry.client.js diff --git a/reflex/.templates/web/app/routes.js b/packages/reflex-core/src/reflex_core/.templates/.templates/web/app/routes.js similarity index 100% rename from reflex/.templates/web/app/routes.js rename to packages/reflex-core/src/reflex_core/.templates/.templates/web/app/routes.js diff --git a/reflex/.templates/web/components/reflex/radix_themes_color_mode_provider.js b/packages/reflex-core/src/reflex_core/.templates/.templates/web/components/reflex/radix_themes_color_mode_provider.js similarity index 100% rename from reflex/.templates/web/components/reflex/radix_themes_color_mode_provider.js rename to packages/reflex-core/src/reflex_core/.templates/.templates/web/components/reflex/radix_themes_color_mode_provider.js diff --git a/reflex/.templates/web/components/shiki/code.js b/packages/reflex-core/src/reflex_core/.templates/.templates/web/components/shiki/code.js similarity index 100% rename from reflex/.templates/web/components/shiki/code.js rename to packages/reflex-core/src/reflex_core/.templates/.templates/web/components/shiki/code.js diff --git a/reflex/.templates/web/jsconfig.json b/packages/reflex-core/src/reflex_core/.templates/.templates/web/jsconfig.json similarity index 100% rename from reflex/.templates/web/jsconfig.json rename to packages/reflex-core/src/reflex_core/.templates/.templates/web/jsconfig.json diff --git a/reflex/.templates/web/postcss.config.js b/packages/reflex-core/src/reflex_core/.templates/.templates/web/postcss.config.js similarity index 100% rename from reflex/.templates/web/postcss.config.js rename to packages/reflex-core/src/reflex_core/.templates/.templates/web/postcss.config.js diff --git a/reflex/.templates/web/react-router.config.js b/packages/reflex-core/src/reflex_core/.templates/.templates/web/react-router.config.js similarity index 100% rename from reflex/.templates/web/react-router.config.js rename to packages/reflex-core/src/reflex_core/.templates/.templates/web/react-router.config.js diff --git a/reflex/.templates/web/styles/__reflex_style_reset.css b/packages/reflex-core/src/reflex_core/.templates/.templates/web/styles/__reflex_style_reset.css similarity index 100% rename from reflex/.templates/web/styles/__reflex_style_reset.css rename to packages/reflex-core/src/reflex_core/.templates/.templates/web/styles/__reflex_style_reset.css diff --git a/reflex/.templates/web/utils/helpers/dataeditor.js b/packages/reflex-core/src/reflex_core/.templates/.templates/web/utils/helpers/dataeditor.js similarity index 100% rename from reflex/.templates/web/utils/helpers/dataeditor.js rename to packages/reflex-core/src/reflex_core/.templates/.templates/web/utils/helpers/dataeditor.js diff --git a/reflex/.templates/web/utils/helpers/debounce.js b/packages/reflex-core/src/reflex_core/.templates/.templates/web/utils/helpers/debounce.js similarity index 100% rename from reflex/.templates/web/utils/helpers/debounce.js rename to packages/reflex-core/src/reflex_core/.templates/.templates/web/utils/helpers/debounce.js diff --git a/reflex/.templates/web/utils/helpers/paste.js b/packages/reflex-core/src/reflex_core/.templates/.templates/web/utils/helpers/paste.js similarity index 100% rename from reflex/.templates/web/utils/helpers/paste.js rename to packages/reflex-core/src/reflex_core/.templates/.templates/web/utils/helpers/paste.js diff --git a/reflex/.templates/web/utils/helpers/range.js b/packages/reflex-core/src/reflex_core/.templates/.templates/web/utils/helpers/range.js similarity index 100% rename from reflex/.templates/web/utils/helpers/range.js rename to packages/reflex-core/src/reflex_core/.templates/.templates/web/utils/helpers/range.js diff --git a/reflex/.templates/web/utils/helpers/throttle.js b/packages/reflex-core/src/reflex_core/.templates/.templates/web/utils/helpers/throttle.js similarity index 100% rename from reflex/.templates/web/utils/helpers/throttle.js rename to packages/reflex-core/src/reflex_core/.templates/.templates/web/utils/helpers/throttle.js diff --git a/reflex/.templates/web/utils/helpers/upload.js b/packages/reflex-core/src/reflex_core/.templates/.templates/web/utils/helpers/upload.js similarity index 100% rename from reflex/.templates/web/utils/helpers/upload.js rename to packages/reflex-core/src/reflex_core/.templates/.templates/web/utils/helpers/upload.js diff --git a/reflex/.templates/web/utils/react-theme.js b/packages/reflex-core/src/reflex_core/.templates/.templates/web/utils/react-theme.js similarity index 100% rename from reflex/.templates/web/utils/react-theme.js rename to packages/reflex-core/src/reflex_core/.templates/.templates/web/utils/react-theme.js diff --git a/reflex/.templates/web/utils/state.js b/packages/reflex-core/src/reflex_core/.templates/.templates/web/utils/state.js similarity index 100% rename from reflex/.templates/web/utils/state.js rename to packages/reflex-core/src/reflex_core/.templates/.templates/web/utils/state.js diff --git a/reflex/.templates/web/vite-plugin-safari-cachebust.js b/packages/reflex-core/src/reflex_core/.templates/.templates/web/vite-plugin-safari-cachebust.js similarity index 100% rename from reflex/.templates/web/vite-plugin-safari-cachebust.js rename to packages/reflex-core/src/reflex_core/.templates/.templates/web/vite-plugin-safari-cachebust.js From 9b6e9d84ef4d8dd8c6c2d9ad57bd744fe6c1feb9 Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Thu, 26 Mar 2026 14:23:26 -0700 Subject: [PATCH 053/113] fix path to templates --- packages/reflex-core/src/reflex_core/constants/base.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/reflex-core/src/reflex_core/constants/base.py b/packages/reflex-core/src/reflex_core/constants/base.py index c8a9a1dfde5..896dd95b5de 100644 --- a/packages/reflex-core/src/reflex_core/constants/base.py +++ b/packages/reflex-core/src/reflex_core/constants/base.py @@ -99,7 +99,7 @@ class Reflex(SimpleNamespace): LOGS_DIR = DIR / "logs" # The root directory of the reflex library. - ROOT_DIR = Path(__file__).parents[2] + ROOT_DIR = Path(__file__).parents[1] RELEASES_URL = "https://api.github.com/repos/reflex-dev/templates/releases" @@ -143,7 +143,7 @@ class Dirs(SimpleNamespace): """Folders used by the template system of Reflex.""" # The template directory used during reflex init. - BASE = Reflex.ROOT_DIR / Reflex.MODULE_NAME / ".templates" + BASE = Reflex.ROOT_DIR / ".templates" # The web subdirectory of the template directory. WEB_TEMPLATE = BASE / "web" # Where the code for the templates is stored. From 5401c8191d4bd1df808a98f24c829cf9116ec997 Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Thu, 26 Mar 2026 14:31:13 -0700 Subject: [PATCH 054/113] move templates to the correct place --- .../{.templates => }/apps/blank/assets/favicon.ico | Bin .../{.templates => }/apps/blank/code/__init__.py | 0 .../{.templates => }/apps/blank/code/blank.py | 0 .../.templates/{.templates => }/web/.gitignore | 0 .../{.templates => }/web/app/entry.client.js | 0 .../.templates/{.templates => }/web/app/routes.js | 0 .../reflex/radix_themes_color_mode_provider.js | 0 .../{.templates => }/web/components/shiki/code.js | 0 .../.templates/{.templates => }/web/jsconfig.json | 0 .../{.templates => }/web/postcss.config.js | 0 .../{.templates => }/web/react-router.config.js | 0 .../web/styles/__reflex_style_reset.css | 0 .../web/utils/helpers/dataeditor.js | 0 .../{.templates => }/web/utils/helpers/debounce.js | 0 .../{.templates => }/web/utils/helpers/paste.js | 0 .../{.templates => }/web/utils/helpers/range.js | 0 .../{.templates => }/web/utils/helpers/throttle.js | 0 .../{.templates => }/web/utils/helpers/upload.js | 0 .../{.templates => }/web/utils/react-theme.js | 0 .../.templates/{.templates => }/web/utils/state.js | 0 .../web/vite-plugin-safari-cachebust.js | 0 21 files changed, 0 insertions(+), 0 deletions(-) rename packages/reflex-core/src/reflex_core/.templates/{.templates => }/apps/blank/assets/favicon.ico (100%) rename packages/reflex-core/src/reflex_core/.templates/{.templates => }/apps/blank/code/__init__.py (100%) rename packages/reflex-core/src/reflex_core/.templates/{.templates => }/apps/blank/code/blank.py (100%) rename packages/reflex-core/src/reflex_core/.templates/{.templates => }/web/.gitignore (100%) rename packages/reflex-core/src/reflex_core/.templates/{.templates => }/web/app/entry.client.js (100%) rename packages/reflex-core/src/reflex_core/.templates/{.templates => }/web/app/routes.js (100%) rename packages/reflex-core/src/reflex_core/.templates/{.templates => }/web/components/reflex/radix_themes_color_mode_provider.js (100%) rename packages/reflex-core/src/reflex_core/.templates/{.templates => }/web/components/shiki/code.js (100%) rename packages/reflex-core/src/reflex_core/.templates/{.templates => }/web/jsconfig.json (100%) rename packages/reflex-core/src/reflex_core/.templates/{.templates => }/web/postcss.config.js (100%) rename packages/reflex-core/src/reflex_core/.templates/{.templates => }/web/react-router.config.js (100%) rename packages/reflex-core/src/reflex_core/.templates/{.templates => }/web/styles/__reflex_style_reset.css (100%) rename packages/reflex-core/src/reflex_core/.templates/{.templates => }/web/utils/helpers/dataeditor.js (100%) rename packages/reflex-core/src/reflex_core/.templates/{.templates => }/web/utils/helpers/debounce.js (100%) rename packages/reflex-core/src/reflex_core/.templates/{.templates => }/web/utils/helpers/paste.js (100%) rename packages/reflex-core/src/reflex_core/.templates/{.templates => }/web/utils/helpers/range.js (100%) rename packages/reflex-core/src/reflex_core/.templates/{.templates => }/web/utils/helpers/throttle.js (100%) rename packages/reflex-core/src/reflex_core/.templates/{.templates => }/web/utils/helpers/upload.js (100%) rename packages/reflex-core/src/reflex_core/.templates/{.templates => }/web/utils/react-theme.js (100%) rename packages/reflex-core/src/reflex_core/.templates/{.templates => }/web/utils/state.js (100%) rename packages/reflex-core/src/reflex_core/.templates/{.templates => }/web/vite-plugin-safari-cachebust.js (100%) diff --git a/packages/reflex-core/src/reflex_core/.templates/.templates/apps/blank/assets/favicon.ico b/packages/reflex-core/src/reflex_core/.templates/apps/blank/assets/favicon.ico similarity index 100% rename from packages/reflex-core/src/reflex_core/.templates/.templates/apps/blank/assets/favicon.ico rename to packages/reflex-core/src/reflex_core/.templates/apps/blank/assets/favicon.ico diff --git a/packages/reflex-core/src/reflex_core/.templates/.templates/apps/blank/code/__init__.py b/packages/reflex-core/src/reflex_core/.templates/apps/blank/code/__init__.py similarity index 100% rename from packages/reflex-core/src/reflex_core/.templates/.templates/apps/blank/code/__init__.py rename to packages/reflex-core/src/reflex_core/.templates/apps/blank/code/__init__.py diff --git a/packages/reflex-core/src/reflex_core/.templates/.templates/apps/blank/code/blank.py b/packages/reflex-core/src/reflex_core/.templates/apps/blank/code/blank.py similarity index 100% rename from packages/reflex-core/src/reflex_core/.templates/.templates/apps/blank/code/blank.py rename to packages/reflex-core/src/reflex_core/.templates/apps/blank/code/blank.py diff --git a/packages/reflex-core/src/reflex_core/.templates/.templates/web/.gitignore b/packages/reflex-core/src/reflex_core/.templates/web/.gitignore similarity index 100% rename from packages/reflex-core/src/reflex_core/.templates/.templates/web/.gitignore rename to packages/reflex-core/src/reflex_core/.templates/web/.gitignore diff --git a/packages/reflex-core/src/reflex_core/.templates/.templates/web/app/entry.client.js b/packages/reflex-core/src/reflex_core/.templates/web/app/entry.client.js similarity index 100% rename from packages/reflex-core/src/reflex_core/.templates/.templates/web/app/entry.client.js rename to packages/reflex-core/src/reflex_core/.templates/web/app/entry.client.js diff --git a/packages/reflex-core/src/reflex_core/.templates/.templates/web/app/routes.js b/packages/reflex-core/src/reflex_core/.templates/web/app/routes.js similarity index 100% rename from packages/reflex-core/src/reflex_core/.templates/.templates/web/app/routes.js rename to packages/reflex-core/src/reflex_core/.templates/web/app/routes.js diff --git a/packages/reflex-core/src/reflex_core/.templates/.templates/web/components/reflex/radix_themes_color_mode_provider.js b/packages/reflex-core/src/reflex_core/.templates/web/components/reflex/radix_themes_color_mode_provider.js similarity index 100% rename from packages/reflex-core/src/reflex_core/.templates/.templates/web/components/reflex/radix_themes_color_mode_provider.js rename to packages/reflex-core/src/reflex_core/.templates/web/components/reflex/radix_themes_color_mode_provider.js diff --git a/packages/reflex-core/src/reflex_core/.templates/.templates/web/components/shiki/code.js b/packages/reflex-core/src/reflex_core/.templates/web/components/shiki/code.js similarity index 100% rename from packages/reflex-core/src/reflex_core/.templates/.templates/web/components/shiki/code.js rename to packages/reflex-core/src/reflex_core/.templates/web/components/shiki/code.js diff --git a/packages/reflex-core/src/reflex_core/.templates/.templates/web/jsconfig.json b/packages/reflex-core/src/reflex_core/.templates/web/jsconfig.json similarity index 100% rename from packages/reflex-core/src/reflex_core/.templates/.templates/web/jsconfig.json rename to packages/reflex-core/src/reflex_core/.templates/web/jsconfig.json diff --git a/packages/reflex-core/src/reflex_core/.templates/.templates/web/postcss.config.js b/packages/reflex-core/src/reflex_core/.templates/web/postcss.config.js similarity index 100% rename from packages/reflex-core/src/reflex_core/.templates/.templates/web/postcss.config.js rename to packages/reflex-core/src/reflex_core/.templates/web/postcss.config.js diff --git a/packages/reflex-core/src/reflex_core/.templates/.templates/web/react-router.config.js b/packages/reflex-core/src/reflex_core/.templates/web/react-router.config.js similarity index 100% rename from packages/reflex-core/src/reflex_core/.templates/.templates/web/react-router.config.js rename to packages/reflex-core/src/reflex_core/.templates/web/react-router.config.js diff --git a/packages/reflex-core/src/reflex_core/.templates/.templates/web/styles/__reflex_style_reset.css b/packages/reflex-core/src/reflex_core/.templates/web/styles/__reflex_style_reset.css similarity index 100% rename from packages/reflex-core/src/reflex_core/.templates/.templates/web/styles/__reflex_style_reset.css rename to packages/reflex-core/src/reflex_core/.templates/web/styles/__reflex_style_reset.css diff --git a/packages/reflex-core/src/reflex_core/.templates/.templates/web/utils/helpers/dataeditor.js b/packages/reflex-core/src/reflex_core/.templates/web/utils/helpers/dataeditor.js similarity index 100% rename from packages/reflex-core/src/reflex_core/.templates/.templates/web/utils/helpers/dataeditor.js rename to packages/reflex-core/src/reflex_core/.templates/web/utils/helpers/dataeditor.js diff --git a/packages/reflex-core/src/reflex_core/.templates/.templates/web/utils/helpers/debounce.js b/packages/reflex-core/src/reflex_core/.templates/web/utils/helpers/debounce.js similarity index 100% rename from packages/reflex-core/src/reflex_core/.templates/.templates/web/utils/helpers/debounce.js rename to packages/reflex-core/src/reflex_core/.templates/web/utils/helpers/debounce.js diff --git a/packages/reflex-core/src/reflex_core/.templates/.templates/web/utils/helpers/paste.js b/packages/reflex-core/src/reflex_core/.templates/web/utils/helpers/paste.js similarity index 100% rename from packages/reflex-core/src/reflex_core/.templates/.templates/web/utils/helpers/paste.js rename to packages/reflex-core/src/reflex_core/.templates/web/utils/helpers/paste.js diff --git a/packages/reflex-core/src/reflex_core/.templates/.templates/web/utils/helpers/range.js b/packages/reflex-core/src/reflex_core/.templates/web/utils/helpers/range.js similarity index 100% rename from packages/reflex-core/src/reflex_core/.templates/.templates/web/utils/helpers/range.js rename to packages/reflex-core/src/reflex_core/.templates/web/utils/helpers/range.js diff --git a/packages/reflex-core/src/reflex_core/.templates/.templates/web/utils/helpers/throttle.js b/packages/reflex-core/src/reflex_core/.templates/web/utils/helpers/throttle.js similarity index 100% rename from packages/reflex-core/src/reflex_core/.templates/.templates/web/utils/helpers/throttle.js rename to packages/reflex-core/src/reflex_core/.templates/web/utils/helpers/throttle.js diff --git a/packages/reflex-core/src/reflex_core/.templates/.templates/web/utils/helpers/upload.js b/packages/reflex-core/src/reflex_core/.templates/web/utils/helpers/upload.js similarity index 100% rename from packages/reflex-core/src/reflex_core/.templates/.templates/web/utils/helpers/upload.js rename to packages/reflex-core/src/reflex_core/.templates/web/utils/helpers/upload.js diff --git a/packages/reflex-core/src/reflex_core/.templates/.templates/web/utils/react-theme.js b/packages/reflex-core/src/reflex_core/.templates/web/utils/react-theme.js similarity index 100% rename from packages/reflex-core/src/reflex_core/.templates/.templates/web/utils/react-theme.js rename to packages/reflex-core/src/reflex_core/.templates/web/utils/react-theme.js diff --git a/packages/reflex-core/src/reflex_core/.templates/.templates/web/utils/state.js b/packages/reflex-core/src/reflex_core/.templates/web/utils/state.js similarity index 100% rename from packages/reflex-core/src/reflex_core/.templates/.templates/web/utils/state.js rename to packages/reflex-core/src/reflex_core/.templates/web/utils/state.js diff --git a/packages/reflex-core/src/reflex_core/.templates/.templates/web/vite-plugin-safari-cachebust.js b/packages/reflex-core/src/reflex_core/.templates/web/vite-plugin-safari-cachebust.js similarity index 100% rename from packages/reflex-core/src/reflex_core/.templates/.templates/web/vite-plugin-safari-cachebust.js rename to packages/reflex-core/src/reflex_core/.templates/web/vite-plugin-safari-cachebust.js From 9d75e2bc495615b5df11100812f367236cd767ca Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Thu, 26 Mar 2026 14:32:08 -0700 Subject: [PATCH 055/113] use artifacts --- packages/reflex-core/pyproject.toml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/reflex-core/pyproject.toml b/packages/reflex-core/pyproject.toml index fd821d49e1a..4169ca41181 100644 --- a/packages/reflex-core/pyproject.toml +++ b/packages/reflex-core/pyproject.toml @@ -20,6 +20,9 @@ source = "uv-dynamic-versioning" pattern-prefix = "reflex-core-" fallback-version = "0.0.0dev0" +[tool.hatch.build] +artifacts = [".templates/**"] + [build-system] requires = ["hatchling", "uv-dynamic-versioning"] build-backend = "hatchling.build" From 794ebcbb576bf595bdabb819beea3db8fc0b202d Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Thu, 26 Mar 2026 14:38:35 -0700 Subject: [PATCH 056/113] fix precommit --- pyproject.toml | 4 ++-- tests/units/components/core/test_match.py | 2 +- tests/units/utils/test_utils.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 33fb0b15602..1a9e291ab04 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -218,11 +218,11 @@ lint.flake8-bugbear.extend-immutable-calls = [ "N", ] "benchmarks/*.py" = ["ANN001", "D100", "D103", "D104", "B018", "PERF", "T", "N"] -"reflex/.templates/*.py" = ["D100", "D103", "D104"] +"*/.templates/*.py" = ["D100", "D103", "D104"] "*.pyi" = ["D301", "D415", "D417", "D418", "E742", "N", "PGH"] "pyi_generator.py" = ["N802"] "reflex/constants/*.py" = ["N"] -"reflex/.templates/apps/blank/code/*" = ["INP001"] +"*/.templates/apps/blank/code/*" = ["INP001"] "*/blank.py" = ["I001"] [tool.pytest.ini_options] diff --git a/tests/units/components/core/test_match.py b/tests/units/components/core/test_match.py index d9fa318113b..5ba471c2c84 100644 --- a/tests/units/components/core/test_match.py +++ b/tests/units/components/core/test_match.py @@ -249,7 +249,7 @@ def test_match_case_tuple_elements(match_case): ), ( 'Match cases should have the same return types. Case 3 with return value `"red"` of type ' - " is not " + " is not " ), ), ( diff --git a/tests/units/utils/test_utils.py b/tests/units/utils/test_utils.py index 33a0b38e873..f5bf506e9af 100644 --- a/tests/units/utils/test_utils.py +++ b/tests/units/utils/test_utils.py @@ -514,7 +514,7 @@ def test_output_system_info(mocker: MockerFixture): This test makes no assertions about the output, other than it executes without crashing. """ - mocker.patch("reflex.utils.console._LOG_LEVEL", constants.LogLevel.DEBUG) + mocker.patch("reflex_core.utils.console._LOG_LEVEL", constants.LogLevel.DEBUG) utils_exec.output_system_info() From c1a08afa022de8fb30aff1862c53bbbbb58d9d37 Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Thu, 26 Mar 2026 14:42:08 -0700 Subject: [PATCH 057/113] coverage isn't detecting quite right --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 1a9e291ab04..8b619443dcd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -249,7 +249,7 @@ omit = [ [tool.coverage.report] show_missing = true # TODO bump back to 79 -fail_under = 67 +fail_under = 60 precision = 2 ignore_errors = true From 163097689bfc1eba883e55f1610f1422f1755b61 Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Thu, 26 Mar 2026 14:46:40 -0700 Subject: [PATCH 058/113] what --- .github/codeql-config.yml | 2 +- .github/workflows/unit_tests.yml | 11 ----------- 2 files changed, 1 insertion(+), 12 deletions(-) diff --git a/.github/codeql-config.yml b/.github/codeql-config.yml index 39d648c8054..27182f0f65e 100644 --- a/.github/codeql-config.yml +++ b/.github/codeql-config.yml @@ -1,6 +1,6 @@ paths: - .github - reflex - - reflex/.templates + - packages paths-ignore: - "**/tests/**" diff --git a/.github/workflows/unit_tests.yml b/.github/workflows/unit_tests.yml index dc8fdee03ba..731bbeb04a4 100644 --- a/.github/workflows/unit_tests.yml +++ b/.github/workflows/unit_tests.yml @@ -69,12 +69,6 @@ jobs: export REFLEX_REDIS_URL=redis://localhost:6379 export REFLEX_OPLOCK_ENABLED=true uv run pytest tests/units --cov --no-cov-on-fail --cov-report= - # Change to explicitly install v1 when reflex-hosting-cli is compatible with v2 - - name: Run unit tests w/ pydantic v1 - run: | - export PYTHONUNBUFFERED=1 - uv pip install "pydantic~=1.10" - uv run pytest tests/units --cov --no-cov-on-fail --cov-report= - name: Generate coverage report run: uv run coverage html @@ -97,8 +91,3 @@ jobs: run: | export PYTHONUNBUFFERED=1 uv run pytest tests/units --cov --no-cov-on-fail --cov-report= - - name: Run unit tests w/ pydantic v1 - run: | - export PYTHONUNBUFFERED=1 - uv pip install "pydantic~=1.10" - uv run pytest tests/units --cov --no-cov-on-fail --cov-report= From 1073b756df03951cbc15df9e8df81f537fbc8fa5 Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Thu, 26 Mar 2026 14:59:00 -0700 Subject: [PATCH 059/113] add raise --- packages/reflex-core/pyproject.toml | 1 + .../reflex-docgen/src/reflex_docgen/_class.py | 41 +++--- pyi_hashes.json | 119 ------------------ uv.lock | 2 + 4 files changed, 28 insertions(+), 135 deletions(-) diff --git a/packages/reflex-core/pyproject.toml b/packages/reflex-core/pyproject.toml index 4169ca41181..ea8c326ff8d 100644 --- a/packages/reflex-core/pyproject.toml +++ b/packages/reflex-core/pyproject.toml @@ -11,6 +11,7 @@ dependencies = [ "pydantic >=1.10.21,<3.0", "rich >=13,<15", "typing_extensions >=4.13.0", + "platformdirs >=4.3.7,<5.0", ] [tool.hatch.version] diff --git a/packages/reflex-docgen/src/reflex_docgen/_class.py b/packages/reflex-docgen/src/reflex_docgen/_class.py index 005cce5152b..a9b7a7cca66 100644 --- a/packages/reflex-docgen/src/reflex_docgen/_class.py +++ b/packages/reflex-docgen/src/reflex_docgen/_class.py @@ -315,22 +315,31 @@ def generate_class_documentation(cls: type) -> ClassDocumentation: Returns: The generated documentation for the class. """ - description = inspect.cleandoc(cls.__doc__) if cls.__doc__ else None + try: + description = inspect.cleandoc(cls.__doc__) if cls.__doc__ else None - if dataclasses.is_dataclass(cls): - fields = _get_dataclass_fields(cls) - elif isinstance(cls, BaseStateMeta): - fields = _get_state_fields(cls) - else: - fields = () + if dataclasses.is_dataclass(cls): + fields = _get_dataclass_fields(cls) + elif isinstance(cls, BaseStateMeta): + fields = _get_state_fields(cls) + else: + fields = () - class_fields = _get_class_vars(cls) - methods = _get_methods(cls) + class_fields = _get_class_vars(cls) + methods = _get_methods(cls) - return ClassDocumentation( - name=f"{cls.__module__}.{cls.__qualname__}", - description=description, - fields=fields, - class_fields=class_fields, - methods=methods, - ) + return ClassDocumentation( + name=f"{cls.__module__}.{cls.__qualname__}", + description=description, + fields=fields, + class_fields=class_fields, + methods=methods, + ) + except Exception as e: + import sys + + if sys.version_info >= (3, 11): + e.add_note( + f"Error generating documentation for class {cls.__module__}.{cls.__qualname__}" + ) + raise diff --git a/pyi_hashes.json b/pyi_hashes.json index 83aade50ba6..535d1d2a7f5 100644 --- a/pyi_hashes.json +++ b/pyi_hashes.json @@ -1,123 +1,4 @@ { - "packages/reflex-components-code/src/reflex_components_code/code.pyi": "2319e7886182f5ef039f0a1ed1d62317", - "packages/reflex-components-code/src/reflex_components_code/shiki_code_block.pyi": "45379e4c1fc1f214b7a5d66d90f76f1c", - "packages/reflex-components-core/src/reflex_components_core/__init__.pyi": "6c3ceff429117483dd0035e7a21930f4", - "packages/reflex-components-core/src/reflex_components_core/base/__init__.pyi": "4beb5ba739680b7974c37241f3f6791c", - "packages/reflex-components-core/src/reflex_components_core/base/app_wrap.pyi": "900f0927a8fbaea6d70a6c5f79ffe390", - "packages/reflex-components-core/src/reflex_components_core/base/body.pyi": "d05d36b0049a1ca9f48828de0a780264", - "packages/reflex-components-core/src/reflex_components_core/base/document.pyi": "abff8b40e875b1fd5a49adc602c0b3cb", - "packages/reflex-components-core/src/reflex_components_core/base/error_boundary.pyi": "8c6128003fc030ae3cad6a3c603f9600", - "packages/reflex-components-core/src/reflex_components_core/base/fragment.pyi": "203b060bc2ed6a1ee4f5139f3643a41b", - "packages/reflex-components-core/src/reflex_components_core/base/link.pyi": "560d2c9c4a21d5a99ecdaf7f220874d3", - "packages/reflex-components-core/src/reflex_components_core/base/meta.pyi": "d094461c8a1d56ba02102fce4940eff6", - "packages/reflex-components-core/src/reflex_components_core/base/script.pyi": "d6c683e3ad0d791b254c5961ff467ada", - "packages/reflex-components-core/src/reflex_components_core/base/strict_mode.pyi": "490676ec4319d738aee31c3975361591", - "packages/reflex-components-core/src/reflex_components_core/core/__init__.pyi": "17f79762cef09c69acd9df227cf3bb35", - "packages/reflex-components-core/src/reflex_components_core/core/auto_scroll.pyi": "3bdf9e60d4b7aa9043143561eacdb911", - "packages/reflex-components-core/src/reflex_components_core/core/banner.pyi": "bd5825c02dafee1b9a85dc9511004bd0", - "packages/reflex-components-core/src/reflex_components_core/core/clipboard.pyi": "be619a31f5f16891f1ad561f7483eb77", - "packages/reflex-components-core/src/reflex_components_core/core/debounce.pyi": "a34556c3d3e3a1cd498be2deba1926d5", - "packages/reflex-components-core/src/reflex_components_core/core/helmet.pyi": "bb1b4d7468cb1496bb301ade46354d3e", - "packages/reflex-components-core/src/reflex_components_core/core/html.pyi": "690ef52cd0c56fcc397826156a79c1e7", - "packages/reflex-components-core/src/reflex_components_core/core/sticky.pyi": "9e817ab33107cb59402e3aebf12c50cc", - "packages/reflex-components-core/src/reflex_components_core/core/upload.pyi": "30a3d28d7e17045f5fd667a50a6731df", - "packages/reflex-components-core/src/reflex_components_core/core/window_events.pyi": "c05b5e617a5f1e534aaa15b6130006cc", - "packages/reflex-components-core/src/reflex_components_core/datadisplay/__init__.pyi": "73e27e0bfd7e914c4baba7b166e75736", - "packages/reflex-components-core/src/reflex_components_core/el/__init__.pyi": "a1d4d65ddc73b5fbba3726f4bcf6d6f0", - "packages/reflex-components-core/src/reflex_components_core/el/element.pyi": "071bd8e46b6b65806c938d343ec739cd", - "packages/reflex-components-core/src/reflex_components_core/el/elements/__init__.pyi": "fbe4c6f4960bcd811311b1f73987cdb1", - "packages/reflex-components-core/src/reflex_components_core/el/elements/base.pyi": "b004e6bfbb7de0789a6581274959dbb3", - "packages/reflex-components-core/src/reflex_components_core/el/elements/forms.pyi": "9ef8d52bef8fd00746c7f28b955ca340", - "packages/reflex-components-core/src/reflex_components_core/el/elements/inline.pyi": "a7d7c110149e0df7fd5798d29789d194", - "packages/reflex-components-core/src/reflex_components_core/el/elements/media.pyi": "88731777d7a7157543a46d44d5d69855", - "packages/reflex-components-core/src/reflex_components_core/el/elements/metadata.pyi": "be4af13583cd210538a5bf401aee4653", - "packages/reflex-components-core/src/reflex_components_core/el/elements/other.pyi": "b38e41dbbc6ab30e9ee5fbb32cf675b9", - "packages/reflex-components-core/src/reflex_components_core/el/elements/scripts.pyi": "6391432c86072d5da87a1a54f4e861e9", - "packages/reflex-components-core/src/reflex_components_core/el/elements/sectioning.pyi": "6cd334cc7c5999ee4ae4c03455688313", - "packages/reflex-components-core/src/reflex_components_core/el/elements/tables.pyi": "28162491f97aeb8af88ec3b3d5faa275", - "packages/reflex-components-core/src/reflex_components_core/el/elements/typography.pyi": "80368be4e5e781a6ece17e452d37719b", - "packages/reflex-components-dataeditor/src/reflex_components_dataeditor/dataeditor.pyi": "d7183df23f15feb744bea0c3ae786a5e", - "packages/reflex-components-gridjs/src/reflex_components_gridjs/datatable.pyi": "ffe864c848a9d775809b4edbc1b731b6", - "packages/reflex-components-lucide/src/reflex_components_lucide/icon.pyi": "a3605c42f5b6b40e1f174dc299df2c9a", - "packages/reflex-components-markdown/src/reflex_components_markdown/markdown.pyi": "1bad32ee95b99fe53e9257186ddf62f5", - "packages/reflex-components-moment/src/reflex_components_moment/moment.pyi": "082cda5a15e08c64be7e2b9490af4828", - "packages/reflex-components-plotly/src/reflex_components_plotly/plotly.pyi": "ac6382621506be5298009616966f9897", - "packages/reflex-components-radix/src/reflex_components_radix/__init__.pyi": "899d63be42eb21c187519920518bf32e", - "packages/reflex-components-radix/src/reflex_components_radix/primitives/__init__.pyi": "11bc17db31334f11d7fcde315b29cae1", - "packages/reflex-components-radix/src/reflex_components_radix/primitives/accordion.pyi": "e9a06d65e8709de70231db48e5aca96a", - "packages/reflex-components-radix/src/reflex_components_radix/primitives/base.pyi": "c5d92880310ab703ea93d4b1a056eb86", - "packages/reflex-components-radix/src/reflex_components_radix/primitives/dialog.pyi": "a79fdb748d331b5987cecc6ee0a34ee6", - "packages/reflex-components-radix/src/reflex_components_radix/primitives/drawer.pyi": "a1c94022c183cae0528d144c0588d62a", - "packages/reflex-components-radix/src/reflex_components_radix/primitives/form.pyi": "a066a50b11c2b9507eb0544d0df3f668", - "packages/reflex-components-radix/src/reflex_components_radix/primitives/progress.pyi": "ac977b63b3640eeaa21204e6e9dcf40c", - "packages/reflex-components-radix/src/reflex_components_radix/primitives/slider.pyi": "a4d76b1220aeb40a53b5f6033f67ff04", - "packages/reflex-components-radix/src/reflex_components_radix/themes/__init__.pyi": "a152450345e7da9124e954843224e936", - "packages/reflex-components-radix/src/reflex_components_radix/themes/base.pyi": "9ae9885a592496736170bf61d3869ee0", - "packages/reflex-components-radix/src/reflex_components_radix/themes/color_mode.pyi": "5f41e358cde825fbdf589bf798b0bf00", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/__init__.pyi": "67d45aa96a97359ee556725641bb583f", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/alert_dialog.pyi": "a58ad421715ce6f80faecc371e6f51d4", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/aspect_ratio.pyi": "f6a1f6388abca0317671e336dcabfc4b", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/avatar.pyi": "99024032ec9f36c918174a3dd13d195e", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/badge.pyi": "04c9570befb04530ceb6f6a1de2dd581", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/button.pyi": "580e9f0b9ad5f5473e11adfdc6513166", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/callout.pyi": "a332c402424d159ab7e9f7a8a2145be1", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/card.pyi": "589ded8d2c453fbfd0c3c5a70f3f9826", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/checkbox.pyi": "6ca7329b87a775ff90cfceade116b28e", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/checkbox_cards.pyi": "aca084957873b350361996cd1e26ff28", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/checkbox_group.pyi": "4af3f42fd55c2a5810aad89f2077f03d", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/context_menu.pyi": "831a5c8aecf3fe8e3afcc863b2e6edbb", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/data_list.pyi": "d526eb5152b616f93214f68670a0f4a9", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/dialog.pyi": "bb0c627b1e07cd8d47ef7eb6218cbcd1", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/dropdown_menu.pyi": "ad62973010567ef84cb29dc485425216", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/hover_card.pyi": "e4e730069f4e3aa20ecbf1f62a2f2911", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/icon_button.pyi": "e357ea5ef2c2c6d59decf528676c768d", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/inset.pyi": "d8ea4c7d2e69dc6fd27d98c3697b95c2", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/popover.pyi": "93a612094af186d17ee2394fce24fb99", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/progress.pyi": "cef3d099f95eb9ac18745df3cb57e9f6", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/radio.pyi": "117da2df15b89b430cd2d6c693d7eb30", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/radio_cards.pyi": "17c13d2209eaf47f43baab0c63574785", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/radio_group.pyi": "4501692b53bf26a18ddf968d4e212b0c", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/scroll_area.pyi": "2e3084f01527cb2a6be0db0cbac27237", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/segmented_control.pyi": "e16843a5243ae03a69c3c13f88910b74", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/select.pyi": "7a9e52419a261ad12ce065443bef7d7c", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/separator.pyi": "445408b7ee359bceddd897f164656e9b", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/skeleton.pyi": "b1bbdcba9221cb264bc202e78ecf7ede", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/slider.pyi": "41a518be074ba740d995cbc0956ddf52", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/spinner.pyi": "4856ba4446ad21985a070d04dd6293d8", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/switch.pyi": "45fb1de0f43bf272ce5bfff9802839e8", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/table.pyi": "9aed7860e0541a82f69ae0ac37a44444", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/tabs.pyi": "7c4cafb3bf6ef7c6254e30f30f8744b0", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/text_area.pyi": "a7689a98975afd666422cf4eec3a3eed", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/text_field.pyi": "743552036a6ae8143d8ce8d23b0d6fc5", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/tooltip.pyi": "7fc4cecd0e2cc70ae25870908c142cb8", - "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/__init__.pyi": "b7dbdecab770a7fc1435ad417d7f91c1", - "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/base.pyi": "b541c3e56e4b9afdb208db97b3c7846d", - "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/box.pyi": "744e6e83ab08c3f151715c5e459a03d2", - "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/center.pyi": "c80413d4051b478e37c10a5e6e132f25", - "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/container.pyi": "b86f337365de46582e71f32135609ad2", - "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/flex.pyi": "4d5128e75445be5cc15d73006abbb1ec", - "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/grid.pyi": "ab58ab7820e10612a3fd1fddb0729003", - "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/list.pyi": "ba6f514524b94cb5e58e3543ac87d4a8", - "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/section.pyi": "295bf854ef0d0feadda6fbfdb7788244", - "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/spacer.pyi": "02275e5a87cf57b880f6ee0cdde9ac94", - "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/stack.pyi": "b8e21fbae89af6a5d72beec914e196c4", - "packages/reflex-components-radix/src/reflex_components_radix/themes/typography/__init__.pyi": "15b13877fc52b82182fc42acd8adb027", - "packages/reflex-components-radix/src/reflex_components_radix/themes/typography/blockquote.pyi": "f03e588746d92248a8b2510cc19e43a6", - "packages/reflex-components-radix/src/reflex_components_radix/themes/typography/code.pyi": "ac3723aef4c9574578c67316e3ec7ebb", - "packages/reflex-components-radix/src/reflex_components_radix/themes/typography/heading.pyi": "b8961f85b0df2f70d345db45d138ea7b", - "packages/reflex-components-radix/src/reflex_components_radix/themes/typography/link.pyi": "2dbd09f648d921572aad7e064d3d2ce1", - "packages/reflex-components-radix/src/reflex_components_radix/themes/typography/text.pyi": "dea370cc7f7e4a75cb671d7ec0ccd949", - "packages/reflex-components-react-player/src/reflex_components_react_player/audio.pyi": "551894a3f295deac9fed3fffb3f171b7", - "packages/reflex-components-react-player/src/reflex_components_react_player/react_player.pyi": "4961725c725fec0538b9e3bdbeafbcfe", - "packages/reflex-components-react-player/src/reflex_components_react_player/video.pyi": "4ca94af43518e83e1b9ee5da55a4843b", - "packages/reflex-components-react-router/src/reflex_components_react_router/dom.pyi": "5c13be18f98b23ed957e53c3a26c6216", - "packages/reflex-components-recharts/src/reflex_components_recharts/__init__.pyi": "1f04ab9f482261f18010fbf728d7b946", - "packages/reflex-components-recharts/src/reflex_components_recharts/cartesian.pyi": "5229afe951d976abc280aa34f72ae8ac", - "packages/reflex-components-recharts/src/reflex_components_recharts/charts.pyi": "c1aef94f3a866c62bec8f82897bd85e8", - "packages/reflex-components-recharts/src/reflex_components_recharts/general.pyi": "e243056a5614ee9e1d8be66d94bba737", - "packages/reflex-components-recharts/src/reflex_components_recharts/polar.pyi": "e27277901bad6b5d2729a6eca8ac4895", - "packages/reflex-components-recharts/src/reflex_components_recharts/recharts.pyi": "fa3dfbba1e25242fdb9787e280b43d1d", - "packages/reflex-components-sonner/src/reflex_components_sonner/toast.pyi": "efd01b2494c53ede3668c8c16e7cdfe7", "reflex/__init__.pyi": "77b2efa084aa8473dce836f78fc4f61a", "reflex/components/__init__.pyi": "d620d52015f908cda828d231c1064236", "reflex/experimental/memo.pyi": "555f5495d3ddd30dc974eacbb046662a" diff --git a/uv.lock b/uv.lock index 294aa58dd83..f0cdeb6f16b 100644 --- a/uv.lock +++ b/uv.lock @@ -2338,6 +2338,7 @@ name = "reflex-core" source = { editable = "packages/reflex-core" } dependencies = [ { name = "packaging" }, + { name = "platformdirs" }, { name = "pydantic" }, { name = "rich" }, { name = "typing-extensions" }, @@ -2346,6 +2347,7 @@ dependencies = [ [package.metadata] requires-dist = [ { name = "packaging", specifier = ">=24.2,<27" }, + { name = "platformdirs", specifier = ">=4.3.7,<5.0" }, { name = "pydantic", specifier = ">=1.10.21,<3.0" }, { name = "rich", specifier = ">=13,<15" }, { name = "typing-extensions", specifier = ">=4.13.0" }, From e921c972164aefaf52902298a90696c5ffa77a07 Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Thu, 26 Mar 2026 15:16:52 -0700 Subject: [PATCH 060/113] imports --- .../src/reflex_components_core/core/banner.py | 2 +- .../src/reflex_core/compiler/templates.py | 2 +- .../src/reflex_core/components/dynamic.py | 2 +- packages/reflex-core/src/reflex_core/event.py | 2 +- .../src/reflex_core/utils/console.py | 21 +++- .../src/reflex_core/utils/format.py | 2 +- .../src/reflex_core/utils/types.py | 2 +- .../reflex-core/src/reflex_core/vars/base.py | 2 +- .../src/reflex_core/vars/sequence.py | 2 +- pyi_hashes.json | 119 ++++++++++++++++++ pyproject.toml | 1 - reflex/_upload.py | 2 +- reflex/app.py | 2 +- reflex/assets.py | 3 +- reflex/compiler/compiler.py | 2 +- reflex/compiler/utils.py | 2 +- reflex/config.py | 9 +- reflex/custom_components/custom_components.py | 5 +- reflex/environment.py | 9 +- reflex/experimental/client_state.py | 2 +- reflex/experimental/memo.py | 2 +- reflex/istate/data.py | 3 +- reflex/istate/manager/__init__.py | 2 +- reflex/middleware/hydrate_middleware.py | 3 +- reflex/plugins/sitemap.py | 3 +- reflex/plugins/tailwind_v3.py | 5 +- reflex/plugins/tailwind_v4.py | 5 +- reflex/reflex.py | 2 +- reflex/route.py | 3 +- reflex/state.py | 2 +- reflex/utils/build.py | 2 +- reflex/utils/exec.py | 2 +- reflex/utils/export.py | 2 +- reflex/utils/frontend_skeleton.py | 3 +- reflex/utils/js_runtimes.py | 2 +- reflex/utils/prerequisites.py | 2 +- reflex/utils/processes.py | 2 +- reflex/utils/redir.py | 4 +- reflex/utils/rename.py | 3 +- reflex/utils/telemetry.py | 3 +- reflex/utils/templates.py | 3 +- tests/__init__.py | 2 +- tests/integration/test_connection_banner.py | 2 +- tests/units/__init__.py | 2 +- tests/units/compiler/test_compiler.py | 2 +- tests/units/test_environment.py | 2 +- tests/units/test_route.py | 2 +- tests/units/test_state.py | 2 +- tests/units/utils/test_utils.py | 2 +- 49 files changed, 205 insertions(+), 62 deletions(-) diff --git a/packages/reflex-components-core/src/reflex_components_core/core/banner.py b/packages/reflex-components-core/src/reflex_components_core/core/banner.py index 151a6cc1a32..d48ebbe9034 100644 --- a/packages/reflex-components-core/src/reflex_components_core/core/banner.py +++ b/packages/reflex-components-core/src/reflex_components_core/core/banner.py @@ -4,13 +4,13 @@ from reflex_components_lucide.icon import Icon from reflex_components_sonner.toast import ToastProps, toast_ref +from reflex_core import constants from reflex_core.vars import VarData from reflex_core.vars.base import LiteralVar, Var from reflex_core.vars.function import FunctionStringVar from reflex_core.vars.number import BooleanVar from reflex_core.vars.sequence import LiteralArrayVar -from reflex import constants from reflex.components.component import Component from reflex.constants import Dirs, Hooks, Imports from reflex.constants.compiler import CompileVars diff --git a/packages/reflex-core/src/reflex_core/compiler/templates.py b/packages/reflex-core/src/reflex_core/compiler/templates.py index c6956e97276..db988bcc9ee 100644 --- a/packages/reflex-core/src/reflex_core/compiler/templates.py +++ b/packages/reflex-core/src/reflex_core/compiler/templates.py @@ -6,7 +6,7 @@ from collections.abc import Iterable, Mapping from typing import TYPE_CHECKING, Any, Literal -from reflex import constants +from reflex_core import constants from reflex_core.constants import Hooks from reflex_core.utils.format import format_state_name, json_dumps from reflex_core.vars.base import VarData diff --git a/packages/reflex-core/src/reflex_core/components/dynamic.py b/packages/reflex-core/src/reflex_core/components/dynamic.py index 3a716be02d2..c207f3e24f7 100644 --- a/packages/reflex-core/src/reflex_core/components/dynamic.py +++ b/packages/reflex-core/src/reflex_core/components/dynamic.py @@ -2,7 +2,7 @@ from typing import TYPE_CHECKING, Union -from reflex import constants +from reflex_core import constants from reflex_core.utils import imports from reflex_core.utils.exceptions import DynamicComponentMissingLibraryError from reflex_core.utils.format import format_library_name diff --git a/packages/reflex-core/src/reflex_core/event.py b/packages/reflex-core/src/reflex_core/event.py index c815a93076c..2204ee93140 100644 --- a/packages/reflex-core/src/reflex_core/event.py +++ b/packages/reflex-core/src/reflex_core/event.py @@ -27,7 +27,7 @@ from typing_extensions import Self, TypeAliasType, TypedDict, TypeVarTuple, Unpack -from reflex import constants +from reflex_core import constants from reflex_core.components.field import BaseField from reflex_core.constants.compiler import CompileVars, Hooks, Imports from reflex_core.constants.state import FRONTEND_EVENT_STATE diff --git a/packages/reflex-core/src/reflex_core/utils/console.py b/packages/reflex-core/src/reflex_core/utils/console.py index f620dfd69c3..b22e724ecdc 100644 --- a/packages/reflex-core/src/reflex_core/utils/console.py +++ b/packages/reflex-core/src/reflex_core/utils/console.py @@ -10,7 +10,7 @@ import sys import time from pathlib import Path -from types import FrameType +from types import FrameType, ModuleType from rich.console import Console from rich.progress import MofNCompleteColumn, Progress, TaskID, TimeElapsedColumn @@ -270,11 +270,24 @@ def _exclude_paths_from_frame_info() -> list[Path]: import socketio import typing_extensions - import reflex as rx + import reflex_core + + try: + import reflex as rx + except ImportError: + rx = None # Exclude utility modules that should never be the source of deprecated reflex usage. - exclude_modules = [click, rx, typing_extensions, socketio, granian] - modules_paths = [file for m in exclude_modules if (file := m.__file__)] + [ + exclude_modules: list[ModuleType | None] = [ + click, + rx, + typing_extensions, + socketio, + granian, + reflex_core, + ] + + modules_paths = [file for m in exclude_modules if m and (file := m.__file__)] + [ spec.origin for m in [*sys.builtin_module_names, *sys.stdlib_module_names] if (spec := importlib.util.find_spec(m)) and spec.origin diff --git a/packages/reflex-core/src/reflex_core/utils/format.py b/packages/reflex-core/src/reflex_core/utils/format.py index 2f1eb20db46..4e673e79af9 100644 --- a/packages/reflex-core/src/reflex_core/utils/format.py +++ b/packages/reflex-core/src/reflex_core/utils/format.py @@ -8,7 +8,7 @@ import re from typing import TYPE_CHECKING, Any -from reflex import constants +from reflex_core import constants from reflex_core.constants.state import FRONTEND_EVENT_STATE from reflex_core.utils import exceptions diff --git a/packages/reflex-core/src/reflex_core/utils/types.py b/packages/reflex-core/src/reflex_core/utils/types.py index c698c4fef44..65ee7581801 100644 --- a/packages/reflex-core/src/reflex_core/utils/types.py +++ b/packages/reflex-core/src/reflex_core/utils/types.py @@ -37,7 +37,7 @@ from typing_extensions import Self as Self from typing_extensions import override as override -from reflex import constants +from reflex_core import constants from reflex_core.utils import console # Potential GenericAlias types for isinstance checks. diff --git a/packages/reflex-core/src/reflex_core/vars/base.py b/packages/reflex-core/src/reflex_core/vars/base.py index 10e8be87385..e9792ff823b 100644 --- a/packages/reflex-core/src/reflex_core/vars/base.py +++ b/packages/reflex-core/src/reflex_core/vars/base.py @@ -39,7 +39,7 @@ from rich.markup import escape from typing_extensions import dataclass_transform, override -from reflex import constants +from reflex_core import constants from reflex_core.constants.compiler import Hooks from reflex_core.constants.state import FIELD_MARKER from reflex_core.utils import console, exceptions, imports, serializers, types diff --git a/packages/reflex-core/src/reflex_core/vars/sequence.py b/packages/reflex-core/src/reflex_core/vars/sequence.py index 8babfeab720..089a2b80813 100644 --- a/packages/reflex-core/src/reflex_core/vars/sequence.py +++ b/packages/reflex-core/src/reflex_core/vars/sequence.py @@ -13,7 +13,7 @@ from typing_extensions import TypeVar as TypingExtensionsTypeVar -from reflex import constants +from reflex_core import constants from reflex_core.constants.base import REFLEX_VAR_OPENING_TAG from reflex_core.utils import types from reflex_core.utils.exceptions import VarTypeError diff --git a/pyi_hashes.json b/pyi_hashes.json index 535d1d2a7f5..83aade50ba6 100644 --- a/pyi_hashes.json +++ b/pyi_hashes.json @@ -1,4 +1,123 @@ { + "packages/reflex-components-code/src/reflex_components_code/code.pyi": "2319e7886182f5ef039f0a1ed1d62317", + "packages/reflex-components-code/src/reflex_components_code/shiki_code_block.pyi": "45379e4c1fc1f214b7a5d66d90f76f1c", + "packages/reflex-components-core/src/reflex_components_core/__init__.pyi": "6c3ceff429117483dd0035e7a21930f4", + "packages/reflex-components-core/src/reflex_components_core/base/__init__.pyi": "4beb5ba739680b7974c37241f3f6791c", + "packages/reflex-components-core/src/reflex_components_core/base/app_wrap.pyi": "900f0927a8fbaea6d70a6c5f79ffe390", + "packages/reflex-components-core/src/reflex_components_core/base/body.pyi": "d05d36b0049a1ca9f48828de0a780264", + "packages/reflex-components-core/src/reflex_components_core/base/document.pyi": "abff8b40e875b1fd5a49adc602c0b3cb", + "packages/reflex-components-core/src/reflex_components_core/base/error_boundary.pyi": "8c6128003fc030ae3cad6a3c603f9600", + "packages/reflex-components-core/src/reflex_components_core/base/fragment.pyi": "203b060bc2ed6a1ee4f5139f3643a41b", + "packages/reflex-components-core/src/reflex_components_core/base/link.pyi": "560d2c9c4a21d5a99ecdaf7f220874d3", + "packages/reflex-components-core/src/reflex_components_core/base/meta.pyi": "d094461c8a1d56ba02102fce4940eff6", + "packages/reflex-components-core/src/reflex_components_core/base/script.pyi": "d6c683e3ad0d791b254c5961ff467ada", + "packages/reflex-components-core/src/reflex_components_core/base/strict_mode.pyi": "490676ec4319d738aee31c3975361591", + "packages/reflex-components-core/src/reflex_components_core/core/__init__.pyi": "17f79762cef09c69acd9df227cf3bb35", + "packages/reflex-components-core/src/reflex_components_core/core/auto_scroll.pyi": "3bdf9e60d4b7aa9043143561eacdb911", + "packages/reflex-components-core/src/reflex_components_core/core/banner.pyi": "bd5825c02dafee1b9a85dc9511004bd0", + "packages/reflex-components-core/src/reflex_components_core/core/clipboard.pyi": "be619a31f5f16891f1ad561f7483eb77", + "packages/reflex-components-core/src/reflex_components_core/core/debounce.pyi": "a34556c3d3e3a1cd498be2deba1926d5", + "packages/reflex-components-core/src/reflex_components_core/core/helmet.pyi": "bb1b4d7468cb1496bb301ade46354d3e", + "packages/reflex-components-core/src/reflex_components_core/core/html.pyi": "690ef52cd0c56fcc397826156a79c1e7", + "packages/reflex-components-core/src/reflex_components_core/core/sticky.pyi": "9e817ab33107cb59402e3aebf12c50cc", + "packages/reflex-components-core/src/reflex_components_core/core/upload.pyi": "30a3d28d7e17045f5fd667a50a6731df", + "packages/reflex-components-core/src/reflex_components_core/core/window_events.pyi": "c05b5e617a5f1e534aaa15b6130006cc", + "packages/reflex-components-core/src/reflex_components_core/datadisplay/__init__.pyi": "73e27e0bfd7e914c4baba7b166e75736", + "packages/reflex-components-core/src/reflex_components_core/el/__init__.pyi": "a1d4d65ddc73b5fbba3726f4bcf6d6f0", + "packages/reflex-components-core/src/reflex_components_core/el/element.pyi": "071bd8e46b6b65806c938d343ec739cd", + "packages/reflex-components-core/src/reflex_components_core/el/elements/__init__.pyi": "fbe4c6f4960bcd811311b1f73987cdb1", + "packages/reflex-components-core/src/reflex_components_core/el/elements/base.pyi": "b004e6bfbb7de0789a6581274959dbb3", + "packages/reflex-components-core/src/reflex_components_core/el/elements/forms.pyi": "9ef8d52bef8fd00746c7f28b955ca340", + "packages/reflex-components-core/src/reflex_components_core/el/elements/inline.pyi": "a7d7c110149e0df7fd5798d29789d194", + "packages/reflex-components-core/src/reflex_components_core/el/elements/media.pyi": "88731777d7a7157543a46d44d5d69855", + "packages/reflex-components-core/src/reflex_components_core/el/elements/metadata.pyi": "be4af13583cd210538a5bf401aee4653", + "packages/reflex-components-core/src/reflex_components_core/el/elements/other.pyi": "b38e41dbbc6ab30e9ee5fbb32cf675b9", + "packages/reflex-components-core/src/reflex_components_core/el/elements/scripts.pyi": "6391432c86072d5da87a1a54f4e861e9", + "packages/reflex-components-core/src/reflex_components_core/el/elements/sectioning.pyi": "6cd334cc7c5999ee4ae4c03455688313", + "packages/reflex-components-core/src/reflex_components_core/el/elements/tables.pyi": "28162491f97aeb8af88ec3b3d5faa275", + "packages/reflex-components-core/src/reflex_components_core/el/elements/typography.pyi": "80368be4e5e781a6ece17e452d37719b", + "packages/reflex-components-dataeditor/src/reflex_components_dataeditor/dataeditor.pyi": "d7183df23f15feb744bea0c3ae786a5e", + "packages/reflex-components-gridjs/src/reflex_components_gridjs/datatable.pyi": "ffe864c848a9d775809b4edbc1b731b6", + "packages/reflex-components-lucide/src/reflex_components_lucide/icon.pyi": "a3605c42f5b6b40e1f174dc299df2c9a", + "packages/reflex-components-markdown/src/reflex_components_markdown/markdown.pyi": "1bad32ee95b99fe53e9257186ddf62f5", + "packages/reflex-components-moment/src/reflex_components_moment/moment.pyi": "082cda5a15e08c64be7e2b9490af4828", + "packages/reflex-components-plotly/src/reflex_components_plotly/plotly.pyi": "ac6382621506be5298009616966f9897", + "packages/reflex-components-radix/src/reflex_components_radix/__init__.pyi": "899d63be42eb21c187519920518bf32e", + "packages/reflex-components-radix/src/reflex_components_radix/primitives/__init__.pyi": "11bc17db31334f11d7fcde315b29cae1", + "packages/reflex-components-radix/src/reflex_components_radix/primitives/accordion.pyi": "e9a06d65e8709de70231db48e5aca96a", + "packages/reflex-components-radix/src/reflex_components_radix/primitives/base.pyi": "c5d92880310ab703ea93d4b1a056eb86", + "packages/reflex-components-radix/src/reflex_components_radix/primitives/dialog.pyi": "a79fdb748d331b5987cecc6ee0a34ee6", + "packages/reflex-components-radix/src/reflex_components_radix/primitives/drawer.pyi": "a1c94022c183cae0528d144c0588d62a", + "packages/reflex-components-radix/src/reflex_components_radix/primitives/form.pyi": "a066a50b11c2b9507eb0544d0df3f668", + "packages/reflex-components-radix/src/reflex_components_radix/primitives/progress.pyi": "ac977b63b3640eeaa21204e6e9dcf40c", + "packages/reflex-components-radix/src/reflex_components_radix/primitives/slider.pyi": "a4d76b1220aeb40a53b5f6033f67ff04", + "packages/reflex-components-radix/src/reflex_components_radix/themes/__init__.pyi": "a152450345e7da9124e954843224e936", + "packages/reflex-components-radix/src/reflex_components_radix/themes/base.pyi": "9ae9885a592496736170bf61d3869ee0", + "packages/reflex-components-radix/src/reflex_components_radix/themes/color_mode.pyi": "5f41e358cde825fbdf589bf798b0bf00", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/__init__.pyi": "67d45aa96a97359ee556725641bb583f", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/alert_dialog.pyi": "a58ad421715ce6f80faecc371e6f51d4", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/aspect_ratio.pyi": "f6a1f6388abca0317671e336dcabfc4b", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/avatar.pyi": "99024032ec9f36c918174a3dd13d195e", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/badge.pyi": "04c9570befb04530ceb6f6a1de2dd581", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/button.pyi": "580e9f0b9ad5f5473e11adfdc6513166", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/callout.pyi": "a332c402424d159ab7e9f7a8a2145be1", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/card.pyi": "589ded8d2c453fbfd0c3c5a70f3f9826", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/checkbox.pyi": "6ca7329b87a775ff90cfceade116b28e", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/checkbox_cards.pyi": "aca084957873b350361996cd1e26ff28", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/checkbox_group.pyi": "4af3f42fd55c2a5810aad89f2077f03d", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/context_menu.pyi": "831a5c8aecf3fe8e3afcc863b2e6edbb", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/data_list.pyi": "d526eb5152b616f93214f68670a0f4a9", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/dialog.pyi": "bb0c627b1e07cd8d47ef7eb6218cbcd1", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/dropdown_menu.pyi": "ad62973010567ef84cb29dc485425216", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/hover_card.pyi": "e4e730069f4e3aa20ecbf1f62a2f2911", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/icon_button.pyi": "e357ea5ef2c2c6d59decf528676c768d", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/inset.pyi": "d8ea4c7d2e69dc6fd27d98c3697b95c2", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/popover.pyi": "93a612094af186d17ee2394fce24fb99", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/progress.pyi": "cef3d099f95eb9ac18745df3cb57e9f6", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/radio.pyi": "117da2df15b89b430cd2d6c693d7eb30", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/radio_cards.pyi": "17c13d2209eaf47f43baab0c63574785", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/radio_group.pyi": "4501692b53bf26a18ddf968d4e212b0c", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/scroll_area.pyi": "2e3084f01527cb2a6be0db0cbac27237", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/segmented_control.pyi": "e16843a5243ae03a69c3c13f88910b74", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/select.pyi": "7a9e52419a261ad12ce065443bef7d7c", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/separator.pyi": "445408b7ee359bceddd897f164656e9b", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/skeleton.pyi": "b1bbdcba9221cb264bc202e78ecf7ede", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/slider.pyi": "41a518be074ba740d995cbc0956ddf52", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/spinner.pyi": "4856ba4446ad21985a070d04dd6293d8", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/switch.pyi": "45fb1de0f43bf272ce5bfff9802839e8", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/table.pyi": "9aed7860e0541a82f69ae0ac37a44444", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/tabs.pyi": "7c4cafb3bf6ef7c6254e30f30f8744b0", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/text_area.pyi": "a7689a98975afd666422cf4eec3a3eed", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/text_field.pyi": "743552036a6ae8143d8ce8d23b0d6fc5", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/tooltip.pyi": "7fc4cecd0e2cc70ae25870908c142cb8", + "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/__init__.pyi": "b7dbdecab770a7fc1435ad417d7f91c1", + "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/base.pyi": "b541c3e56e4b9afdb208db97b3c7846d", + "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/box.pyi": "744e6e83ab08c3f151715c5e459a03d2", + "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/center.pyi": "c80413d4051b478e37c10a5e6e132f25", + "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/container.pyi": "b86f337365de46582e71f32135609ad2", + "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/flex.pyi": "4d5128e75445be5cc15d73006abbb1ec", + "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/grid.pyi": "ab58ab7820e10612a3fd1fddb0729003", + "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/list.pyi": "ba6f514524b94cb5e58e3543ac87d4a8", + "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/section.pyi": "295bf854ef0d0feadda6fbfdb7788244", + "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/spacer.pyi": "02275e5a87cf57b880f6ee0cdde9ac94", + "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/stack.pyi": "b8e21fbae89af6a5d72beec914e196c4", + "packages/reflex-components-radix/src/reflex_components_radix/themes/typography/__init__.pyi": "15b13877fc52b82182fc42acd8adb027", + "packages/reflex-components-radix/src/reflex_components_radix/themes/typography/blockquote.pyi": "f03e588746d92248a8b2510cc19e43a6", + "packages/reflex-components-radix/src/reflex_components_radix/themes/typography/code.pyi": "ac3723aef4c9574578c67316e3ec7ebb", + "packages/reflex-components-radix/src/reflex_components_radix/themes/typography/heading.pyi": "b8961f85b0df2f70d345db45d138ea7b", + "packages/reflex-components-radix/src/reflex_components_radix/themes/typography/link.pyi": "2dbd09f648d921572aad7e064d3d2ce1", + "packages/reflex-components-radix/src/reflex_components_radix/themes/typography/text.pyi": "dea370cc7f7e4a75cb671d7ec0ccd949", + "packages/reflex-components-react-player/src/reflex_components_react_player/audio.pyi": "551894a3f295deac9fed3fffb3f171b7", + "packages/reflex-components-react-player/src/reflex_components_react_player/react_player.pyi": "4961725c725fec0538b9e3bdbeafbcfe", + "packages/reflex-components-react-player/src/reflex_components_react_player/video.pyi": "4ca94af43518e83e1b9ee5da55a4843b", + "packages/reflex-components-react-router/src/reflex_components_react_router/dom.pyi": "5c13be18f98b23ed957e53c3a26c6216", + "packages/reflex-components-recharts/src/reflex_components_recharts/__init__.pyi": "1f04ab9f482261f18010fbf728d7b946", + "packages/reflex-components-recharts/src/reflex_components_recharts/cartesian.pyi": "5229afe951d976abc280aa34f72ae8ac", + "packages/reflex-components-recharts/src/reflex_components_recharts/charts.pyi": "c1aef94f3a866c62bec8f82897bd85e8", + "packages/reflex-components-recharts/src/reflex_components_recharts/general.pyi": "e243056a5614ee9e1d8be66d94bba737", + "packages/reflex-components-recharts/src/reflex_components_recharts/polar.pyi": "e27277901bad6b5d2729a6eca8ac4895", + "packages/reflex-components-recharts/src/reflex_components_recharts/recharts.pyi": "fa3dfbba1e25242fdb9787e280b43d1d", + "packages/reflex-components-sonner/src/reflex_components_sonner/toast.pyi": "efd01b2494c53ede3668c8c16e7cdfe7", "reflex/__init__.pyi": "77b2efa084aa8473dce836f78fc4f61a", "reflex/components/__init__.pyi": "d620d52015f908cda828d231c1064236", "reflex/experimental/memo.pyi": "555f5495d3ddd30dc974eacbb046662a" diff --git a/pyproject.toml b/pyproject.toml index 8b619443dcd..722dbd47c42 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -25,7 +25,6 @@ dependencies = [ "granian[reload] >=2.5.5", "httpx >=0.23.3,<1.0", "packaging >=24.2,<27", - "platformdirs >=4.3.7,<5.0", "psutil >=7.0.0,<8.0; sys_platform == 'win32'", "pydantic >=2.12.0,<3.0", "python-multipart >=0.0.20,<1.0", diff --git a/reflex/_upload.py b/reflex/_upload.py index 1e7ca21537c..0e006bd5d4f 100644 --- a/reflex/_upload.py +++ b/reflex/_upload.py @@ -11,6 +11,7 @@ from typing import TYPE_CHECKING, Any, BinaryIO, cast from python_multipart.multipart import MultipartParser, parse_options_header +from reflex_core import constants from starlette.datastructures import Headers from starlette.datastructures import UploadFile as StarletteUploadFile from starlette.exceptions import HTTPException @@ -19,7 +20,6 @@ from starlette.responses import JSONResponse, Response, StreamingResponse from typing_extensions import Self -from reflex import constants from reflex.utils import exceptions if TYPE_CHECKING: diff --git a/reflex/app.py b/reflex/app.py index 01cb8f536f6..e94eab78ed4 100644 --- a/reflex/app.py +++ b/reflex/app.py @@ -43,6 +43,7 @@ from reflex_components_core.core.sticky import sticky from reflex_components_radix import themes from reflex_components_sonner.toast import toast +from reflex_core import constants from reflex_core.event import ( _EVENT_FIELDS, Event, @@ -62,7 +63,6 @@ from starlette.staticfiles import StaticFiles from typing_extensions import Unpack -from reflex import constants from reflex._upload import UploadFile as UploadFile from reflex._upload import upload from reflex.admin import AdminDash diff --git a/reflex/assets.py b/reflex/assets.py index d86e93ad9c6..c3a3232063b 100644 --- a/reflex/assets.py +++ b/reflex/assets.py @@ -3,7 +3,8 @@ import inspect from pathlib import Path -from reflex import constants +from reflex_core import constants + from reflex.environment import EnvironmentVariables diff --git a/reflex/compiler/compiler.py b/reflex/compiler/compiler.py index 896643861e5..02090d3bb70 100644 --- a/reflex/compiler/compiler.py +++ b/reflex/compiler/compiler.py @@ -9,9 +9,9 @@ from typing import TYPE_CHECKING, Any from reflex_components_core.base.fragment import Fragment +from reflex_core import constants from reflex_core.vars.base import LiteralVar, Var -from reflex import constants from reflex.compiler import templates, utils from reflex.components.component import ( BaseComponent, diff --git a/reflex/compiler/utils.py b/reflex/compiler/utils.py index ddd2bf55433..a072c28e07f 100644 --- a/reflex/compiler/utils.py +++ b/reflex/compiler/utils.py @@ -19,10 +19,10 @@ from reflex_components_core.el.elements.metadata import Head, Link, Meta, Title from reflex_components_core.el.elements.other import Html from reflex_components_core.el.elements.sectioning import Body +from reflex_core import constants from reflex_core.vars.base import Field, Var, VarData from reflex_core.vars.function import DestructuredArg -from reflex import constants from reflex.components.component import Component, ComponentStyle, CustomComponent from reflex.constants.state import CAMEL_CASE_MEMO_MARKER, FIELD_MARKER from reflex.experimental.memo import ( diff --git a/reflex/config.py b/reflex/config.py index f3f17aa2949..4ce095299ba 100644 --- a/reflex/config.py +++ b/reflex/config.py @@ -12,8 +12,11 @@ from types import ModuleType from typing import TYPE_CHECKING, Annotated, Any, ClassVar, Literal -from reflex import constants -from reflex.constants.base import LogLevel +from reflex_core import constants +from reflex_core.constants.base import LogLevel +from reflex_core.utils import console +from reflex_core.utils.exceptions import ConfigError + from reflex.environment import EnvironmentVariables as EnvironmentVariables from reflex.environment import EnvVar as EnvVar from reflex.environment import ( @@ -27,8 +30,6 @@ from reflex.environment import environment as environment from reflex.plugins import Plugin from reflex.plugins.sitemap import SitemapPlugin -from reflex.utils import console -from reflex.utils.exceptions import ConfigError @dataclasses.dataclass(kw_only=True) diff --git a/reflex/custom_components/custom_components.py b/reflex/custom_components/custom_components.py index cce9058194c..0ffc6ea13cb 100644 --- a/reflex/custom_components/custom_components.py +++ b/reflex/custom_components/custom_components.py @@ -12,8 +12,8 @@ from typing import Any import click +from reflex_core import constants -from reflex import constants from reflex.constants import CustomComponents from reflex.utils import console, frontend_skeleton @@ -328,7 +328,8 @@ def _populate_demo_app(name_variants: NameVariants): Args: name_variants: the tuple including various names such as package name, class name needed for the project. """ - from reflex import constants + from reflex_core import constants + from reflex.reflex import _init demo_app_dir = Path(name_variants.demo_app_dir) diff --git a/reflex/environment.py b/reflex/environment.py index c9895facbfd..63fc8b58a2f 100644 --- a/reflex/environment.py +++ b/reflex/environment.py @@ -24,11 +24,12 @@ get_type_hints, ) -from reflex import constants -from reflex.constants.base import LogLevel +from reflex_core import constants +from reflex_core.constants.base import LogLevel +from reflex_core.utils.exceptions import EnvironmentVarValueError +from reflex_core.utils.types import GenericType, is_union, value_inside_optional + from reflex.plugins import Plugin -from reflex.utils.exceptions import EnvironmentVarValueError -from reflex.utils.types import GenericType, is_union, value_inside_optional def get_default_value_for_field(field: dataclasses.Field) -> Any: diff --git a/reflex/experimental/client_state.py b/reflex/experimental/client_state.py index 952e3ad2d98..fcbab6cd2ed 100644 --- a/reflex/experimental/client_state.py +++ b/reflex/experimental/client_state.py @@ -7,11 +7,11 @@ from collections.abc import Callable from typing import Any +from reflex_core import constants from reflex_core.vars import VarData, get_unique_variable_name from reflex_core.vars.base import LiteralVar, Var from reflex_core.vars.function import ArgsFunctionOperationBuilder, FunctionVar -from reflex import constants from reflex.event import EventChain, EventHandler, EventSpec, run_script from reflex.utils.imports import ImportVar diff --git a/reflex/experimental/memo.py b/reflex/experimental/memo.py index 1c6297fe20f..057e867f1e7 100644 --- a/reflex/experimental/memo.py +++ b/reflex/experimental/memo.py @@ -10,6 +10,7 @@ from reflex_components_core.base.bare import Bare from reflex_components_core.base.fragment import Fragment +from reflex_core import constants from reflex_core.vars import VarData from reflex_core.vars.base import LiteralVar, Var from reflex_core.vars.function import ( @@ -21,7 +22,6 @@ ) from reflex_core.vars.object import RestProp -from reflex import constants from reflex.components.component import Component from reflex.components.dynamic import bundled_libraries from reflex.constants.compiler import SpecialAttributes diff --git a/reflex/istate/data.py b/reflex/istate/data.py index ba75a8df4dc..4e86370ab73 100644 --- a/reflex/istate/data.py +++ b/reflex/istate/data.py @@ -6,7 +6,8 @@ from typing import TYPE_CHECKING from urllib.parse import _NetlocResultMixinStr, parse_qsl, urlsplit -from reflex import constants +from reflex_core import constants + from reflex.utils import console, format from reflex.utils.serializers import serializer diff --git a/reflex/istate/manager/__init__.py b/reflex/istate/manager/__init__.py index 327d08811b0..d8df0895e44 100644 --- a/reflex/istate/manager/__init__.py +++ b/reflex/istate/manager/__init__.py @@ -6,9 +6,9 @@ from collections.abc import AsyncIterator from typing import TypedDict +from reflex_core import constants from typing_extensions import ReadOnly, Unpack -from reflex import constants from reflex.config import get_config from reflex.event import Event from reflex.state import BaseState diff --git a/reflex/middleware/hydrate_middleware.py b/reflex/middleware/hydrate_middleware.py index ec18939dee3..0e0b6970717 100644 --- a/reflex/middleware/hydrate_middleware.py +++ b/reflex/middleware/hydrate_middleware.py @@ -5,7 +5,8 @@ import dataclasses from typing import TYPE_CHECKING -from reflex import constants +from reflex_core import constants + from reflex.event import Event, get_hydrate_event from reflex.middleware.middleware import Middleware from reflex.state import BaseState, StateUpdate, _resolve_delta diff --git a/reflex/plugins/sitemap.py b/reflex/plugins/sitemap.py index e2ba3257435..dea05afd26d 100644 --- a/reflex/plugins/sitemap.py +++ b/reflex/plugins/sitemap.py @@ -7,10 +7,9 @@ from typing import TYPE_CHECKING, Literal, TypedDict from xml.etree.ElementTree import Element, SubElement, indent, tostring +from reflex_core import constants from typing_extensions import NotRequired -from reflex import constants - from .base import Plugin as PluginBase if TYPE_CHECKING: diff --git a/reflex/plugins/tailwind_v3.py b/reflex/plugins/tailwind_v3.py index 04e25904018..5d0e937a622 100644 --- a/reflex/plugins/tailwind_v3.py +++ b/reflex/plugins/tailwind_v3.py @@ -4,8 +4,9 @@ from pathlib import Path from types import SimpleNamespace -from reflex.constants.base import Dirs -from reflex.constants.compiler import Ext, PageNames +from reflex_core.constants.base import Dirs +from reflex_core.constants.compiler import Ext, PageNames + from reflex.plugins.shared_tailwind import ( TailwindConfig, TailwindPlugin, diff --git a/reflex/plugins/tailwind_v4.py b/reflex/plugins/tailwind_v4.py index 072ca088a7a..751472261ac 100644 --- a/reflex/plugins/tailwind_v4.py +++ b/reflex/plugins/tailwind_v4.py @@ -4,8 +4,9 @@ from pathlib import Path from types import SimpleNamespace -from reflex.constants.base import Dirs -from reflex.constants.compiler import Ext, PageNames +from reflex_core.constants.base import Dirs +from reflex_core.constants.compiler import Ext, PageNames + from reflex.plugins.shared_tailwind import ( TailwindConfig, TailwindPlugin, diff --git a/reflex/reflex.py b/reflex/reflex.py index 7c3f98465b2..45061a6e685 100644 --- a/reflex/reflex.py +++ b/reflex/reflex.py @@ -8,8 +8,8 @@ import click from reflex_cli.v2.deployments import hosting_cli +from reflex_core import constants -from reflex import constants from reflex.config import get_config from reflex.custom_components.custom_components import custom_components_cli from reflex.environment import environment diff --git a/reflex/route.py b/reflex/route.py index 761ec8f974d..51db722f470 100644 --- a/reflex/route.py +++ b/reflex/route.py @@ -5,7 +5,8 @@ import re from collections.abc import Callable -from reflex import constants +from reflex_core import constants + from reflex.config import get_config diff --git a/reflex/state.py b/reflex/state.py index 185f91a6e2e..ef084b997c6 100644 --- a/reflex/state.py +++ b/reflex/state.py @@ -32,6 +32,7 @@ get_type_hints, ) +from reflex_core import constants, event from reflex_core.utils.types import _isinstance, is_union, value_inside_optional from reflex_core.vars import Field, VarData, field from reflex_core.vars.base import ( @@ -47,7 +48,6 @@ from typing_extensions import Self import reflex.istate.dynamic -from reflex import constants, event from reflex.constants.state import FIELD_MARKER from reflex.environment import PerformanceMode, environment from reflex.event import ( diff --git a/reflex/utils/build.py b/reflex/utils/build.py index 7b7408f8b3b..f1ae7424589 100644 --- a/reflex/utils/build.py +++ b/reflex/utils/build.py @@ -6,9 +6,9 @@ import zipfile from pathlib import Path, PosixPath +from reflex_core import constants from rich.progress import MofNCompleteColumn, Progress, TimeElapsedColumn -from reflex import constants from reflex.config import get_config from reflex.utils import console, js_runtimes, path_ops, prerequisites, processes from reflex.utils.exec import is_in_app_harness diff --git a/reflex/utils/exec.py b/reflex/utils/exec.py index 28be5ee4171..73c5a58c579 100644 --- a/reflex/utils/exec.py +++ b/reflex/utils/exec.py @@ -15,9 +15,9 @@ from typing import Any, NamedTuple, TypedDict from urllib.parse import urljoin +from reflex_core import constants from reflex_core.utils import console -from reflex import constants from reflex.config import get_config from reflex.constants.base import LogLevel from reflex.environment import environment diff --git a/reflex/utils/export.py b/reflex/utils/export.py index c6c22c4fe19..198e5c59a59 100644 --- a/reflex/utils/export.py +++ b/reflex/utils/export.py @@ -2,9 +2,9 @@ from pathlib import Path +from reflex_core import constants from reflex_core.utils import console -from reflex import constants from reflex.config import get_config from reflex.environment import environment from reflex.utils import build, exec, prerequisites, telemetry diff --git a/reflex/utils/frontend_skeleton.py b/reflex/utils/frontend_skeleton.py index 37d66fac7db..3af617f5277 100644 --- a/reflex/utils/frontend_skeleton.py +++ b/reflex/utils/frontend_skeleton.py @@ -5,7 +5,8 @@ import re from pathlib import Path -from reflex import constants +from reflex_core import constants + from reflex.compiler import templates from reflex.config import Config, get_config from reflex.environment import environment diff --git a/reflex/utils/js_runtimes.py b/reflex/utils/js_runtimes.py index 76eb9b12654..527d06cac86 100644 --- a/reflex/utils/js_runtimes.py +++ b/reflex/utils/js_runtimes.py @@ -7,8 +7,8 @@ from pathlib import Path from packaging import version +from reflex_core import constants -from reflex import constants from reflex.config import Config, get_config from reflex.environment import environment from reflex.utils import console, net, path_ops, processes diff --git a/reflex/utils/prerequisites.py b/reflex/utils/prerequisites.py index 6e9353d9633..084d21b266a 100644 --- a/reflex/utils/prerequisites.py +++ b/reflex/utils/prerequisites.py @@ -18,8 +18,8 @@ from typing import NamedTuple from packaging import version +from reflex_core import constants, model -from reflex import constants, model from reflex.config import Config, get_config from reflex.environment import environment from reflex.utils import console, net, path_ops diff --git a/reflex/utils/processes.py b/reflex/utils/processes.py index a8f893fa661..0ed8a494359 100644 --- a/reflex/utils/processes.py +++ b/reflex/utils/processes.py @@ -16,9 +16,9 @@ from typing import Any, Literal, overload import rich.markup +from reflex_core import constants from rich.progress import Progress -from reflex import constants from reflex.config import get_config from reflex.environment import environment from reflex.utils import console, path_ops, prerequisites diff --git a/reflex/utils/redir.py b/reflex/utils/redir.py index 0193ac1d223..76333bc93fa 100644 --- a/reflex/utils/redir.py +++ b/reflex/utils/redir.py @@ -29,7 +29,7 @@ def reflex_build_redirect() -> None: """Open the browser window to reflex.build.""" from urllib.parse import urlsplit - from reflex import constants + from reflex_core import constants open_browser(urlsplit(constants.Templates.REFLEX_BUILD_FRONTEND_WITH_REFERRER)) @@ -38,6 +38,6 @@ def reflex_templates(): """Open the browser window to reflex.build/templates.""" from urllib.parse import urlsplit - from reflex import constants + from reflex_core import constants open_browser(urlsplit(constants.Templates.REFLEX_TEMPLATES_URL)) diff --git a/reflex/utils/rename.py b/reflex/utils/rename.py index a51e6c990b1..3af84b956cb 100644 --- a/reflex/utils/rename.py +++ b/reflex/utils/rename.py @@ -4,7 +4,8 @@ import sys from pathlib import Path -from reflex import constants +from reflex_core import constants + from reflex.config import get_config from reflex.utils import console from reflex.utils.misc import get_module_path diff --git a/reflex/utils/telemetry.py b/reflex/utils/telemetry.py index 87202215268..a6dc9308179 100644 --- a/reflex/utils/telemetry.py +++ b/reflex/utils/telemetry.py @@ -11,7 +11,8 @@ from datetime import datetime, timezone from typing import TypedDict -from reflex import constants +from reflex_core import constants + from reflex.environment import environment from reflex.utils import console, processes from reflex.utils.decorator import once, once_unless_none diff --git a/reflex/utils/templates.py b/reflex/utils/templates.py index 6bd12253dfe..f1a82b70d3e 100644 --- a/reflex/utils/templates.py +++ b/reflex/utils/templates.py @@ -7,7 +7,8 @@ from pathlib import Path from urllib.parse import urlparse -from reflex import constants +from reflex_core import constants + from reflex.config import get_config from reflex.utils import console, net, path_ops, redir diff --git a/tests/__init__.py b/tests/__init__.py index d0196603cf2..e9d07476c29 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -2,4 +2,4 @@ import os -from reflex import constants +from reflex_core import constants diff --git a/tests/integration/test_connection_banner.py b/tests/integration/test_connection_banner.py index b6dbb5e1bf5..8ff1516005d 100644 --- a/tests/integration/test_connection_banner.py +++ b/tests/integration/test_connection_banner.py @@ -4,10 +4,10 @@ from collections.abc import Generator import pytest +from reflex_core import constants from selenium.common.exceptions import NoSuchElementException from selenium.webdriver.common.by import By -from reflex import constants from reflex.environment import environment from reflex.istate.manager.redis import StateManagerRedis from reflex.testing import AppHarness, WebDriver diff --git a/tests/units/__init__.py b/tests/units/__init__.py index d0196603cf2..e9d07476c29 100644 --- a/tests/units/__init__.py +++ b/tests/units/__init__.py @@ -2,4 +2,4 @@ import os -from reflex import constants +from reflex_core import constants diff --git a/tests/units/compiler/test_compiler.py b/tests/units/compiler/test_compiler.py index c112e6e9267..d64879373a5 100644 --- a/tests/units/compiler/test_compiler.py +++ b/tests/units/compiler/test_compiler.py @@ -6,10 +6,10 @@ from pytest_mock import MockerFixture from reflex_components_core.base import document from reflex_components_core.el.elements.metadata import Link +from reflex_core import constants from reflex_core.vars.base import Var from reflex_core.vars.sequence import LiteralStringVar -from reflex import constants from reflex.compiler import compiler, utils from reflex.constants.compiler import PageNames from reflex.utils.imports import ImportVar, ParsedImportDict diff --git a/tests/units/test_environment.py b/tests/units/test_environment.py index ee22bcc1357..371cdf69e70 100644 --- a/tests/units/test_environment.py +++ b/tests/units/test_environment.py @@ -8,8 +8,8 @@ from unittest.mock import patch import pytest +from reflex_core import constants -from reflex import constants from reflex.environment import ( EnvironmentVariables, EnvVar, diff --git a/tests/units/test_route.py b/tests/units/test_route.py index db095b3c802..feb94d3e5e2 100644 --- a/tests/units/test_route.py +++ b/tests/units/test_route.py @@ -1,7 +1,7 @@ import pytest from pytest_mock import MockerFixture +from reflex_core import constants -from reflex import constants from reflex.app import App from reflex.route import get_route_args, verify_route_validity diff --git a/tests/units/test_state.py b/tests/units/test_state.py index 5caad311587..b2a6cf33bc8 100644 --- a/tests/units/test_state.py +++ b/tests/units/test_state.py @@ -20,11 +20,11 @@ from plotly.graph_objects import Figure from pydantic import BaseModel as Base from pytest_mock import MockerFixture +from reflex_core import constants from reflex_core.vars.base import Field, Var, computed_var, field import reflex as rx import reflex.config -from reflex import constants from reflex.app import App from reflex.constants import CompileVars, RouteVar, SocketEvent from reflex.constants.state import FIELD_MARKER diff --git a/tests/units/utils/test_utils.py b/tests/units/utils/test_utils.py index f5bf506e9af..83b0fba5858 100644 --- a/tests/units/utils/test_utils.py +++ b/tests/units/utils/test_utils.py @@ -8,9 +8,9 @@ import pytest from packaging import version from pytest_mock import MockerFixture +from reflex_core import constants from reflex_core.vars.base import Var -from reflex import constants from reflex.environment import environment from reflex.event import EventHandler from reflex.state import BaseState From e80bdd9c976b2df8eee50600807fb05a067aca95 Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Thu, 26 Mar 2026 15:18:15 -0700 Subject: [PATCH 061/113] move plugins --- .../reflex-core/src/reflex_core}/plugins/__init__.py | 0 .../reflex-core/src/reflex_core}/plugins/_screenshot.py | 0 {reflex => packages/reflex-core/src/reflex_core}/plugins/base.py | 0 .../reflex-core/src/reflex_core}/plugins/shared_tailwind.py | 0 .../reflex-core/src/reflex_core}/plugins/sitemap.py | 0 .../reflex-core/src/reflex_core}/plugins/tailwind_v3.py | 0 .../reflex-core/src/reflex_core}/plugins/tailwind_v4.py | 0 7 files changed, 0 insertions(+), 0 deletions(-) rename {reflex => packages/reflex-core/src/reflex_core}/plugins/__init__.py (100%) rename {reflex => packages/reflex-core/src/reflex_core}/plugins/_screenshot.py (100%) rename {reflex => packages/reflex-core/src/reflex_core}/plugins/base.py (100%) rename {reflex => packages/reflex-core/src/reflex_core}/plugins/shared_tailwind.py (100%) rename {reflex => packages/reflex-core/src/reflex_core}/plugins/sitemap.py (100%) rename {reflex => packages/reflex-core/src/reflex_core}/plugins/tailwind_v3.py (100%) rename {reflex => packages/reflex-core/src/reflex_core}/plugins/tailwind_v4.py (100%) diff --git a/reflex/plugins/__init__.py b/packages/reflex-core/src/reflex_core/plugins/__init__.py similarity index 100% rename from reflex/plugins/__init__.py rename to packages/reflex-core/src/reflex_core/plugins/__init__.py diff --git a/reflex/plugins/_screenshot.py b/packages/reflex-core/src/reflex_core/plugins/_screenshot.py similarity index 100% rename from reflex/plugins/_screenshot.py rename to packages/reflex-core/src/reflex_core/plugins/_screenshot.py diff --git a/reflex/plugins/base.py b/packages/reflex-core/src/reflex_core/plugins/base.py similarity index 100% rename from reflex/plugins/base.py rename to packages/reflex-core/src/reflex_core/plugins/base.py diff --git a/reflex/plugins/shared_tailwind.py b/packages/reflex-core/src/reflex_core/plugins/shared_tailwind.py similarity index 100% rename from reflex/plugins/shared_tailwind.py rename to packages/reflex-core/src/reflex_core/plugins/shared_tailwind.py diff --git a/reflex/plugins/sitemap.py b/packages/reflex-core/src/reflex_core/plugins/sitemap.py similarity index 100% rename from reflex/plugins/sitemap.py rename to packages/reflex-core/src/reflex_core/plugins/sitemap.py diff --git a/reflex/plugins/tailwind_v3.py b/packages/reflex-core/src/reflex_core/plugins/tailwind_v3.py similarity index 100% rename from reflex/plugins/tailwind_v3.py rename to packages/reflex-core/src/reflex_core/plugins/tailwind_v3.py diff --git a/reflex/plugins/tailwind_v4.py b/packages/reflex-core/src/reflex_core/plugins/tailwind_v4.py similarity index 100% rename from reflex/plugins/tailwind_v4.py rename to packages/reflex-core/src/reflex_core/plugins/tailwind_v4.py From b595e73cb4dbfec2a56d378c496a73c1f33603c7 Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Thu, 26 Mar 2026 15:18:55 -0700 Subject: [PATCH 062/113] move plugins --- .../src/reflex_core/plugins/_screenshot.py | 4 ++-- .../src/reflex_core/plugins/sitemap.py | 3 ++- .../src/reflex_core/plugins/tailwind_v3.py | 3 +-- .../src/reflex_core/plugins/tailwind_v4.py | 3 +-- reflex/plugins/__init__.py | 22 +++++++++++++++++++ reflex/plugins/_screenshot.py | 4 ++++ reflex/plugins/base.py | 9 ++++++++ reflex/plugins/shared_tailwind.py | 3 +++ reflex/plugins/sitemap.py | 4 ++++ reflex/plugins/tailwind_v3.py | 4 ++++ reflex/plugins/tailwind_v4.py | 4 ++++ 11 files changed, 56 insertions(+), 7 deletions(-) create mode 100644 reflex/plugins/__init__.py create mode 100644 reflex/plugins/_screenshot.py create mode 100644 reflex/plugins/base.py create mode 100644 reflex/plugins/shared_tailwind.py create mode 100644 reflex/plugins/sitemap.py create mode 100644 reflex/plugins/tailwind_v3.py create mode 100644 reflex/plugins/tailwind_v4.py diff --git a/packages/reflex-core/src/reflex_core/plugins/_screenshot.py b/packages/reflex-core/src/reflex_core/plugins/_screenshot.py index f0741c9fd0e..7b3b1d5e8b7 100644 --- a/packages/reflex-core/src/reflex_core/plugins/_screenshot.py +++ b/packages/reflex-core/src/reflex_core/plugins/_screenshot.py @@ -2,7 +2,7 @@ from typing import TYPE_CHECKING -from reflex.plugins.base import Plugin as BasePlugin +from reflex_core.plugins.base import Plugin as BasePlugin if TYPE_CHECKING: from starlette.requests import Request @@ -10,8 +10,8 @@ from typing_extensions import Unpack from reflex.app import App - from reflex.plugins.base import PostCompileContext from reflex.state import BaseState + from reflex_core.plugins.base import PostCompileContext ACTIVE_CONNECTIONS = "/_active_connections" CLONE_STATE = "/_clone_state" diff --git a/packages/reflex-core/src/reflex_core/plugins/sitemap.py b/packages/reflex-core/src/reflex_core/plugins/sitemap.py index dea05afd26d..9fb8a45dbb7 100644 --- a/packages/reflex-core/src/reflex_core/plugins/sitemap.py +++ b/packages/reflex-core/src/reflex_core/plugins/sitemap.py @@ -7,9 +7,10 @@ from typing import TYPE_CHECKING, Literal, TypedDict from xml.etree.ElementTree import Element, SubElement, indent, tostring -from reflex_core import constants from typing_extensions import NotRequired +from reflex_core import constants + from .base import Plugin as PluginBase if TYPE_CHECKING: diff --git a/packages/reflex-core/src/reflex_core/plugins/tailwind_v3.py b/packages/reflex-core/src/reflex_core/plugins/tailwind_v3.py index 5d0e937a622..ed9e641c18b 100644 --- a/packages/reflex-core/src/reflex_core/plugins/tailwind_v3.py +++ b/packages/reflex-core/src/reflex_core/plugins/tailwind_v3.py @@ -6,8 +6,7 @@ from reflex_core.constants.base import Dirs from reflex_core.constants.compiler import Ext, PageNames - -from reflex.plugins.shared_tailwind import ( +from reflex_core.plugins.shared_tailwind import ( TailwindConfig, TailwindPlugin, tailwind_config_js_template, diff --git a/packages/reflex-core/src/reflex_core/plugins/tailwind_v4.py b/packages/reflex-core/src/reflex_core/plugins/tailwind_v4.py index 751472261ac..58ff794cab8 100644 --- a/packages/reflex-core/src/reflex_core/plugins/tailwind_v4.py +++ b/packages/reflex-core/src/reflex_core/plugins/tailwind_v4.py @@ -6,8 +6,7 @@ from reflex_core.constants.base import Dirs from reflex_core.constants.compiler import Ext, PageNames - -from reflex.plugins.shared_tailwind import ( +from reflex_core.plugins.shared_tailwind import ( TailwindConfig, TailwindPlugin, tailwind_config_js_template, diff --git a/reflex/plugins/__init__.py b/reflex/plugins/__init__.py new file mode 100644 index 00000000000..1e32c6eeb89 --- /dev/null +++ b/reflex/plugins/__init__.py @@ -0,0 +1,22 @@ +"""Re-export from reflex_core.plugins.""" + +from reflex_core.plugins import * # noqa: F401, F403 +from reflex_core.plugins import ( + CommonContext, + Plugin, + PreCompileContext, + SitemapPlugin, + TailwindV3Plugin, + TailwindV4Plugin, + _ScreenshotPlugin, +) + +__all__ = [ + "CommonContext", + "Plugin", + "PreCompileContext", + "SitemapPlugin", + "TailwindV3Plugin", + "TailwindV4Plugin", + "_ScreenshotPlugin", +] diff --git a/reflex/plugins/_screenshot.py b/reflex/plugins/_screenshot.py new file mode 100644 index 00000000000..98b8c486e62 --- /dev/null +++ b/reflex/plugins/_screenshot.py @@ -0,0 +1,4 @@ +"""Re-export from reflex_core.plugins._screenshot.""" + +from reflex_core.plugins._screenshot import * # noqa: F401, F403 +from reflex_core.plugins._screenshot import ScreenshotPlugin diff --git a/reflex/plugins/base.py b/reflex/plugins/base.py new file mode 100644 index 00000000000..93fbb23fac5 --- /dev/null +++ b/reflex/plugins/base.py @@ -0,0 +1,9 @@ +"""Re-export from reflex_core.plugins.base.""" + +from reflex_core.plugins.base import * # noqa: F401, F403 +from reflex_core.plugins.base import ( + CommonContext, + Plugin, + PostCompileContext, + PreCompileContext, +) diff --git a/reflex/plugins/shared_tailwind.py b/reflex/plugins/shared_tailwind.py new file mode 100644 index 00000000000..1361168bc9d --- /dev/null +++ b/reflex/plugins/shared_tailwind.py @@ -0,0 +1,3 @@ +"""Re-export from reflex_core.plugins.shared_tailwind.""" + +from reflex_core.plugins.shared_tailwind import * # noqa: F401, F403 diff --git a/reflex/plugins/sitemap.py b/reflex/plugins/sitemap.py new file mode 100644 index 00000000000..76d45da1eb8 --- /dev/null +++ b/reflex/plugins/sitemap.py @@ -0,0 +1,4 @@ +"""Re-export from reflex_core.plugins.sitemap.""" + +from reflex_core.plugins.sitemap import * # noqa: F401, F403 +from reflex_core.plugins.sitemap import SitemapPlugin diff --git a/reflex/plugins/tailwind_v3.py b/reflex/plugins/tailwind_v3.py new file mode 100644 index 00000000000..99073f656c7 --- /dev/null +++ b/reflex/plugins/tailwind_v3.py @@ -0,0 +1,4 @@ +"""Re-export from reflex_core.plugins.tailwind_v3.""" + +from reflex_core.plugins.tailwind_v3 import * # noqa: F401, F403 +from reflex_core.plugins.tailwind_v3 import TailwindV3Plugin diff --git a/reflex/plugins/tailwind_v4.py b/reflex/plugins/tailwind_v4.py new file mode 100644 index 00000000000..6425cb32b1d --- /dev/null +++ b/reflex/plugins/tailwind_v4.py @@ -0,0 +1,4 @@ +"""Re-export from reflex_core.plugins.tailwind_v4.""" + +from reflex_core.plugins.tailwind_v4 import * # noqa: F401, F403 +from reflex_core.plugins.tailwind_v4 import TailwindV4Plugin From 7d854bbbad97bf2dac9326b1db798b87d1ef81c4 Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Thu, 26 Mar 2026 15:23:38 -0700 Subject: [PATCH 063/113] imports --- docs/wrapping-react/custom-code-and-hooks.md | 2 +- .../src/reflex_components_code/code.py | 2 +- .../core/auto_scroll.py | 2 +- .../src/reflex_components_core/core/banner.py | 4 +- .../reflex_components_core/core/clipboard.py | 2 +- .../src/reflex_components_core/core/colors.py | 7 +- .../src/reflex_components_core/core/cond.py | 2 +- .../reflex_components_core/core/debounce.py | 2 +- .../reflex_components_core/core/foreach.py | 4 +- .../src/reflex_components_core/core/upload.py | 4 +- .../core/window_events.py | 2 +- .../el/elements/forms.py | 2 +- .../el/elements/media.py | 2 +- .../primitives/accordion.py | 2 +- .../primitives/dialog.py | 2 +- .../primitives/drawer.py | 2 +- .../themes/components/alert_dialog.py | 2 +- .../themes/components/context_menu.py | 2 +- .../themes/components/dialog.py | 2 +- .../themes/components/dropdown_menu.py | 2 +- .../themes/components/hover_card.py | 2 +- .../themes/components/popover.py | 2 +- .../themes/components/select.py | 2 +- .../themes/components/skeleton.py | 2 +- .../themes/components/tabs.py | 2 +- .../themes/components/tooltip.py | 2 +- .../reflex_components_recharts/cartesian.py | 4 +- .../src/reflex_components_recharts/charts.py | 4 +- .../src/reflex_components_recharts/general.py | 2 +- .../src/reflex_components_recharts/polar.py | 4 +- .../src/reflex_components_sonner/toast.py | 2 +- .../src/reflex_core/constants/__init__.py | 101 +++++++++++++++++- .../src/reflex_core/plugins/tailwind_v3.py | 2 +- .../src/reflex_core/plugins/tailwind_v4.py | 2 +- pyi_hashes.json | 50 ++++----- reflex/compiler/compiler.py | 4 +- reflex/compiler/utils.py | 2 +- reflex/constants/config.py | 2 +- reflex/custom_components/custom_components.py | 2 +- reflex/experimental/memo.py | 4 +- reflex/istate/shared.py | 3 +- reflex/plugins/__init__.py | 2 +- reflex/plugins/_screenshot.py | 3 +- reflex/plugins/base.py | 8 +- reflex/plugins/shared_tailwind.py | 2 +- reflex/plugins/sitemap.py | 3 +- reflex/plugins/tailwind_v3.py | 3 +- reflex/plugins/tailwind_v4.py | 3 +- reflex/reflex.py | 3 +- reflex/state.py | 2 +- reflex/utils/codespaces.py | 2 +- reflex/utils/exec.py | 2 +- tests/integration/test_client_storage.py | 2 +- tests/integration/test_login_flow.py | 2 +- tests/integration/test_upload.py | 2 +- tests/units/compiler/test_compiler.py | 2 +- tests/units/components/core/test_colors.py | 4 +- tests/units/components/core/test_cond.py | 2 +- tests/units/components/core/test_foreach.py | 2 +- tests/units/components/core/test_match.py | 2 +- .../components/datadisplay/test_datatable.py | 2 +- tests/units/components/test_component.py | 4 +- .../units/docgen/test_class_and_component.py | 2 +- tests/units/test_app.py | 2 +- tests/units/test_config.py | 2 +- tests/units/test_event.py | 2 +- tests/units/test_model.py | 2 +- tests/units/test_prerequisites.py | 2 +- tests/units/test_state.py | 4 +- tests/units/test_state_tree.py | 2 +- tests/units/test_testing.py | 2 +- tests/units/test_var.py | 4 +- tests/units/utils/test_format.py | 2 +- 73 files changed, 210 insertions(+), 124 deletions(-) diff --git a/docs/wrapping-react/custom-code-and-hooks.md b/docs/wrapping-react/custom-code-and-hooks.md index 91b39412afe..fc18a5d84d5 100644 --- a/docs/wrapping-react/custom-code-and-hooks.md +++ b/docs/wrapping-react/custom-code-and-hooks.md @@ -45,7 +45,7 @@ Custom hooks are any hooks that need to be included in your component. This can ```python from reflex.vars.base import Var, VarData -from reflex.constants import Hooks +from reflex_core.constants import Hooks from reflex.components.el.elements import Div class ComponentWithHooks(Div, MyBaseComponent): diff --git a/packages/reflex-components-code/src/reflex_components_code/code.py b/packages/reflex-components-code/src/reflex_components_code/code.py index 8bc700ab3e4..5c1b5b9d28a 100644 --- a/packages/reflex-components-code/src/reflex_components_code/code.py +++ b/packages/reflex-components-code/src/reflex_components_code/code.py @@ -10,10 +10,10 @@ from reflex_components_lucide.icon import Icon from reflex_components_radix.themes.components.button import Button from reflex_components_radix.themes.layout.box import Box +from reflex_core.constants.colors import Color from reflex_core.vars.base import LiteralVar, Var, VarData from reflex.components.component import Component, ComponentNamespace, field -from reflex.constants.colors import Color from reflex.event import set_clipboard from reflex.style import Style from reflex.utils import format diff --git a/packages/reflex-components-core/src/reflex_components_core/core/auto_scroll.py b/packages/reflex-components-core/src/reflex_components_core/core/auto_scroll.py index e97f8d5a7a0..a46a8ed009b 100644 --- a/packages/reflex-components-core/src/reflex_components_core/core/auto_scroll.py +++ b/packages/reflex-components-core/src/reflex_components_core/core/auto_scroll.py @@ -4,9 +4,9 @@ import dataclasses +from reflex_core.constants.compiler import MemoizationDisposition, MemoizationMode from reflex_core.vars.base import Var, get_unique_variable_name -from reflex.constants.compiler import MemoizationDisposition, MemoizationMode from reflex.utils.imports import ImportDict from reflex_components_core.el.elements.typography import Div diff --git a/packages/reflex-components-core/src/reflex_components_core/core/banner.py b/packages/reflex-components-core/src/reflex_components_core/core/banner.py index d48ebbe9034..997c4b29f79 100644 --- a/packages/reflex-components-core/src/reflex_components_core/core/banner.py +++ b/packages/reflex-components-core/src/reflex_components_core/core/banner.py @@ -5,6 +5,8 @@ from reflex_components_lucide.icon import Icon from reflex_components_sonner.toast import ToastProps, toast_ref from reflex_core import constants +from reflex_core.constants import Dirs, Hooks, Imports +from reflex_core.constants.compiler import CompileVars from reflex_core.vars import VarData from reflex_core.vars.base import LiteralVar, Var from reflex_core.vars.function import FunctionStringVar @@ -12,8 +14,6 @@ from reflex_core.vars.sequence import LiteralArrayVar from reflex.components.component import Component -from reflex.constants import Dirs, Hooks, Imports -from reflex.constants.compiler import CompileVars from reflex.environment import environment from reflex.utils.imports import ImportVar from reflex_components_core.base.fragment import Fragment diff --git a/packages/reflex-components-core/src/reflex_components_core/core/clipboard.py b/packages/reflex-components-core/src/reflex_components_core/core/clipboard.py index 02e8c249733..17c02a3d0ca 100644 --- a/packages/reflex-components-core/src/reflex_components_core/core/clipboard.py +++ b/packages/reflex-components-core/src/reflex_components_core/core/clipboard.py @@ -4,12 +4,12 @@ from collections.abc import Sequence +from reflex_core.constants.compiler import Hooks from reflex_core.vars import get_unique_variable_name from reflex_core.vars.base import Var, VarData from reflex.components.component import field from reflex.components.tags.tag import Tag -from reflex.constants.compiler import Hooks from reflex.event import EventChain, EventHandler, passthrough_event_spec from reflex.utils.format import format_prop, wrap from reflex.utils.imports import ImportVar diff --git a/packages/reflex-components-core/src/reflex_components_core/core/colors.py b/packages/reflex-components-core/src/reflex_components_core/core/colors.py index 65ec006041b..7e2fca69726 100644 --- a/packages/reflex-components-core/src/reflex_components_core/core/colors.py +++ b/packages/reflex-components-core/src/reflex_components_core/core/colors.py @@ -1,9 +1,7 @@ """The colors used in Reflex are a wrapper around https://www.radix-ui.com/colors.""" -from reflex_core.vars.base import Var - -from reflex.constants.base import REFLEX_VAR_OPENING_TAG -from reflex.constants.colors import ( +from reflex_core.constants.base import REFLEX_VAR_OPENING_TAG +from reflex_core.constants.colors import ( COLORS, MAX_SHADE_VALUE, MIN_SHADE_VALUE, @@ -11,6 +9,7 @@ ColorType, ShadeType, ) +from reflex_core.vars.base import Var def color( diff --git a/packages/reflex-components-core/src/reflex_components_core/core/cond.py b/packages/reflex-components-core/src/reflex_components_core/core/cond.py index c8fbfa2e47d..836dcdc60e1 100644 --- a/packages/reflex-components-core/src/reflex_components_core/core/cond.py +++ b/packages/reflex-components-core/src/reflex_components_core/core/cond.py @@ -4,6 +4,7 @@ from typing import Any, overload +from reflex_core.constants import Dirs from reflex_core.utils import types from reflex_core.vars import VarData from reflex_core.vars.base import LiteralVar, Var @@ -11,7 +12,6 @@ from reflex.components.component import BaseComponent, Component, field from reflex.components.tags import CondTag, Tag -from reflex.constants import Dirs from reflex.style import LIGHT_COLOR_MODE, resolved_color_mode from reflex.utils.imports import ImportDict, ImportVar from reflex_components_core.base.fragment import Fragment diff --git a/packages/reflex-components-core/src/reflex_components_core/core/debounce.py b/packages/reflex-components-core/src/reflex_components_core/core/debounce.py index 0905b85d932..6d6c09b8d58 100644 --- a/packages/reflex-components-core/src/reflex_components_core/core/debounce.py +++ b/packages/reflex-components-core/src/reflex_components_core/core/debounce.py @@ -4,11 +4,11 @@ from typing import Any +from reflex_core.constants import EventTriggers from reflex_core.vars import VarData from reflex_core.vars.base import Var from reflex.components.component import Component, field -from reflex.constants import EventTriggers from reflex.event import EventHandler, no_args_event_spec DEFAULT_DEBOUNCE_TIMEOUT = 300 diff --git a/packages/reflex-components-core/src/reflex_components_core/core/foreach.py b/packages/reflex-components-core/src/reflex_components_core/core/foreach.py index cd355fce079..dad9ab765e2 100644 --- a/packages/reflex-components-core/src/reflex_components_core/core/foreach.py +++ b/packages/reflex-components-core/src/reflex_components_core/core/foreach.py @@ -8,12 +8,12 @@ from hashlib import md5 from typing import Any +from reflex_core.constants import MemoizationMode +from reflex_core.constants.state import FIELD_MARKER from reflex_core.vars.base import LiteralVar, Var from reflex.components.component import Component, field from reflex.components.tags import IterTag -from reflex.constants import MemoizationMode -from reflex.constants.state import FIELD_MARKER from reflex.state import ComponentState from reflex.utils import types from reflex.utils.exceptions import UntypedVarError diff --git a/packages/reflex-components-core/src/reflex_components_core/core/upload.py b/packages/reflex-components-core/src/reflex_components_core/core/upload.py index 556b47d056d..d618c8de8d9 100644 --- a/packages/reflex-components-core/src/reflex_components_core/core/upload.py +++ b/packages/reflex-components-core/src/reflex_components_core/core/upload.py @@ -7,6 +7,8 @@ from typing import Any, ClassVar from reflex_components_sonner.toast import toast +from reflex_core.constants import Dirs +from reflex_core.constants.compiler import Hooks, Imports from reflex_core.vars import VarData from reflex_core.vars.base import Var, get_unique_variable_name from reflex_core.vars.function import FunctionVar @@ -21,8 +23,6 @@ StatefulComponent, field, ) -from reflex.constants import Dirs -from reflex.constants.compiler import Hooks, Imports from reflex.environment import environment from reflex.event import ( CallableEventSpec, diff --git a/packages/reflex-components-core/src/reflex_components_core/core/window_events.py b/packages/reflex-components-core/src/reflex_components_core/core/window_events.py index ffff72f9ec9..0265891ee93 100644 --- a/packages/reflex-components-core/src/reflex_components_core/core/window_events.py +++ b/packages/reflex-components-core/src/reflex_components_core/core/window_events.py @@ -4,12 +4,12 @@ from typing import Any, cast +from reflex_core.constants.compiler import Hooks from reflex_core.vars.base import Var, VarData from reflex_core.vars.object import ObjectVar import reflex as rx from reflex.components.component import StatefulComponent, field -from reflex.constants.compiler import Hooks from reflex.event import key_event, no_args_event_spec from reflex_components_core.base.fragment import Fragment diff --git a/packages/reflex-components-core/src/reflex_components_core/el/elements/forms.py b/packages/reflex-components-core/src/reflex_components_core/el/elements/forms.py index f3f2283b290..36330225cfe 100644 --- a/packages/reflex-components-core/src/reflex_components_core/el/elements/forms.py +++ b/packages/reflex-components-core/src/reflex_components_core/el/elements/forms.py @@ -6,13 +6,13 @@ from hashlib import md5 from typing import Any, ClassVar, Literal +from reflex_core.constants import Dirs, EventTriggers from reflex_core.vars import VarData from reflex_core.vars.base import LiteralVar, Var from reflex_core.vars.number import ternary_operation from reflex.components.component import field from reflex.components.tags.tag import Tag -from reflex.constants import Dirs, EventTriggers from reflex.event import ( FORM_DATA, EventChain, diff --git a/packages/reflex-components-core/src/reflex_components_core/el/elements/media.py b/packages/reflex-components-core/src/reflex_components_core/el/elements/media.py index c66f547fa41..0ab671c2231 100644 --- a/packages/reflex-components-core/src/reflex_components_core/el/elements/media.py +++ b/packages/reflex-components-core/src/reflex_components_core/el/elements/media.py @@ -2,11 +2,11 @@ from typing import Any, Literal +from reflex_core.constants.colors import Color from reflex_core.vars.base import Var from reflex import Component, ComponentNamespace from reflex.components.component import field -from reflex.constants.colors import Color from reflex_components_core.el.elements.inline import ReferrerPolicy from .base import BaseHTML diff --git a/packages/reflex-components-radix/src/reflex_components_radix/primitives/accordion.py b/packages/reflex-components-radix/src/reflex_components_radix/primitives/accordion.py index 7e85ec70cee..d8430e970ad 100644 --- a/packages/reflex-components-radix/src/reflex_components_radix/primitives/accordion.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/primitives/accordion.py @@ -8,11 +8,11 @@ from reflex_components_core.core.colors import color from reflex_components_core.core.cond import cond from reflex_components_lucide.icon import Icon +from reflex_core.constants.compiler import MemoizationMode from reflex_core.vars import get_uuid_string_var from reflex_core.vars.base import LiteralVar, Var from reflex.components.component import Component, ComponentNamespace, field -from reflex.constants.compiler import MemoizationMode from reflex.event import EventHandler from reflex.style import Style from reflex_components_radix.primitives.base import RadixPrimitiveComponent diff --git a/packages/reflex-components-radix/src/reflex_components_radix/primitives/dialog.py b/packages/reflex-components-radix/src/reflex_components_radix/primitives/dialog.py index ec076ba6a1f..94e7c5792f8 100644 --- a/packages/reflex-components-radix/src/reflex_components_radix/primitives/dialog.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/primitives/dialog.py @@ -3,10 +3,10 @@ from typing import Any, ClassVar from reflex_components_core.el import elements +from reflex_core.constants.compiler import MemoizationMode from reflex_core.vars.base import Var from reflex.components.component import ComponentNamespace, field -from reflex.constants.compiler import MemoizationMode from reflex.event import EventHandler, no_args_event_spec, passthrough_event_spec from .base import RadixPrimitiveComponent, RadixPrimitiveTriggerComponent diff --git a/packages/reflex-components-radix/src/reflex_components_radix/primitives/drawer.py b/packages/reflex-components-radix/src/reflex_components_radix/primitives/drawer.py index aab8ebc729e..81ca5b8fb7f 100644 --- a/packages/reflex-components-radix/src/reflex_components_radix/primitives/drawer.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/primitives/drawer.py @@ -7,10 +7,10 @@ from collections.abc import Sequence from typing import Any, Literal +from reflex_core.constants.compiler import MemoizationMode from reflex_core.vars.base import Var from reflex.components.component import Component, ComponentNamespace, field -from reflex.constants.compiler import MemoizationMode from reflex.event import EventHandler, no_args_event_spec, passthrough_event_spec from reflex_components_radix.primitives.base import RadixPrimitiveComponent from reflex_components_radix.themes.base import Theme diff --git a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/alert_dialog.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/alert_dialog.py index 8415a479ea3..93ac0ed729b 100644 --- a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/alert_dialog.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/alert_dialog.py @@ -4,10 +4,10 @@ from reflex_components_core.core.breakpoints import Responsive from reflex_components_core.el import elements +from reflex_core.constants.compiler import MemoizationMode from reflex_core.vars.base import Var from reflex.components.component import ComponentNamespace, field -from reflex.constants.compiler import MemoizationMode from reflex.event import EventHandler, no_args_event_spec, passthrough_event_spec from reflex_components_radix.themes.base import ( RadixThemesComponent, diff --git a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/context_menu.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/context_menu.py index f2a42bb8502..74db7b3beac 100644 --- a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/context_menu.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/context_menu.py @@ -3,10 +3,10 @@ from typing import ClassVar, Literal from reflex_components_core.core.breakpoints import Responsive +from reflex_core.constants.compiler import MemoizationMode from reflex_core.vars.base import Var from reflex.components.component import ComponentNamespace, field -from reflex.constants.compiler import MemoizationMode from reflex.event import EventHandler, no_args_event_spec, passthrough_event_spec from reflex_components_radix.themes.base import LiteralAccentColor, RadixThemesComponent diff --git a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/dialog.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/dialog.py index 8ffbf20821f..5155c1a3d5b 100644 --- a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/dialog.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/dialog.py @@ -4,10 +4,10 @@ from reflex_components_core.core.breakpoints import Responsive from reflex_components_core.el import elements +from reflex_core.constants.compiler import MemoizationMode from reflex_core.vars.base import Var from reflex.components.component import ComponentNamespace, field -from reflex.constants.compiler import MemoizationMode from reflex.event import EventHandler, no_args_event_spec, passthrough_event_spec from reflex_components_radix.themes.base import ( RadixThemesComponent, diff --git a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/dropdown_menu.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/dropdown_menu.py index bd2b8f0b381..ff7b1af5810 100644 --- a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/dropdown_menu.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/dropdown_menu.py @@ -3,10 +3,10 @@ from typing import ClassVar, Literal from reflex_components_core.core.breakpoints import Responsive +from reflex_core.constants.compiler import MemoizationMode from reflex_core.vars.base import Var from reflex.components.component import ComponentNamespace, field -from reflex.constants.compiler import MemoizationMode from reflex.event import EventHandler, no_args_event_spec, passthrough_event_spec from reflex_components_radix.themes.base import ( LiteralAccentColor, diff --git a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/hover_card.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/hover_card.py index 6d262952dfb..784f14296d7 100644 --- a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/hover_card.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/hover_card.py @@ -4,10 +4,10 @@ from reflex_components_core.core.breakpoints import Responsive from reflex_components_core.el import elements +from reflex_core.constants.compiler import MemoizationMode from reflex_core.vars.base import Var from reflex.components.component import ComponentNamespace, field -from reflex.constants.compiler import MemoizationMode from reflex.event import EventHandler, passthrough_event_spec from reflex_components_radix.themes.base import ( RadixThemesComponent, diff --git a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/popover.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/popover.py index 6fd1f5b49c7..a7596c92ca8 100644 --- a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/popover.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/popover.py @@ -4,10 +4,10 @@ from reflex_components_core.core.breakpoints import Responsive from reflex_components_core.el import elements +from reflex_core.constants.compiler import MemoizationMode from reflex_core.vars.base import Var from reflex.components.component import ComponentNamespace, field -from reflex.constants.compiler import MemoizationMode from reflex.event import EventHandler, no_args_event_spec, passthrough_event_spec from reflex_components_radix.themes.base import ( RadixThemesComponent, diff --git a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/select.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/select.py index cf08538956a..31efbe9c089 100644 --- a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/select.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/select.py @@ -4,11 +4,11 @@ from typing import ClassVar, Literal from reflex_components_core.core.breakpoints import Responsive +from reflex_core.constants.compiler import MemoizationMode from reflex_core.vars.base import Var import reflex as rx from reflex.components.component import Component, ComponentNamespace, field -from reflex.constants.compiler import MemoizationMode from reflex.event import no_args_event_spec, passthrough_event_spec from reflex_components_radix.themes.base import ( LiteralAccentColor, diff --git a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/skeleton.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/skeleton.py index 55f9e37349e..bd30799ac03 100644 --- a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/skeleton.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/skeleton.py @@ -1,10 +1,10 @@ """Skeleton theme from Radix components.""" from reflex_components_core.core.breakpoints import Responsive +from reflex_core.constants.compiler import MemoizationMode from reflex_core.vars.base import Var from reflex.components.component import field -from reflex.constants.compiler import MemoizationMode from reflex_components_radix.themes.base import RadixLoadingProp, RadixThemesComponent diff --git a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/tabs.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/tabs.py index 41c63f4a559..a7cb4e06c0f 100644 --- a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/tabs.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/tabs.py @@ -6,10 +6,10 @@ from reflex_components_core.core.breakpoints import Responsive from reflex_components_core.core.colors import color +from reflex_core.constants.compiler import MemoizationMode from reflex_core.vars.base import Var from reflex.components.component import Component, ComponentNamespace, field -from reflex.constants.compiler import MemoizationMode from reflex.event import EventHandler, passthrough_event_spec from reflex_components_radix.themes.base import LiteralAccentColor, RadixThemesComponent diff --git a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/tooltip.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/tooltip.py index 4b8d4d4abe8..32cd6fbb0a3 100644 --- a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/tooltip.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/tooltip.py @@ -2,10 +2,10 @@ from typing import Literal +from reflex_core.constants.compiler import MemoizationMode from reflex_core.vars.base import Var from reflex.components.component import Component, field -from reflex.constants.compiler import MemoizationMode from reflex.event import EventHandler, no_args_event_spec, passthrough_event_spec from reflex.utils import format from reflex_components_radix.themes.base import RadixThemesComponent diff --git a/packages/reflex-components-recharts/src/reflex_components_recharts/cartesian.py b/packages/reflex-components-recharts/src/reflex_components_recharts/cartesian.py index 84c13ec6baa..81a7f976b6d 100644 --- a/packages/reflex-components-recharts/src/reflex_components_recharts/cartesian.py +++ b/packages/reflex-components-recharts/src/reflex_components_recharts/cartesian.py @@ -5,11 +5,11 @@ from collections.abc import Sequence from typing import Any, ClassVar, TypedDict +from reflex_core.constants import EventTriggers +from reflex_core.constants.colors import Color from reflex_core.vars.base import LiteralVar, Var from reflex.components.component import field -from reflex.constants import EventTriggers -from reflex.constants.colors import Color from reflex.event import EventHandler, no_args_event_spec from .recharts import ( diff --git a/packages/reflex-components-recharts/src/reflex_components_recharts/charts.py b/packages/reflex-components-recharts/src/reflex_components_recharts/charts.py index ee0132edcd1..f5802cf8a55 100644 --- a/packages/reflex-components-recharts/src/reflex_components_recharts/charts.py +++ b/packages/reflex-components-recharts/src/reflex_components_recharts/charts.py @@ -5,11 +5,11 @@ from collections.abc import Sequence from typing import Any, ClassVar +from reflex_core.constants import EventTriggers +from reflex_core.constants.colors import Color from reflex_core.vars.base import Var from reflex.components.component import Component, field -from reflex.constants import EventTriggers -from reflex.constants.colors import Color from reflex.event import EventHandler, no_args_event_spec from reflex_components_recharts.general import ResponsiveContainer diff --git a/packages/reflex-components-recharts/src/reflex_components_recharts/general.py b/packages/reflex-components-recharts/src/reflex_components_recharts/general.py index 76431c272ee..d1bc78d92fa 100644 --- a/packages/reflex-components-recharts/src/reflex_components_recharts/general.py +++ b/packages/reflex-components-recharts/src/reflex_components_recharts/general.py @@ -5,10 +5,10 @@ from collections.abc import Sequence from typing import Any, ClassVar +from reflex_core.constants.colors import Color from reflex_core.vars.base import LiteralVar, Var from reflex.components.component import MemoizationLeaf, field -from reflex.constants.colors import Color from reflex.event import EventHandler, no_args_event_spec from .recharts import ( diff --git a/packages/reflex-components-recharts/src/reflex_components_recharts/polar.py b/packages/reflex-components-recharts/src/reflex_components_recharts/polar.py index c7962a5a9f0..22083226fb7 100644 --- a/packages/reflex-components-recharts/src/reflex_components_recharts/polar.py +++ b/packages/reflex-components-recharts/src/reflex_components_recharts/polar.py @@ -5,11 +5,11 @@ from collections.abc import Sequence from typing import Any, ClassVar +from reflex_core.constants import EventTriggers +from reflex_core.constants.colors import Color from reflex_core.vars.base import LiteralVar, Var from reflex.components.component import field -from reflex.constants import EventTriggers -from reflex.constants.colors import Color from reflex.event import EventHandler, no_args_event_spec from .recharts import ( diff --git a/packages/reflex-components-sonner/src/reflex_components_sonner/toast.py b/packages/reflex-components-sonner/src/reflex_components_sonner/toast.py index 938235d58c0..fae7d76985f 100644 --- a/packages/reflex-components-sonner/src/reflex_components_sonner/toast.py +++ b/packages/reflex-components-sonner/src/reflex_components_sonner/toast.py @@ -6,6 +6,7 @@ from typing import Any, Literal from reflex_components_lucide.icon import Icon +from reflex_core.constants.base import Dirs from reflex_core.vars import VarData from reflex_core.vars.base import LiteralVar, Var from reflex_core.vars.function import FunctionVar @@ -14,7 +15,6 @@ from reflex.components.component import Component, ComponentNamespace, field from reflex.components.props import NoExtrasAllowedProps -from reflex.constants.base import Dirs from reflex.event import EventSpec, run_script from reflex.style import Style, resolved_color_mode from reflex.utils import format diff --git a/packages/reflex-core/src/reflex_core/constants/__init__.py b/packages/reflex-core/src/reflex_core/constants/__init__.py index f9068c74cb5..69f79271d9e 100644 --- a/packages/reflex-core/src/reflex_core/constants/__init__.py +++ b/packages/reflex-core/src/reflex_core/constants/__init__.py @@ -1,21 +1,118 @@ -"""Reflex core constants.""" +"""The constants package.""" from .base import ( + APP_HARNESS_FLAG, + COOKIES, + IS_LINUX, + IS_MACOS, + IS_WINDOWS, + LOCAL_STORAGE, + POLLING_MAX_HTTP_BUFFER_SIZE, + PYTEST_CURRENT_TEST, REFLEX_VAR_CLOSING_TAG, REFLEX_VAR_OPENING_TAG, + SESSION_STORAGE, ColorMode, Dirs, Env, LogLevel, + Ping, + ReactRouter, Reflex, + ReflexHostingCLI, + Templates, ) from .compiler import ( + NOCOMPILE_FILE, SETTER_PREFIX, + CompileContext, + CompileVars, + ComponentName, + Ext, Hooks, Imports, MemoizationDisposition, MemoizationMode, PageNames, ) -from .event import EventTriggers +from .config import ( + ALEMBIC_CONFIG, + Config, + DefaultPorts, + Expiration, + GitIgnore, + PyprojectToml, + RequirementsTxt, +) +from .custom_components import CustomComponents +from .event import Endpoint, EventTriggers, SocketEvent +from .installer import Bun, Node, PackageJson +from .route import ( + ROUTE_NOT_FOUND, + ROUTER, + ROUTER_DATA, + ROUTER_DATA_INCLUDE, + DefaultPage, + Page404, + RouteArgType, + RouteRegex, + RouteVar, +) from .state import StateManagerMode + +__all__ = [ + "ALEMBIC_CONFIG", + "APP_HARNESS_FLAG", + "COOKIES", + "IS_LINUX", + "IS_MACOS", + "IS_WINDOWS", + "LOCAL_STORAGE", + "NOCOMPILE_FILE", + "POLLING_MAX_HTTP_BUFFER_SIZE", + "PYTEST_CURRENT_TEST", + "REFLEX_VAR_CLOSING_TAG", + "REFLEX_VAR_OPENING_TAG", + "ROUTER", + "ROUTER_DATA", + "ROUTER_DATA_INCLUDE", + "ROUTE_NOT_FOUND", + "SESSION_STORAGE", + "SETTER_PREFIX", + "Bun", + "ColorMode", + "CompileContext", + "CompileVars", + "ComponentName", + "Config", + "CustomComponents", + "DefaultPage", + "DefaultPorts", + "Dirs", + "Endpoint", + "Env", + "EventTriggers", + "Expiration", + "Ext", + "GitIgnore", + "Hooks", + "Imports", + "LogLevel", + "MemoizationDisposition", + "MemoizationMode", + "Node", + "PackageJson", + "Page404", + "PageNames", + "Ping", + "PyprojectToml", + "ReactRouter", + "Reflex", + "RequirementsTxt", + "RouteArgType", + "RouteRegex", + "RouteVar", + "SocketEvent", + "StateManagerMode", + "Templates", +] diff --git a/packages/reflex-core/src/reflex_core/plugins/tailwind_v3.py b/packages/reflex-core/src/reflex_core/plugins/tailwind_v3.py index ed9e641c18b..c32c80835b6 100644 --- a/packages/reflex-core/src/reflex_core/plugins/tailwind_v3.py +++ b/packages/reflex-core/src/reflex_core/plugins/tailwind_v3.py @@ -85,7 +85,7 @@ def add_tailwind_to_postcss_config(postcss_file_content: str) -> str: Returns: The modified postcss config file content. """ - from reflex.constants import Dirs + from reflex_core.constants import Dirs postcss_file_lines = postcss_file_content.splitlines() diff --git a/packages/reflex-core/src/reflex_core/plugins/tailwind_v4.py b/packages/reflex-core/src/reflex_core/plugins/tailwind_v4.py index 58ff794cab8..8901c5220c9 100644 --- a/packages/reflex-core/src/reflex_core/plugins/tailwind_v4.py +++ b/packages/reflex-core/src/reflex_core/plugins/tailwind_v4.py @@ -84,7 +84,7 @@ def add_tailwind_to_postcss_config(postcss_file_content: str) -> str: Returns: The modified postcss config file content. """ - from reflex.constants import Dirs + from reflex_core.constants import Dirs postcss_file_lines = postcss_file_content.splitlines() diff --git a/pyi_hashes.json b/pyi_hashes.json index 83aade50ba6..650647b61b7 100644 --- a/pyi_hashes.json +++ b/pyi_hashes.json @@ -1,5 +1,5 @@ { - "packages/reflex-components-code/src/reflex_components_code/code.pyi": "2319e7886182f5ef039f0a1ed1d62317", + "packages/reflex-components-code/src/reflex_components_code/code.pyi": "a77a5aa436f768aed8dd7dd449d3d49f", "packages/reflex-components-code/src/reflex_components_code/shiki_code_block.pyi": "45379e4c1fc1f214b7a5d66d90f76f1c", "packages/reflex-components-core/src/reflex_components_core/__init__.pyi": "6c3ceff429117483dd0035e7a21930f4", "packages/reflex-components-core/src/reflex_components_core/base/__init__.pyi": "4beb5ba739680b7974c37241f3f6791c", @@ -13,23 +13,23 @@ "packages/reflex-components-core/src/reflex_components_core/base/script.pyi": "d6c683e3ad0d791b254c5961ff467ada", "packages/reflex-components-core/src/reflex_components_core/base/strict_mode.pyi": "490676ec4319d738aee31c3975361591", "packages/reflex-components-core/src/reflex_components_core/core/__init__.pyi": "17f79762cef09c69acd9df227cf3bb35", - "packages/reflex-components-core/src/reflex_components_core/core/auto_scroll.pyi": "3bdf9e60d4b7aa9043143561eacdb911", + "packages/reflex-components-core/src/reflex_components_core/core/auto_scroll.pyi": "27d668338d326d7546854af30f757446", "packages/reflex-components-core/src/reflex_components_core/core/banner.pyi": "bd5825c02dafee1b9a85dc9511004bd0", - "packages/reflex-components-core/src/reflex_components_core/core/clipboard.pyi": "be619a31f5f16891f1ad561f7483eb77", - "packages/reflex-components-core/src/reflex_components_core/core/debounce.pyi": "a34556c3d3e3a1cd498be2deba1926d5", + "packages/reflex-components-core/src/reflex_components_core/core/clipboard.pyi": "77b73071a42d9effc89421c7664e6b01", + "packages/reflex-components-core/src/reflex_components_core/core/debounce.pyi": "883c94a3e37a4a85c8478ce341d3f873", "packages/reflex-components-core/src/reflex_components_core/core/helmet.pyi": "bb1b4d7468cb1496bb301ade46354d3e", "packages/reflex-components-core/src/reflex_components_core/core/html.pyi": "690ef52cd0c56fcc397826156a79c1e7", "packages/reflex-components-core/src/reflex_components_core/core/sticky.pyi": "9e817ab33107cb59402e3aebf12c50cc", "packages/reflex-components-core/src/reflex_components_core/core/upload.pyi": "30a3d28d7e17045f5fd667a50a6731df", - "packages/reflex-components-core/src/reflex_components_core/core/window_events.pyi": "c05b5e617a5f1e534aaa15b6130006cc", + "packages/reflex-components-core/src/reflex_components_core/core/window_events.pyi": "66593daba42db6ffa072f705cd208ad7", "packages/reflex-components-core/src/reflex_components_core/datadisplay/__init__.pyi": "73e27e0bfd7e914c4baba7b166e75736", "packages/reflex-components-core/src/reflex_components_core/el/__init__.pyi": "a1d4d65ddc73b5fbba3726f4bcf6d6f0", "packages/reflex-components-core/src/reflex_components_core/el/element.pyi": "071bd8e46b6b65806c938d343ec739cd", "packages/reflex-components-core/src/reflex_components_core/el/elements/__init__.pyi": "fbe4c6f4960bcd811311b1f73987cdb1", "packages/reflex-components-core/src/reflex_components_core/el/elements/base.pyi": "b004e6bfbb7de0789a6581274959dbb3", - "packages/reflex-components-core/src/reflex_components_core/el/elements/forms.pyi": "9ef8d52bef8fd00746c7f28b955ca340", + "packages/reflex-components-core/src/reflex_components_core/el/elements/forms.pyi": "1a23f604a039ee9ed0a7663f7ccdbfcb", "packages/reflex-components-core/src/reflex_components_core/el/elements/inline.pyi": "a7d7c110149e0df7fd5798d29789d194", - "packages/reflex-components-core/src/reflex_components_core/el/elements/media.pyi": "88731777d7a7157543a46d44d5d69855", + "packages/reflex-components-core/src/reflex_components_core/el/elements/media.pyi": "91c5dfa222ac448f3734e34f780c6e31", "packages/reflex-components-core/src/reflex_components_core/el/elements/metadata.pyi": "be4af13583cd210538a5bf401aee4653", "packages/reflex-components-core/src/reflex_components_core/el/elements/other.pyi": "b38e41dbbc6ab30e9ee5fbb32cf675b9", "packages/reflex-components-core/src/reflex_components_core/el/elements/scripts.pyi": "6391432c86072d5da87a1a54f4e861e9", @@ -44,10 +44,10 @@ "packages/reflex-components-plotly/src/reflex_components_plotly/plotly.pyi": "ac6382621506be5298009616966f9897", "packages/reflex-components-radix/src/reflex_components_radix/__init__.pyi": "899d63be42eb21c187519920518bf32e", "packages/reflex-components-radix/src/reflex_components_radix/primitives/__init__.pyi": "11bc17db31334f11d7fcde315b29cae1", - "packages/reflex-components-radix/src/reflex_components_radix/primitives/accordion.pyi": "e9a06d65e8709de70231db48e5aca96a", + "packages/reflex-components-radix/src/reflex_components_radix/primitives/accordion.pyi": "4ddaa4093bd4a9225544cda8d4a847d8", "packages/reflex-components-radix/src/reflex_components_radix/primitives/base.pyi": "c5d92880310ab703ea93d4b1a056eb86", - "packages/reflex-components-radix/src/reflex_components_radix/primitives/dialog.pyi": "a79fdb748d331b5987cecc6ee0a34ee6", - "packages/reflex-components-radix/src/reflex_components_radix/primitives/drawer.pyi": "a1c94022c183cae0528d144c0588d62a", + "packages/reflex-components-radix/src/reflex_components_radix/primitives/dialog.pyi": "064336aa87a72bc65327cc3154a29a45", + "packages/reflex-components-radix/src/reflex_components_radix/primitives/drawer.pyi": "1bcfb807f8629214f6a872c84c954eb7", "packages/reflex-components-radix/src/reflex_components_radix/primitives/form.pyi": "a066a50b11c2b9507eb0544d0df3f668", "packages/reflex-components-radix/src/reflex_components_radix/primitives/progress.pyi": "ac977b63b3640eeaa21204e6e9dcf40c", "packages/reflex-components-radix/src/reflex_components_radix/primitives/slider.pyi": "a4d76b1220aeb40a53b5f6033f67ff04", @@ -55,7 +55,7 @@ "packages/reflex-components-radix/src/reflex_components_radix/themes/base.pyi": "9ae9885a592496736170bf61d3869ee0", "packages/reflex-components-radix/src/reflex_components_radix/themes/color_mode.pyi": "5f41e358cde825fbdf589bf798b0bf00", "packages/reflex-components-radix/src/reflex_components_radix/themes/components/__init__.pyi": "67d45aa96a97359ee556725641bb583f", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/alert_dialog.pyi": "a58ad421715ce6f80faecc371e6f51d4", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/alert_dialog.pyi": "1cd18bb9e1223b305f97e33f200563c1", "packages/reflex-components-radix/src/reflex_components_radix/themes/components/aspect_ratio.pyi": "f6a1f6388abca0317671e336dcabfc4b", "packages/reflex-components-radix/src/reflex_components_radix/themes/components/avatar.pyi": "99024032ec9f36c918174a3dd13d195e", "packages/reflex-components-radix/src/reflex_components_radix/themes/components/badge.pyi": "04c9570befb04530ceb6f6a1de2dd581", @@ -65,31 +65,31 @@ "packages/reflex-components-radix/src/reflex_components_radix/themes/components/checkbox.pyi": "6ca7329b87a775ff90cfceade116b28e", "packages/reflex-components-radix/src/reflex_components_radix/themes/components/checkbox_cards.pyi": "aca084957873b350361996cd1e26ff28", "packages/reflex-components-radix/src/reflex_components_radix/themes/components/checkbox_group.pyi": "4af3f42fd55c2a5810aad89f2077f03d", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/context_menu.pyi": "831a5c8aecf3fe8e3afcc863b2e6edbb", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/context_menu.pyi": "d68c3406e1e0cd77c88cb4294f58ffa3", "packages/reflex-components-radix/src/reflex_components_radix/themes/components/data_list.pyi": "d526eb5152b616f93214f68670a0f4a9", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/dialog.pyi": "bb0c627b1e07cd8d47ef7eb6218cbcd1", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/dropdown_menu.pyi": "ad62973010567ef84cb29dc485425216", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/hover_card.pyi": "e4e730069f4e3aa20ecbf1f62a2f2911", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/dialog.pyi": "0c98db910a1da7029b9e37a077827613", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/dropdown_menu.pyi": "cedf9b90b2bc5590251eef1a40d02192", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/hover_card.pyi": "c6f94e10b03fb3ed02e993a6dfb405ec", "packages/reflex-components-radix/src/reflex_components_radix/themes/components/icon_button.pyi": "e357ea5ef2c2c6d59decf528676c768d", "packages/reflex-components-radix/src/reflex_components_radix/themes/components/inset.pyi": "d8ea4c7d2e69dc6fd27d98c3697b95c2", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/popover.pyi": "93a612094af186d17ee2394fce24fb99", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/popover.pyi": "6343eea4f955dfbe9149735b6c74fcc0", "packages/reflex-components-radix/src/reflex_components_radix/themes/components/progress.pyi": "cef3d099f95eb9ac18745df3cb57e9f6", "packages/reflex-components-radix/src/reflex_components_radix/themes/components/radio.pyi": "117da2df15b89b430cd2d6c693d7eb30", "packages/reflex-components-radix/src/reflex_components_radix/themes/components/radio_cards.pyi": "17c13d2209eaf47f43baab0c63574785", "packages/reflex-components-radix/src/reflex_components_radix/themes/components/radio_group.pyi": "4501692b53bf26a18ddf968d4e212b0c", "packages/reflex-components-radix/src/reflex_components_radix/themes/components/scroll_area.pyi": "2e3084f01527cb2a6be0db0cbac27237", "packages/reflex-components-radix/src/reflex_components_radix/themes/components/segmented_control.pyi": "e16843a5243ae03a69c3c13f88910b74", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/select.pyi": "7a9e52419a261ad12ce065443bef7d7c", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/select.pyi": "605eef88c8e9ce47c1ce0ed1771fb5ed", "packages/reflex-components-radix/src/reflex_components_radix/themes/components/separator.pyi": "445408b7ee359bceddd897f164656e9b", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/skeleton.pyi": "b1bbdcba9221cb264bc202e78ecf7ede", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/skeleton.pyi": "eb5eb682a0d9875ecd3fd880cb283687", "packages/reflex-components-radix/src/reflex_components_radix/themes/components/slider.pyi": "41a518be074ba740d995cbc0956ddf52", "packages/reflex-components-radix/src/reflex_components_radix/themes/components/spinner.pyi": "4856ba4446ad21985a070d04dd6293d8", "packages/reflex-components-radix/src/reflex_components_radix/themes/components/switch.pyi": "45fb1de0f43bf272ce5bfff9802839e8", "packages/reflex-components-radix/src/reflex_components_radix/themes/components/table.pyi": "9aed7860e0541a82f69ae0ac37a44444", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/tabs.pyi": "7c4cafb3bf6ef7c6254e30f30f8744b0", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/tabs.pyi": "4b16f26ee6a8f8bef43bc9338921c3ae", "packages/reflex-components-radix/src/reflex_components_radix/themes/components/text_area.pyi": "a7689a98975afd666422cf4eec3a3eed", "packages/reflex-components-radix/src/reflex_components_radix/themes/components/text_field.pyi": "743552036a6ae8143d8ce8d23b0d6fc5", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/tooltip.pyi": "7fc4cecd0e2cc70ae25870908c142cb8", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/tooltip.pyi": "da70fa40026aa2a1726e5140890da7b1", "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/__init__.pyi": "b7dbdecab770a7fc1435ad417d7f91c1", "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/base.pyi": "b541c3e56e4b9afdb208db97b3c7846d", "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/box.pyi": "744e6e83ab08c3f151715c5e459a03d2", @@ -112,12 +112,12 @@ "packages/reflex-components-react-player/src/reflex_components_react_player/video.pyi": "4ca94af43518e83e1b9ee5da55a4843b", "packages/reflex-components-react-router/src/reflex_components_react_router/dom.pyi": "5c13be18f98b23ed957e53c3a26c6216", "packages/reflex-components-recharts/src/reflex_components_recharts/__init__.pyi": "1f04ab9f482261f18010fbf728d7b946", - "packages/reflex-components-recharts/src/reflex_components_recharts/cartesian.pyi": "5229afe951d976abc280aa34f72ae8ac", - "packages/reflex-components-recharts/src/reflex_components_recharts/charts.pyi": "c1aef94f3a866c62bec8f82897bd85e8", - "packages/reflex-components-recharts/src/reflex_components_recharts/general.pyi": "e243056a5614ee9e1d8be66d94bba737", - "packages/reflex-components-recharts/src/reflex_components_recharts/polar.pyi": "e27277901bad6b5d2729a6eca8ac4895", + "packages/reflex-components-recharts/src/reflex_components_recharts/cartesian.pyi": "d5f15d0a45eecec8eb8a74d44a97b523", + "packages/reflex-components-recharts/src/reflex_components_recharts/charts.pyi": "9c3e409cd5188487150223af7a9c0b8d", + "packages/reflex-components-recharts/src/reflex_components_recharts/general.pyi": "3d4b81db2bfa294fb18df151ed72d4f9", + "packages/reflex-components-recharts/src/reflex_components_recharts/polar.pyi": "6406446ea9c0c04ddfab4d6e7ed91df3", "packages/reflex-components-recharts/src/reflex_components_recharts/recharts.pyi": "fa3dfbba1e25242fdb9787e280b43d1d", - "packages/reflex-components-sonner/src/reflex_components_sonner/toast.pyi": "efd01b2494c53ede3668c8c16e7cdfe7", + "packages/reflex-components-sonner/src/reflex_components_sonner/toast.pyi": "f77ae3b4afe892aed403aae1e609d2a3", "reflex/__init__.pyi": "77b2efa084aa8473dce836f78fc4f61a", "reflex/components/__init__.pyi": "d620d52015f908cda828d231c1064236", "reflex/experimental/memo.pyi": "555f5495d3ddd30dc974eacbb046662a" diff --git a/reflex/compiler/compiler.py b/reflex/compiler/compiler.py index 02090d3bb70..778c820b56c 100644 --- a/reflex/compiler/compiler.py +++ b/reflex/compiler/compiler.py @@ -10,6 +10,8 @@ from reflex_components_core.base.fragment import Fragment from reflex_core import constants +from reflex_core.constants.compiler import PageNames, ResetStylesheet +from reflex_core.constants.state import FIELD_MARKER from reflex_core.vars.base import LiteralVar, Var from reflex.compiler import templates, utils @@ -21,8 +23,6 @@ StatefulComponent, ) from reflex.config import get_config -from reflex.constants.compiler import PageNames, ResetStylesheet -from reflex.constants.state import FIELD_MARKER from reflex.environment import environment from reflex.experimental.memo import ( ExperimentalMemoComponentDefinition, diff --git a/reflex/compiler/utils.py b/reflex/compiler/utils.py index a072c28e07f..874f42d9845 100644 --- a/reflex/compiler/utils.py +++ b/reflex/compiler/utils.py @@ -20,11 +20,11 @@ from reflex_components_core.el.elements.other import Html from reflex_components_core.el.elements.sectioning import Body from reflex_core import constants +from reflex_core.constants.state import CAMEL_CASE_MEMO_MARKER, FIELD_MARKER from reflex_core.vars.base import Field, Var, VarData from reflex_core.vars.function import DestructuredArg from reflex.components.component import Component, ComponentStyle, CustomComponent -from reflex.constants.state import CAMEL_CASE_MEMO_MARKER, FIELD_MARKER from reflex.experimental.memo import ( ExperimentalMemoComponentDefinition, ExperimentalMemoFunctionDefinition, diff --git a/reflex/constants/config.py b/reflex/constants/config.py index 6ae12a92aeb..d1fd39ab93c 100644 --- a/reflex/constants/config.py +++ b/reflex/constants/config.py @@ -3,7 +3,7 @@ from pathlib import Path from types import SimpleNamespace -from reflex.constants.base import Dirs, Reflex +from reflex_core.constants.base import Dirs, Reflex from .compiler import Ext diff --git a/reflex/custom_components/custom_components.py b/reflex/custom_components/custom_components.py index 0ffc6ea13cb..e6dbc1a7ec5 100644 --- a/reflex/custom_components/custom_components.py +++ b/reflex/custom_components/custom_components.py @@ -13,8 +13,8 @@ import click from reflex_core import constants +from reflex_core.constants import CustomComponents -from reflex.constants import CustomComponents from reflex.utils import console, frontend_skeleton diff --git a/reflex/experimental/memo.py b/reflex/experimental/memo.py index 057e867f1e7..b3341945e31 100644 --- a/reflex/experimental/memo.py +++ b/reflex/experimental/memo.py @@ -11,6 +11,8 @@ from reflex_components_core.base.bare import Bare from reflex_components_core.base.fragment import Fragment from reflex_core import constants +from reflex_core.constants.compiler import SpecialAttributes +from reflex_core.constants.state import CAMEL_CASE_MEMO_MARKER from reflex_core.vars import VarData from reflex_core.vars.base import LiteralVar, Var from reflex_core.vars.function import ( @@ -24,8 +26,6 @@ from reflex.components.component import Component from reflex.components.dynamic import bundled_libraries -from reflex.constants.compiler import SpecialAttributes -from reflex.constants.state import CAMEL_CASE_MEMO_MARKER from reflex.utils import format from reflex.utils import types as type_utils from reflex.utils.imports import ImportVar diff --git a/reflex/istate/shared.py b/reflex/istate/shared.py index 01bf154d913..405d57b94ac 100644 --- a/reflex/istate/shared.py +++ b/reflex/istate/shared.py @@ -5,7 +5,8 @@ from collections.abc import AsyncIterator from typing import Self, TypeVar -from reflex.constants import ROUTER_DATA +from reflex_core.constants import ROUTER_DATA + from reflex.event import Event, get_hydrate_event from reflex.state import BaseState, State, _override_base_method, _substate_key from reflex.utils import console diff --git a/reflex/plugins/__init__.py b/reflex/plugins/__init__.py index 1e32c6eeb89..b796e670257 100644 --- a/reflex/plugins/__init__.py +++ b/reflex/plugins/__init__.py @@ -1,6 +1,6 @@ """Re-export from reflex_core.plugins.""" -from reflex_core.plugins import * # noqa: F401, F403 +from reflex_core.plugins import * from reflex_core.plugins import ( CommonContext, Plugin, diff --git a/reflex/plugins/_screenshot.py b/reflex/plugins/_screenshot.py index 98b8c486e62..60041fc06dd 100644 --- a/reflex/plugins/_screenshot.py +++ b/reflex/plugins/_screenshot.py @@ -1,4 +1,3 @@ """Re-export from reflex_core.plugins._screenshot.""" -from reflex_core.plugins._screenshot import * # noqa: F401, F403 -from reflex_core.plugins._screenshot import ScreenshotPlugin +from reflex_core.plugins._screenshot import * diff --git a/reflex/plugins/base.py b/reflex/plugins/base.py index 93fbb23fac5..fb786a6ca47 100644 --- a/reflex/plugins/base.py +++ b/reflex/plugins/base.py @@ -1,9 +1,3 @@ """Re-export from reflex_core.plugins.base.""" -from reflex_core.plugins.base import * # noqa: F401, F403 -from reflex_core.plugins.base import ( - CommonContext, - Plugin, - PostCompileContext, - PreCompileContext, -) +from reflex_core.plugins.base import * diff --git a/reflex/plugins/shared_tailwind.py b/reflex/plugins/shared_tailwind.py index 1361168bc9d..81e736d0f97 100644 --- a/reflex/plugins/shared_tailwind.py +++ b/reflex/plugins/shared_tailwind.py @@ -1,3 +1,3 @@ """Re-export from reflex_core.plugins.shared_tailwind.""" -from reflex_core.plugins.shared_tailwind import * # noqa: F401, F403 +from reflex_core.plugins.shared_tailwind import * diff --git a/reflex/plugins/sitemap.py b/reflex/plugins/sitemap.py index 76d45da1eb8..085a5ba7879 100644 --- a/reflex/plugins/sitemap.py +++ b/reflex/plugins/sitemap.py @@ -1,4 +1,3 @@ """Re-export from reflex_core.plugins.sitemap.""" -from reflex_core.plugins.sitemap import * # noqa: F401, F403 -from reflex_core.plugins.sitemap import SitemapPlugin +from reflex_core.plugins.sitemap import * diff --git a/reflex/plugins/tailwind_v3.py b/reflex/plugins/tailwind_v3.py index 99073f656c7..2e7cad305a6 100644 --- a/reflex/plugins/tailwind_v3.py +++ b/reflex/plugins/tailwind_v3.py @@ -1,4 +1,3 @@ """Re-export from reflex_core.plugins.tailwind_v3.""" -from reflex_core.plugins.tailwind_v3 import * # noqa: F401, F403 -from reflex_core.plugins.tailwind_v3 import TailwindV3Plugin +from reflex_core.plugins.tailwind_v3 import * diff --git a/reflex/plugins/tailwind_v4.py b/reflex/plugins/tailwind_v4.py index 6425cb32b1d..f78999b1fc4 100644 --- a/reflex/plugins/tailwind_v4.py +++ b/reflex/plugins/tailwind_v4.py @@ -1,4 +1,3 @@ """Re-export from reflex_core.plugins.tailwind_v4.""" -from reflex_core.plugins.tailwind_v4 import * # noqa: F401, F403 -from reflex_core.plugins.tailwind_v4 import TailwindV4Plugin +from reflex_core.plugins.tailwind_v4 import * diff --git a/reflex/reflex.py b/reflex/reflex.py index 45061a6e685..ba877696017 100644 --- a/reflex/reflex.py +++ b/reflex/reflex.py @@ -17,8 +17,7 @@ if TYPE_CHECKING: from reflex_cli.constants.base import LogLevel as HostingLogLevel - - from reflex.constants.base import LITERAL_ENV + from reflex_core.constants.base import LITERAL_ENV def set_loglevel(ctx: click.Context, self: click.Parameter, value: str | None): diff --git a/reflex/state.py b/reflex/state.py index ef084b997c6..e171f4e11a9 100644 --- a/reflex/state.py +++ b/reflex/state.py @@ -33,6 +33,7 @@ ) from reflex_core import constants, event +from reflex_core.constants.state import FIELD_MARKER from reflex_core.utils.types import _isinstance, is_union, value_inside_optional from reflex_core.vars import Field, VarData, field from reflex_core.vars.base import ( @@ -48,7 +49,6 @@ from typing_extensions import Self import reflex.istate.dynamic -from reflex.constants.state import FIELD_MARKER from reflex.environment import PerformanceMode, environment from reflex.event import ( BACKGROUND_TASK_MARKER, diff --git a/reflex/utils/codespaces.py b/reflex/utils/codespaces.py index c0decaf5b5c..ec12ea641c3 100644 --- a/reflex/utils/codespaces.py +++ b/reflex/utils/codespaces.py @@ -7,11 +7,11 @@ from reflex_components_core.base.script import Script from reflex_components_core.core.banner import has_connection_errors from reflex_components_core.core.cond import cond +from reflex_core.constants import Endpoint from starlette.requests import Request from starlette.responses import HTMLResponse from reflex.components.component import Component -from reflex.constants import Endpoint from reflex.utils.decorator import once diff --git a/reflex/utils/exec.py b/reflex/utils/exec.py index 73c5a58c579..4571d4f14c9 100644 --- a/reflex/utils/exec.py +++ b/reflex/utils/exec.py @@ -16,10 +16,10 @@ from urllib.parse import urljoin from reflex_core import constants +from reflex_core.constants.base import LogLevel from reflex_core.utils import console from reflex.config import get_config -from reflex.constants.base import LogLevel from reflex.environment import environment from reflex.utils import path_ops from reflex.utils.decorator import once diff --git a/tests/integration/test_client_storage.py b/tests/integration/test_client_storage.py index 80766407d31..e38edd55ad6 100644 --- a/tests/integration/test_client_storage.py +++ b/tests/integration/test_client_storage.py @@ -6,11 +6,11 @@ from collections.abc import Generator import pytest +from reflex_core.constants.state import FIELD_MARKER from selenium.webdriver.common.by import By from selenium.webdriver.firefox.webdriver import WebDriver as Firefox from selenium.webdriver.remote.webdriver import WebDriver -from reflex.constants.state import FIELD_MARKER from reflex.istate.manager.disk import StateManagerDisk from reflex.istate.manager.memory import StateManagerMemory from reflex.istate.manager.redis import StateManagerRedis diff --git a/tests/integration/test_login_flow.py b/tests/integration/test_login_flow.py index e06008ecfee..7814617a544 100644 --- a/tests/integration/test_login_flow.py +++ b/tests/integration/test_login_flow.py @@ -5,11 +5,11 @@ from collections.abc import Generator import pytest +from reflex_core.constants.state import FIELD_MARKER from selenium.common.exceptions import NoSuchElementException from selenium.webdriver.common.by import By from selenium.webdriver.remote.webdriver import WebDriver -from reflex.constants.state import FIELD_MARKER from reflex.testing import AppHarness from . import utils diff --git a/tests/integration/test_upload.py b/tests/integration/test_upload.py index 9af85805a1f..c698efb2b0c 100644 --- a/tests/integration/test_upload.py +++ b/tests/integration/test_upload.py @@ -10,10 +10,10 @@ from urllib.parse import urlsplit import pytest +from reflex_core.constants.event import Endpoint from selenium.webdriver.common.by import By import reflex as rx -from reflex.constants.event import Endpoint from reflex.testing import AppHarness, WebDriver from .utils import poll_for_navigation diff --git a/tests/units/compiler/test_compiler.py b/tests/units/compiler/test_compiler.py index d64879373a5..92a111cdfa4 100644 --- a/tests/units/compiler/test_compiler.py +++ b/tests/units/compiler/test_compiler.py @@ -7,11 +7,11 @@ from reflex_components_core.base import document from reflex_components_core.el.elements.metadata import Link from reflex_core import constants +from reflex_core.constants.compiler import PageNames from reflex_core.vars.base import Var from reflex_core.vars.sequence import LiteralStringVar from reflex.compiler import compiler, utils -from reflex.constants.compiler import PageNames from reflex.utils.imports import ImportVar, ParsedImportDict diff --git a/tests/units/components/core/test_colors.py b/tests/units/components/core/test_colors.py index 17ece7305c9..931385f93ef 100644 --- a/tests/units/components/core/test_colors.py +++ b/tests/units/components/core/test_colors.py @@ -1,10 +1,10 @@ import pytest from reflex_components_code.code import CodeBlock +from reflex_core.constants.colors import Color +from reflex_core.constants.state import FIELD_MARKER from reflex_core.vars.base import LiteralVar import reflex as rx -from reflex.constants.colors import Color -from reflex.constants.state import FIELD_MARKER class ColorState(rx.State): diff --git a/tests/units/components/core/test_cond.py b/tests/units/components/core/test_cond.py index 45eb760d680..8355aeb4158 100644 --- a/tests/units/components/core/test_cond.py +++ b/tests/units/components/core/test_cond.py @@ -5,9 +5,9 @@ from reflex_components_core.base.fragment import Fragment from reflex_components_core.core.cond import Cond, cond from reflex_components_radix.themes.typography.text import Text +from reflex_core.constants.state import FIELD_MARKER from reflex_core.vars.base import LiteralVar, Var, computed_var -from reflex.constants.state import FIELD_MARKER from reflex.state import BaseState from reflex.utils.format import format_state_name diff --git a/tests/units/components/core/test_foreach.py b/tests/units/components/core/test_foreach.py index 11b16c51314..7aa15daba7a 100644 --- a/tests/units/components/core/test_foreach.py +++ b/tests/units/components/core/test_foreach.py @@ -8,13 +8,13 @@ ) from reflex_components_radix.themes.layout.box import box from reflex_components_radix.themes.typography.text import text +from reflex_core.constants.state import FIELD_MARKER from reflex_core.vars.number import NumberVar from reflex_core.vars.sequence import ArrayVar import reflex as rx from reflex import el from reflex.components.component import Component -from reflex.constants.state import FIELD_MARKER from reflex.state import BaseState, ComponentState diff --git a/tests/units/components/core/test_match.py b/tests/units/components/core/test_match.py index 5ba471c2c84..6882242934f 100644 --- a/tests/units/components/core/test_match.py +++ b/tests/units/components/core/test_match.py @@ -2,11 +2,11 @@ import pytest from reflex_components_core.core.match import Match +from reflex_core.constants.state import FIELD_MARKER from reflex_core.vars.base import Var import reflex as rx from reflex.components.component import Component -from reflex.constants.state import FIELD_MARKER from reflex.state import BaseState from reflex.utils.exceptions import MatchTypeError diff --git a/tests/units/components/datadisplay/test_datatable.py b/tests/units/components/datadisplay/test_datatable.py index de991618465..59bdaf7c453 100644 --- a/tests/units/components/datadisplay/test_datatable.py +++ b/tests/units/components/datadisplay/test_datatable.py @@ -1,9 +1,9 @@ import pandas as pd import pytest from reflex_components_gridjs.datatable import DataTable +from reflex_core.constants.state import FIELD_MARKER import reflex as rx -from reflex.constants.state import FIELD_MARKER from reflex.utils import types from reflex.utils.exceptions import UntypedComputedVarError from reflex.utils.serializers import serialize, serialize_dataframe diff --git a/tests/units/components/test_component.py b/tests/units/components/test_component.py index b248b1fc92a..3da575f5849 100644 --- a/tests/units/components/test_component.py +++ b/tests/units/components/test_component.py @@ -6,6 +6,8 @@ from reflex_components_core.base.bare import Bare from reflex_components_core.base.fragment import Fragment from reflex_components_radix.themes.layout.box import Box +from reflex_core.constants import EventTriggers +from reflex_core.constants.state import FIELD_MARKER from reflex_core.vars import VarData from reflex_core.vars.base import LiteralVar, Var from reflex_core.vars.object import ObjectVar @@ -19,8 +21,6 @@ StatefulComponent, custom_component, ) -from reflex.constants import EventTriggers -from reflex.constants.state import FIELD_MARKER from reflex.event import ( EventChain, EventHandler, diff --git a/tests/units/docgen/test_class_and_component.py b/tests/units/docgen/test_class_and_component.py index 09536f707cb..62787c110f3 100644 --- a/tests/units/docgen/test_class_and_component.py +++ b/tests/units/docgen/test_class_and_component.py @@ -5,6 +5,7 @@ import sys import pytest +from reflex_core.constants import EventTriggers from reflex_docgen import ( generate_class_documentation, generate_documentation, @@ -17,7 +18,6 @@ TriggerDefinition, field, ) -from reflex.constants import EventTriggers from reflex.event import EventHandler, no_args_event_spec diff --git a/tests/units/test_app.py b/tests/units/test_app.py index 32c2bf12a3e..80197de6ddc 100644 --- a/tests/units/test_app.py +++ b/tests/units/test_app.py @@ -20,6 +20,7 @@ from reflex_components_core.base.fragment import Fragment from reflex_components_core.core.cond import Cond from reflex_components_radix.themes.typography.text import Text +from reflex_core.constants.state import FIELD_MARKER from reflex_core.vars.base import computed_var from starlette.applications import Starlette from starlette.datastructures import FormData, Headers, UploadFile @@ -35,7 +36,6 @@ upload, ) from reflex.components import Component -from reflex.constants.state import FIELD_MARKER from reflex.environment import environment from reflex.event import Event from reflex.istate.manager.disk import StateManagerDisk diff --git a/tests/units/test_config.py b/tests/units/test_config.py index a29b526f52a..c5ba6483992 100644 --- a/tests/units/test_config.py +++ b/tests/units/test_config.py @@ -5,10 +5,10 @@ import pytest from pytest_mock import MockerFixture +from reflex_core.constants import Endpoint, Env import reflex as rx import reflex.config -from reflex.constants import Endpoint, Env from reflex.environment import ( EnvVar, env_var, diff --git a/tests/units/test_event.py b/tests/units/test_event.py index e3185cbea2d..22f87f2f281 100644 --- a/tests/units/test_event.py +++ b/tests/units/test_event.py @@ -3,10 +3,10 @@ from typing import Any, cast import pytest +from reflex_core.constants.compiler import Hooks, Imports from reflex_core.vars.base import Field, LiteralVar, Var, VarData, field import reflex as rx -from reflex.constants.compiler import Hooks, Imports from reflex.event import ( BACKGROUND_TASK_MARKER, Event, diff --git a/tests/units/test_model.py b/tests/units/test_model.py index e7f64f736e5..7cfb7782fd2 100644 --- a/tests/units/test_model.py +++ b/tests/units/test_model.py @@ -3,10 +3,10 @@ from unittest import mock import pytest +from reflex_core.constants.state import FIELD_MARKER import reflex.constants import reflex.model -from reflex.constants.state import FIELD_MARKER from reflex.model import Model, ModelRegistry from reflex.state import BaseState, State from tests.units.test_state import ( diff --git a/tests/units/test_prerequisites.py b/tests/units/test_prerequisites.py index cf22404bd69..496e86dd014 100644 --- a/tests/units/test_prerequisites.py +++ b/tests/units/test_prerequisites.py @@ -4,9 +4,9 @@ import pytest from click.testing import CliRunner +from reflex_core.constants.installer import PackageJson from reflex.config import Config -from reflex.constants.installer import PackageJson from reflex.reflex import cli from reflex.testing import chdir from reflex.utils.decorator import cached_procedure diff --git a/tests/units/test_state.py b/tests/units/test_state.py index b2a6cf33bc8..543deb3365e 100644 --- a/tests/units/test_state.py +++ b/tests/units/test_state.py @@ -21,13 +21,13 @@ from pydantic import BaseModel as Base from pytest_mock import MockerFixture from reflex_core import constants +from reflex_core.constants import CompileVars, RouteVar, SocketEvent +from reflex_core.constants.state import FIELD_MARKER from reflex_core.vars.base import Field, Var, computed_var, field import reflex as rx import reflex.config from reflex.app import App -from reflex.constants import CompileVars, RouteVar, SocketEvent -from reflex.constants.state import FIELD_MARKER from reflex.environment import environment from reflex.event import Event, EventHandler from reflex.istate.data import HeaderData, _FrozenDictStrStr diff --git a/tests/units/test_state_tree.py b/tests/units/test_state_tree.py index 7ed19500cc2..e36185de893 100644 --- a/tests/units/test_state_tree.py +++ b/tests/units/test_state_tree.py @@ -4,9 +4,9 @@ import pytest import pytest_asyncio +from reflex_core.constants.state import FIELD_MARKER import reflex as rx -from reflex.constants.state import FIELD_MARKER from reflex.istate.manager.redis import StateManagerRedis from reflex.state import BaseState, StateManager, _substate_key diff --git a/tests/units/test_testing.py b/tests/units/test_testing.py index 36c8f173b01..b521f019ad5 100644 --- a/tests/units/test_testing.py +++ b/tests/units/test_testing.py @@ -5,13 +5,13 @@ from unittest import mock import pytest +from reflex_core.constants import IS_WINDOWS import reflex.config import reflex.reflex as reflex_cli import reflex.testing as reflex_testing import reflex.utils.prerequisites from reflex.components.component import CUSTOM_COMPONENTS -from reflex.constants import IS_WINDOWS from reflex.experimental.memo import EXPERIMENTAL_MEMOS from reflex.testing import AppHarness diff --git a/tests/units/test_var.py b/tests/units/test_var.py index 78fd82c6545..74961ce7bf8 100644 --- a/tests/units/test_var.py +++ b/tests/units/test_var.py @@ -9,6 +9,8 @@ from pandas import DataFrame from pydantic import BaseModel as Base from pytest_mock import MockerFixture +from reflex_core.constants.base import REFLEX_VAR_CLOSING_TAG, REFLEX_VAR_OPENING_TAG +from reflex_core.constants.state import FIELD_MARKER from reflex_core.vars import VarData from reflex_core.vars.base import ( ComputedVar, @@ -33,8 +35,6 @@ ) import reflex as rx -from reflex.constants.base import REFLEX_VAR_CLOSING_TAG, REFLEX_VAR_OPENING_TAG -from reflex.constants.state import FIELD_MARKER from reflex.environment import PerformanceMode from reflex.state import BaseState from reflex.utils.exceptions import ( diff --git a/tests/units/utils/test_format.py b/tests/units/utils/test_format.py index 4174a2b7941..d0900cb6347 100644 --- a/tests/units/utils/test_format.py +++ b/tests/units/utils/test_format.py @@ -6,13 +6,13 @@ import plotly.graph_objects as go import pytest +from reflex_core.constants.state import FIELD_MARKER from reflex_core.utils import format from reflex_core.vars.base import LiteralVar, Var from reflex_core.vars.function import FunctionStringVar from reflex_core.vars.object import ObjectVar from reflex.components.tags.tag import Tag -from reflex.constants.state import FIELD_MARKER from reflex.event import ( EventChain, EventHandler, From 2b9b4652aba39095f436703d7b6717fa23d1fef4 Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Thu, 26 Mar 2026 15:24:53 -0700 Subject: [PATCH 064/113] move config files --- .../reflex-core/src/reflex_core}/constants/config.py | 0 .../reflex-core/src/reflex_core}/constants/custom_components.py | 0 .../reflex-core/src/reflex_core}/constants/installer.py | 0 .../reflex-core/src/reflex_core}/constants/route.py | 0 4 files changed, 0 insertions(+), 0 deletions(-) rename {reflex => packages/reflex-core/src/reflex_core}/constants/config.py (100%) rename {reflex => packages/reflex-core/src/reflex_core}/constants/custom_components.py (100%) rename {reflex => packages/reflex-core/src/reflex_core}/constants/installer.py (100%) rename {reflex => packages/reflex-core/src/reflex_core}/constants/route.py (100%) diff --git a/reflex/constants/config.py b/packages/reflex-core/src/reflex_core/constants/config.py similarity index 100% rename from reflex/constants/config.py rename to packages/reflex-core/src/reflex_core/constants/config.py diff --git a/reflex/constants/custom_components.py b/packages/reflex-core/src/reflex_core/constants/custom_components.py similarity index 100% rename from reflex/constants/custom_components.py rename to packages/reflex-core/src/reflex_core/constants/custom_components.py diff --git a/reflex/constants/installer.py b/packages/reflex-core/src/reflex_core/constants/installer.py similarity index 100% rename from reflex/constants/installer.py rename to packages/reflex-core/src/reflex_core/constants/installer.py diff --git a/reflex/constants/route.py b/packages/reflex-core/src/reflex_core/constants/route.py similarity index 100% rename from reflex/constants/route.py rename to packages/reflex-core/src/reflex_core/constants/route.py From e002b28b30ab1b053592caca515827433bf43f5d Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Thu, 26 Mar 2026 15:25:58 -0700 Subject: [PATCH 065/113] add new files --- pyproject.toml | 2 +- reflex/constants/config.py | 3 +++ reflex/constants/custom_components.py | 3 +++ reflex/constants/installer.py | 3 +++ reflex/constants/route.py | 3 +++ 5 files changed, 13 insertions(+), 1 deletion(-) create mode 100644 reflex/constants/config.py create mode 100644 reflex/constants/custom_components.py create mode 100644 reflex/constants/installer.py create mode 100644 reflex/constants/route.py diff --git a/pyproject.toml b/pyproject.toml index 722dbd47c42..ea8a4964450 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -220,7 +220,7 @@ lint.flake8-bugbear.extend-immutable-calls = [ "*/.templates/*.py" = ["D100", "D103", "D104"] "*.pyi" = ["D301", "D415", "D417", "D418", "E742", "N", "PGH"] "pyi_generator.py" = ["N802"] -"reflex/constants/*.py" = ["N"] +"packages/reflex-core/src/reflex_core/constants/*.py" = ["N"] "*/.templates/apps/blank/code/*" = ["INP001"] "*/blank.py" = ["I001"] diff --git a/reflex/constants/config.py b/reflex/constants/config.py new file mode 100644 index 00000000000..27d575f6658 --- /dev/null +++ b/reflex/constants/config.py @@ -0,0 +1,3 @@ +"""Re-export from reflex_core.constants.config.""" + +from reflex_core.constants.config import * diff --git a/reflex/constants/custom_components.py b/reflex/constants/custom_components.py new file mode 100644 index 00000000000..6adaad1cf43 --- /dev/null +++ b/reflex/constants/custom_components.py @@ -0,0 +1,3 @@ +"""Re-export from reflex_core.constants.custom_components.""" + +from reflex_core.constants.custom_components import * diff --git a/reflex/constants/installer.py b/reflex/constants/installer.py new file mode 100644 index 00000000000..06036d6872a --- /dev/null +++ b/reflex/constants/installer.py @@ -0,0 +1,3 @@ +"""Re-export from reflex_core.constants.installer.""" + +from reflex_core.constants.installer import * diff --git a/reflex/constants/route.py b/reflex/constants/route.py new file mode 100644 index 00000000000..1c27808e7a0 --- /dev/null +++ b/reflex/constants/route.py @@ -0,0 +1,3 @@ +"""Re-export from reflex_core.constants.route.""" + +from reflex_core.constants.route import * From a5d763cc6e96f204bd1b515ce241ca57fdc1d8c6 Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Thu, 26 Mar 2026 15:26:44 -0700 Subject: [PATCH 066/113] move utils --- .../reflex-core/src/reflex_core}/constants/utils.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename {reflex => packages/reflex-core/src/reflex_core}/constants/utils.py (100%) diff --git a/reflex/constants/utils.py b/packages/reflex-core/src/reflex_core/constants/utils.py similarity index 100% rename from reflex/constants/utils.py rename to packages/reflex-core/src/reflex_core/constants/utils.py From a609e4d760f4d66082ad16ae269efb91d2fd27d5 Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Thu, 26 Mar 2026 15:26:49 -0700 Subject: [PATCH 067/113] add shim --- reflex/constants/utils.py | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 reflex/constants/utils.py diff --git a/reflex/constants/utils.py b/reflex/constants/utils.py new file mode 100644 index 00000000000..54e33d3d9d8 --- /dev/null +++ b/reflex/constants/utils.py @@ -0,0 +1,3 @@ +"""Re-export from reflex_core.constants.utils.""" + +from reflex_core.constants.utils import * From 1dae62e9bf1e447e2f9c67c4da3c45b130b1d8f7 Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Thu, 26 Mar 2026 15:28:06 -0700 Subject: [PATCH 068/113] fix imports --- pyi_hashes.json | 6 +++--- reflex/utils/prerequisites.py | 3 ++- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/pyi_hashes.json b/pyi_hashes.json index 650647b61b7..1f767435025 100644 --- a/pyi_hashes.json +++ b/pyi_hashes.json @@ -14,13 +14,13 @@ "packages/reflex-components-core/src/reflex_components_core/base/strict_mode.pyi": "490676ec4319d738aee31c3975361591", "packages/reflex-components-core/src/reflex_components_core/core/__init__.pyi": "17f79762cef09c69acd9df227cf3bb35", "packages/reflex-components-core/src/reflex_components_core/core/auto_scroll.pyi": "27d668338d326d7546854af30f757446", - "packages/reflex-components-core/src/reflex_components_core/core/banner.pyi": "bd5825c02dafee1b9a85dc9511004bd0", + "packages/reflex-components-core/src/reflex_components_core/core/banner.pyi": "248ec037a63b2e06b1c0f39f4cf0a8bc", "packages/reflex-components-core/src/reflex_components_core/core/clipboard.pyi": "77b73071a42d9effc89421c7664e6b01", "packages/reflex-components-core/src/reflex_components_core/core/debounce.pyi": "883c94a3e37a4a85c8478ce341d3f873", "packages/reflex-components-core/src/reflex_components_core/core/helmet.pyi": "bb1b4d7468cb1496bb301ade46354d3e", "packages/reflex-components-core/src/reflex_components_core/core/html.pyi": "690ef52cd0c56fcc397826156a79c1e7", "packages/reflex-components-core/src/reflex_components_core/core/sticky.pyi": "9e817ab33107cb59402e3aebf12c50cc", - "packages/reflex-components-core/src/reflex_components_core/core/upload.pyi": "30a3d28d7e17045f5fd667a50a6731df", + "packages/reflex-components-core/src/reflex_components_core/core/upload.pyi": "49d2fc5b872c939b24738f10f9057051", "packages/reflex-components-core/src/reflex_components_core/core/window_events.pyi": "66593daba42db6ffa072f705cd208ad7", "packages/reflex-components-core/src/reflex_components_core/datadisplay/__init__.pyi": "73e27e0bfd7e914c4baba7b166e75736", "packages/reflex-components-core/src/reflex_components_core/el/__init__.pyi": "a1d4d65ddc73b5fbba3726f4bcf6d6f0", @@ -120,5 +120,5 @@ "packages/reflex-components-sonner/src/reflex_components_sonner/toast.pyi": "f77ae3b4afe892aed403aae1e609d2a3", "reflex/__init__.pyi": "77b2efa084aa8473dce836f78fc4f61a", "reflex/components/__init__.pyi": "d620d52015f908cda828d231c1064236", - "reflex/experimental/memo.pyi": "555f5495d3ddd30dc974eacbb046662a" + "reflex/experimental/memo.pyi": "2e34dd962a56046723428dc29d6ed585" } diff --git a/reflex/utils/prerequisites.py b/reflex/utils/prerequisites.py index 084d21b266a..cb77cfcf0d8 100644 --- a/reflex/utils/prerequisites.py +++ b/reflex/utils/prerequisites.py @@ -18,8 +18,9 @@ from typing import NamedTuple from packaging import version -from reflex_core import constants, model +from reflex_core import constants +from reflex import model from reflex.config import Config, get_config from reflex.environment import environment from reflex.utils import console, net, path_ops From 714b378ce1415f102c879b3af525299e4a5942a7 Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Thu, 26 Mar 2026 15:29:52 -0700 Subject: [PATCH 069/113] update imports --- reflex/config.py | 4 ++-- reflex/environment.py | 3 +-- tests/units/plugins/test_sitemap.py | 7 ++++++- tests/units/test_config.py | 4 ++-- tests/units/test_environment.py | 2 +- 5 files changed, 12 insertions(+), 8 deletions(-) diff --git a/reflex/config.py b/reflex/config.py index 4ce095299ba..2d2afdd652f 100644 --- a/reflex/config.py +++ b/reflex/config.py @@ -14,6 +14,8 @@ from reflex_core import constants from reflex_core.constants.base import LogLevel +from reflex_core.plugins import Plugin +from reflex_core.plugins.sitemap import SitemapPlugin from reflex_core.utils import console from reflex_core.utils.exceptions import ConfigError @@ -28,8 +30,6 @@ ) from reflex.environment import env_var as env_var from reflex.environment import environment as environment -from reflex.plugins import Plugin -from reflex.plugins.sitemap import SitemapPlugin @dataclasses.dataclass(kw_only=True) diff --git a/reflex/environment.py b/reflex/environment.py index 63fc8b58a2f..4559d1ddd32 100644 --- a/reflex/environment.py +++ b/reflex/environment.py @@ -26,11 +26,10 @@ from reflex_core import constants from reflex_core.constants.base import LogLevel +from reflex_core.plugins import Plugin from reflex_core.utils.exceptions import EnvironmentVarValueError from reflex_core.utils.types import GenericType, is_union, value_inside_optional -from reflex.plugins import Plugin - def get_default_value_for_field(field: dataclasses.Field) -> Any: """Get the default value for a field. diff --git a/tests/units/plugins/test_sitemap.py b/tests/units/plugins/test_sitemap.py index 5c7958a772e..dcd23681da5 100644 --- a/tests/units/plugins/test_sitemap.py +++ b/tests/units/plugins/test_sitemap.py @@ -3,9 +3,14 @@ import datetime from unittest.mock import MagicMock, patch +from reflex_core.plugins.sitemap import ( + SitemapLink, + generate_links_for_sitemap, + generate_xml, +) + import reflex as rx from reflex.app import UnevaluatedPage -from reflex.plugins.sitemap import SitemapLink, generate_links_for_sitemap, generate_xml def test_generate_xml_empty_links(): diff --git a/tests/units/test_config.py b/tests/units/test_config.py index c5ba6483992..e826d4fbad6 100644 --- a/tests/units/test_config.py +++ b/tests/units/test_config.py @@ -6,6 +6,8 @@ import pytest from pytest_mock import MockerFixture from reflex_core.constants import Endpoint, Env +from reflex_core.plugins import Plugin +from reflex_core.plugins.sitemap import SitemapPlugin import reflex as rx import reflex.config @@ -17,8 +19,6 @@ interpret_enum_env, interpret_int_env, ) -from reflex.plugins import Plugin -from reflex.plugins.sitemap import SitemapPlugin def test_requires_app_name(): diff --git a/tests/units/test_environment.py b/tests/units/test_environment.py index 371cdf69e70..5695d1129a6 100644 --- a/tests/units/test_environment.py +++ b/tests/units/test_environment.py @@ -9,6 +9,7 @@ import pytest from reflex_core import constants +from reflex_core.plugins import Plugin from reflex.environment import ( EnvironmentVariables, @@ -33,7 +34,6 @@ interpret_plugin_class_env, interpret_plugin_env, ) -from reflex.plugins import Plugin from reflex.utils.exceptions import EnvironmentVarValueError From f546d62789b8652cbd279c1e58a5cde844faeafc Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Thu, 26 Mar 2026 15:30:22 -0700 Subject: [PATCH 070/113] move config and environment --- {reflex => packages/reflex-core/src/reflex_core}/config.py | 0 {reflex => packages/reflex-core/src/reflex_core}/environment.py | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename {reflex => packages/reflex-core/src/reflex_core}/config.py (100%) rename {reflex => packages/reflex-core/src/reflex_core}/environment.py (100%) diff --git a/reflex/config.py b/packages/reflex-core/src/reflex_core/config.py similarity index 100% rename from reflex/config.py rename to packages/reflex-core/src/reflex_core/config.py diff --git a/reflex/environment.py b/packages/reflex-core/src/reflex_core/environment.py similarity index 100% rename from reflex/environment.py rename to packages/reflex-core/src/reflex_core/environment.py From 582bc12b85a0a6358a02acb5260f5e1c15a7b681 Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Thu, 26 Mar 2026 15:36:01 -0700 Subject: [PATCH 071/113] update reflex config imports --- .../src/reflex_components_core/base/bare.py | 2 +- .../src/reflex_components_core/core/banner.py | 2 +- .../src/reflex_components_core/core/upload.py | 2 +- .../reflex-core/src/reflex_core/config.py | 21 +++++++++---------- .../src/reflex_core/constants/event.py | 2 +- .../src/reflex_core/constants/installer.py | 2 +- .../reflex_core/plugins/shared_tailwind.py | 2 +- .../src/reflex_core/plugins/sitemap.py | 2 +- .../src/reflex_core/utils/console.py | 4 ++-- pyi_hashes.json | 4 ++-- reflex/app.py | 4 ++-- reflex/assets.py | 3 +-- reflex/compiler/compiler.py | 4 ++-- reflex/config.py | 3 +++ reflex/environment.py | 3 +++ reflex/istate/manager/__init__.py | 2 +- reflex/istate/manager/disk.py | 2 +- reflex/istate/manager/redis.py | 4 ++-- reflex/model.py | 5 +++-- reflex/page.py | 2 +- reflex/reflex.py | 8 +++---- reflex/route.py | 3 +-- reflex/state.py | 13 +++++++----- reflex/testing.py | 12 +++++------ reflex/utils/build.py | 2 +- reflex/utils/exec.py | 7 +++---- reflex/utils/export.py | 4 ++-- reflex/utils/frontend_skeleton.py | 4 ++-- reflex/utils/js_runtimes.py | 4 ++-- reflex/utils/net.py | 4 ++-- reflex/utils/path_ops.py | 4 ++-- reflex/utils/prerequisites.py | 4 ++-- reflex/utils/processes.py | 4 ++-- reflex/utils/registry.py | 3 ++- reflex/utils/rename.py | 2 +- reflex/utils/telemetry.py | 4 ++-- reflex/utils/templates.py | 2 +- reflex/utils/token_manager.py | 2 +- tests/units/plugins/test_sitemap.py | 20 +++++++++--------- tests/units/test_app.py | 4 ++-- tests/units/test_config.py | 8 +++---- tests/units/test_db_config.py | 3 +-- tests/units/test_environment.py | 6 +++--- tests/units/test_page.py | 3 ++- tests/units/test_prerequisites.py | 2 +- tests/units/test_state.py | 10 ++++----- tests/units/test_testing.py | 6 ++++-- tests/units/utils/test_token_manager.py | 2 +- 48 files changed, 117 insertions(+), 108 deletions(-) create mode 100644 reflex/config.py create mode 100644 reflex/environment.py diff --git a/packages/reflex-components-core/src/reflex_components_core/base/bare.py b/packages/reflex-components-core/src/reflex_components_core/base/bare.py index 9679e54ec3f..d1ce3a680a1 100644 --- a/packages/reflex-components-core/src/reflex_components_core/base/bare.py +++ b/packages/reflex-components-core/src/reflex_components_core/base/bare.py @@ -5,6 +5,7 @@ from collections.abc import Iterator, Sequence from typing import Any +from reflex_core.environment import PerformanceMode, environment from reflex_core.vars import BooleanVar, ObjectVar, Var from reflex_core.vars.base import GLOBAL_CACHE, VarData from reflex_core.vars.sequence import LiteralStringVar @@ -12,7 +13,6 @@ from reflex.components.component import BaseComponent, Component, ComponentStyle from reflex.components.tags import Tag from reflex.components.tags.tagless import Tagless -from reflex.environment import PerformanceMode, environment from reflex.utils import console from reflex.utils.decorator import once from reflex.utils.imports import ParsedImportDict diff --git a/packages/reflex-components-core/src/reflex_components_core/core/banner.py b/packages/reflex-components-core/src/reflex_components_core/core/banner.py index 997c4b29f79..d1752115f02 100644 --- a/packages/reflex-components-core/src/reflex_components_core/core/banner.py +++ b/packages/reflex-components-core/src/reflex_components_core/core/banner.py @@ -7,6 +7,7 @@ from reflex_core import constants from reflex_core.constants import Dirs, Hooks, Imports from reflex_core.constants.compiler import CompileVars +from reflex_core.environment import environment from reflex_core.vars import VarData from reflex_core.vars.base import LiteralVar, Var from reflex_core.vars.function import FunctionStringVar @@ -14,7 +15,6 @@ from reflex_core.vars.sequence import LiteralArrayVar from reflex.components.component import Component -from reflex.environment import environment from reflex.utils.imports import ImportVar from reflex_components_core.base.fragment import Fragment from reflex_components_core.core.cond import cond diff --git a/packages/reflex-components-core/src/reflex_components_core/core/upload.py b/packages/reflex-components-core/src/reflex_components_core/core/upload.py index d618c8de8d9..c92df47819d 100644 --- a/packages/reflex-components-core/src/reflex_components_core/core/upload.py +++ b/packages/reflex-components-core/src/reflex_components_core/core/upload.py @@ -9,6 +9,7 @@ from reflex_components_sonner.toast import toast from reflex_core.constants import Dirs from reflex_core.constants.compiler import Hooks, Imports +from reflex_core.environment import environment from reflex_core.vars import VarData from reflex_core.vars.base import Var, get_unique_variable_name from reflex_core.vars.function import FunctionVar @@ -23,7 +24,6 @@ StatefulComponent, field, ) -from reflex.environment import environment from reflex.event import ( CallableEventSpec, EventChain, diff --git a/packages/reflex-core/src/reflex_core/config.py b/packages/reflex-core/src/reflex_core/config.py index 2d2afdd652f..1607caf1467 100644 --- a/packages/reflex-core/src/reflex_core/config.py +++ b/packages/reflex-core/src/reflex_core/config.py @@ -14,22 +14,21 @@ from reflex_core import constants from reflex_core.constants.base import LogLevel -from reflex_core.plugins import Plugin -from reflex_core.plugins.sitemap import SitemapPlugin -from reflex_core.utils import console -from reflex_core.utils.exceptions import ConfigError - -from reflex.environment import EnvironmentVariables as EnvironmentVariables -from reflex.environment import EnvVar as EnvVar -from reflex.environment import ( +from reflex_core.environment import EnvironmentVariables as EnvironmentVariables +from reflex_core.environment import EnvVar as EnvVar +from reflex_core.environment import ( ExistingPath, SequenceOptions, _load_dotenv_from_files, _paths_from_env_files, interpret_env_var_value, ) -from reflex.environment import env_var as env_var -from reflex.environment import environment as environment +from reflex_core.environment import env_var as env_var +from reflex_core.environment import environment as environment +from reflex_core.plugins import Plugin +from reflex_core.plugins.sitemap import SitemapPlugin +from reflex_core.utils import console +from reflex_core.utils.exceptions import ConfigError @dataclasses.dataclass(kw_only=True) @@ -385,7 +384,7 @@ def _normalize_disable_plugins(self): removal_version="0.9.0", ) try: - from reflex.environment import interpret_plugin_class_env + from reflex_core.environment import interpret_plugin_class_env normalized.append( interpret_plugin_class_env(entry, "disable_plugins") diff --git a/packages/reflex-core/src/reflex_core/constants/event.py b/packages/reflex-core/src/reflex_core/constants/event.py index 6a0f71ec161..d69ccf246df 100644 --- a/packages/reflex-core/src/reflex_core/constants/event.py +++ b/packages/reflex-core/src/reflex_core/constants/event.py @@ -29,7 +29,7 @@ def get_url(self) -> str: The full URL for the endpoint. """ # Import here to avoid circular imports. - from reflex.config import get_config + from reflex_core.config import get_config # Get the API URL from the config. config = get_config() diff --git a/packages/reflex-core/src/reflex_core/constants/installer.py b/packages/reflex-core/src/reflex_core/constants/installer.py index aab166c7d39..a270f1db968 100644 --- a/packages/reflex-core/src/reflex_core/constants/installer.py +++ b/packages/reflex-core/src/reflex_core/constants/installer.py @@ -38,7 +38,7 @@ def ROOT_PATH(cls): Returns: The directory to store the bun. """ - from reflex.environment import environment + from reflex_core.environment import environment return environment.REFLEX_DIR.get() / "bun" diff --git a/packages/reflex-core/src/reflex_core/plugins/shared_tailwind.py b/packages/reflex-core/src/reflex_core/plugins/shared_tailwind.py index 0ea8a6d394a..4bb6c345cfc 100644 --- a/packages/reflex-core/src/reflex_core/plugins/shared_tailwind.py +++ b/packages/reflex-core/src/reflex_core/plugins/shared_tailwind.py @@ -201,7 +201,7 @@ def get_config(self) -> TailwindConfig: Returns: The Tailwind CSS configuration. """ - from reflex.config import get_config + from reflex_core.config import get_config rxconfig_config = getattr(get_config(), "tailwind", None) diff --git a/packages/reflex-core/src/reflex_core/plugins/sitemap.py b/packages/reflex-core/src/reflex_core/plugins/sitemap.py index 9fb8a45dbb7..acc9f18eeb1 100644 --- a/packages/reflex-core/src/reflex_core/plugins/sitemap.py +++ b/packages/reflex-core/src/reflex_core/plugins/sitemap.py @@ -130,8 +130,8 @@ def generate_links_for_sitemap( Returns: A list of SitemapLink dictionaries. """ - from reflex.config import get_config from reflex.utils import console + from reflex_core.config import get_config deploy_url = get_config().deploy_url diff --git a/packages/reflex-core/src/reflex_core/utils/console.py b/packages/reflex-core/src/reflex_core/utils/console.py index b22e724ecdc..de7f61c6ac8 100644 --- a/packages/reflex-core/src/reflex_core/utils/console.py +++ b/packages/reflex-core/src/reflex_core/utils/console.py @@ -119,7 +119,7 @@ def log_file_console(): Returns: A Console object that logs to a file. """ - from reflex.environment import environment + from reflex_core.environment import environment if not (env_log_file := environment.REFLEX_LOG_FILE.get()): subseconds = int((time.time() % 1) * 1000) @@ -141,7 +141,7 @@ def should_use_log_file_console() -> bool: Returns: True if the log file console should be used, False otherwise. """ - from reflex.environment import environment + from reflex_core.environment import environment return environment.REFLEX_ENABLE_FULL_LOGGING.get() diff --git a/pyi_hashes.json b/pyi_hashes.json index 1f767435025..cc3b60114b4 100644 --- a/pyi_hashes.json +++ b/pyi_hashes.json @@ -14,13 +14,13 @@ "packages/reflex-components-core/src/reflex_components_core/base/strict_mode.pyi": "490676ec4319d738aee31c3975361591", "packages/reflex-components-core/src/reflex_components_core/core/__init__.pyi": "17f79762cef09c69acd9df227cf3bb35", "packages/reflex-components-core/src/reflex_components_core/core/auto_scroll.pyi": "27d668338d326d7546854af30f757446", - "packages/reflex-components-core/src/reflex_components_core/core/banner.pyi": "248ec037a63b2e06b1c0f39f4cf0a8bc", + "packages/reflex-components-core/src/reflex_components_core/core/banner.pyi": "80aa12b641acd53d738b0038eccad904", "packages/reflex-components-core/src/reflex_components_core/core/clipboard.pyi": "77b73071a42d9effc89421c7664e6b01", "packages/reflex-components-core/src/reflex_components_core/core/debounce.pyi": "883c94a3e37a4a85c8478ce341d3f873", "packages/reflex-components-core/src/reflex_components_core/core/helmet.pyi": "bb1b4d7468cb1496bb301ade46354d3e", "packages/reflex-components-core/src/reflex_components_core/core/html.pyi": "690ef52cd0c56fcc397826156a79c1e7", "packages/reflex-components-core/src/reflex_components_core/core/sticky.pyi": "9e817ab33107cb59402e3aebf12c50cc", - "packages/reflex-components-core/src/reflex_components_core/core/upload.pyi": "49d2fc5b872c939b24738f10f9057051", + "packages/reflex-components-core/src/reflex_components_core/core/upload.pyi": "af6855eecfb39f177d34d3b750fd957a", "packages/reflex-components-core/src/reflex_components_core/core/window_events.pyi": "66593daba42db6ffa072f705cd208ad7", "packages/reflex-components-core/src/reflex_components_core/datadisplay/__init__.pyi": "73e27e0bfd7e914c4baba7b166e75736", "packages/reflex-components-core/src/reflex_components_core/el/__init__.pyi": "a1d4d65ddc73b5fbba3726f4bcf6d6f0", diff --git a/reflex/app.py b/reflex/app.py index e94eab78ed4..1cf46053f40 100644 --- a/reflex/app.py +++ b/reflex/app.py @@ -44,6 +44,8 @@ from reflex_components_radix import themes from reflex_components_sonner.toast import toast from reflex_core import constants +from reflex_core.config import get_config +from reflex_core.environment import ExecutorType, environment from reflex_core.event import ( _EVENT_FIELDS, Event, @@ -80,8 +82,6 @@ ComponentStyle, evaluate_style_namespaces, ) -from reflex.config import get_config -from reflex.environment import ExecutorType, environment from reflex.experimental.memo import EXPERIMENTAL_MEMOS from reflex.istate.manager import StateModificationContext from reflex.istate.proxy import StateProxy diff --git a/reflex/assets.py b/reflex/assets.py index c3a3232063b..c7dcd46a93f 100644 --- a/reflex/assets.py +++ b/reflex/assets.py @@ -4,8 +4,7 @@ from pathlib import Path from reflex_core import constants - -from reflex.environment import EnvironmentVariables +from reflex_core.environment import EnvironmentVariables def remove_stale_external_asset_symlinks(): diff --git a/reflex/compiler/compiler.py b/reflex/compiler/compiler.py index 778c820b56c..7974a0d7e9e 100644 --- a/reflex/compiler/compiler.py +++ b/reflex/compiler/compiler.py @@ -10,8 +10,10 @@ from reflex_components_core.base.fragment import Fragment from reflex_core import constants +from reflex_core.config import get_config from reflex_core.constants.compiler import PageNames, ResetStylesheet from reflex_core.constants.state import FIELD_MARKER +from reflex_core.environment import environment from reflex_core.vars.base import LiteralVar, Var from reflex.compiler import templates, utils @@ -22,8 +24,6 @@ CustomComponent, StatefulComponent, ) -from reflex.config import get_config -from reflex.environment import environment from reflex.experimental.memo import ( ExperimentalMemoComponentDefinition, ExperimentalMemoDefinition, diff --git a/reflex/config.py b/reflex/config.py new file mode 100644 index 00000000000..76a1e9cb1d1 --- /dev/null +++ b/reflex/config.py @@ -0,0 +1,3 @@ +"""Re-export from reflex_core.config.""" + +from reflex_core.config import * diff --git a/reflex/environment.py b/reflex/environment.py new file mode 100644 index 00000000000..85890d88341 --- /dev/null +++ b/reflex/environment.py @@ -0,0 +1,3 @@ +"""Re-export from reflex_core.environment.""" + +from reflex_core.environment import * diff --git a/reflex/istate/manager/__init__.py b/reflex/istate/manager/__init__.py index d8df0895e44..96c50841a84 100644 --- a/reflex/istate/manager/__init__.py +++ b/reflex/istate/manager/__init__.py @@ -7,9 +7,9 @@ from typing import TypedDict from reflex_core import constants +from reflex_core.config import get_config from typing_extensions import ReadOnly, Unpack -from reflex.config import get_config from reflex.event import Event from reflex.state import BaseState from reflex.utils import console, prerequisites diff --git a/reflex/istate/manager/disk.py b/reflex/istate/manager/disk.py index 30db2f25481..17d66ca9762 100644 --- a/reflex/istate/manager/disk.py +++ b/reflex/istate/manager/disk.py @@ -9,9 +9,9 @@ from hashlib import md5 from pathlib import Path +from reflex_core.environment import environment from typing_extensions import Unpack, override -from reflex.environment import environment from reflex.istate.manager import ( StateManager, StateModificationContext, diff --git a/reflex/istate/manager/redis.py b/reflex/istate/manager/redis.py index 35b87b92eaa..0aef94c96fc 100644 --- a/reflex/istate/manager/redis.py +++ b/reflex/istate/manager/redis.py @@ -13,10 +13,10 @@ from redis import ResponseError from redis.asyncio import Redis +from reflex_core.config import get_config +from reflex_core.environment import environment from typing_extensions import Unpack, override -from reflex.config import get_config -from reflex.environment import environment from reflex.istate.manager import ( StateManager, StateModificationContext, diff --git a/reflex/model.py b/reflex/model.py index af6c3805e02..3fa01254032 100644 --- a/reflex/model.py +++ b/reflex/model.py @@ -8,8 +8,9 @@ from importlib.util import find_spec from typing import TYPE_CHECKING, Any, ClassVar -from reflex.config import get_config -from reflex.environment import environment +from reflex_core.config import get_config +from reflex_core.environment import environment + from reflex.utils import console from reflex.utils.compat import sqlmodel_field_has_primary_key from reflex.utils.serializers import serializer diff --git a/reflex/page.py b/reflex/page.py index 9b09682436f..257e7d469db 100644 --- a/reflex/page.py +++ b/reflex/page.py @@ -45,7 +45,7 @@ def page( Returns: The decorated function. """ - from reflex.config import get_config + from reflex_core.config import get_config def decorator(render_fn: Callable): kwargs = {} diff --git a/reflex/reflex.py b/reflex/reflex.py index ba877696017..c449378c21b 100644 --- a/reflex/reflex.py +++ b/reflex/reflex.py @@ -9,10 +9,10 @@ import click from reflex_cli.v2.deployments import hosting_cli from reflex_core import constants +from reflex_core.config import get_config +from reflex_core.environment import environment -from reflex.config import get_config from reflex.custom_components.custom_components import custom_components_cli -from reflex.environment import environment from reflex.utils import console if TYPE_CHECKING: @@ -632,9 +632,9 @@ def status(): return # Run alembic check command and display output - import reflex.config + import reflex_core.config - config = reflex.config.get_config() + config = reflex_core.config.get_config() console.print(f"[bold]\\[{config.db_url}][/bold]") # Get migration history using Model method diff --git a/reflex/route.py b/reflex/route.py index 51db722f470..930b457ac63 100644 --- a/reflex/route.py +++ b/reflex/route.py @@ -6,8 +6,7 @@ from collections.abc import Callable from reflex_core import constants - -from reflex.config import get_config +from reflex_core.config import get_config def verify_route_validity(route: str) -> None: diff --git a/reflex/state.py b/reflex/state.py index e171f4e11a9..4cbefb659d8 100644 --- a/reflex/state.py +++ b/reflex/state.py @@ -32,8 +32,9 @@ get_type_hints, ) -from reflex_core import constants, event +from reflex_core import constants from reflex_core.constants.state import FIELD_MARKER +from reflex_core.environment import PerformanceMode, environment from reflex_core.utils.types import _isinstance, is_union, value_inside_optional from reflex_core.vars import Field, VarData, field from reflex_core.vars.base import ( @@ -49,7 +50,7 @@ from typing_extensions import Self import reflex.istate.dynamic -from reflex.environment import PerformanceMode, environment +from reflex import event from reflex.event import ( BACKGROUND_TASK_MARKER, EVENT_ACTIONS_MARKER, @@ -230,7 +231,8 @@ def __call__(self, *args: Any) -> EventSpec: EventHandlerValueError: If the given Var name is not a str NotImplementedError: If the setter for the given Var is async """ - from reflex.config import get_config + from reflex_core.config import get_config + from reflex.utils.exceptions import EventHandlerValueError config = get_config() @@ -1082,7 +1084,8 @@ def _init_var(cls, name: str, prop: Var): Raises: VarTypeError: if the variable has an incorrect type """ - from reflex.config import get_config + from reflex_core.config import get_config + from reflex.utils.exceptions import VarTypeError if not types.is_valid_var_type(prop._var_type): @@ -1186,7 +1189,7 @@ def _create_setter(cls, name: str, prop: Var): name: The name of the var. prop: The var to create a setter for. """ - from reflex.config import get_config + from reflex_core.config import get_config config = get_config() create_event_handler_kwargs = {} diff --git a/reflex/testing.py b/reflex/testing.py index 66e1f11b485..092d6ab7bfe 100644 --- a/reflex/testing.py +++ b/reflex/testing.py @@ -26,6 +26,8 @@ from typing import TYPE_CHECKING, Any, Literal, TypeVar import uvicorn +from reflex_core.config import get_config +from reflex_core.environment import environment from typing_extensions import Self import reflex @@ -35,8 +37,6 @@ import reflex.utils.prerequisites import reflex.utils.processes from reflex.components.component import CUSTOM_COMPONENTS, CustomComponent -from reflex.config import get_config -from reflex.environment import environment from reflex.experimental.memo import EXPERIMENTAL_MEMOS from reflex.istate.manager.disk import StateManagerDisk from reflex.istate.manager.memory import StateManagerMemory @@ -274,7 +274,7 @@ def _initialize_app(self): reflex.utils.prerequisites.initialize_frontend_dependencies() with chdir(self.app_path): # ensure config and app are reloaded when testing different app - config = reflex.config.get_config(reload=True) + config = get_config(reload=True) # Ensure the AppHarness test does not skip State assignment due to running via pytest os.environ.pop(reflex.constants.PYTEST_CURRENT_TEST, None) os.environ[reflex.constants.APP_HARNESS_FLAG] = "true" @@ -405,7 +405,7 @@ async def _reset_backend_state_manager(self): def _start_frontend(self): # Set up the frontend. with chdir(self.app_path): - config = reflex.config.get_config() + config = get_config() print("Polling for servers...") # for pytest diagnosis #noqa: T201 config.api_url = "http://{}:{}".format( *self._poll_for_servers(timeout=30).getsockname(), @@ -439,7 +439,7 @@ def _wait_frontend(self): m = re.search(reflex.constants.ReactRouter.FRONTEND_LISTENING_REGEX, line) if m is not None: self.frontend_url = m.group(1) - config = reflex.config.get_config() + config = get_config() config.deploy_url = self.frontend_url break if self.frontend_url is None: @@ -1075,7 +1075,7 @@ def _run_frontend(self): def _start_frontend(self): # Set up the frontend. with chdir(self.app_path): - config = reflex.config.get_config() + config = get_config() print("Polling for servers...") # for pytest diagnosis #noqa: T201 config.api_url = "http://{}:{}".format( *self._poll_for_servers(timeout=30).getsockname(), diff --git a/reflex/utils/build.py b/reflex/utils/build.py index f1ae7424589..605d933908a 100644 --- a/reflex/utils/build.py +++ b/reflex/utils/build.py @@ -7,9 +7,9 @@ from pathlib import Path, PosixPath from reflex_core import constants +from reflex_core.config import get_config from rich.progress import MofNCompleteColumn, Progress, TimeElapsedColumn -from reflex.config import get_config from reflex.utils import console, js_runtimes, path_ops, prerequisites, processes from reflex.utils.exec import is_in_app_harness diff --git a/reflex/utils/exec.py b/reflex/utils/exec.py index 4571d4f14c9..06b9a2bf2d8 100644 --- a/reflex/utils/exec.py +++ b/reflex/utils/exec.py @@ -16,11 +16,11 @@ from urllib.parse import urljoin from reflex_core import constants +from reflex_core.config import get_config from reflex_core.constants.base import LogLevel +from reflex_core.environment import environment from reflex_core.utils import console -from reflex.config import get_config -from reflex.environment import environment from reflex.utils import path_ops from reflex.utils.decorator import once from reflex.utils.misc import get_module_path @@ -541,8 +541,7 @@ def run_granian_backend(host: str, port: int, loglevel: LogLevel): from granian.constants import Interfaces from granian.log import LogLevels from granian.server import Server as Granian - - from reflex.environment import _load_dotenv_from_env + from reflex_core.environment import _load_dotenv_from_env granian_app = Granian( target=get_app_instance_from_file(), diff --git a/reflex/utils/export.py b/reflex/utils/export.py index 198e5c59a59..cc6aa783fea 100644 --- a/reflex/utils/export.py +++ b/reflex/utils/export.py @@ -3,10 +3,10 @@ from pathlib import Path from reflex_core import constants +from reflex_core.config import get_config +from reflex_core.environment import environment from reflex_core.utils import console -from reflex.config import get_config -from reflex.environment import environment from reflex.utils import build, exec, prerequisites, telemetry diff --git a/reflex/utils/frontend_skeleton.py b/reflex/utils/frontend_skeleton.py index 3af617f5277..5674845cc44 100644 --- a/reflex/utils/frontend_skeleton.py +++ b/reflex/utils/frontend_skeleton.py @@ -6,10 +6,10 @@ from pathlib import Path from reflex_core import constants +from reflex_core.config import Config, get_config +from reflex_core.environment import environment from reflex.compiler import templates -from reflex.config import Config, get_config -from reflex.environment import environment from reflex.utils import console, path_ops from reflex.utils.prerequisites import get_project_hash, get_web_dir from reflex.utils.registry import get_npm_registry diff --git a/reflex/utils/js_runtimes.py b/reflex/utils/js_runtimes.py index 527d06cac86..a3eba7f758d 100644 --- a/reflex/utils/js_runtimes.py +++ b/reflex/utils/js_runtimes.py @@ -8,9 +8,9 @@ from packaging import version from reflex_core import constants +from reflex_core.config import Config, get_config +from reflex_core.environment import environment -from reflex.config import Config, get_config -from reflex.environment import environment from reflex.utils import console, net, path_ops, processes from reflex.utils.decorator import cached_procedure, once from reflex.utils.exceptions import SystemPackageMissingError diff --git a/reflex/utils/net.py b/reflex/utils/net.py index 38642d4da0a..67ddd715ba1 100644 --- a/reflex/utils/net.py +++ b/reflex/utils/net.py @@ -17,7 +17,7 @@ def _httpx_verify_kwarg() -> bool: Returns: True if SSL verification is enabled, False otherwise """ - from reflex.environment import environment + from reflex_core.environment import environment return not environment.SSL_NO_VERIFY.get() @@ -135,7 +135,7 @@ def _httpx_local_address_kwarg() -> str: Returns: The local address to bind to """ - from reflex.environment import environment + from reflex_core.environment import environment return environment.REFLEX_HTTP_CLIENT_BIND_ADDRESS.get() or ( "::" if _should_use_ipv6() else "0.0.0.0" diff --git a/reflex/utils/path_ops.py b/reflex/utils/path_ops.py index 152fac596fa..f076da1b9a7 100644 --- a/reflex/utils/path_ops.py +++ b/reflex/utils/path_ops.py @@ -9,8 +9,8 @@ import stat from pathlib import Path -from reflex.config import get_config -from reflex.environment import environment +from reflex_core.config import get_config +from reflex_core.environment import environment # Shorthand for join. join = os.linesep.join diff --git a/reflex/utils/prerequisites.py b/reflex/utils/prerequisites.py index cb77cfcf0d8..7e840c3676b 100644 --- a/reflex/utils/prerequisites.py +++ b/reflex/utils/prerequisites.py @@ -19,10 +19,10 @@ from packaging import version from reflex_core import constants +from reflex_core.config import Config, get_config +from reflex_core.environment import environment from reflex import model -from reflex.config import Config, get_config -from reflex.environment import environment from reflex.utils import console, net, path_ops from reflex.utils.decorator import once from reflex.utils.misc import get_module_path diff --git a/reflex/utils/processes.py b/reflex/utils/processes.py index 0ed8a494359..a89d53869b0 100644 --- a/reflex/utils/processes.py +++ b/reflex/utils/processes.py @@ -17,10 +17,10 @@ import rich.markup from reflex_core import constants +from reflex_core.config import get_config +from reflex_core.environment import environment from rich.progress import Progress -from reflex.config import get_config -from reflex.environment import environment from reflex.utils import console, path_ops, prerequisites from reflex.utils.registry import get_npm_registry diff --git a/reflex/utils/registry.py b/reflex/utils/registry.py index 64f10ba1ee9..1c96c82161d 100644 --- a/reflex/utils/registry.py +++ b/reflex/utils/registry.py @@ -2,7 +2,8 @@ from pathlib import Path -from reflex.environment import environment +from reflex_core.environment import environment + from reflex.utils import console, net from reflex.utils.decorator import cache_result_in_disk, once diff --git a/reflex/utils/rename.py b/reflex/utils/rename.py index 3af84b956cb..c53d2a29770 100644 --- a/reflex/utils/rename.py +++ b/reflex/utils/rename.py @@ -5,8 +5,8 @@ from pathlib import Path from reflex_core import constants +from reflex_core.config import get_config -from reflex.config import get_config from reflex.utils import console from reflex.utils.misc import get_module_path diff --git a/reflex/utils/telemetry.py b/reflex/utils/telemetry.py index a6dc9308179..251cfdd776e 100644 --- a/reflex/utils/telemetry.py +++ b/reflex/utils/telemetry.py @@ -12,8 +12,8 @@ from typing import TypedDict from reflex_core import constants +from reflex_core.environment import environment -from reflex.environment import environment from reflex.utils import console, processes from reflex.utils.decorator import once, once_unless_none from reflex.utils.exceptions import ReflexError @@ -317,7 +317,7 @@ def _send_event(event_data: _Event) -> bool: def _send(event: str, telemetry_enabled: bool | None, **kwargs) -> bool: - from reflex.config import get_config + from reflex_core.config import get_config # Get the telemetry_enabled from the config if it is not specified. if telemetry_enabled is None: diff --git a/reflex/utils/templates.py b/reflex/utils/templates.py index f1a82b70d3e..4fbc76a92e4 100644 --- a/reflex/utils/templates.py +++ b/reflex/utils/templates.py @@ -8,8 +8,8 @@ from urllib.parse import urlparse from reflex_core import constants +from reflex_core.config import get_config -from reflex.config import get_config from reflex.utils import console, net, path_ops, redir diff --git a/reflex/utils/token_manager.py b/reflex/utils/token_manager.py index 514d641cea6..95c73fda086 100644 --- a/reflex/utils/token_manager.py +++ b/reflex/utils/token_manager.py @@ -193,7 +193,7 @@ def __init__(self, redis: Redis): self.redis = redis # Get token expiration from config (default 1 hour) - from reflex.config import get_config + from reflex_core.config import get_config config = get_config() self.token_expiration = config.redis_token_expiration diff --git a/tests/units/plugins/test_sitemap.py b/tests/units/plugins/test_sitemap.py index dcd23681da5..4ce8ba29cbd 100644 --- a/tests/units/plugins/test_sitemap.py +++ b/tests/units/plugins/test_sitemap.py @@ -70,7 +70,7 @@ def test_generate_xml_multiple_links_all_fields(): assert xml_output == expected -@patch("reflex.config.get_config") +@patch("reflex_core.config.get_config") @patch("reflex.utils.console.warn") def test_generate_links_for_sitemap_static_routes( mock_warn: MagicMock, mock_get_config: MagicMock @@ -130,7 +130,7 @@ def mock_component(): mock_warn.assert_not_called() -@patch("reflex.config.get_config") +@patch("reflex_core.config.get_config") @patch("reflex.utils.console.warn") def test_generate_links_for_sitemap_dynamic_routes( mock_warn: MagicMock, mock_get_config: MagicMock @@ -199,7 +199,7 @@ def mock_component(): ) -@patch("reflex.config.get_config") +@patch("reflex_core.config.get_config") @patch("reflex.utils.console.warn") def test_generate_links_for_sitemap_404_route( mock_warn: MagicMock, mock_get_config: MagicMock @@ -245,7 +245,7 @@ def mock_component(): ) -@patch("reflex.config.get_config") +@patch("reflex_core.config.get_config") def test_generate_links_for_sitemap_opt_out(mock_get_config: MagicMock): """Test generate_links_for_sitemap with sitemap set to None. @@ -284,7 +284,7 @@ def mock_component(): assert {"loc": "/listed"} in links -@patch("reflex.config.get_config") +@patch("reflex_core.config.get_config") def test_generate_links_for_sitemap_loc_override(mock_get_config: MagicMock): """Test generate_links_for_sitemap with loc override in sitemap config. @@ -324,7 +324,7 @@ def mock_component(): assert {"loc": "http://localhost:3000/custom_pricing"} in links -@patch("reflex.config.get_config") +@patch("reflex_core.config.get_config") def test_generate_links_for_sitemap_priority_clamping(mock_get_config: MagicMock): """Test that priority is clamped between 0.0 and 1.0. @@ -378,7 +378,7 @@ def mock_component(): assert expected_link in links -@patch("reflex.config.get_config") +@patch("reflex_core.config.get_config") def test_generate_links_for_sitemap_no_deploy_url(mock_get_config: MagicMock): """Test generate_links_for_sitemap when deploy_url is not set. @@ -429,7 +429,7 @@ def mock_component(): assert expected_link in links -@patch("reflex.config.get_config") +@patch("reflex_core.config.get_config") def test_generate_links_for_sitemap_deploy_url_trailing_slash( mock_get_config: MagicMock, ): @@ -460,7 +460,7 @@ def mock_component(): assert {"loc": "https://example.com/testpage"} in links -@patch("reflex.config.get_config") +@patch("reflex_core.config.get_config") def test_generate_links_for_sitemap_loc_leading_slash(mock_get_config: MagicMock): """Test generate_links_for_sitemap with loc having a leading slash. @@ -489,7 +489,7 @@ def mock_component(): assert {"loc": "https://example.com/another"} in links -@patch("reflex.config.get_config") +@patch("reflex_core.config.get_config") def test_generate_links_for_sitemap_loc_full_url(mock_get_config: MagicMock): """Test generate_links_for_sitemap with loc being a full URL. diff --git a/tests/units/test_app.py b/tests/units/test_app.py index 80197de6ddc..aa2c9e3fa51 100644 --- a/tests/units/test_app.py +++ b/tests/units/test_app.py @@ -2048,7 +2048,7 @@ def test_app_wrap_compile_theme( mocker: pytest mocker object. """ conf = rx.Config(app_name="testing", react_strict_mode=react_strict_mode) - mocker.patch("reflex.config._get_config", return_value=conf) + mocker.patch("reflex_core.config._get_config", return_value=conf) app, web_dir = compilable_app mocker.patch("reflex.utils.prerequisites.get_web_dir", return_value=web_dir) app.theme = rx.theme(accent_color="plum") @@ -2100,7 +2100,7 @@ def test_app_wrap_priority( mocker: pytest mocker object. """ conf = rx.Config(app_name="testing", react_strict_mode=react_strict_mode) - mocker.patch("reflex.config._get_config", return_value=conf) + mocker.patch("reflex_core.config._get_config", return_value=conf) app, web_dir = compilable_app diff --git a/tests/units/test_config.py b/tests/units/test_config.py index e826d4fbad6..ee64d6e293a 100644 --- a/tests/units/test_config.py +++ b/tests/units/test_config.py @@ -4,13 +4,13 @@ from typing import Any import pytest +import reflex_core.config from pytest_mock import MockerFixture from reflex_core.constants import Endpoint, Env from reflex_core.plugins import Plugin from reflex_core.plugins.sitemap import SitemapPlugin import reflex as rx -import reflex.config from reflex.environment import ( EnvVar, env_var, @@ -155,9 +155,9 @@ def test_event_namespace(mocker: MockerFixture, kwargs, expected): expected: Expected namespace """ conf = rx.Config(**kwargs) - mocker.patch("reflex.config.get_config", return_value=conf) + mocker.patch("reflex_core.config.get_config", return_value=conf) - config = reflex.config.get_config() + config = reflex_core.config.get_config() assert conf == config assert config.get_event_namespace() == expected @@ -246,7 +246,7 @@ def test_replace_defaults( exp_config_values: The expected config values. """ mock_os_env = os.environ.copy() - monkeypatch.setattr(reflex.config.os, "environ", mock_os_env) + monkeypatch.setattr(reflex_core.config.os, "environ", mock_os_env) mock_os_env.update({k: str(v) for k, v in env_vars.items()}) c = rx.Config(app_name="a", **config_kwargs) c._set_persistent(**set_persistent_vars) diff --git a/tests/units/test_db_config.py b/tests/units/test_db_config.py index d778098713a..61699d31b51 100644 --- a/tests/units/test_db_config.py +++ b/tests/units/test_db_config.py @@ -1,8 +1,7 @@ import urllib.parse import pytest - -from reflex.config import DBConfig +from reflex_core.config import DBConfig @pytest.mark.parametrize( diff --git a/tests/units/test_environment.py b/tests/units/test_environment.py index 5695d1129a6..bf533a6f37d 100644 --- a/tests/units/test_environment.py +++ b/tests/units/test_environment.py @@ -9,9 +9,7 @@ import pytest from reflex_core import constants -from reflex_core.plugins import Plugin - -from reflex.environment import ( +from reflex_core.environment import ( EnvironmentVariables, EnvVar, ExecutorType, @@ -34,6 +32,8 @@ interpret_plugin_class_env, interpret_plugin_env, ) +from reflex_core.plugins import Plugin + from reflex.utils.exceptions import EnvironmentVarValueError diff --git a/tests/units/test_page.py b/tests/units/test_page.py index bf4b637601d..e25792fa043 100644 --- a/tests/units/test_page.py +++ b/tests/units/test_page.py @@ -1,5 +1,6 @@ +from reflex_core.config import get_config + from reflex import text -from reflex.config import get_config from reflex.page import DECORATED_PAGES, page diff --git a/tests/units/test_prerequisites.py b/tests/units/test_prerequisites.py index 496e86dd014..d02596c4e3c 100644 --- a/tests/units/test_prerequisites.py +++ b/tests/units/test_prerequisites.py @@ -4,9 +4,9 @@ import pytest from click.testing import CliRunner +from reflex_core.config import Config from reflex_core.constants.installer import PackageJson -from reflex.config import Config from reflex.reflex import cli from reflex.testing import chdir from reflex.utils.decorator import cached_procedure diff --git a/tests/units/test_state.py b/tests/units/test_state.py index 543deb3365e..18ee99ceeed 100644 --- a/tests/units/test_state.py +++ b/tests/units/test_state.py @@ -17,6 +17,7 @@ import pytest import pytest_asyncio +import reflex_core.config from plotly.graph_objects import Figure from pydantic import BaseModel as Base from pytest_mock import MockerFixture @@ -26,7 +27,6 @@ from reflex_core.vars.base import Field, Var, computed_var, field import reflex as rx -import reflex.config from reflex.app import App from reflex.environment import environment from reflex.event import Event, EventHandler @@ -3550,7 +3550,7 @@ def test_redis_state_manager_config_knobs(tmp_path, expiration_kwargs, expected_ with chdir(proj_root): # reload config for each parameter to avoid stale values - reflex.config.get_config(reload=True) + reflex_core.config.get_config(reload=True) from reflex.state import State state_manager = StateManagerRedis(state=State, redis=mock_redis()) @@ -3587,7 +3587,7 @@ def test_redis_state_manager_config_knobs_invalid_lock_warning_threshold( with chdir(proj_root): # reload config for each parameter to avoid stale values - reflex.config.get_config(reload=True) + reflex_core.config.get_config(reload=True) from reflex.state import State with pytest.raises(InvalidLockWarningThresholdError): @@ -3613,7 +3613,7 @@ def test_state_manager_create_respects_explicit_memory_mode_with_redis_url( monkeypatch.setenv("REFLEX_REDIS_URL", "redis://localhost:6379") with chdir(proj_root): - reflex.config.get_config(reload=True) + reflex_core.config.get_config(reload=True) monkeypatch.setattr(prerequisites, "get_redis", mock_redis) from reflex.state import State @@ -3639,7 +3639,7 @@ def test_auto_setters_off(tmp_path): with chdir(proj_root): # reload config for each parameter to avoid stale values - reflex.config.get_config(reload=True) + reflex_core.config.get_config(reload=True) from reflex.state import State class TestState(State): diff --git a/tests/units/test_testing.py b/tests/units/test_testing.py index b521f019ad5..e34e8d26e38 100644 --- a/tests/units/test_testing.py +++ b/tests/units/test_testing.py @@ -5,9 +5,9 @@ from unittest import mock import pytest +import reflex_core.config from reflex_core.constants import IS_WINDOWS -import reflex.config import reflex.reflex as reflex_cli import reflex.testing as reflex_testing import reflex.utils.prerequisites @@ -70,7 +70,9 @@ def harness_mocks(monkeypatch): ) monkeypatch.setattr(reflex_testing, "get_config", lambda: fake_config) - monkeypatch.setattr(reflex.config, "get_config", lambda reload=False: fake_config) + monkeypatch.setattr( + reflex_core.config, "get_config", lambda reload=False: fake_config + ) monkeypatch.setattr( reflex.utils.prerequisites, "get_and_validate_app", diff --git a/tests/units/utils/test_token_manager.py b/tests/units/utils/test_token_manager.py index 9f740a29f37..2f01cc9b1f6 100644 --- a/tests/units/utils/test_token_manager.py +++ b/tests/units/utils/test_token_manager.py @@ -265,7 +265,7 @@ def manager(self, mock_redis): Returns: RedisTokenManager instance for testing. """ - with patch("reflex.config.get_config") as mock_get_config: + with patch("reflex_core.config.get_config") as mock_get_config: mock_config = Mock() mock_config.redis_token_expiration = 3600 mock_get_config.return_value = mock_config From 95185cb5a5344f7ead4d2a1e940361e19c2d7691 Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Thu, 26 Mar 2026 15:40:17 -0700 Subject: [PATCH 072/113] update imports --- packages/reflex-core/src/reflex_core/constants/installer.py | 4 ++-- packages/reflex-core/src/reflex_core/environment.py | 4 ++-- .../reflex-core/src/reflex_core/plugins/shared_tailwind.py | 6 +++--- packages/reflex-core/src/reflex_core/plugins/sitemap.py | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/reflex-core/src/reflex_core/constants/installer.py b/packages/reflex-core/src/reflex_core/constants/installer.py index a270f1db968..c2f62047369 100644 --- a/packages/reflex-core/src/reflex_core/constants/installer.py +++ b/packages/reflex-core/src/reflex_core/constants/installer.py @@ -77,7 +77,7 @@ class Node(SimpleNamespace): def _determine_react_router_version() -> str: default_version = "7.13.1" if (version := os.getenv("REACT_ROUTER_VERSION")) and version != default_version: - from reflex.utils import console + from reflex_core.utils import console console.warn( f"You have requested react-router@{version} but the supported version is {default_version}, abandon all hope ye who enter here." @@ -89,7 +89,7 @@ def _determine_react_router_version() -> str: def _determine_react_version() -> str: default_version = "19.2.4" if (version := os.getenv("REACT_VERSION")) and version != default_version: - from reflex.utils import console + from reflex_core.utils import console console.warn( f"You have requested react@{version} but the supported version is {default_version}, abandon all hope ye who enter here." diff --git a/packages/reflex-core/src/reflex_core/environment.py b/packages/reflex-core/src/reflex_core/environment.py index 4559d1ddd32..a747cd21ba1 100644 --- a/packages/reflex-core/src/reflex_core/environment.py +++ b/packages/reflex-core/src/reflex_core/environment.py @@ -543,7 +543,7 @@ def get_executor_from_environment(cls): Returns: The executor. """ - from reflex.utils import console + from reflex_core.utils import console executor_type = environment.REFLEX_COMPILE_EXECUTOR.get() @@ -839,7 +839,7 @@ def _load_dotenv_from_files(files: list[Path]): Args: files: A list of Path objects representing the environment variable files. """ - from reflex.utils import console + from reflex_core.utils import console if not files: return diff --git a/packages/reflex-core/src/reflex_core/plugins/shared_tailwind.py b/packages/reflex-core/src/reflex_core/plugins/shared_tailwind.py index 4bb6c345cfc..ce4dfe24ab6 100644 --- a/packages/reflex-core/src/reflex_core/plugins/shared_tailwind.py +++ b/packages/reflex-core/src/reflex_core/plugins/shared_tailwind.py @@ -49,7 +49,7 @@ def remove_version_from_plugin(plugin: TailwindPluginConfig) -> TailwindPluginCo Returns: The plugin without the version. """ - from reflex.utils.format import format_library_name + from reflex_core.utils.format import format_library_name if isinstance(plugin, str): return format_library_name(plugin) @@ -206,7 +206,7 @@ def get_config(self) -> TailwindConfig: rxconfig_config = getattr(get_config(), "tailwind", None) if rxconfig_config is not None and rxconfig_config != self.config: - from reflex.utils import console + from reflex_core.utils import console console.warn( "It seems you have provided a tailwind configuration in your call to `rx.Config`." @@ -222,7 +222,7 @@ def get_unversioned_config(self) -> TailwindConfig: Returns: The Tailwind CSS configuration without version-specific adjustments. """ - from reflex.utils.format import format_library_name + from reflex_core.utils.format import format_library_name config = deepcopy(self.get_config()) if presets := config.get("presets"): diff --git a/packages/reflex-core/src/reflex_core/plugins/sitemap.py b/packages/reflex-core/src/reflex_core/plugins/sitemap.py index acc9f18eeb1..85e526cbb05 100644 --- a/packages/reflex-core/src/reflex_core/plugins/sitemap.py +++ b/packages/reflex-core/src/reflex_core/plugins/sitemap.py @@ -130,8 +130,8 @@ def generate_links_for_sitemap( Returns: A list of SitemapLink dictionaries. """ - from reflex.utils import console from reflex_core.config import get_config + from reflex_core.utils import console deploy_url = get_config().deploy_url From fcd7f6b5a4cb15183ecf31105a930e54c630ef08 Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Thu, 26 Mar 2026 15:48:32 -0700 Subject: [PATCH 073/113] move more imports to reflex_core --- .../src/reflex_components_code/code.py | 4 +- .../shiki_code_block.py | 4 +- .../src/reflex_components_core/base/bare.py | 6 +-- .../src/reflex_components_core/base/script.py | 3 +- .../core/auto_scroll.py | 2 +- .../src/reflex_components_core/core/banner.py | 2 +- .../reflex_components_core/core/clipboard.py | 4 +- .../src/reflex_components_core/core/cond.py | 2 +- .../reflex_components_core/core/foreach.py | 4 +- .../src/reflex_components_core/core/match.py | 6 +-- .../src/reflex_components_core/core/upload.py | 4 +- .../el/elements/__init__.py | 2 +- .../el/elements/forms.py | 2 +- .../dataeditor.py | 6 +-- .../src/reflex_components_gridjs/datatable.py | 4 +- .../src/reflex_components_lucide/icon.py | 2 +- .../reflex_components_markdown/markdown.py | 2 +- .../src/reflex_components_moment/moment.py | 2 +- .../src/reflex_components_plotly/plotly.py | 2 +- .../reflex_components_radix/themes/base.py | 2 +- .../themes/typography/link.py | 2 +- .../src/reflex_components_sonner/toast.py | 2 +- pyi_hashes.json | 40 +++++++++---------- reflex/__init__.py | 3 +- reflex/_upload.py | 3 +- reflex/app.py | 6 +-- reflex/app_mixins/lifespan.py | 5 +-- reflex/compiler/compiler.py | 8 ++-- reflex/compiler/utils.py | 2 +- reflex/components/props.py | 2 +- reflex/experimental/client_state.py | 2 +- reflex/experimental/hooks.py | 3 +- reflex/experimental/memo.py | 2 +- reflex/istate/manager/__init__.py | 2 +- reflex/istate/manager/redis.py | 12 +++--- reflex/istate/proxy.py | 2 +- reflex/istate/shared.py | 4 +- reflex/model.py | 2 +- reflex/state.py | 40 +++++++++---------- reflex/utils/js_runtimes.py | 2 +- reflex/utils/rename.py | 2 +- reflex/utils/telemetry.py | 2 +- tests/units/compiler/test_compiler.py | 2 +- tests/units/components/core/test_cond.py | 2 +- tests/units/components/core/test_match.py | 2 +- .../components/datadisplay/test_datatable.py | 2 +- tests/units/components/test_component.py | 17 +++++--- .../units/components/test_component_state.py | 2 +- tests/units/components/test_props.py | 2 +- tests/units/experimental/test_memo.py | 2 +- tests/units/plugins/test_sitemap.py | 6 +-- tests/units/test_environment.py | 11 +++-- tests/units/test_state.py | 18 ++++----- tests/units/test_style.py | 2 +- tests/units/test_var.py | 10 ++--- tests/units/utils/test_imports.py | 3 +- tests/units/utils/test_serializers.py | 2 +- tests/units/utils/test_utils.py | 2 +- tests/units/vars/test_dep_tracking.py | 2 +- 59 files changed, 151 insertions(+), 149 deletions(-) diff --git a/packages/reflex-components-code/src/reflex_components_code/code.py b/packages/reflex-components-code/src/reflex_components_code/code.py index 5c1b5b9d28a..72aabfd8c79 100644 --- a/packages/reflex-components-code/src/reflex_components_code/code.py +++ b/packages/reflex-components-code/src/reflex_components_code/code.py @@ -11,13 +11,13 @@ from reflex_components_radix.themes.components.button import Button from reflex_components_radix.themes.layout.box import Box from reflex_core.constants.colors import Color +from reflex_core.utils import format +from reflex_core.utils.imports import ImportVar from reflex_core.vars.base import LiteralVar, Var, VarData from reflex.components.component import Component, ComponentNamespace, field from reflex.event import set_clipboard from reflex.style import Style -from reflex.utils import format -from reflex.utils.imports import ImportVar LiteralCodeLanguage = Literal[ "abap", diff --git a/packages/reflex-components-code/src/reflex_components_code/shiki_code_block.py b/packages/reflex-components-code/src/reflex_components_code/shiki_code_block.py index c3a4ccadd3c..05ccffe778f 100644 --- a/packages/reflex-components-code/src/reflex_components_code/shiki_code_block.py +++ b/packages/reflex-components-code/src/reflex_components_code/shiki_code_block.py @@ -14,6 +14,8 @@ from reflex_components_core.el.elements.forms import Button from reflex_components_lucide.icon import Icon from reflex_components_radix.themes.layout.box import Box +from reflex_core.utils.exceptions import VarTypeError +from reflex_core.utils.imports import ImportVar from reflex_core.vars.base import LiteralVar, Var from reflex_core.vars.function import FunctionStringVar from reflex_core.vars.sequence import StringVar, string_replace_operation @@ -22,8 +24,6 @@ from reflex.components.props import NoExtrasAllowedProps from reflex.event import run_script, set_clipboard from reflex.style import Style -from reflex.utils.exceptions import VarTypeError -from reflex.utils.imports import ImportVar def copy_script() -> Any: diff --git a/packages/reflex-components-core/src/reflex_components_core/base/bare.py b/packages/reflex-components-core/src/reflex_components_core/base/bare.py index d1ce3a680a1..15ebd5bcb6e 100644 --- a/packages/reflex-components-core/src/reflex_components_core/base/bare.py +++ b/packages/reflex-components-core/src/reflex_components_core/base/bare.py @@ -6,6 +6,9 @@ from typing import Any from reflex_core.environment import PerformanceMode, environment +from reflex_core.utils import console +from reflex_core.utils.decorator import once +from reflex_core.utils.imports import ParsedImportDict from reflex_core.vars import BooleanVar, ObjectVar, Var from reflex_core.vars.base import GLOBAL_CACHE, VarData from reflex_core.vars.sequence import LiteralStringVar @@ -13,9 +16,6 @@ from reflex.components.component import BaseComponent, Component, ComponentStyle from reflex.components.tags import Tag from reflex.components.tags.tagless import Tagless -from reflex.utils import console -from reflex.utils.decorator import once -from reflex.utils.imports import ParsedImportDict @once diff --git a/packages/reflex-components-core/src/reflex_components_core/base/script.py b/packages/reflex-components-core/src/reflex_components_core/base/script.py index dcd121d023c..1282b982d7b 100644 --- a/packages/reflex-components-core/src/reflex_components_core/base/script.py +++ b/packages/reflex-components-core/src/reflex_components_core/base/script.py @@ -2,7 +2,8 @@ from __future__ import annotations -from reflex.utils import console +from reflex_core.utils import console + from reflex_components_core import el as elements from reflex_components_core.core.helmet import helmet diff --git a/packages/reflex-components-core/src/reflex_components_core/core/auto_scroll.py b/packages/reflex-components-core/src/reflex_components_core/core/auto_scroll.py index a46a8ed009b..e6bbd9b9f15 100644 --- a/packages/reflex-components-core/src/reflex_components_core/core/auto_scroll.py +++ b/packages/reflex-components-core/src/reflex_components_core/core/auto_scroll.py @@ -5,9 +5,9 @@ import dataclasses from reflex_core.constants.compiler import MemoizationDisposition, MemoizationMode +from reflex_core.utils.imports import ImportDict from reflex_core.vars.base import Var, get_unique_variable_name -from reflex.utils.imports import ImportDict from reflex_components_core.el.elements.typography import Div diff --git a/packages/reflex-components-core/src/reflex_components_core/core/banner.py b/packages/reflex-components-core/src/reflex_components_core/core/banner.py index d1752115f02..3ad9559583a 100644 --- a/packages/reflex-components-core/src/reflex_components_core/core/banner.py +++ b/packages/reflex-components-core/src/reflex_components_core/core/banner.py @@ -8,6 +8,7 @@ from reflex_core.constants import Dirs, Hooks, Imports from reflex_core.constants.compiler import CompileVars from reflex_core.environment import environment +from reflex_core.utils.imports import ImportVar from reflex_core.vars import VarData from reflex_core.vars.base import LiteralVar, Var from reflex_core.vars.function import FunctionStringVar @@ -15,7 +16,6 @@ from reflex_core.vars.sequence import LiteralArrayVar from reflex.components.component import Component -from reflex.utils.imports import ImportVar from reflex_components_core.base.fragment import Fragment from reflex_components_core.core.cond import cond from reflex_components_core.el.elements.inline import Span diff --git a/packages/reflex-components-core/src/reflex_components_core/core/clipboard.py b/packages/reflex-components-core/src/reflex_components_core/core/clipboard.py index 17c02a3d0ca..baa60d6555b 100644 --- a/packages/reflex-components-core/src/reflex_components_core/core/clipboard.py +++ b/packages/reflex-components-core/src/reflex_components_core/core/clipboard.py @@ -5,14 +5,14 @@ from collections.abc import Sequence from reflex_core.constants.compiler import Hooks +from reflex_core.utils.format import format_prop, wrap +from reflex_core.utils.imports import ImportVar from reflex_core.vars import get_unique_variable_name from reflex_core.vars.base import Var, VarData from reflex.components.component import field from reflex.components.tags.tag import Tag from reflex.event import EventChain, EventHandler, passthrough_event_spec -from reflex.utils.format import format_prop, wrap -from reflex.utils.imports import ImportVar from reflex_components_core.base.fragment import Fragment diff --git a/packages/reflex-components-core/src/reflex_components_core/core/cond.py b/packages/reflex-components-core/src/reflex_components_core/core/cond.py index 836dcdc60e1..99c4f0abd81 100644 --- a/packages/reflex-components-core/src/reflex_components_core/core/cond.py +++ b/packages/reflex-components-core/src/reflex_components_core/core/cond.py @@ -6,6 +6,7 @@ from reflex_core.constants import Dirs from reflex_core.utils import types +from reflex_core.utils.imports import ImportDict, ImportVar from reflex_core.vars import VarData from reflex_core.vars.base import LiteralVar, Var from reflex_core.vars.number import ternary_operation @@ -13,7 +14,6 @@ from reflex.components.component import BaseComponent, Component, field from reflex.components.tags import CondTag, Tag from reflex.style import LIGHT_COLOR_MODE, resolved_color_mode -from reflex.utils.imports import ImportDict, ImportVar from reflex_components_core.base.fragment import Fragment _IS_TRUE_IMPORT: ImportDict = { diff --git a/packages/reflex-components-core/src/reflex_components_core/core/foreach.py b/packages/reflex-components-core/src/reflex_components_core/core/foreach.py index dad9ab765e2..7f74ef74671 100644 --- a/packages/reflex-components-core/src/reflex_components_core/core/foreach.py +++ b/packages/reflex-components-core/src/reflex_components_core/core/foreach.py @@ -10,13 +10,13 @@ from reflex_core.constants import MemoizationMode from reflex_core.constants.state import FIELD_MARKER +from reflex_core.utils import types +from reflex_core.utils.exceptions import UntypedVarError from reflex_core.vars.base import LiteralVar, Var from reflex.components.component import Component, field from reflex.components.tags import IterTag from reflex.state import ComponentState -from reflex.utils import types -from reflex.utils.exceptions import UntypedVarError from reflex_components_core.base.fragment import Fragment from reflex_components_core.core.cond import cond diff --git a/packages/reflex-components-core/src/reflex_components_core/core/match.py b/packages/reflex-components-core/src/reflex_components_core/core/match.py index 41e592c11b4..79c82c4f8a9 100644 --- a/packages/reflex-components-core/src/reflex_components_core/core/match.py +++ b/packages/reflex-components-core/src/reflex_components_core/core/match.py @@ -3,6 +3,9 @@ import textwrap from typing import Any, cast +from reflex_core.utils import format +from reflex_core.utils.exceptions import MatchTypeError +from reflex_core.utils.imports import ImportDict from reflex_core.vars import VarData from reflex_core.vars.base import LiteralVar, Var @@ -10,9 +13,6 @@ from reflex.components.tags import Tag from reflex.components.tags.match_tag import MatchTag from reflex.style import Style -from reflex.utils import format -from reflex.utils.exceptions import MatchTypeError -from reflex.utils.imports import ImportDict from reflex_components_core.base import Fragment diff --git a/packages/reflex-components-core/src/reflex_components_core/core/upload.py b/packages/reflex-components-core/src/reflex_components_core/core/upload.py index c92df47819d..a3caf3583f1 100644 --- a/packages/reflex-components-core/src/reflex_components_core/core/upload.py +++ b/packages/reflex-components-core/src/reflex_components_core/core/upload.py @@ -10,6 +10,8 @@ from reflex_core.constants import Dirs from reflex_core.constants.compiler import Hooks, Imports from reflex_core.environment import environment +from reflex_core.utils import format +from reflex_core.utils.imports import ImportVar from reflex_core.vars import VarData from reflex_core.vars.base import Var, get_unique_variable_name from reflex_core.vars.function import FunctionVar @@ -37,8 +39,6 @@ upload_files, ) from reflex.style import Style -from reflex.utils import format -from reflex.utils.imports import ImportVar from reflex_components_core.base.fragment import Fragment from reflex_components_core.core.cond import cond from reflex_components_core.el.elements.forms import Input diff --git a/packages/reflex-components-core/src/reflex_components_core/el/elements/__init__.py b/packages/reflex-components-core/src/reflex_components_core/el/elements/__init__.py index eb7c894a032..75f39f7e53b 100644 --- a/packages/reflex-components-core/src/reflex_components_core/el/elements/__init__.py +++ b/packages/reflex-components-core/src/reflex_components_core/el/elements/__init__.py @@ -142,7 +142,7 @@ EXCLUDE = ["del_", "Del", "image", "style"] for v in _MAPPING.values(): - from reflex.utils.format import to_camel_case + from reflex_core.utils.format import to_camel_case v.extend([ to_camel_case(mod)[0].upper() + to_camel_case(mod)[1:] diff --git a/packages/reflex-components-core/src/reflex_components_core/el/elements/forms.py b/packages/reflex-components-core/src/reflex_components_core/el/elements/forms.py index 36330225cfe..1be189fc26b 100644 --- a/packages/reflex-components-core/src/reflex_components_core/el/elements/forms.py +++ b/packages/reflex-components-core/src/reflex_components_core/el/elements/forms.py @@ -7,6 +7,7 @@ from typing import Any, ClassVar, Literal from reflex_core.constants import Dirs, EventTriggers +from reflex_core.utils.imports import ImportDict from reflex_core.vars import VarData from reflex_core.vars.base import LiteralVar, Var from reflex_core.vars.number import ternary_operation @@ -26,7 +27,6 @@ on_submit_string_event, prevent_default, ) -from reflex.utils.imports import ImportDict from reflex_components_core.el.element import Element from .base import BaseHTML diff --git a/packages/reflex-components-dataeditor/src/reflex_components_dataeditor/dataeditor.py b/packages/reflex-components-dataeditor/src/reflex_components_dataeditor/dataeditor.py index fc514073dfd..b79ac338721 100644 --- a/packages/reflex-components-dataeditor/src/reflex_components_dataeditor/dataeditor.py +++ b/packages/reflex-components-dataeditor/src/reflex_components_dataeditor/dataeditor.py @@ -7,6 +7,9 @@ from enum import Enum from typing import Any, Literal, TypedDict +from reflex_core.utils import console, format, types +from reflex_core.utils.imports import ImportDict, ImportVar +from reflex_core.utils.serializers import serializer from reflex_core.vars import get_unique_variable_name from reflex_core.vars.base import Var from reflex_core.vars.function import FunctionStringVar @@ -15,9 +18,6 @@ from reflex.components.component import Component, NoSSRComponent, field from reflex.components.literals import LiteralRowMarker from reflex.event import EventHandler, no_args_event_spec, passthrough_event_spec -from reflex.utils import console, format, types -from reflex.utils.imports import ImportDict, ImportVar -from reflex.utils.serializers import serializer # TODO: Fix the serialization issue for custom types. diff --git a/packages/reflex-components-gridjs/src/reflex_components_gridjs/datatable.py b/packages/reflex-components-gridjs/src/reflex_components_gridjs/datatable.py index d8e67a87c25..5c7bc0e154a 100644 --- a/packages/reflex-components-gridjs/src/reflex_components_gridjs/datatable.py +++ b/packages/reflex-components-gridjs/src/reflex_components_gridjs/datatable.py @@ -5,12 +5,12 @@ from collections.abc import Sequence from typing import Any +from reflex_core.utils import types +from reflex_core.utils.imports import ImportDict from reflex_core.vars.base import LiteralVar, Var, is_computed_var from reflex.components.component import NoSSRComponent, field from reflex.components.tags import Tag -from reflex.utils import types -from reflex.utils.imports import ImportDict from reflex.utils.serializers import serialize diff --git a/packages/reflex-components-lucide/src/reflex_components_lucide/icon.py b/packages/reflex-components-lucide/src/reflex_components_lucide/icon.py index a63fc87fe28..613d3e52657 100644 --- a/packages/reflex-components-lucide/src/reflex_components_lucide/icon.py +++ b/packages/reflex-components-lucide/src/reflex_components_lucide/icon.py @@ -1,11 +1,11 @@ """Lucide Icon component.""" +from reflex_core.utils.imports import ImportVar from reflex_core.vars.base import LiteralVar, Var from reflex_core.vars.sequence import LiteralStringVar, StringVar from reflex.components.component import Component, field from reflex.utils import console, format -from reflex.utils.imports import ImportVar LUCIDE_LIBRARY = "lucide-react@0.577.0" diff --git a/packages/reflex-components-markdown/src/reflex_components_markdown/markdown.py b/packages/reflex-components-markdown/src/reflex_components_markdown/markdown.py index 07c6eece206..b017b3aaca0 100644 --- a/packages/reflex-components-markdown/src/reflex_components_markdown/markdown.py +++ b/packages/reflex-components-markdown/src/reflex_components_markdown/markdown.py @@ -11,6 +11,7 @@ from reflex_components_core.core.markdown_component_map import MarkdownComponentMap from reflex_components_core.el.elements.typography import Div +from reflex_core.utils.imports import ImportDict, ImportTypes, ImportVar from reflex_core.vars.base import LiteralVar, Var, VarData from reflex_core.vars.number import ternary_operation from reflex_core.vars.sequence import LiteralArrayVar @@ -24,7 +25,6 @@ ) from reflex.components.tags.tag import Tag from reflex.utils import console -from reflex.utils.imports import ImportDict, ImportTypes, ImportVar # Special vars used in the component map. _CHILDREN = Var(_js_expr="children", _var_type=str) diff --git a/packages/reflex-components-moment/src/reflex_components_moment/moment.py b/packages/reflex-components-moment/src/reflex_components_moment/moment.py index c67c4495c9d..49777a05baf 100644 --- a/packages/reflex-components-moment/src/reflex_components_moment/moment.py +++ b/packages/reflex-components-moment/src/reflex_components_moment/moment.py @@ -5,11 +5,11 @@ import dataclasses from datetime import date, datetime, time, timedelta +from reflex_core.utils.imports import ImportDict from reflex_core.vars.base import LiteralVar, Var from reflex.components.component import NoSSRComponent, field from reflex.event import EventHandler, passthrough_event_spec -from reflex.utils.imports import ImportDict @dataclasses.dataclass(frozen=True) diff --git a/packages/reflex-components-plotly/src/reflex_components_plotly/plotly.py b/packages/reflex-components-plotly/src/reflex_components_plotly/plotly.py index 4598b5f42c3..2fd7bd19322 100644 --- a/packages/reflex-components-plotly/src/reflex_components_plotly/plotly.py +++ b/packages/reflex-components-plotly/src/reflex_components_plotly/plotly.py @@ -5,12 +5,12 @@ from typing import TYPE_CHECKING, Any, TypedDict, TypeVar from reflex_components_core.core.cond import color_mode_cond +from reflex_core.utils.imports import ImportDict, ImportVar from reflex_core.vars.base import LiteralVar, Var from reflex.components.component import Component, NoSSRComponent, field from reflex.event import EventHandler, no_args_event_spec from reflex.utils import console -from reflex.utils.imports import ImportDict, ImportVar try: from plotly.graph_objs import Figure diff --git a/packages/reflex-components-radix/src/reflex_components_radix/themes/base.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/base.py index 4c3256af234..73bd7cc8dc2 100644 --- a/packages/reflex-components-radix/src/reflex_components_radix/themes/base.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/base.py @@ -5,12 +5,12 @@ from typing import Any, ClassVar, Literal from reflex_components_core.core.breakpoints import Responsive +from reflex_core.utils.imports import ImportDict, ImportVar from reflex_core.vars.base import Var from reflex.components import Component from reflex.components.component import field from reflex.components.tags import Tag -from reflex.utils.imports import ImportDict, ImportVar LiteralAlign = Literal["start", "center", "end", "baseline", "stretch"] LiteralJustify = Literal["start", "center", "end", "between"] diff --git a/packages/reflex-components-radix/src/reflex_components_radix/themes/typography/link.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/typography/link.py index f27803d0516..9ed1609bfa2 100644 --- a/packages/reflex-components-radix/src/reflex_components_radix/themes/typography/link.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/typography/link.py @@ -13,10 +13,10 @@ from reflex_components_core.core.markdown_component_map import MarkdownComponentMap from reflex_components_core.el.elements.inline import A from reflex_components_react_router.dom import ReactRouterLink +from reflex_core.utils.imports import ImportDict, ImportVar from reflex_core.vars.base import Var from reflex.components.component import Component, MemoizationLeaf, field -from reflex.utils.imports import ImportDict, ImportVar from reflex_components_radix.themes.base import LiteralAccentColor, RadixThemesComponent from .base import LiteralTextSize, LiteralTextTrim, LiteralTextWeight diff --git a/packages/reflex-components-sonner/src/reflex_components_sonner/toast.py b/packages/reflex-components-sonner/src/reflex_components_sonner/toast.py index fae7d76985f..265e4d5c29e 100644 --- a/packages/reflex-components-sonner/src/reflex_components_sonner/toast.py +++ b/packages/reflex-components-sonner/src/reflex_components_sonner/toast.py @@ -7,6 +7,7 @@ from reflex_components_lucide.icon import Icon from reflex_core.constants.base import Dirs +from reflex_core.utils.imports import ImportVar from reflex_core.vars import VarData from reflex_core.vars.base import LiteralVar, Var from reflex_core.vars.function import FunctionVar @@ -18,7 +19,6 @@ from reflex.event import EventSpec, run_script from reflex.style import Style, resolved_color_mode from reflex.utils import format -from reflex.utils.imports import ImportVar from reflex.utils.serializers import serializer LiteralPosition = Literal[ diff --git a/pyi_hashes.json b/pyi_hashes.json index cc3b60114b4..a3e83041afa 100644 --- a/pyi_hashes.json +++ b/pyi_hashes.json @@ -1,6 +1,6 @@ { - "packages/reflex-components-code/src/reflex_components_code/code.pyi": "a77a5aa436f768aed8dd7dd449d3d49f", - "packages/reflex-components-code/src/reflex_components_code/shiki_code_block.pyi": "45379e4c1fc1f214b7a5d66d90f76f1c", + "packages/reflex-components-code/src/reflex_components_code/code.pyi": "ee909f77bbd0fb2e3f1a47906756d143", + "packages/reflex-components-code/src/reflex_components_code/shiki_code_block.pyi": "2c24228690a74c84e47dca765e0ac252", "packages/reflex-components-core/src/reflex_components_core/__init__.pyi": "6c3ceff429117483dd0035e7a21930f4", "packages/reflex-components-core/src/reflex_components_core/base/__init__.pyi": "4beb5ba739680b7974c37241f3f6791c", "packages/reflex-components-core/src/reflex_components_core/base/app_wrap.pyi": "900f0927a8fbaea6d70a6c5f79ffe390", @@ -10,24 +10,24 @@ "packages/reflex-components-core/src/reflex_components_core/base/fragment.pyi": "203b060bc2ed6a1ee4f5139f3643a41b", "packages/reflex-components-core/src/reflex_components_core/base/link.pyi": "560d2c9c4a21d5a99ecdaf7f220874d3", "packages/reflex-components-core/src/reflex_components_core/base/meta.pyi": "d094461c8a1d56ba02102fce4940eff6", - "packages/reflex-components-core/src/reflex_components_core/base/script.pyi": "d6c683e3ad0d791b254c5961ff467ada", + "packages/reflex-components-core/src/reflex_components_core/base/script.pyi": "5d8adfa6c03b3cac1d51ac0b6bb1c015", "packages/reflex-components-core/src/reflex_components_core/base/strict_mode.pyi": "490676ec4319d738aee31c3975361591", "packages/reflex-components-core/src/reflex_components_core/core/__init__.pyi": "17f79762cef09c69acd9df227cf3bb35", - "packages/reflex-components-core/src/reflex_components_core/core/auto_scroll.pyi": "27d668338d326d7546854af30f757446", - "packages/reflex-components-core/src/reflex_components_core/core/banner.pyi": "80aa12b641acd53d738b0038eccad904", - "packages/reflex-components-core/src/reflex_components_core/core/clipboard.pyi": "77b73071a42d9effc89421c7664e6b01", + "packages/reflex-components-core/src/reflex_components_core/core/auto_scroll.pyi": "74dbe0be2d13b0e56ab601a1018641ac", + "packages/reflex-components-core/src/reflex_components_core/core/banner.pyi": "cbaf7af560c335460cb0f5248574a3f9", + "packages/reflex-components-core/src/reflex_components_core/core/clipboard.pyi": "4c8bca4b16aa8a5e06b7a0ce367e087e", "packages/reflex-components-core/src/reflex_components_core/core/debounce.pyi": "883c94a3e37a4a85c8478ce341d3f873", "packages/reflex-components-core/src/reflex_components_core/core/helmet.pyi": "bb1b4d7468cb1496bb301ade46354d3e", "packages/reflex-components-core/src/reflex_components_core/core/html.pyi": "690ef52cd0c56fcc397826156a79c1e7", "packages/reflex-components-core/src/reflex_components_core/core/sticky.pyi": "9e817ab33107cb59402e3aebf12c50cc", - "packages/reflex-components-core/src/reflex_components_core/core/upload.pyi": "af6855eecfb39f177d34d3b750fd957a", + "packages/reflex-components-core/src/reflex_components_core/core/upload.pyi": "fc802e5e985bd850e02aae2bc7265fd7", "packages/reflex-components-core/src/reflex_components_core/core/window_events.pyi": "66593daba42db6ffa072f705cd208ad7", "packages/reflex-components-core/src/reflex_components_core/datadisplay/__init__.pyi": "73e27e0bfd7e914c4baba7b166e75736", "packages/reflex-components-core/src/reflex_components_core/el/__init__.pyi": "a1d4d65ddc73b5fbba3726f4bcf6d6f0", "packages/reflex-components-core/src/reflex_components_core/el/element.pyi": "071bd8e46b6b65806c938d343ec739cd", - "packages/reflex-components-core/src/reflex_components_core/el/elements/__init__.pyi": "fbe4c6f4960bcd811311b1f73987cdb1", + "packages/reflex-components-core/src/reflex_components_core/el/elements/__init__.pyi": "ef8c4981b144e16b57e2fb805bf6dad5", "packages/reflex-components-core/src/reflex_components_core/el/elements/base.pyi": "b004e6bfbb7de0789a6581274959dbb3", - "packages/reflex-components-core/src/reflex_components_core/el/elements/forms.pyi": "1a23f604a039ee9ed0a7663f7ccdbfcb", + "packages/reflex-components-core/src/reflex_components_core/el/elements/forms.pyi": "4ffd680ba7d473a1836b078c1523c855", "packages/reflex-components-core/src/reflex_components_core/el/elements/inline.pyi": "a7d7c110149e0df7fd5798d29789d194", "packages/reflex-components-core/src/reflex_components_core/el/elements/media.pyi": "91c5dfa222ac448f3734e34f780c6e31", "packages/reflex-components-core/src/reflex_components_core/el/elements/metadata.pyi": "be4af13583cd210538a5bf401aee4653", @@ -36,12 +36,12 @@ "packages/reflex-components-core/src/reflex_components_core/el/elements/sectioning.pyi": "6cd334cc7c5999ee4ae4c03455688313", "packages/reflex-components-core/src/reflex_components_core/el/elements/tables.pyi": "28162491f97aeb8af88ec3b3d5faa275", "packages/reflex-components-core/src/reflex_components_core/el/elements/typography.pyi": "80368be4e5e781a6ece17e452d37719b", - "packages/reflex-components-dataeditor/src/reflex_components_dataeditor/dataeditor.pyi": "d7183df23f15feb744bea0c3ae786a5e", - "packages/reflex-components-gridjs/src/reflex_components_gridjs/datatable.pyi": "ffe864c848a9d775809b4edbc1b731b6", - "packages/reflex-components-lucide/src/reflex_components_lucide/icon.pyi": "a3605c42f5b6b40e1f174dc299df2c9a", - "packages/reflex-components-markdown/src/reflex_components_markdown/markdown.pyi": "1bad32ee95b99fe53e9257186ddf62f5", - "packages/reflex-components-moment/src/reflex_components_moment/moment.pyi": "082cda5a15e08c64be7e2b9490af4828", - "packages/reflex-components-plotly/src/reflex_components_plotly/plotly.pyi": "ac6382621506be5298009616966f9897", + "packages/reflex-components-dataeditor/src/reflex_components_dataeditor/dataeditor.pyi": "707afe3ba888c8c6b02bdf727354866e", + "packages/reflex-components-gridjs/src/reflex_components_gridjs/datatable.pyi": "7ac73744df1f5453c1dea3a8f192d460", + "packages/reflex-components-lucide/src/reflex_components_lucide/icon.pyi": "97469d8dc6b343d02c94ae3bc0c734e1", + "packages/reflex-components-markdown/src/reflex_components_markdown/markdown.pyi": "6e8d87e26ec0ed358f7f47ec25f0620f", + "packages/reflex-components-moment/src/reflex_components_moment/moment.pyi": "0d1cfd7fbb72efd4b79490fab739ee92", + "packages/reflex-components-plotly/src/reflex_components_plotly/plotly.pyi": "d8cb28a35e62d5d434f80fbc0be9f8a8", "packages/reflex-components-radix/src/reflex_components_radix/__init__.pyi": "899d63be42eb21c187519920518bf32e", "packages/reflex-components-radix/src/reflex_components_radix/primitives/__init__.pyi": "11bc17db31334f11d7fcde315b29cae1", "packages/reflex-components-radix/src/reflex_components_radix/primitives/accordion.pyi": "4ddaa4093bd4a9225544cda8d4a847d8", @@ -52,7 +52,7 @@ "packages/reflex-components-radix/src/reflex_components_radix/primitives/progress.pyi": "ac977b63b3640eeaa21204e6e9dcf40c", "packages/reflex-components-radix/src/reflex_components_radix/primitives/slider.pyi": "a4d76b1220aeb40a53b5f6033f67ff04", "packages/reflex-components-radix/src/reflex_components_radix/themes/__init__.pyi": "a152450345e7da9124e954843224e936", - "packages/reflex-components-radix/src/reflex_components_radix/themes/base.pyi": "9ae9885a592496736170bf61d3869ee0", + "packages/reflex-components-radix/src/reflex_components_radix/themes/base.pyi": "aa534530d2947e6ac46205d5ccb3888b", "packages/reflex-components-radix/src/reflex_components_radix/themes/color_mode.pyi": "5f41e358cde825fbdf589bf798b0bf00", "packages/reflex-components-radix/src/reflex_components_radix/themes/components/__init__.pyi": "67d45aa96a97359ee556725641bb583f", "packages/reflex-components-radix/src/reflex_components_radix/themes/components/alert_dialog.pyi": "1cd18bb9e1223b305f97e33f200563c1", @@ -105,7 +105,7 @@ "packages/reflex-components-radix/src/reflex_components_radix/themes/typography/blockquote.pyi": "f03e588746d92248a8b2510cc19e43a6", "packages/reflex-components-radix/src/reflex_components_radix/themes/typography/code.pyi": "ac3723aef4c9574578c67316e3ec7ebb", "packages/reflex-components-radix/src/reflex_components_radix/themes/typography/heading.pyi": "b8961f85b0df2f70d345db45d138ea7b", - "packages/reflex-components-radix/src/reflex_components_radix/themes/typography/link.pyi": "2dbd09f648d921572aad7e064d3d2ce1", + "packages/reflex-components-radix/src/reflex_components_radix/themes/typography/link.pyi": "677f87119fdbada1734e016c4e80347f", "packages/reflex-components-radix/src/reflex_components_radix/themes/typography/text.pyi": "dea370cc7f7e4a75cb671d7ec0ccd949", "packages/reflex-components-react-player/src/reflex_components_react_player/audio.pyi": "551894a3f295deac9fed3fffb3f171b7", "packages/reflex-components-react-player/src/reflex_components_react_player/react_player.pyi": "4961725c725fec0538b9e3bdbeafbcfe", @@ -117,8 +117,8 @@ "packages/reflex-components-recharts/src/reflex_components_recharts/general.pyi": "3d4b81db2bfa294fb18df151ed72d4f9", "packages/reflex-components-recharts/src/reflex_components_recharts/polar.pyi": "6406446ea9c0c04ddfab4d6e7ed91df3", "packages/reflex-components-recharts/src/reflex_components_recharts/recharts.pyi": "fa3dfbba1e25242fdb9787e280b43d1d", - "packages/reflex-components-sonner/src/reflex_components_sonner/toast.pyi": "f77ae3b4afe892aed403aae1e609d2a3", - "reflex/__init__.pyi": "77b2efa084aa8473dce836f78fc4f61a", + "packages/reflex-components-sonner/src/reflex_components_sonner/toast.pyi": "6d0f9dba8d0c3c5ff2212766800e9f4d", + "reflex/__init__.pyi": "c8154c3dde9c519bff7b6da72805b563", "reflex/components/__init__.pyi": "d620d52015f908cda828d231c1064236", - "reflex/experimental/memo.pyi": "2e34dd962a56046723428dc29d6ed585" + "reflex/experimental/memo.pyi": "702b5693c5ee3ede31fe602643e470bc" } diff --git a/reflex/__init__.py b/reflex/__init__.py index a90aa7af250..c23f7dc4ea1 100644 --- a/reflex/__init__.py +++ b/reflex/__init__.py @@ -89,7 +89,7 @@ from reflex.utils import lazy_loader if sys.version_info < (3, 11): - from reflex.utils import console + from reflex_core.utils import console console.warn( "Reflex support for Python 3.10 is deprecated and will be removed in a future release. Please upgrade to Python 3.11 or higher for continued support." @@ -355,6 +355,7 @@ "style", "admin", "base", + "constants", "model", "testing", "utils", diff --git a/reflex/_upload.py b/reflex/_upload.py index 0e006bd5d4f..efc1f21a6da 100644 --- a/reflex/_upload.py +++ b/reflex/_upload.py @@ -504,8 +504,9 @@ async def _upload_buffered_file( Returns: A streaming response for the buffered upload. """ + from reflex_core.utils.exceptions import UploadValueError + from reflex.event import Event - from reflex.utils.exceptions import UploadValueError try: form_data = await request.form() diff --git a/reflex/app.py b/reflex/app.py index 1cf46053f40..75dc5a573c6 100644 --- a/reflex/app.py +++ b/reflex/app.py @@ -55,6 +55,7 @@ get_hydrate_event, noop, ) +from reflex_core.utils.imports import ImportVar from rich.progress import MofNCompleteColumn, Progress, TimeElapsedColumn from socketio import ASGIApp as EngineIOApp from socketio import AsyncNamespace, AsyncServer @@ -118,7 +119,6 @@ is_testing_env, should_prerender_routes, ) -from reflex.utils.imports import ImportVar from reflex.utils.misc import run_in_thread from reflex.utils.token_manager import RedisTokenManager, TokenManager from reflex.utils.types import ASGIApp, Message, Receive, Scope, Send @@ -897,7 +897,7 @@ def _check_routes_conflict(self, new_route: str): Raises: RouteValueError: exception showing which conflict exist with the route to be added """ - from reflex.utils.exceptions import RouteValueError + from reflex_core.utils.exceptions import RouteValueError if "[" not in new_route: return @@ -1124,7 +1124,7 @@ def _compile( ReflexRuntimeError: When any page uses state, but no rx.State subclass is defined. FileNotFoundError: When a plugin requires a file that does not exist. """ - from reflex.utils.exceptions import ReflexRuntimeError + from reflex_core.utils.exceptions import ReflexRuntimeError self._apply_decorated_pages() diff --git a/reflex/app_mixins/lifespan.py b/reflex/app_mixins/lifespan.py index 075129024c5..e6a61d34245 100644 --- a/reflex/app_mixins/lifespan.py +++ b/reflex/app_mixins/lifespan.py @@ -10,11 +10,10 @@ import time from collections.abc import Callable, Coroutine +from reflex_core.utils import console +from reflex_core.utils.exceptions import InvalidLifespanTaskTypeError from starlette.applications import Starlette -from reflex.utils import console -from reflex.utils.exceptions import InvalidLifespanTaskTypeError - from .mixin import AppMixin diff --git a/reflex/compiler/compiler.py b/reflex/compiler/compiler.py index 7974a0d7e9e..e51402fccc8 100644 --- a/reflex/compiler/compiler.py +++ b/reflex/compiler/compiler.py @@ -14,6 +14,9 @@ from reflex_core.constants.compiler import PageNames, ResetStylesheet from reflex_core.constants.state import FIELD_MARKER from reflex_core.environment import environment +from reflex_core.utils.exceptions import ReflexError +from reflex_core.utils.format import to_title_case +from reflex_core.utils.imports import ImportVar, ParsedImportDict from reflex_core.vars.base import LiteralVar, Var from reflex.compiler import templates, utils @@ -32,10 +35,7 @@ from reflex.state import BaseState from reflex.style import SYSTEM_COLOR_MODE from reflex.utils import console, path_ops -from reflex.utils.exceptions import ReflexError from reflex.utils.exec import is_prod_mode -from reflex.utils.format import to_title_case -from reflex.utils.imports import ImportVar, ParsedImportDict from reflex.utils.prerequisites import get_web_dir @@ -840,7 +840,7 @@ def compile_unevaluated_page( component._add_style_recursive(style or {}, theme) - from reflex.utils.format import make_default_page_title + from reflex_core.utils.format import make_default_page_title component = Fragment.create(component) diff --git a/reflex/compiler/utils.py b/reflex/compiler/utils.py index 874f42d9845..d3d28f5f09a 100644 --- a/reflex/compiler/utils.py +++ b/reflex/compiler/utils.py @@ -21,6 +21,7 @@ from reflex_components_core.el.elements.sectioning import Body from reflex_core import constants from reflex_core.constants.state import CAMEL_CASE_MEMO_MARKER, FIELD_MARKER +from reflex_core.utils.imports import ImportVar, ParsedImportDict from reflex_core.vars.base import Field, Var, VarData from reflex_core.vars.function import DestructuredArg @@ -33,7 +34,6 @@ from reflex.state import BaseState, _resolve_delta from reflex.style import Style from reflex.utils import format, imports, path_ops -from reflex.utils.imports import ImportVar, ParsedImportDict from reflex.utils.prerequisites import get_web_dir # To re-export this function. diff --git a/reflex/components/props.py b/reflex/components/props.py index c1334231b4c..0d5a078a641 100644 --- a/reflex/components/props.py +++ b/reflex/components/props.py @@ -6,13 +6,13 @@ from dataclasses import _MISSING_TYPE, MISSING from typing import Any, TypeVar, get_args, get_origin +from reflex_core.utils.exceptions import InvalidPropValueError from reflex_core.vars.object import LiteralObjectVar from typing_extensions import dataclass_transform from reflex.components.field import BaseField, FieldBasedMeta from reflex.event import EventChain, args_specs_from_fields from reflex.utils import format -from reflex.utils.exceptions import InvalidPropValueError from reflex.utils.serializers import serializer from reflex.utils.types import is_union diff --git a/reflex/experimental/client_state.py b/reflex/experimental/client_state.py index fcbab6cd2ed..6098c2bf76e 100644 --- a/reflex/experimental/client_state.py +++ b/reflex/experimental/client_state.py @@ -8,12 +8,12 @@ from typing import Any from reflex_core import constants +from reflex_core.utils.imports import ImportVar from reflex_core.vars import VarData, get_unique_variable_name from reflex_core.vars.base import LiteralVar, Var from reflex_core.vars.function import ArgsFunctionOperationBuilder, FunctionVar from reflex.event import EventChain, EventHandler, EventSpec, run_script -from reflex.utils.imports import ImportVar NoValue = object() diff --git a/reflex/experimental/hooks.py b/reflex/experimental/hooks.py index ef2eba3b0e2..131680b7772 100644 --- a/reflex/experimental/hooks.py +++ b/reflex/experimental/hooks.py @@ -2,11 +2,10 @@ from __future__ import annotations +from reflex_core.utils.imports import ImportVar from reflex_core.vars import VarData from reflex_core.vars.base import Var -from reflex.utils.imports import ImportVar - def _compose_react_imports(tags: list[str]) -> dict[str, list[ImportVar]]: return {"react": [ImportVar(tag=tag) for tag in tags]} diff --git a/reflex/experimental/memo.py b/reflex/experimental/memo.py index b3341945e31..bcc0a2692a8 100644 --- a/reflex/experimental/memo.py +++ b/reflex/experimental/memo.py @@ -13,6 +13,7 @@ from reflex_core import constants from reflex_core.constants.compiler import SpecialAttributes from reflex_core.constants.state import CAMEL_CASE_MEMO_MARKER +from reflex_core.utils.imports import ImportVar from reflex_core.vars import VarData from reflex_core.vars.base import LiteralVar, Var from reflex_core.vars.function import ( @@ -28,7 +29,6 @@ from reflex.components.dynamic import bundled_libraries from reflex.utils import format from reflex.utils import types as type_utils -from reflex.utils.imports import ImportVar @dataclasses.dataclass(frozen=True, slots=True, kw_only=True) diff --git a/reflex/istate/manager/__init__.py b/reflex/istate/manager/__init__.py index 96c50841a84..023b021f396 100644 --- a/reflex/istate/manager/__init__.py +++ b/reflex/istate/manager/__init__.py @@ -8,12 +8,12 @@ from reflex_core import constants from reflex_core.config import get_config +from reflex_core.utils.exceptions import InvalidStateManagerModeError from typing_extensions import ReadOnly, Unpack from reflex.event import Event from reflex.state import BaseState from reflex.utils import console, prerequisites -from reflex.utils.exceptions import InvalidStateManagerModeError class StateModificationContext(TypedDict, total=False): diff --git a/reflex/istate/manager/redis.py b/reflex/istate/manager/redis.py index 0aef94c96fc..a6dd72321c5 100644 --- a/reflex/istate/manager/redis.py +++ b/reflex/istate/manager/redis.py @@ -15,6 +15,12 @@ from redis.asyncio import Redis from reflex_core.config import get_config from reflex_core.environment import environment +from reflex_core.utils import console +from reflex_core.utils.exceptions import ( + InvalidLockWarningThresholdError, + LockExpiredError, + StateSchemaMismatchError, +) from typing_extensions import Unpack, override from reflex.istate.manager import ( @@ -23,12 +29,6 @@ _default_token_expiration, ) from reflex.state import BaseState, _split_substate_key, _substate_key -from reflex.utils import console -from reflex.utils.exceptions import ( - InvalidLockWarningThresholdError, - LockExpiredError, - StateSchemaMismatchError, -) from reflex.utils.tasks import ensure_task diff --git a/reflex/istate/proxy.py b/reflex/istate/proxy.py index 89b7b2260ec..cad7cdead96 100644 --- a/reflex/istate/proxy.py +++ b/reflex/istate/proxy.py @@ -15,12 +15,12 @@ from typing import TYPE_CHECKING, Any, SupportsIndex, TypeVar import wrapt +from reflex_core.utils.exceptions import ImmutableStateError from reflex_core.vars.base import Var from typing_extensions import Self from reflex.event import Event from reflex.utils import prerequisites -from reflex.utils.exceptions import ImmutableStateError from reflex.utils.serializers import can_serialize, serialize, serializer if TYPE_CHECKING: diff --git a/reflex/istate/shared.py b/reflex/istate/shared.py index 405d57b94ac..16f031b17a2 100644 --- a/reflex/istate/shared.py +++ b/reflex/istate/shared.py @@ -6,11 +6,11 @@ from typing import Self, TypeVar from reflex_core.constants import ROUTER_DATA +from reflex_core.utils import console +from reflex_core.utils.exceptions import ReflexRuntimeError from reflex.event import Event, get_hydrate_event from reflex.state import BaseState, State, _override_base_method, _substate_key -from reflex.utils import console -from reflex.utils.exceptions import ReflexRuntimeError UPDATE_OTHER_CLIENT_TASKS: set[asyncio.Task] = set() LINKED_STATE = TypeVar("LINKED_STATE", bound="SharedStateBaseInternal") diff --git a/reflex/model.py b/reflex/model.py index 3fa01254032..37717ba1655 100644 --- a/reflex/model.py +++ b/reflex/model.py @@ -10,8 +10,8 @@ from reflex_core.config import get_config from reflex_core.environment import environment +from reflex_core.utils import console -from reflex.utils import console from reflex.utils.compat import sqlmodel_field_has_primary_key from reflex.utils.serializers import serializer diff --git a/reflex/state.py b/reflex/state.py index 4cbefb659d8..068b98a6c3f 100644 --- a/reflex/state.py +++ b/reflex/state.py @@ -35,6 +35,21 @@ from reflex_core import constants from reflex_core.constants.state import FIELD_MARKER from reflex_core.environment import PerformanceMode, environment +from reflex_core.utils.exceptions import ( + ComputedVarShadowsBaseVarsError, + ComputedVarShadowsStateVarError, + DynamicComponentInvalidSignatureError, + DynamicRouteArgShadowsStateVarError, + EventHandlerShadowsBuiltInStateMethodError, + ReflexRuntimeError, + SetUndefinedStateVarError, + StateMismatchError, + StateSchemaMismatchError, + StateSerializationError, + StateTooLargeError, + UnretrievableVarValueError, +) +from reflex_core.utils.exceptions import ImmutableStateError as ImmutableStateError from reflex_core.utils.types import _isinstance, is_union, value_inside_optional from reflex_core.vars import Field, VarData, field from reflex_core.vars.base import ( @@ -67,21 +82,6 @@ from reflex.istate.storage import ClientStorageBase from reflex.model import Model from reflex.utils import console, format, prerequisites, types -from reflex.utils.exceptions import ( - ComputedVarShadowsBaseVarsError, - ComputedVarShadowsStateVarError, - DynamicComponentInvalidSignatureError, - DynamicRouteArgShadowsStateVarError, - EventHandlerShadowsBuiltInStateMethodError, - ReflexRuntimeError, - SetUndefinedStateVarError, - StateMismatchError, - StateSchemaMismatchError, - StateSerializationError, - StateTooLargeError, - UnretrievableVarValueError, -) -from reflex.utils.exceptions import ImmutableStateError as ImmutableStateError from reflex.utils.exec import is_testing_env if TYPE_CHECKING: @@ -232,8 +232,7 @@ def __call__(self, *args: Any) -> EventSpec: NotImplementedError: If the setter for the given Var is async """ from reflex_core.config import get_config - - from reflex.utils.exceptions import EventHandlerValueError + from reflex_core.utils.exceptions import EventHandlerValueError config = get_config() if config.state_auto_setters is None: @@ -445,7 +444,7 @@ def __init__( Raises: ReflexRuntimeError: If the state is instantiated directly by end user. """ - from reflex.utils.exceptions import ReflexRuntimeError + from reflex_core.utils.exceptions import ReflexRuntimeError if not _reflex_internal_init and not is_testing_env(): msg = ( @@ -519,7 +518,7 @@ def __init_subclass__(cls, mixin: bool = False, **kwargs): Raises: StateValueError: If a substate class shadows another. """ - from reflex.utils.exceptions import StateValueError + from reflex_core.utils.exceptions import StateValueError super().__init_subclass__(**kwargs) @@ -1085,8 +1084,7 @@ def _init_var(cls, name: str, prop: Var): VarTypeError: if the variable has an incorrect type """ from reflex_core.config import get_config - - from reflex.utils.exceptions import VarTypeError + from reflex_core.utils.exceptions import VarTypeError if not types.is_valid_var_type(prop._var_type): msg = ( diff --git a/reflex/utils/js_runtimes.py b/reflex/utils/js_runtimes.py index a3eba7f758d..022ad94ab7b 100644 --- a/reflex/utils/js_runtimes.py +++ b/reflex/utils/js_runtimes.py @@ -10,10 +10,10 @@ from reflex_core import constants from reflex_core.config import Config, get_config from reflex_core.environment import environment +from reflex_core.utils.exceptions import SystemPackageMissingError from reflex.utils import console, net, path_ops, processes from reflex.utils.decorator import cached_procedure, once -from reflex.utils.exceptions import SystemPackageMissingError from reflex.utils.prerequisites import get_web_dir, windows_check_onedrive_in_path diff --git a/reflex/utils/rename.py b/reflex/utils/rename.py index c53d2a29770..52421a9e852 100644 --- a/reflex/utils/rename.py +++ b/reflex/utils/rename.py @@ -6,8 +6,8 @@ from reflex_core import constants from reflex_core.config import get_config +from reflex_core.utils import console -from reflex.utils import console from reflex.utils.misc import get_module_path diff --git a/reflex/utils/telemetry.py b/reflex/utils/telemetry.py index 251cfdd776e..7d938e92134 100644 --- a/reflex/utils/telemetry.py +++ b/reflex/utils/telemetry.py @@ -13,10 +13,10 @@ from reflex_core import constants from reflex_core.environment import environment +from reflex_core.utils.exceptions import ReflexError from reflex.utils import console, processes from reflex.utils.decorator import once, once_unless_none -from reflex.utils.exceptions import ReflexError from reflex.utils.js_runtimes import get_bun_version, get_node_version from reflex.utils.prerequisites import ensure_reflex_installation_id, get_project_hash diff --git a/tests/units/compiler/test_compiler.py b/tests/units/compiler/test_compiler.py index 92a111cdfa4..b2ce38ad8ca 100644 --- a/tests/units/compiler/test_compiler.py +++ b/tests/units/compiler/test_compiler.py @@ -8,11 +8,11 @@ from reflex_components_core.el.elements.metadata import Link from reflex_core import constants from reflex_core.constants.compiler import PageNames +from reflex_core.utils.imports import ImportVar, ParsedImportDict from reflex_core.vars.base import Var from reflex_core.vars.sequence import LiteralStringVar from reflex.compiler import compiler, utils -from reflex.utils.imports import ImportVar, ParsedImportDict @pytest.mark.parametrize( diff --git a/tests/units/components/core/test_cond.py b/tests/units/components/core/test_cond.py index 8355aeb4158..b1f7d82d56c 100644 --- a/tests/units/components/core/test_cond.py +++ b/tests/units/components/core/test_cond.py @@ -6,10 +6,10 @@ from reflex_components_core.core.cond import Cond, cond from reflex_components_radix.themes.typography.text import Text from reflex_core.constants.state import FIELD_MARKER +from reflex_core.utils.format import format_state_name from reflex_core.vars.base import LiteralVar, Var, computed_var from reflex.state import BaseState -from reflex.utils.format import format_state_name @pytest.fixture diff --git a/tests/units/components/core/test_match.py b/tests/units/components/core/test_match.py index 6882242934f..04bf909c1cb 100644 --- a/tests/units/components/core/test_match.py +++ b/tests/units/components/core/test_match.py @@ -3,12 +3,12 @@ import pytest from reflex_components_core.core.match import Match from reflex_core.constants.state import FIELD_MARKER +from reflex_core.utils.exceptions import MatchTypeError from reflex_core.vars.base import Var import reflex as rx from reflex.components.component import Component from reflex.state import BaseState -from reflex.utils.exceptions import MatchTypeError class MatchState(BaseState): diff --git a/tests/units/components/datadisplay/test_datatable.py b/tests/units/components/datadisplay/test_datatable.py index 59bdaf7c453..6243bd93631 100644 --- a/tests/units/components/datadisplay/test_datatable.py +++ b/tests/units/components/datadisplay/test_datatable.py @@ -2,10 +2,10 @@ import pytest from reflex_components_gridjs.datatable import DataTable from reflex_core.constants.state import FIELD_MARKER +from reflex_core.utils.exceptions import UntypedComputedVarError import reflex as rx from reflex.utils import types -from reflex.utils.exceptions import UntypedComputedVarError from reflex.utils.serializers import serialize, serialize_dataframe diff --git a/tests/units/components/test_component.py b/tests/units/components/test_component.py index 3da575f5849..66cebc78045 100644 --- a/tests/units/components/test_component.py +++ b/tests/units/components/test_component.py @@ -8,6 +8,17 @@ from reflex_components_radix.themes.layout.box import Box from reflex_core.constants import EventTriggers from reflex_core.constants.state import FIELD_MARKER +from reflex_core.utils.exceptions import ( + ChildrenTypeError, + EventFnArgMismatchError, + EventHandlerArgTypeMismatchError, +) +from reflex_core.utils.imports import ( + ImportDict, + ImportVar, + ParsedImportDict, + parse_imports, +) from reflex_core.vars import VarData from reflex_core.vars.base import LiteralVar, Var from reflex_core.vars.object import ObjectVar @@ -33,12 +44,6 @@ from reflex.state import BaseState from reflex.style import Style from reflex.utils import imports -from reflex.utils.exceptions import ( - ChildrenTypeError, - EventFnArgMismatchError, - EventHandlerArgTypeMismatchError, -) -from reflex.utils.imports import ImportDict, ImportVar, ParsedImportDict, parse_imports class TestState(BaseState): diff --git a/tests/units/components/test_component_state.py b/tests/units/components/test_component_state.py index 333f9f5603d..3dcd7a01694 100644 --- a/tests/units/components/test_component_state.py +++ b/tests/units/components/test_component_state.py @@ -2,9 +2,9 @@ import pytest from reflex_components_core.base.bare import Bare +from reflex_core.utils.exceptions import ReflexRuntimeError import reflex as rx -from reflex.utils.exceptions import ReflexRuntimeError def test_component_state(): diff --git a/tests/units/components/test_props.py b/tests/units/components/test_props.py index cbe7d3e52b8..a81b4156106 100644 --- a/tests/units/components/test_props.py +++ b/tests/units/components/test_props.py @@ -1,6 +1,7 @@ from __future__ import annotations import pytest +from reflex_core.utils.exceptions import InvalidPropValueError from reflex.components.props import NoExtrasAllowedProps, PropsBase from reflex.event import ( @@ -11,7 +12,6 @@ passthrough_event_spec, ) from reflex.state import State -from reflex.utils.exceptions import InvalidPropValueError class PropA(NoExtrasAllowedProps): diff --git a/tests/units/experimental/test_memo.py b/tests/units/experimental/test_memo.py index 3abfe75ae8f..cb68633769d 100644 --- a/tests/units/experimental/test_memo.py +++ b/tests/units/experimental/test_memo.py @@ -6,6 +6,7 @@ from typing import Any import pytest +from reflex_core.utils.imports import ImportVar from reflex_core.vars import VarData from reflex_core.vars.base import Var from reflex_core.vars.function import FunctionVar @@ -21,7 +22,6 @@ ExperimentalMemoFunctionDefinition, ) from reflex.style import Style -from reflex.utils.imports import ImportVar @pytest.fixture(autouse=True) diff --git a/tests/units/plugins/test_sitemap.py b/tests/units/plugins/test_sitemap.py index 4ce8ba29cbd..72df63ce113 100644 --- a/tests/units/plugins/test_sitemap.py +++ b/tests/units/plugins/test_sitemap.py @@ -71,7 +71,7 @@ def test_generate_xml_multiple_links_all_fields(): @patch("reflex_core.config.get_config") -@patch("reflex.utils.console.warn") +@patch("reflex_core.utils.console.warn") def test_generate_links_for_sitemap_static_routes( mock_warn: MagicMock, mock_get_config: MagicMock ): @@ -131,7 +131,7 @@ def mock_component(): @patch("reflex_core.config.get_config") -@patch("reflex.utils.console.warn") +@patch("reflex_core.utils.console.warn") def test_generate_links_for_sitemap_dynamic_routes( mock_warn: MagicMock, mock_get_config: MagicMock ): @@ -200,7 +200,7 @@ def mock_component(): @patch("reflex_core.config.get_config") -@patch("reflex.utils.console.warn") +@patch("reflex_core.utils.console.warn") def test_generate_links_for_sitemap_404_route( mock_warn: MagicMock, mock_get_config: MagicMock ): diff --git a/tests/units/test_environment.py b/tests/units/test_environment.py index bf533a6f37d..ab1b805a4d1 100644 --- a/tests/units/test_environment.py +++ b/tests/units/test_environment.py @@ -33,8 +33,7 @@ interpret_plugin_env, ) from reflex_core.plugins import Plugin - -from reflex.utils.exceptions import EnvironmentVarValueError +from reflex_core.utils.exceptions import EnvironmentVarValueError class TestPlugin(Plugin): @@ -510,7 +509,7 @@ def test_paths_from_environment_not_set(self): result = _paths_from_environment() assert result == [] - @patch("reflex.environment.load_dotenv") + @patch("reflex_core.environment.load_dotenv") def test_load_dotenv_from_files_with_dotenv(self, mock_load_dotenv): """Test _load_dotenv_from_files when dotenv is available. @@ -529,8 +528,8 @@ def test_load_dotenv_from_files_with_dotenv(self, mock_load_dotenv): mock_load_dotenv.assert_any_call(file1, override=True) mock_load_dotenv.assert_any_call(file2, override=True) - @patch("reflex.environment.load_dotenv", None) - @patch("reflex.utils.console") + @patch("reflex_core.environment.load_dotenv", None) + @patch("reflex_core.utils.console") def test_load_dotenv_from_files_without_dotenv(self, mock_console): """Test _load_dotenv_from_files when dotenv is not available. @@ -549,7 +548,7 @@ def test_load_dotenv_from_files_empty_list(self): # Should not raise any errors _load_dotenv_from_files([]) - @patch("reflex.environment.load_dotenv") + @patch("reflex_core.environment.load_dotenv") def test_load_dotenv_from_files_nonexistent_file(self, mock_load_dotenv): """Test _load_dotenv_from_files with non-existent file. diff --git a/tests/units/test_state.py b/tests/units/test_state.py index 18ee99ceeed..376d4a47f0c 100644 --- a/tests/units/test_state.py +++ b/tests/units/test_state.py @@ -24,6 +24,15 @@ from reflex_core import constants from reflex_core.constants import CompileVars, RouteVar, SocketEvent from reflex_core.constants.state import FIELD_MARKER +from reflex_core.utils.exceptions import ( + InvalidLockWarningThresholdError, + LockExpiredError, + ReflexRuntimeError, + SetUndefinedStateVarError, + StateSerializationError, + UnretrievableVarValueError, +) +from reflex_core.utils.format import json_dumps from reflex_core.vars.base import Field, Var, computed_var, field import reflex as rx @@ -49,15 +58,6 @@ ) from reflex.testing import chdir from reflex.utils import format, prerequisites, types -from reflex.utils.exceptions import ( - InvalidLockWarningThresholdError, - LockExpiredError, - ReflexRuntimeError, - SetUndefinedStateVarError, - StateSerializationError, - UnretrievableVarValueError, -) -from reflex.utils.format import json_dumps from reflex.utils.token_manager import SocketRecord from tests.units.mock_redis import mock_redis diff --git a/tests/units/test_style.py b/tests/units/test_style.py index 193b7c74339..10b4541b28c 100644 --- a/tests/units/test_style.py +++ b/tests/units/test_style.py @@ -3,6 +3,7 @@ from typing import Any import pytest +from reflex_core.utils.exceptions import ReflexError from reflex_core.vars import VarData from reflex_core.vars.base import LiteralVar, Var @@ -10,7 +11,6 @@ from reflex import style from reflex.components.component import evaluate_style_namespaces from reflex.style import Style -from reflex.utils.exceptions import ReflexError style_var = rx.Var.create({"height": "42px"}) diff --git a/tests/units/test_var.py b/tests/units/test_var.py index 74961ce7bf8..97635d39301 100644 --- a/tests/units/test_var.py +++ b/tests/units/test_var.py @@ -11,6 +11,11 @@ from pytest_mock import MockerFixture from reflex_core.constants.base import REFLEX_VAR_CLOSING_TAG, REFLEX_VAR_OPENING_TAG from reflex_core.constants.state import FIELD_MARKER +from reflex_core.utils.exceptions import ( + PrimitiveUnserializableToJSONError, + UntypedComputedVarError, +) +from reflex_core.utils.imports import ImportVar from reflex_core.vars import VarData from reflex_core.vars.base import ( ComputedVar, @@ -37,11 +42,6 @@ import reflex as rx from reflex.environment import PerformanceMode from reflex.state import BaseState -from reflex.utils.exceptions import ( - PrimitiveUnserializableToJSONError, - UntypedComputedVarError, -) -from reflex.utils.imports import ImportVar from reflex.utils.types import get_default_value_for_type test_vars = [ diff --git a/tests/units/utils/test_imports.py b/tests/units/utils/test_imports.py index 4166a62e8f8..a232f489ef8 100644 --- a/tests/units/utils/test_imports.py +++ b/tests/units/utils/test_imports.py @@ -1,6 +1,5 @@ import pytest - -from reflex.utils.imports import ( +from reflex_core.utils.imports import ( ImportDict, ImportVar, ParsedImportDict, diff --git a/tests/units/utils/test_serializers.py b/tests/units/utils/test_serializers.py index acf87261b23..fb7418b8379 100644 --- a/tests/units/utils/test_serializers.py +++ b/tests/units/utils/test_serializers.py @@ -8,10 +8,10 @@ import pytest from pydantic import BaseModel as Base from reflex_components_core.core.colors import Color +from reflex_core.utils.format import json_dumps from reflex_core.vars.base import LiteralVar from reflex.utils import serializers -from reflex.utils.format import json_dumps pytest.importorskip("pydantic") diff --git a/tests/units/utils/test_utils.py b/tests/units/utils/test_utils.py index 83b0fba5858..554eeee7215 100644 --- a/tests/units/utils/test_utils.py +++ b/tests/units/utils/test_utils.py @@ -9,6 +9,7 @@ from packaging import version from pytest_mock import MockerFixture from reflex_core import constants +from reflex_core.utils.exceptions import ReflexError, SystemPackageMissingError from reflex_core.vars.base import Var from reflex.environment import environment @@ -16,7 +17,6 @@ from reflex.state import BaseState from reflex.utils import exec as utils_exec from reflex.utils import frontend_skeleton, js_runtimes, prerequisites, templates, types -from reflex.utils.exceptions import ReflexError, SystemPackageMissingError class ExampleTestState(BaseState): diff --git a/tests/units/vars/test_dep_tracking.py b/tests/units/vars/test_dep_tracking.py index 0ab3d08677c..87dc848d37b 100644 --- a/tests/units/vars/test_dep_tracking.py +++ b/tests/units/vars/test_dep_tracking.py @@ -5,6 +5,7 @@ import sys import pytest +from reflex_core.utils.exceptions import VarValueError from reflex_core.vars.dep_tracking import ( DependencyTracker, UntrackedLocalVarError, @@ -14,7 +15,6 @@ import reflex as rx import tests.units.states.upload as tus_upload from reflex.state import State -from reflex.utils.exceptions import VarValueError class DependencyTestState(State): From 25820c87738f724b511e4cf05453c4c2edb5def4 Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Thu, 26 Mar 2026 15:54:56 -0700 Subject: [PATCH 074/113] update component, event, and style imports --- .../src/reflex_components_code/code.py | 7 +- .../shiki_code_block.py | 9 +- .../reflex_components_core/base/app_wrap.py | 2 +- .../src/reflex_components_core/base/bare.py | 7 +- .../reflex_components_core/base/document.py | 2 +- .../base/error_boundary.py | 4 +- .../reflex_components_core/base/fragment.py | 2 +- .../src/reflex_components_core/base/link.py | 2 +- .../src/reflex_components_core/base/meta.py | 2 +- .../base/strict_mode.py | 2 +- .../src/reflex_components_core/core/banner.py | 2 +- .../reflex_components_core/core/clipboard.py | 6 +- .../src/reflex_components_core/core/cond.py | 6 +- .../reflex_components_core/core/debounce.py | 5 +- .../reflex_components_core/core/foreach.py | 4 +- .../src/reflex_components_core/core/helmet.py | 2 +- .../src/reflex_components_core/core/html.py | 2 +- .../src/reflex_components_core/core/match.py | 13 +- .../src/reflex_components_core/core/sticky.py | 5 +- .../src/reflex_components_core/core/upload.py | 30 +-- .../core/window_events.py | 4 +- .../src/reflex_components_core/el/element.py | 2 +- .../el/elements/base.py | 2 +- .../el/elements/forms.py | 16 +- .../el/elements/inline.py | 3 +- .../el/elements/media.py | 2 +- .../el/elements/metadata.py | 2 +- .../el/elements/other.py | 3 +- .../el/elements/scripts.py | 2 +- .../el/elements/tables.py | 3 +- .../el/elements/typography.py | 3 +- .../dataeditor.py | 7 +- .../src/reflex_components_gridjs/datatable.py | 4 +- .../src/reflex_components_lucide/icon.py | 2 +- .../reflex_components_markdown/markdown.py | 14 +- .../src/reflex_components_moment/moment.py | 5 +- .../src/reflex_components_plotly/__init__.py | 2 +- .../src/reflex_components_plotly/plotly.py | 4 +- .../primitives/accordion.py | 6 +- .../primitives/base.py | 4 +- .../primitives/dialog.py | 5 +- .../primitives/drawer.py | 4 +- .../primitives/form.py | 4 +- .../primitives/progress.py | 2 +- .../primitives/slider.py | 4 +- .../reflex_components_radix/themes/base.py | 6 +- .../themes/color_mode.py | 10 +- .../themes/components/alert_dialog.py | 4 +- .../themes/components/aspect_ratio.py | 2 +- .../themes/components/avatar.py | 2 +- .../themes/components/badge.py | 2 +- .../themes/components/button.py | 2 +- .../themes/components/callout.py | 2 +- .../themes/components/card.py | 2 +- .../themes/components/checkbox.py | 4 +- .../themes/components/checkbox_cards.py | 2 +- .../themes/components/checkbox_group.py | 2 +- .../themes/components/context_menu.py | 4 +- .../themes/components/data_list.py | 2 +- .../themes/components/dialog.py | 4 +- .../themes/components/dropdown_menu.py | 4 +- .../themes/components/hover_card.py | 4 +- .../themes/components/icon_button.py | 4 +- .../themes/components/inset.py | 2 +- .../themes/components/popover.py | 4 +- .../themes/components/progress.py | 4 +- .../themes/components/radio.py | 2 +- .../themes/components/radio_cards.py | 4 +- .../themes/components/radio_group.py | 4 +- .../themes/components/scroll_area.py | 2 +- .../themes/components/segmented_control.py | 4 +- .../themes/components/select.py | 4 +- .../themes/components/separator.py | 2 +- .../themes/components/skeleton.py | 2 +- .../themes/components/slider.py | 6 +- .../themes/components/spinner.py | 2 +- .../themes/components/switch.py | 4 +- .../themes/components/table.py | 2 +- .../themes/components/tabs.py | 4 +- .../themes/components/text_area.py | 2 +- .../themes/components/text_field.py | 6 +- .../themes/components/tooltip.py | 4 +- .../themes/layout/base.py | 2 +- .../themes/layout/container.py | 4 +- .../themes/layout/flex.py | 2 +- .../themes/layout/grid.py | 2 +- .../themes/layout/list.py | 2 +- .../themes/layout/section.py | 2 +- .../themes/layout/stack.py | 2 +- .../themes/typography/blockquote.py | 2 +- .../themes/typography/code.py | 2 +- .../themes/typography/heading.py | 2 +- .../themes/typography/link.py | 2 +- .../themes/typography/text.py | 2 +- .../react_player.py | 4 +- .../src/reflex_components_react_router/dom.py | 3 +- .../reflex_components_recharts/cartesian.py | 5 +- .../src/reflex_components_recharts/charts.py | 4 +- .../src/reflex_components_recharts/general.py | 5 +- .../src/reflex_components_recharts/polar.py | 5 +- .../reflex_components_recharts/recharts.py | 2 +- .../src/reflex_components_sonner/toast.py | 8 +- .../src/reflex_docgen/_component.py | 4 +- pyi_hashes.json | 184 +++++++++--------- reflex/_upload.py | 12 +- reflex/app.py | 20 +- reflex/app_mixins/middleware.py | 3 +- reflex/compiler/compiler.py | 18 +- reflex/compiler/utils.py | 4 +- reflex/components/__init__.py | 2 +- reflex/components/props.py | 8 +- reflex/custom_components/custom_components.py | 4 +- reflex/experimental/client_state.py | 3 +- reflex/experimental/memo.py | 4 +- reflex/istate/manager/__init__.py | 2 +- reflex/istate/proxy.py | 2 +- reflex/istate/shared.py | 2 +- reflex/middleware/hydrate_middleware.py | 2 +- reflex/middleware/middleware.py | 3 +- reflex/page.py | 2 +- reflex/state.py | 22 +-- reflex/testing.py | 4 +- reflex/utils/codespaces.py | 5 +- reflex/utils/exec.py | 2 +- reflex/utils/js_runtimes.py | 2 +- reflex/utils/net.py | 4 +- reflex/utils/prerequisites.py | 2 +- reflex/utils/registry.py | 2 +- reflex/utils/telemetry.py | 2 +- tests/benchmarks/test_compilation.py | 2 +- tests/benchmarks/test_evaluate.py | 3 +- .../tests_playwright/test_appearance.py | 12 +- tests/units/components/core/test_foreach.py | 2 +- tests/units/components/core/test_match.py | 2 +- tests/units/components/core/test_upload.py | 2 +- .../components/datadisplay/test_shiki_code.py | 3 +- tests/units/components/forms/test_form.py | 3 +- .../components/markdown/test_markdown.py | 3 +- .../components/radix/test_icon_button.py | 3 +- tests/units/components/test_component.py | 34 ++-- .../test_component_future_annotations.py | 4 +- tests/units/components/test_props.py | 8 +- tests/units/components/test_tag.py | 3 +- tests/units/conftest.py | 4 +- .../units/docgen/test_class_and_component.py | 15 +- tests/units/experimental/test_memo.py | 4 +- tests/units/middleware/conftest.py | 2 +- tests/units/test_app.py | 6 +- tests/units/test_attribute_access_type.py | 2 +- tests/units/test_event.py | 8 +- tests/units/test_prerequisites.py | 2 +- tests/units/test_state.py | 2 +- tests/units/test_style.py | 4 +- tests/units/test_testing.py | 2 +- tests/units/test_var.py | 2 +- tests/units/utils/test_format.py | 16 +- tests/units/utils/test_utils.py | 2 +- tests/units/vars/test_object.py | 2 +- 158 files changed, 439 insertions(+), 453 deletions(-) diff --git a/packages/reflex-components-code/src/reflex_components_code/code.py b/packages/reflex-components-code/src/reflex_components_code/code.py index 72aabfd8c79..9dbbcb9a65f 100644 --- a/packages/reflex-components-code/src/reflex_components_code/code.py +++ b/packages/reflex-components-code/src/reflex_components_code/code.py @@ -10,15 +10,14 @@ from reflex_components_lucide.icon import Icon from reflex_components_radix.themes.components.button import Button from reflex_components_radix.themes.layout.box import Box +from reflex_core.components.component import Component, ComponentNamespace, field from reflex_core.constants.colors import Color +from reflex_core.event import set_clipboard +from reflex_core.style import Style from reflex_core.utils import format from reflex_core.utils.imports import ImportVar from reflex_core.vars.base import LiteralVar, Var, VarData -from reflex.components.component import Component, ComponentNamespace, field -from reflex.event import set_clipboard -from reflex.style import Style - LiteralCodeLanguage = Literal[ "abap", "abnf", diff --git a/packages/reflex-components-code/src/reflex_components_code/shiki_code_block.py b/packages/reflex-components-code/src/reflex_components_code/shiki_code_block.py index 05ccffe778f..52e79d6f242 100644 --- a/packages/reflex-components-code/src/reflex_components_code/shiki_code_block.py +++ b/packages/reflex-components-code/src/reflex_components_code/shiki_code_block.py @@ -14,17 +14,16 @@ from reflex_components_core.el.elements.forms import Button from reflex_components_lucide.icon import Icon from reflex_components_radix.themes.layout.box import Box +from reflex_core.components.component import Component, ComponentNamespace, field +from reflex_core.components.props import NoExtrasAllowedProps +from reflex_core.event import run_script, set_clipboard +from reflex_core.style import Style from reflex_core.utils.exceptions import VarTypeError from reflex_core.utils.imports import ImportVar from reflex_core.vars.base import LiteralVar, Var from reflex_core.vars.function import FunctionStringVar from reflex_core.vars.sequence import StringVar, string_replace_operation -from reflex.components.component import Component, ComponentNamespace, field -from reflex.components.props import NoExtrasAllowedProps -from reflex.event import run_script, set_clipboard -from reflex.style import Style - def copy_script() -> Any: """Copy script for the code block and modify the child SVG element. diff --git a/packages/reflex-components-core/src/reflex_components_core/base/app_wrap.py b/packages/reflex-components-core/src/reflex_components_core/base/app_wrap.py index 7736b49399e..17a6a86d7f3 100644 --- a/packages/reflex-components-core/src/reflex_components_core/base/app_wrap.py +++ b/packages/reflex-components-core/src/reflex_components_core/base/app_wrap.py @@ -1,8 +1,8 @@ """Top-level component that wraps the entire app.""" +from reflex_core.components.component import Component from reflex_core.vars.base import Var -from reflex.components.component import Component from reflex_components_core.base.fragment import Fragment diff --git a/packages/reflex-components-core/src/reflex_components_core/base/bare.py b/packages/reflex-components-core/src/reflex_components_core/base/bare.py index 15ebd5bcb6e..50da3a6b775 100644 --- a/packages/reflex-components-core/src/reflex_components_core/base/bare.py +++ b/packages/reflex-components-core/src/reflex_components_core/base/bare.py @@ -5,6 +5,9 @@ from collections.abc import Iterator, Sequence from typing import Any +from reflex_core.components.component import BaseComponent, Component, ComponentStyle +from reflex_core.components.tags import Tag +from reflex_core.components.tags.tagless import Tagless from reflex_core.environment import PerformanceMode, environment from reflex_core.utils import console from reflex_core.utils.decorator import once @@ -13,10 +16,6 @@ from reflex_core.vars.base import GLOBAL_CACHE, VarData from reflex_core.vars.sequence import LiteralStringVar -from reflex.components.component import BaseComponent, Component, ComponentStyle -from reflex.components.tags import Tag -from reflex.components.tags.tagless import Tagless - @once def get_performance_mode(): diff --git a/packages/reflex-components-core/src/reflex_components_core/base/document.py b/packages/reflex-components-core/src/reflex_components_core/base/document.py index 93979c5451b..9157a805a76 100644 --- a/packages/reflex-components-core/src/reflex_components_core/base/document.py +++ b/packages/reflex-components-core/src/reflex_components_core/base/document.py @@ -1,6 +1,6 @@ """Document components.""" -from reflex.components.component import Component +from reflex_core.components.component import Component class ReactRouterLib(Component): diff --git a/packages/reflex-components-core/src/reflex_components_core/base/error_boundary.py b/packages/reflex-components-core/src/reflex_components_core/base/error_boundary.py index 924a9cc80f6..497aa9f3641 100644 --- a/packages/reflex-components-core/src/reflex_components_core/base/error_boundary.py +++ b/packages/reflex-components-core/src/reflex_components_core/base/error_boundary.py @@ -2,12 +2,12 @@ from __future__ import annotations +from reflex_core.components.component import Component, field +from reflex_core.event import EventHandler, set_clipboard from reflex_core.vars.base import Var from reflex_core.vars.function import ArgsFunctionOperation from reflex_core.vars.object import ObjectVar -from reflex.components.component import Component, field -from reflex.event import EventHandler, set_clipboard from reflex.state import FrontendEventExceptionState from reflex_components_core.datadisplay.logo import svg_logo from reflex_components_core.el import a, button, div, h2, hr, p, pre, svg diff --git a/packages/reflex-components-core/src/reflex_components_core/base/fragment.py b/packages/reflex-components-core/src/reflex_components_core/base/fragment.py index 84ec86145e0..0d6697a776f 100644 --- a/packages/reflex-components-core/src/reflex_components_core/base/fragment.py +++ b/packages/reflex-components-core/src/reflex_components_core/base/fragment.py @@ -1,6 +1,6 @@ """React fragments to enable bare returns of component trees from functions.""" -from reflex.components.component import Component +from reflex_core.components.component import Component class Fragment(Component): diff --git a/packages/reflex-components-core/src/reflex_components_core/base/link.py b/packages/reflex-components-core/src/reflex_components_core/base/link.py index 2e44a2f401e..bdbc454ad9c 100644 --- a/packages/reflex-components-core/src/reflex_components_core/base/link.py +++ b/packages/reflex-components-core/src/reflex_components_core/base/link.py @@ -1,8 +1,8 @@ """Display the title of the current page.""" +from reflex_core.components.component import field from reflex_core.vars.base import Var -from reflex.components.component import field from reflex_components_core.el.elements.base import BaseHTML diff --git a/packages/reflex-components-core/src/reflex_components_core/base/meta.py b/packages/reflex-components-core/src/reflex_components_core/base/meta.py index ded1ab0a4d4..42ce2990089 100644 --- a/packages/reflex-components-core/src/reflex_components_core/base/meta.py +++ b/packages/reflex-components-core/src/reflex_components_core/base/meta.py @@ -2,9 +2,9 @@ from __future__ import annotations +from reflex_core.components.component import field from reflex_core.vars.base import Var -from reflex.components.component import field from reflex_components_core.base.bare import Bare from reflex_components_core.el import elements from reflex_components_core.el.elements.metadata import ( diff --git a/packages/reflex-components-core/src/reflex_components_core/base/strict_mode.py b/packages/reflex-components-core/src/reflex_components_core/base/strict_mode.py index 46b01ad872c..2ce993cdc53 100644 --- a/packages/reflex-components-core/src/reflex_components_core/base/strict_mode.py +++ b/packages/reflex-components-core/src/reflex_components_core/base/strict_mode.py @@ -1,6 +1,6 @@ """Module for the StrictMode component.""" -from reflex.components.component import Component +from reflex_core.components.component import Component class StrictMode(Component): diff --git a/packages/reflex-components-core/src/reflex_components_core/core/banner.py b/packages/reflex-components-core/src/reflex_components_core/core/banner.py index 3ad9559583a..3699bc6146f 100644 --- a/packages/reflex-components-core/src/reflex_components_core/core/banner.py +++ b/packages/reflex-components-core/src/reflex_components_core/core/banner.py @@ -5,6 +5,7 @@ from reflex_components_lucide.icon import Icon from reflex_components_sonner.toast import ToastProps, toast_ref from reflex_core import constants +from reflex_core.components.component import Component from reflex_core.constants import Dirs, Hooks, Imports from reflex_core.constants.compiler import CompileVars from reflex_core.environment import environment @@ -15,7 +16,6 @@ from reflex_core.vars.number import BooleanVar from reflex_core.vars.sequence import LiteralArrayVar -from reflex.components.component import Component from reflex_components_core.base.fragment import Fragment from reflex_components_core.core.cond import cond from reflex_components_core.el.elements.inline import Span diff --git a/packages/reflex-components-core/src/reflex_components_core/core/clipboard.py b/packages/reflex-components-core/src/reflex_components_core/core/clipboard.py index baa60d6555b..1a64a2ba13b 100644 --- a/packages/reflex-components-core/src/reflex_components_core/core/clipboard.py +++ b/packages/reflex-components-core/src/reflex_components_core/core/clipboard.py @@ -4,15 +4,15 @@ from collections.abc import Sequence +from reflex_core.components.component import field +from reflex_core.components.tags.tag import Tag from reflex_core.constants.compiler import Hooks +from reflex_core.event import EventChain, EventHandler, passthrough_event_spec from reflex_core.utils.format import format_prop, wrap from reflex_core.utils.imports import ImportVar from reflex_core.vars import get_unique_variable_name from reflex_core.vars.base import Var, VarData -from reflex.components.component import field -from reflex.components.tags.tag import Tag -from reflex.event import EventChain, EventHandler, passthrough_event_spec from reflex_components_core.base.fragment import Fragment diff --git a/packages/reflex-components-core/src/reflex_components_core/core/cond.py b/packages/reflex-components-core/src/reflex_components_core/core/cond.py index 99c4f0abd81..9d8deb53fde 100644 --- a/packages/reflex-components-core/src/reflex_components_core/core/cond.py +++ b/packages/reflex-components-core/src/reflex_components_core/core/cond.py @@ -4,16 +4,16 @@ from typing import Any, overload +from reflex_core.components.component import BaseComponent, Component, field +from reflex_core.components.tags import CondTag, Tag from reflex_core.constants import Dirs +from reflex_core.style import LIGHT_COLOR_MODE, resolved_color_mode from reflex_core.utils import types from reflex_core.utils.imports import ImportDict, ImportVar from reflex_core.vars import VarData from reflex_core.vars.base import LiteralVar, Var from reflex_core.vars.number import ternary_operation -from reflex.components.component import BaseComponent, Component, field -from reflex.components.tags import CondTag, Tag -from reflex.style import LIGHT_COLOR_MODE, resolved_color_mode from reflex_components_core.base.fragment import Fragment _IS_TRUE_IMPORT: ImportDict = { diff --git a/packages/reflex-components-core/src/reflex_components_core/core/debounce.py b/packages/reflex-components-core/src/reflex_components_core/core/debounce.py index 6d6c09b8d58..fc814e64c32 100644 --- a/packages/reflex-components-core/src/reflex_components_core/core/debounce.py +++ b/packages/reflex-components-core/src/reflex_components_core/core/debounce.py @@ -4,13 +4,12 @@ from typing import Any +from reflex_core.components.component import Component, field from reflex_core.constants import EventTriggers +from reflex_core.event import EventHandler, no_args_event_spec from reflex_core.vars import VarData from reflex_core.vars.base import Var -from reflex.components.component import Component, field -from reflex.event import EventHandler, no_args_event_spec - DEFAULT_DEBOUNCE_TIMEOUT = 300 diff --git a/packages/reflex-components-core/src/reflex_components_core/core/foreach.py b/packages/reflex-components-core/src/reflex_components_core/core/foreach.py index 7f74ef74671..10e80ce4255 100644 --- a/packages/reflex-components-core/src/reflex_components_core/core/foreach.py +++ b/packages/reflex-components-core/src/reflex_components_core/core/foreach.py @@ -8,14 +8,14 @@ from hashlib import md5 from typing import Any +from reflex_core.components.component import Component, field +from reflex_core.components.tags import IterTag from reflex_core.constants import MemoizationMode from reflex_core.constants.state import FIELD_MARKER from reflex_core.utils import types from reflex_core.utils.exceptions import UntypedVarError from reflex_core.vars.base import LiteralVar, Var -from reflex.components.component import Component, field -from reflex.components.tags import IterTag from reflex.state import ComponentState from reflex_components_core.base.fragment import Fragment from reflex_components_core.core.cond import cond diff --git a/packages/reflex-components-core/src/reflex_components_core/core/helmet.py b/packages/reflex-components-core/src/reflex_components_core/core/helmet.py index e4d1730759e..823003808f5 100644 --- a/packages/reflex-components-core/src/reflex_components_core/core/helmet.py +++ b/packages/reflex-components-core/src/reflex_components_core/core/helmet.py @@ -1,6 +1,6 @@ """Helmet component module.""" -from reflex.components.component import Component +from reflex_core.components.component import Component class Helmet(Component): diff --git a/packages/reflex-components-core/src/reflex_components_core/core/html.py b/packages/reflex-components-core/src/reflex_components_core/core/html.py index c842dc0cf2c..2e989cc1782 100644 --- a/packages/reflex-components-core/src/reflex_components_core/core/html.py +++ b/packages/reflex-components-core/src/reflex_components_core/core/html.py @@ -1,8 +1,8 @@ """A html component.""" +from reflex_core.components.component import field from reflex_core.vars.base import Var -from reflex.components.component import field from reflex_components_core.el.elements.typography import Div diff --git a/packages/reflex-components-core/src/reflex_components_core/core/match.py b/packages/reflex-components-core/src/reflex_components_core/core/match.py index 79c82c4f8a9..b7285ebb0b3 100644 --- a/packages/reflex-components-core/src/reflex_components_core/core/match.py +++ b/packages/reflex-components-core/src/reflex_components_core/core/match.py @@ -3,16 +3,21 @@ import textwrap from typing import Any, cast +from reflex_core.components.component import ( + BaseComponent, + Component, + MemoizationLeaf, + field, +) +from reflex_core.components.tags import Tag +from reflex_core.components.tags.match_tag import MatchTag +from reflex_core.style import Style from reflex_core.utils import format from reflex_core.utils.exceptions import MatchTypeError from reflex_core.utils.imports import ImportDict from reflex_core.vars import VarData from reflex_core.vars.base import LiteralVar, Var -from reflex.components.component import BaseComponent, Component, MemoizationLeaf, field -from reflex.components.tags import Tag -from reflex.components.tags.match_tag import MatchTag -from reflex.style import Style from reflex_components_core.base import Fragment diff --git a/packages/reflex-components-core/src/reflex_components_core/core/sticky.py b/packages/reflex-components-core/src/reflex_components_core/core/sticky.py index 2de1282271b..864064f4330 100644 --- a/packages/reflex-components-core/src/reflex_components_core/core/sticky.py +++ b/packages/reflex-components-core/src/reflex_components_core/core/sticky.py @@ -1,7 +1,8 @@ """Components for displaying the Reflex sticky logo.""" -from reflex.components.component import ComponentNamespace -from reflex.style import Style +from reflex_core.components.component import ComponentNamespace +from reflex_core.style import Style + from reflex_components_core.core.colors import color from reflex_components_core.core.cond import color_mode_cond from reflex_components_core.core.responsive import desktop_only diff --git a/packages/reflex-components-core/src/reflex_components_core/core/upload.py b/packages/reflex-components-core/src/reflex_components_core/core/upload.py index a3caf3583f1..71a3d2031ef 100644 --- a/packages/reflex-components-core/src/reflex_components_core/core/upload.py +++ b/packages/reflex-components-core/src/reflex_components_core/core/upload.py @@ -7,26 +7,17 @@ from typing import Any, ClassVar from reflex_components_sonner.toast import toast -from reflex_core.constants import Dirs -from reflex_core.constants.compiler import Hooks, Imports -from reflex_core.environment import environment -from reflex_core.utils import format -from reflex_core.utils.imports import ImportVar -from reflex_core.vars import VarData -from reflex_core.vars.base import Var, get_unique_variable_name -from reflex_core.vars.function import FunctionVar -from reflex_core.vars.object import ObjectVar -from reflex_core.vars.sequence import ArrayVar, LiteralStringVar - -from reflex._upload import UploadChunkIterator, UploadFile -from reflex.components.component import ( +from reflex_core.components.component import ( Component, ComponentNamespace, MemoizationLeaf, StatefulComponent, field, ) -from reflex.event import ( +from reflex_core.constants import Dirs +from reflex_core.constants.compiler import Hooks, Imports +from reflex_core.environment import environment +from reflex_core.event import ( CallableEventSpec, EventChain, EventHandler, @@ -38,7 +29,16 @@ run_script, upload_files, ) -from reflex.style import Style +from reflex_core.style import Style +from reflex_core.utils import format +from reflex_core.utils.imports import ImportVar +from reflex_core.vars import VarData +from reflex_core.vars.base import Var, get_unique_variable_name +from reflex_core.vars.function import FunctionVar +from reflex_core.vars.object import ObjectVar +from reflex_core.vars.sequence import ArrayVar, LiteralStringVar + +from reflex._upload import UploadChunkIterator, UploadFile from reflex_components_core.base.fragment import Fragment from reflex_components_core.core.cond import cond from reflex_components_core.el.elements.forms import Input diff --git a/packages/reflex-components-core/src/reflex_components_core/core/window_events.py b/packages/reflex-components-core/src/reflex_components_core/core/window_events.py index 0265891ee93..8fec63ddbe1 100644 --- a/packages/reflex-components-core/src/reflex_components_core/core/window_events.py +++ b/packages/reflex-components-core/src/reflex_components_core/core/window_events.py @@ -4,13 +4,13 @@ from typing import Any, cast +from reflex_core.components.component import StatefulComponent, field from reflex_core.constants.compiler import Hooks +from reflex_core.event import key_event, no_args_event_spec from reflex_core.vars.base import Var, VarData from reflex_core.vars.object import ObjectVar import reflex as rx -from reflex.components.component import StatefulComponent, field -from reflex.event import key_event, no_args_event_spec from reflex_components_core.base.fragment import Fragment diff --git a/packages/reflex-components-core/src/reflex_components_core/el/element.py b/packages/reflex-components-core/src/reflex_components_core/el/element.py index 7232ee38f17..9903284446b 100644 --- a/packages/reflex-components-core/src/reflex_components_core/el/element.py +++ b/packages/reflex-components-core/src/reflex_components_core/el/element.py @@ -2,7 +2,7 @@ from typing import ClassVar -from reflex.components.component import Component +from reflex_core.components.component import Component class Element(Component): diff --git a/packages/reflex-components-core/src/reflex_components_core/el/elements/base.py b/packages/reflex-components-core/src/reflex_components_core/el/elements/base.py index f4b7941b041..296a7030e83 100644 --- a/packages/reflex-components-core/src/reflex_components_core/el/elements/base.py +++ b/packages/reflex-components-core/src/reflex_components_core/el/elements/base.py @@ -2,9 +2,9 @@ from typing import Literal +from reflex_core.components.component import field from reflex_core.vars.base import Var -from reflex.components.component import field from reflex_components_core.el.element import Element AutoCapitalize = Literal["off", "none", "on", "sentences", "words", "characters"] diff --git a/packages/reflex-components-core/src/reflex_components_core/el/elements/forms.py b/packages/reflex-components-core/src/reflex_components_core/el/elements/forms.py index 1be189fc26b..0c4ab4feeb3 100644 --- a/packages/reflex-components-core/src/reflex_components_core/el/elements/forms.py +++ b/packages/reflex-components-core/src/reflex_components_core/el/elements/forms.py @@ -6,15 +6,10 @@ from hashlib import md5 from typing import Any, ClassVar, Literal +from reflex_core.components.component import field +from reflex_core.components.tags.tag import Tag from reflex_core.constants import Dirs, EventTriggers -from reflex_core.utils.imports import ImportDict -from reflex_core.vars import VarData -from reflex_core.vars.base import LiteralVar, Var -from reflex_core.vars.number import ternary_operation - -from reflex.components.component import field -from reflex.components.tags.tag import Tag -from reflex.event import ( +from reflex_core.event import ( FORM_DATA, EventChain, EventHandler, @@ -27,6 +22,11 @@ on_submit_string_event, prevent_default, ) +from reflex_core.utils.imports import ImportDict +from reflex_core.vars import VarData +from reflex_core.vars.base import LiteralVar, Var +from reflex_core.vars.number import ternary_operation + from reflex_components_core.el.element import Element from .base import BaseHTML diff --git a/packages/reflex-components-core/src/reflex_components_core/el/elements/inline.py b/packages/reflex-components-core/src/reflex_components_core/el/elements/inline.py index 8cff0b35045..c3b43aad317 100644 --- a/packages/reflex-components-core/src/reflex_components_core/el/elements/inline.py +++ b/packages/reflex-components-core/src/reflex_components_core/el/elements/inline.py @@ -2,10 +2,9 @@ from typing import ClassVar, Literal +from reflex_core.components.component import field from reflex_core.vars.base import Var -from reflex.components.component import field - from .base import BaseHTML ReferrerPolicy = Literal[ diff --git a/packages/reflex-components-core/src/reflex_components_core/el/elements/media.py b/packages/reflex-components-core/src/reflex_components_core/el/elements/media.py index 0ab671c2231..cbf43ee1d20 100644 --- a/packages/reflex-components-core/src/reflex_components_core/el/elements/media.py +++ b/packages/reflex-components-core/src/reflex_components_core/el/elements/media.py @@ -2,11 +2,11 @@ from typing import Any, Literal +from reflex_core.components.component import field from reflex_core.constants.colors import Color from reflex_core.vars.base import Var from reflex import Component, ComponentNamespace -from reflex.components.component import field from reflex_components_core.el.elements.inline import ReferrerPolicy from .base import BaseHTML diff --git a/packages/reflex-components-core/src/reflex_components_core/el/elements/metadata.py b/packages/reflex-components-core/src/reflex_components_core/el/elements/metadata.py index 9c12154528d..39d487fb8cb 100644 --- a/packages/reflex-components-core/src/reflex_components_core/el/elements/metadata.py +++ b/packages/reflex-components-core/src/reflex_components_core/el/elements/metadata.py @@ -1,8 +1,8 @@ """Metadata classes.""" +from reflex_core.components.component import field from reflex_core.vars.base import Var -from reflex.components.component import field from reflex_components_core.el.element import Element from reflex_components_core.el.elements.inline import ReferrerPolicy from reflex_components_core.el.elements.media import CrossOrigin diff --git a/packages/reflex-components-core/src/reflex_components_core/el/elements/other.py b/packages/reflex-components-core/src/reflex_components_core/el/elements/other.py index 23f0052a16d..1b9a5c28a31 100644 --- a/packages/reflex-components-core/src/reflex_components_core/el/elements/other.py +++ b/packages/reflex-components-core/src/reflex_components_core/el/elements/other.py @@ -1,9 +1,8 @@ """Other classes.""" +from reflex_core.components.component import field from reflex_core.vars.base import Var -from reflex.components.component import field - from .base import BaseHTML diff --git a/packages/reflex-components-core/src/reflex_components_core/el/elements/scripts.py b/packages/reflex-components-core/src/reflex_components_core/el/elements/scripts.py index 9bbdd329e12..d6cdb7966c8 100644 --- a/packages/reflex-components-core/src/reflex_components_core/el/elements/scripts.py +++ b/packages/reflex-components-core/src/reflex_components_core/el/elements/scripts.py @@ -1,8 +1,8 @@ """Scripts classes.""" +from reflex_core.components.component import field from reflex_core.vars.base import Var -from reflex.components.component import field from reflex_components_core.el.elements.inline import ReferrerPolicy from reflex_components_core.el.elements.media import CrossOrigin diff --git a/packages/reflex-components-core/src/reflex_components_core/el/elements/tables.py b/packages/reflex-components-core/src/reflex_components_core/el/elements/tables.py index 0321c5c5a17..6ef588830af 100644 --- a/packages/reflex-components-core/src/reflex_components_core/el/elements/tables.py +++ b/packages/reflex-components-core/src/reflex_components_core/el/elements/tables.py @@ -2,10 +2,9 @@ from typing import Literal +from reflex_core.components.component import field from reflex_core.vars.base import Var -from reflex.components.component import field - from .base import BaseHTML diff --git a/packages/reflex-components-core/src/reflex_components_core/el/elements/typography.py b/packages/reflex-components-core/src/reflex_components_core/el/elements/typography.py index 3e53dfcc19b..85d3807d97d 100644 --- a/packages/reflex-components-core/src/reflex_components_core/el/elements/typography.py +++ b/packages/reflex-components-core/src/reflex_components_core/el/elements/typography.py @@ -2,10 +2,9 @@ from typing import ClassVar, Literal +from reflex_core.components.component import field from reflex_core.vars.base import Var -from reflex.components.component import field - from .base import BaseHTML diff --git a/packages/reflex-components-dataeditor/src/reflex_components_dataeditor/dataeditor.py b/packages/reflex-components-dataeditor/src/reflex_components_dataeditor/dataeditor.py index b79ac338721..569263eef84 100644 --- a/packages/reflex-components-dataeditor/src/reflex_components_dataeditor/dataeditor.py +++ b/packages/reflex-components-dataeditor/src/reflex_components_dataeditor/dataeditor.py @@ -7,6 +7,9 @@ from enum import Enum from typing import Any, Literal, TypedDict +from reflex_core.components.component import Component, NoSSRComponent, field +from reflex_core.components.literals import LiteralRowMarker +from reflex_core.event import EventHandler, no_args_event_spec, passthrough_event_spec from reflex_core.utils import console, format, types from reflex_core.utils.imports import ImportDict, ImportVar from reflex_core.utils.serializers import serializer @@ -15,10 +18,6 @@ from reflex_core.vars.function import FunctionStringVar from reflex_core.vars.sequence import ArrayVar -from reflex.components.component import Component, NoSSRComponent, field -from reflex.components.literals import LiteralRowMarker -from reflex.event import EventHandler, no_args_event_spec, passthrough_event_spec - # TODO: Fix the serialization issue for custom types. class GridColumnIcons(Enum): diff --git a/packages/reflex-components-gridjs/src/reflex_components_gridjs/datatable.py b/packages/reflex-components-gridjs/src/reflex_components_gridjs/datatable.py index 5c7bc0e154a..b25cd12336a 100644 --- a/packages/reflex-components-gridjs/src/reflex_components_gridjs/datatable.py +++ b/packages/reflex-components-gridjs/src/reflex_components_gridjs/datatable.py @@ -5,12 +5,12 @@ from collections.abc import Sequence from typing import Any +from reflex_core.components.component import NoSSRComponent, field +from reflex_core.components.tags import Tag from reflex_core.utils import types from reflex_core.utils.imports import ImportDict from reflex_core.vars.base import LiteralVar, Var, is_computed_var -from reflex.components.component import NoSSRComponent, field -from reflex.components.tags import Tag from reflex.utils.serializers import serialize diff --git a/packages/reflex-components-lucide/src/reflex_components_lucide/icon.py b/packages/reflex-components-lucide/src/reflex_components_lucide/icon.py index 613d3e52657..42cc7cb8b8c 100644 --- a/packages/reflex-components-lucide/src/reflex_components_lucide/icon.py +++ b/packages/reflex-components-lucide/src/reflex_components_lucide/icon.py @@ -1,10 +1,10 @@ """Lucide Icon component.""" +from reflex_core.components.component import Component, field from reflex_core.utils.imports import ImportVar from reflex_core.vars.base import LiteralVar, Var from reflex_core.vars.sequence import LiteralStringVar, StringVar -from reflex.components.component import Component, field from reflex.utils import console, format LUCIDE_LIBRARY = "lucide-react@0.577.0" diff --git a/packages/reflex-components-markdown/src/reflex_components_markdown/markdown.py b/packages/reflex-components-markdown/src/reflex_components_markdown/markdown.py index b017b3aaca0..8ab7ab68a2d 100644 --- a/packages/reflex-components-markdown/src/reflex_components_markdown/markdown.py +++ b/packages/reflex-components-markdown/src/reflex_components_markdown/markdown.py @@ -11,19 +11,19 @@ from reflex_components_core.core.markdown_component_map import MarkdownComponentMap from reflex_components_core.el.elements.typography import Div -from reflex_core.utils.imports import ImportDict, ImportTypes, ImportVar -from reflex_core.vars.base import LiteralVar, Var, VarData -from reflex_core.vars.number import ternary_operation -from reflex_core.vars.sequence import LiteralArrayVar - -from reflex.components.component import ( +from reflex_core.components.component import ( BaseComponent, Component, ComponentNamespace, CustomComponent, field, ) -from reflex.components.tags.tag import Tag +from reflex_core.components.tags.tag import Tag +from reflex_core.utils.imports import ImportDict, ImportTypes, ImportVar +from reflex_core.vars.base import LiteralVar, Var, VarData +from reflex_core.vars.number import ternary_operation +from reflex_core.vars.sequence import LiteralArrayVar + from reflex.utils import console # Special vars used in the component map. diff --git a/packages/reflex-components-moment/src/reflex_components_moment/moment.py b/packages/reflex-components-moment/src/reflex_components_moment/moment.py index 49777a05baf..979a9ffc83e 100644 --- a/packages/reflex-components-moment/src/reflex_components_moment/moment.py +++ b/packages/reflex-components-moment/src/reflex_components_moment/moment.py @@ -5,12 +5,11 @@ import dataclasses from datetime import date, datetime, time, timedelta +from reflex_core.components.component import NoSSRComponent, field +from reflex_core.event import EventHandler, passthrough_event_spec from reflex_core.utils.imports import ImportDict from reflex_core.vars.base import LiteralVar, Var -from reflex.components.component import NoSSRComponent, field -from reflex.event import EventHandler, passthrough_event_spec - @dataclasses.dataclass(frozen=True) class MomentDelta: diff --git a/packages/reflex-components-plotly/src/reflex_components_plotly/__init__.py b/packages/reflex-components-plotly/src/reflex_components_plotly/__init__.py index 8743b31b288..e852a08c82e 100644 --- a/packages/reflex-components-plotly/src/reflex_components_plotly/__init__.py +++ b/packages/reflex-components-plotly/src/reflex_components_plotly/__init__.py @@ -1,6 +1,6 @@ """Plotly components.""" -from reflex.components.component import ComponentNamespace +from reflex_core.components.component import ComponentNamespace from .plotly import ( Plotly, diff --git a/packages/reflex-components-plotly/src/reflex_components_plotly/plotly.py b/packages/reflex-components-plotly/src/reflex_components_plotly/plotly.py index 2fd7bd19322..db3009121e7 100644 --- a/packages/reflex-components-plotly/src/reflex_components_plotly/plotly.py +++ b/packages/reflex-components-plotly/src/reflex_components_plotly/plotly.py @@ -5,11 +5,11 @@ from typing import TYPE_CHECKING, Any, TypedDict, TypeVar from reflex_components_core.core.cond import color_mode_cond +from reflex_core.components.component import Component, NoSSRComponent, field +from reflex_core.event import EventHandler, no_args_event_spec from reflex_core.utils.imports import ImportDict, ImportVar from reflex_core.vars.base import LiteralVar, Var -from reflex.components.component import Component, NoSSRComponent, field -from reflex.event import EventHandler, no_args_event_spec from reflex.utils import console try: diff --git a/packages/reflex-components-radix/src/reflex_components_radix/primitives/accordion.py b/packages/reflex-components-radix/src/reflex_components_radix/primitives/accordion.py index d8430e970ad..9eb2eb208f3 100644 --- a/packages/reflex-components-radix/src/reflex_components_radix/primitives/accordion.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/primitives/accordion.py @@ -8,13 +8,13 @@ from reflex_components_core.core.colors import color from reflex_components_core.core.cond import cond from reflex_components_lucide.icon import Icon +from reflex_core.components.component import Component, ComponentNamespace, field from reflex_core.constants.compiler import MemoizationMode +from reflex_core.event import EventHandler +from reflex_core.style import Style from reflex_core.vars import get_uuid_string_var from reflex_core.vars.base import LiteralVar, Var -from reflex.components.component import Component, ComponentNamespace, field -from reflex.event import EventHandler -from reflex.style import Style from reflex_components_radix.primitives.base import RadixPrimitiveComponent from reflex_components_radix.themes.base import LiteralAccentColor, LiteralRadius diff --git a/packages/reflex-components-radix/src/reflex_components_radix/primitives/base.py b/packages/reflex-components-radix/src/reflex_components_radix/primitives/base.py index bd65bb04c60..a1e32bd6d63 100644 --- a/packages/reflex-components-radix/src/reflex_components_radix/primitives/base.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/primitives/base.py @@ -2,10 +2,10 @@ from typing import Any +from reflex_core.components.component import Component, field +from reflex_core.components.tags.tag import Tag from reflex_core.vars.base import Var -from reflex.components.component import Component, field -from reflex.components.tags.tag import Tag from reflex.utils import format diff --git a/packages/reflex-components-radix/src/reflex_components_radix/primitives/dialog.py b/packages/reflex-components-radix/src/reflex_components_radix/primitives/dialog.py index 94e7c5792f8..69098177118 100644 --- a/packages/reflex-components-radix/src/reflex_components_radix/primitives/dialog.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/primitives/dialog.py @@ -3,12 +3,11 @@ from typing import Any, ClassVar from reflex_components_core.el import elements +from reflex_core.components.component import ComponentNamespace, field from reflex_core.constants.compiler import MemoizationMode +from reflex_core.event import EventHandler, no_args_event_spec, passthrough_event_spec from reflex_core.vars.base import Var -from reflex.components.component import ComponentNamespace, field -from reflex.event import EventHandler, no_args_event_spec, passthrough_event_spec - from .base import RadixPrimitiveComponent, RadixPrimitiveTriggerComponent diff --git a/packages/reflex-components-radix/src/reflex_components_radix/primitives/drawer.py b/packages/reflex-components-radix/src/reflex_components_radix/primitives/drawer.py index 81ca5b8fb7f..96564aef7d3 100644 --- a/packages/reflex-components-radix/src/reflex_components_radix/primitives/drawer.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/primitives/drawer.py @@ -7,11 +7,11 @@ from collections.abc import Sequence from typing import Any, Literal +from reflex_core.components.component import Component, ComponentNamespace, field from reflex_core.constants.compiler import MemoizationMode +from reflex_core.event import EventHandler, no_args_event_spec, passthrough_event_spec from reflex_core.vars.base import Var -from reflex.components.component import Component, ComponentNamespace, field -from reflex.event import EventHandler, no_args_event_spec, passthrough_event_spec from reflex_components_radix.primitives.base import RadixPrimitiveComponent from reflex_components_radix.themes.base import Theme from reflex_components_radix.themes.layout.flex import Flex diff --git a/packages/reflex-components-radix/src/reflex_components_radix/primitives/form.py b/packages/reflex-components-radix/src/reflex_components_radix/primitives/form.py index 1e95e9ba668..7433e96d1bb 100644 --- a/packages/reflex-components-radix/src/reflex_components_radix/primitives/form.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/primitives/form.py @@ -6,10 +6,10 @@ from reflex_components_core.core.debounce import DebounceInput from reflex_components_core.el.elements.forms import Form as HTMLForm +from reflex_core.components.component import ComponentNamespace, field +from reflex_core.event import EventHandler, no_args_event_spec from reflex_core.vars.base import Var -from reflex.components.component import ComponentNamespace, field -from reflex.event import EventHandler, no_args_event_spec from reflex_components_radix.themes.components.text_field import TextFieldRoot from .base import RadixPrimitiveComponentWithClassName diff --git a/packages/reflex-components-radix/src/reflex_components_radix/primitives/progress.py b/packages/reflex-components-radix/src/reflex_components_radix/primitives/progress.py index ea74b342b46..263537969a2 100644 --- a/packages/reflex-components-radix/src/reflex_components_radix/primitives/progress.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/primitives/progress.py @@ -5,9 +5,9 @@ from typing import Any from reflex_components_core.core.colors import color +from reflex_core.components.component import Component, ComponentNamespace, field from reflex_core.vars.base import Var -from reflex.components.component import Component, ComponentNamespace, field from reflex_components_radix.primitives.accordion import DEFAULT_ANIMATION_DURATION from reflex_components_radix.primitives.base import RadixPrimitiveComponentWithClassName from reflex_components_radix.themes.base import LiteralAccentColor, LiteralRadius diff --git a/packages/reflex-components-radix/src/reflex_components_radix/primitives/slider.py b/packages/reflex-components-radix/src/reflex_components_radix/primitives/slider.py index 3e16686fccd..187ddf3b505 100644 --- a/packages/reflex-components-radix/src/reflex_components_radix/primitives/slider.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/primitives/slider.py @@ -5,10 +5,10 @@ from collections.abc import Sequence from typing import Any, Literal +from reflex_core.components.component import Component, ComponentNamespace, field +from reflex_core.event import EventHandler, passthrough_event_spec from reflex_core.vars.base import Var -from reflex.components.component import Component, ComponentNamespace, field -from reflex.event import EventHandler, passthrough_event_spec from reflex_components_radix.primitives.base import RadixPrimitiveComponentWithClassName LiteralSliderOrientation = Literal["horizontal", "vertical"] diff --git a/packages/reflex-components-radix/src/reflex_components_radix/themes/base.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/base.py index 73bd7cc8dc2..f5a1f886fe4 100644 --- a/packages/reflex-components-radix/src/reflex_components_radix/themes/base.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/base.py @@ -5,13 +5,11 @@ from typing import Any, ClassVar, Literal from reflex_components_core.core.breakpoints import Responsive +from reflex_core.components.component import Component, field +from reflex_core.components.tags import Tag from reflex_core.utils.imports import ImportDict, ImportVar from reflex_core.vars.base import Var -from reflex.components import Component -from reflex.components.component import field -from reflex.components.tags import Tag - LiteralAlign = Literal["start", "center", "end", "baseline", "stretch"] LiteralJustify = Literal["start", "center", "end", "between"] LiteralSpacing = Literal["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"] diff --git a/packages/reflex-components-radix/src/reflex_components_radix/themes/color_mode.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/color_mode.py index 3a74843bafc..8d5195009e8 100644 --- a/packages/reflex-components-radix/src/reflex_components_radix/themes/color_mode.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/color_mode.py @@ -21,17 +21,17 @@ from reflex_components_core.core.cond import Cond, color_mode_cond, cond from reflex_components_lucide.icon import Icon -from reflex_core.vars.base import Var -from reflex_core.vars.sequence import LiteralArrayVar - -from reflex.components.component import BaseComponent, field -from reflex.style import ( +from reflex_core.components.component import BaseComponent, field +from reflex_core.style import ( LIGHT_COLOR_MODE, color_mode, resolved_color_mode, set_color_mode, toggle_color_mode, ) +from reflex_core.vars.base import Var +from reflex_core.vars.sequence import LiteralArrayVar + from reflex_components_radix.themes.components.dropdown_menu import dropdown_menu from reflex_components_radix.themes.components.switch import Switch diff --git a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/alert_dialog.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/alert_dialog.py index 93ac0ed729b..0cbfcce5913 100644 --- a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/alert_dialog.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/alert_dialog.py @@ -4,11 +4,11 @@ from reflex_components_core.core.breakpoints import Responsive from reflex_components_core.el import elements +from reflex_core.components.component import ComponentNamespace, field from reflex_core.constants.compiler import MemoizationMode +from reflex_core.event import EventHandler, no_args_event_spec, passthrough_event_spec from reflex_core.vars.base import Var -from reflex.components.component import ComponentNamespace, field -from reflex.event import EventHandler, no_args_event_spec, passthrough_event_spec from reflex_components_radix.themes.base import ( RadixThemesComponent, RadixThemesTriggerComponent, diff --git a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/aspect_ratio.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/aspect_ratio.py index 69a39d4e0d2..8a369eaefbb 100644 --- a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/aspect_ratio.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/aspect_ratio.py @@ -1,8 +1,8 @@ """Interactive components provided by @radix-ui/themes.""" +from reflex_core.components.component import field from reflex_core.vars.base import Var -from reflex.components.component import field from reflex_components_radix.themes.base import RadixThemesComponent diff --git a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/avatar.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/avatar.py index ecf32e217f2..da7fe956829 100644 --- a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/avatar.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/avatar.py @@ -3,9 +3,9 @@ from typing import Literal from reflex_components_core.core.breakpoints import Responsive +from reflex_core.components.component import field from reflex_core.vars.base import Var -from reflex.components.component import field from reflex_components_radix.themes.base import ( LiteralAccentColor, LiteralRadius, diff --git a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/badge.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/badge.py index 5391bd6458a..a8e2a5205c4 100644 --- a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/badge.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/badge.py @@ -4,9 +4,9 @@ from reflex_components_core.core.breakpoints import Responsive from reflex_components_core.el import elements +from reflex_core.components.component import field from reflex_core.vars.base import Var -from reflex.components.component import field from reflex_components_radix.themes.base import ( LiteralAccentColor, LiteralRadius, diff --git a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/button.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/button.py index e70912d7fc5..79352474d24 100644 --- a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/button.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/button.py @@ -4,9 +4,9 @@ from reflex_components_core.core.breakpoints import Responsive from reflex_components_core.el import elements +from reflex_core.components.component import field from reflex_core.vars.base import Var -from reflex.components.component import field from reflex_components_radix.themes.base import ( LiteralAccentColor, LiteralRadius, diff --git a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/callout.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/callout.py index 9ea65b0b60d..4269ae2bf46 100644 --- a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/callout.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/callout.py @@ -5,10 +5,10 @@ from reflex_components_core.core.breakpoints import Responsive from reflex_components_core.el import elements from reflex_components_lucide.icon import Icon +from reflex_core.components.component import Component, ComponentNamespace, field from reflex_core.vars.base import Var import reflex as rx -from reflex.components.component import Component, ComponentNamespace, field from reflex_components_radix.themes.base import LiteralAccentColor, RadixThemesComponent CalloutVariant = Literal["soft", "surface", "outline"] diff --git a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/card.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/card.py index 8e96af1617e..88feb783c31 100644 --- a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/card.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/card.py @@ -4,9 +4,9 @@ from reflex_components_core.core.breakpoints import Responsive from reflex_components_core.el import elements +from reflex_core.components.component import field from reflex_core.vars.base import Var -from reflex.components.component import field from reflex_components_radix.themes.base import RadixThemesComponent diff --git a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/checkbox.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/checkbox.py index 43025f48722..7e93a758563 100644 --- a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/checkbox.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/checkbox.py @@ -3,10 +3,10 @@ from typing import Literal from reflex_components_core.core.breakpoints import Responsive +from reflex_core.components.component import Component, ComponentNamespace, field +from reflex_core.event import EventHandler, passthrough_event_spec from reflex_core.vars.base import Var -from reflex.components.component import Component, ComponentNamespace, field -from reflex.event import EventHandler, passthrough_event_spec from reflex_components_radix.themes.base import ( LiteralAccentColor, LiteralSpacing, diff --git a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/checkbox_cards.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/checkbox_cards.py index 8dcfed838ea..d67af623929 100644 --- a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/checkbox_cards.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/checkbox_cards.py @@ -4,9 +4,9 @@ from typing import Literal from reflex_components_core.core.breakpoints import Responsive +from reflex_core.components.component import field from reflex_core.vars.base import Var -from reflex.components.component import field from reflex_components_radix.themes.base import LiteralAccentColor, RadixThemesComponent diff --git a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/checkbox_group.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/checkbox_group.py index 0b9813517c6..4cfb20a4823 100644 --- a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/checkbox_group.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/checkbox_group.py @@ -5,9 +5,9 @@ from typing import Literal from reflex_components_core.core.breakpoints import Responsive +from reflex_core.components.component import field from reflex_core.vars.base import Var -from reflex.components.component import field from reflex_components_radix.themes.base import LiteralAccentColor, RadixThemesComponent diff --git a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/context_menu.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/context_menu.py index 74db7b3beac..0d026073f6c 100644 --- a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/context_menu.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/context_menu.py @@ -3,11 +3,11 @@ from typing import ClassVar, Literal from reflex_components_core.core.breakpoints import Responsive +from reflex_core.components.component import ComponentNamespace, field from reflex_core.constants.compiler import MemoizationMode +from reflex_core.event import EventHandler, no_args_event_spec, passthrough_event_spec from reflex_core.vars.base import Var -from reflex.components.component import ComponentNamespace, field -from reflex.event import EventHandler, no_args_event_spec, passthrough_event_spec from reflex_components_radix.themes.base import LiteralAccentColor, RadixThemesComponent from .checkbox import Checkbox diff --git a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/data_list.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/data_list.py index 0197f16a228..e82d9be9596 100644 --- a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/data_list.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/data_list.py @@ -4,9 +4,9 @@ from typing import Literal from reflex_components_core.core.breakpoints import Responsive +from reflex_core.components.component import field from reflex_core.vars.base import Var -from reflex.components.component import field from reflex_components_radix.themes.base import LiteralAccentColor, RadixThemesComponent diff --git a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/dialog.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/dialog.py index 5155c1a3d5b..5cb49e2d63c 100644 --- a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/dialog.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/dialog.py @@ -4,11 +4,11 @@ from reflex_components_core.core.breakpoints import Responsive from reflex_components_core.el import elements +from reflex_core.components.component import ComponentNamespace, field from reflex_core.constants.compiler import MemoizationMode +from reflex_core.event import EventHandler, no_args_event_spec, passthrough_event_spec from reflex_core.vars.base import Var -from reflex.components.component import ComponentNamespace, field -from reflex.event import EventHandler, no_args_event_spec, passthrough_event_spec from reflex_components_radix.themes.base import ( RadixThemesComponent, RadixThemesTriggerComponent, diff --git a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/dropdown_menu.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/dropdown_menu.py index ff7b1af5810..a93a4aa8006 100644 --- a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/dropdown_menu.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/dropdown_menu.py @@ -3,11 +3,11 @@ from typing import ClassVar, Literal from reflex_components_core.core.breakpoints import Responsive +from reflex_core.components.component import ComponentNamespace, field from reflex_core.constants.compiler import MemoizationMode +from reflex_core.event import EventHandler, no_args_event_spec, passthrough_event_spec from reflex_core.vars.base import Var -from reflex.components.component import ComponentNamespace, field -from reflex.event import EventHandler, no_args_event_spec, passthrough_event_spec from reflex_components_radix.themes.base import ( LiteralAccentColor, RadixThemesComponent, diff --git a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/hover_card.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/hover_card.py index 784f14296d7..a0ffb444e68 100644 --- a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/hover_card.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/hover_card.py @@ -4,11 +4,11 @@ from reflex_components_core.core.breakpoints import Responsive from reflex_components_core.el import elements +from reflex_core.components.component import ComponentNamespace, field from reflex_core.constants.compiler import MemoizationMode +from reflex_core.event import EventHandler, passthrough_event_spec from reflex_core.vars.base import Var -from reflex.components.component import ComponentNamespace, field -from reflex.event import EventHandler, passthrough_event_spec from reflex_components_radix.themes.base import ( RadixThemesComponent, RadixThemesTriggerComponent, diff --git a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/icon_button.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/icon_button.py index a3e3bbb11db..02a7fdad726 100644 --- a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/icon_button.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/icon_button.py @@ -8,10 +8,10 @@ from reflex_components_core.core.match import Match from reflex_components_core.el import elements from reflex_components_lucide import Icon +from reflex_core.components.component import Component, field +from reflex_core.style import Style from reflex_core.vars.base import Var -from reflex.components.component import Component, field -from reflex.style import Style from reflex_components_radix.themes.base import ( LiteralAccentColor, LiteralRadius, diff --git a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/inset.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/inset.py index f7174ce9db4..9545d09217a 100644 --- a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/inset.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/inset.py @@ -4,9 +4,9 @@ from reflex_components_core.core.breakpoints import Responsive from reflex_components_core.el import elements +from reflex_core.components.component import field from reflex_core.vars.base import Var -from reflex.components.component import field from reflex_components_radix.themes.base import RadixThemesComponent LiteralButtonSize = Literal["1", "2", "3", "4"] diff --git a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/popover.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/popover.py index a7596c92ca8..9ea7b9db628 100644 --- a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/popover.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/popover.py @@ -4,11 +4,11 @@ from reflex_components_core.core.breakpoints import Responsive from reflex_components_core.el import elements +from reflex_core.components.component import ComponentNamespace, field from reflex_core.constants.compiler import MemoizationMode +from reflex_core.event import EventHandler, no_args_event_spec, passthrough_event_spec from reflex_core.vars.base import Var -from reflex.components.component import ComponentNamespace, field -from reflex.event import EventHandler, no_args_event_spec, passthrough_event_spec from reflex_components_radix.themes.base import ( RadixThemesComponent, RadixThemesTriggerComponent, diff --git a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/progress.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/progress.py index c03546dee41..40157d3134e 100644 --- a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/progress.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/progress.py @@ -3,10 +3,10 @@ from typing import Literal from reflex_components_core.core.breakpoints import Responsive +from reflex_core.components.component import Component, field +from reflex_core.style import Style from reflex_core.vars.base import Var -from reflex.components.component import Component, field -from reflex.style import Style from reflex_components_radix.themes.base import LiteralAccentColor, RadixThemesComponent diff --git a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/radio.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/radio.py index 96e0aaf6e70..f71519607bf 100644 --- a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/radio.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/radio.py @@ -3,9 +3,9 @@ from typing import Literal from reflex_components_core.core.breakpoints import Responsive +from reflex_core.components.component import field from reflex_core.vars.base import Var -from reflex.components.component import field from reflex_components_radix.themes.base import LiteralAccentColor, RadixThemesComponent diff --git a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/radio_cards.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/radio_cards.py index b8de190074d..e86913cf0a7 100644 --- a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/radio_cards.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/radio_cards.py @@ -4,10 +4,10 @@ from typing import ClassVar, Literal from reflex_components_core.core.breakpoints import Responsive +from reflex_core.components.component import field +from reflex_core.event import EventHandler, passthrough_event_spec from reflex_core.vars.base import Var -from reflex.components.component import field -from reflex.event import EventHandler, passthrough_event_spec from reflex_components_radix.themes.base import LiteralAccentColor, RadixThemesComponent diff --git a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/radio_group.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/radio_group.py index 04b1f87b244..6f8854b207f 100644 --- a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/radio_group.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/radio_group.py @@ -6,12 +6,12 @@ from typing import Literal from reflex_components_core.core.breakpoints import Responsive +from reflex_core.components.component import Component, ComponentNamespace, field +from reflex_core.event import EventHandler, passthrough_event_spec from reflex_core.vars.base import LiteralVar, Var from reflex_core.vars.sequence import StringVar import reflex as rx -from reflex.components.component import Component, ComponentNamespace, field -from reflex.event import EventHandler, passthrough_event_spec from reflex.utils import types from reflex_components_radix.themes.base import ( LiteralAccentColor, diff --git a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/scroll_area.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/scroll_area.py index 3b4a05a2ee8..31488ee0ce4 100644 --- a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/scroll_area.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/scroll_area.py @@ -2,9 +2,9 @@ from typing import Literal +from reflex_core.components.component import field from reflex_core.vars.base import Var -from reflex.components.component import field from reflex_components_radix.themes.base import RadixThemesComponent diff --git a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/segmented_control.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/segmented_control.py index 2599d1e1d63..6e1d8049ec4 100644 --- a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/segmented_control.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/segmented_control.py @@ -7,10 +7,10 @@ from typing import ClassVar, Literal from reflex_components_core.core.breakpoints import Responsive +from reflex_core.components.component import field +from reflex_core.event import EventHandler from reflex_core.vars.base import Var -from reflex.components.component import field -from reflex.event import EventHandler from reflex_components_radix.themes.base import LiteralAccentColor, RadixThemesComponent diff --git a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/select.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/select.py index 31efbe9c089..43737b145d0 100644 --- a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/select.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/select.py @@ -4,12 +4,12 @@ from typing import ClassVar, Literal from reflex_components_core.core.breakpoints import Responsive +from reflex_core.components.component import Component, ComponentNamespace, field from reflex_core.constants.compiler import MemoizationMode +from reflex_core.event import no_args_event_spec, passthrough_event_spec from reflex_core.vars.base import Var import reflex as rx -from reflex.components.component import Component, ComponentNamespace, field -from reflex.event import no_args_event_spec, passthrough_event_spec from reflex_components_radix.themes.base import ( LiteralAccentColor, LiteralRadius, diff --git a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/separator.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/separator.py index aa2fe3be7ad..76902fe07e5 100644 --- a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/separator.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/separator.py @@ -3,9 +3,9 @@ from typing import Literal from reflex_components_core.core.breakpoints import Responsive +from reflex_core.components.component import field from reflex_core.vars.base import LiteralVar, Var -from reflex.components.component import field from reflex_components_radix.themes.base import LiteralAccentColor, RadixThemesComponent LiteralSeparatorSize = Literal["1", "2", "3", "4"] diff --git a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/skeleton.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/skeleton.py index bd30799ac03..6259f3dcd13 100644 --- a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/skeleton.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/skeleton.py @@ -1,10 +1,10 @@ """Skeleton theme from Radix components.""" from reflex_components_core.core.breakpoints import Responsive +from reflex_core.components.component import field from reflex_core.constants.compiler import MemoizationMode from reflex_core.vars.base import Var -from reflex.components.component import field from reflex_components_radix.themes.base import RadixLoadingProp, RadixThemesComponent diff --git a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/slider.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/slider.py index 43e8aff9e3f..80f2c4e0e5a 100644 --- a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/slider.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/slider.py @@ -6,11 +6,11 @@ from typing import Literal from reflex_components_core.core.breakpoints import Responsive +from reflex_core.components.component import Component, field +from reflex_core.event import EventHandler, passthrough_event_spec +from reflex_core.utils.types import typehint_issubclass from reflex_core.vars.base import Var -from reflex.components.component import Component, field -from reflex.event import EventHandler, passthrough_event_spec -from reflex.utils.types import typehint_issubclass from reflex_components_radix.themes.base import LiteralAccentColor, RadixThemesComponent on_value_event_spec = ( diff --git a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/spinner.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/spinner.py index 33a1a45b095..8c2714ef947 100644 --- a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/spinner.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/spinner.py @@ -3,9 +3,9 @@ from typing import Literal from reflex_components_core.core.breakpoints import Responsive +from reflex_core.components.component import field from reflex_core.vars.base import Var -from reflex.components.component import field from reflex_components_radix.themes.base import RadixLoadingProp, RadixThemesComponent LiteralSpinnerSize = Literal["1", "2", "3"] diff --git a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/switch.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/switch.py index c2b9b28c6f9..293f35aabf6 100644 --- a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/switch.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/switch.py @@ -3,10 +3,10 @@ from typing import Literal from reflex_components_core.core.breakpoints import Responsive +from reflex_core.components.component import field +from reflex_core.event import EventHandler, passthrough_event_spec from reflex_core.vars.base import Var -from reflex.components.component import field -from reflex.event import EventHandler, passthrough_event_spec from reflex_components_radix.themes.base import LiteralAccentColor, RadixThemesComponent LiteralSwitchSize = Literal["1", "2", "3"] diff --git a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/table.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/table.py index 01011d38628..8773f85bc2d 100644 --- a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/table.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/table.py @@ -4,9 +4,9 @@ from reflex_components_core.core.breakpoints import Responsive from reflex_components_core.el import elements +from reflex_core.components.component import ComponentNamespace, field from reflex_core.vars.base import Var -from reflex.components.component import ComponentNamespace, field from reflex_components_radix.themes.base import CommonPaddingProps, RadixThemesComponent diff --git a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/tabs.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/tabs.py index a7cb4e06c0f..3c9dd20c641 100644 --- a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/tabs.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/tabs.py @@ -6,11 +6,11 @@ from reflex_components_core.core.breakpoints import Responsive from reflex_components_core.core.colors import color +from reflex_core.components.component import Component, ComponentNamespace, field from reflex_core.constants.compiler import MemoizationMode +from reflex_core.event import EventHandler, passthrough_event_spec from reflex_core.vars.base import Var -from reflex.components.component import Component, ComponentNamespace, field -from reflex.event import EventHandler, passthrough_event_spec from reflex_components_radix.themes.base import LiteralAccentColor, RadixThemesComponent vertical_orientation_css = "&[data-orientation='vertical']" diff --git a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/text_area.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/text_area.py index d7a490f82db..db463668949 100644 --- a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/text_area.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/text_area.py @@ -5,9 +5,9 @@ from reflex_components_core.core.breakpoints import Responsive from reflex_components_core.core.debounce import DebounceInput from reflex_components_core.el import elements +from reflex_core.components.component import Component, field from reflex_core.vars.base import Var -from reflex.components.component import Component, field from reflex_components_radix.themes.base import ( LiteralAccentColor, LiteralRadius, diff --git a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/text_field.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/text_field.py index 32b9e213996..5747ed80717 100644 --- a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/text_field.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/text_field.py @@ -7,12 +7,12 @@ from reflex_components_core.core.breakpoints import Responsive from reflex_components_core.core.debounce import DebounceInput from reflex_components_core.el import elements +from reflex_core.components.component import Component, ComponentNamespace, field +from reflex_core.event import EventHandler, input_event, key_event +from reflex_core.utils.types import is_optional from reflex_core.vars.base import Var from reflex_core.vars.number import ternary_operation -from reflex.components.component import Component, ComponentNamespace, field -from reflex.event import EventHandler, input_event, key_event -from reflex.utils.types import is_optional from reflex_components_radix.themes.base import ( LiteralAccentColor, LiteralRadius, diff --git a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/tooltip.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/tooltip.py index 32cd6fbb0a3..74e5a2d9dad 100644 --- a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/tooltip.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/tooltip.py @@ -2,11 +2,11 @@ from typing import Literal +from reflex_core.components.component import Component, field from reflex_core.constants.compiler import MemoizationMode +from reflex_core.event import EventHandler, no_args_event_spec, passthrough_event_spec from reflex_core.vars.base import Var -from reflex.components.component import Component, field -from reflex.event import EventHandler, no_args_event_spec, passthrough_event_spec from reflex.utils import format from reflex_components_radix.themes.base import RadixThemesComponent diff --git a/packages/reflex-components-radix/src/reflex_components_radix/themes/layout/base.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/layout/base.py index 72f096aa380..d1dc3277ffc 100644 --- a/packages/reflex-components-radix/src/reflex_components_radix/themes/layout/base.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/layout/base.py @@ -5,9 +5,9 @@ from typing import Literal from reflex_components_core.core.breakpoints import Responsive +from reflex_core.components.component import field from reflex_core.vars.base import Var -from reflex.components.component import field from reflex_components_radix.themes.base import ( CommonMarginProps, CommonPaddingProps, diff --git a/packages/reflex-components-radix/src/reflex_components_radix/themes/layout/container.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/layout/container.py index f62592d2068..c38e893c20e 100644 --- a/packages/reflex-components-radix/src/reflex_components_radix/themes/layout/container.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/layout/container.py @@ -6,10 +6,10 @@ from reflex_components_core.core.breakpoints import Responsive from reflex_components_core.el import elements +from reflex_core.components.component import field +from reflex_core.style import STACK_CHILDREN_FULL_WIDTH from reflex_core.vars.base import LiteralVar, Var -from reflex.components.component import field -from reflex.style import STACK_CHILDREN_FULL_WIDTH from reflex_components_radix.themes.base import RadixThemesComponent LiteralContainerSize = Literal["1", "2", "3", "4"] diff --git a/packages/reflex-components-radix/src/reflex_components_radix/themes/layout/flex.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/layout/flex.py index e6e19b8c5a2..1f6b45e393c 100644 --- a/packages/reflex-components-radix/src/reflex_components_radix/themes/layout/flex.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/layout/flex.py @@ -6,9 +6,9 @@ from reflex_components_core.core.breakpoints import Responsive from reflex_components_core.el import elements +from reflex_core.components.component import field from reflex_core.vars.base import Var -from reflex.components.component import field from reflex_components_radix.themes.base import ( LiteralAlign, LiteralJustify, diff --git a/packages/reflex-components-radix/src/reflex_components_radix/themes/layout/grid.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/layout/grid.py index 18e05d70538..2b9388f4bb3 100644 --- a/packages/reflex-components-radix/src/reflex_components_radix/themes/layout/grid.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/layout/grid.py @@ -6,9 +6,9 @@ from reflex_components_core.core.breakpoints import Responsive from reflex_components_core.el import elements +from reflex_core.components.component import field from reflex_core.vars.base import Var -from reflex.components.component import field from reflex_components_radix.themes.base import ( LiteralAlign, LiteralJustify, diff --git a/packages/reflex-components-radix/src/reflex_components_radix/themes/layout/list.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/layout/list.py index 923ebba5b9b..5dd8d25c002 100644 --- a/packages/reflex-components-radix/src/reflex_components_radix/themes/layout/list.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/layout/list.py @@ -10,9 +10,9 @@ from reflex_components_core.el.elements.base import BaseHTML from reflex_components_core.el.elements.typography import Li, Ol, Ul from reflex_components_lucide.icon import Icon +from reflex_core.components.component import ComponentNamespace, field from reflex_core.vars.base import Var -from reflex.components.component import ComponentNamespace, field from reflex_components_radix.themes.typography.text import Text LiteralListStyleTypeUnordered = Literal[ diff --git a/packages/reflex-components-radix/src/reflex_components_radix/themes/layout/section.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/layout/section.py index c2db752a286..22334616059 100644 --- a/packages/reflex-components-radix/src/reflex_components_radix/themes/layout/section.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/layout/section.py @@ -6,9 +6,9 @@ from reflex_components_core.core.breakpoints import Responsive from reflex_components_core.el import elements +from reflex_core.components.component import field from reflex_core.vars.base import LiteralVar, Var -from reflex.components.component import field from reflex_components_radix.themes.base import RadixThemesComponent LiteralSectionSize = Literal["1", "2", "3"] diff --git a/packages/reflex-components-radix/src/reflex_components_radix/themes/layout/stack.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/layout/stack.py index b0fb41e3f06..b77863ea1d2 100644 --- a/packages/reflex-components-radix/src/reflex_components_radix/themes/layout/stack.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/layout/stack.py @@ -3,9 +3,9 @@ from __future__ import annotations from reflex_components_core.core.breakpoints import Responsive +from reflex_core.components.component import Component, field from reflex_core.vars.base import Var -from reflex.components.component import Component, field from reflex_components_radix.themes.base import LiteralAlign, LiteralSpacing from .flex import Flex, LiteralFlexDirection diff --git a/packages/reflex-components-radix/src/reflex_components_radix/themes/typography/blockquote.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/typography/blockquote.py index 48801e09123..7a608b6ff9e 100644 --- a/packages/reflex-components-radix/src/reflex_components_radix/themes/typography/blockquote.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/typography/blockquote.py @@ -7,9 +7,9 @@ from reflex_components_core.core.breakpoints import Responsive from reflex_components_core.el import elements +from reflex_core.components.component import field from reflex_core.vars.base import Var -from reflex.components.component import field from reflex_components_radix.themes.base import LiteralAccentColor, RadixThemesComponent from .base import LiteralTextSize, LiteralTextWeight diff --git a/packages/reflex-components-radix/src/reflex_components_radix/themes/typography/code.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/typography/code.py index f552232fe01..4efff1d54bb 100644 --- a/packages/reflex-components-radix/src/reflex_components_radix/themes/typography/code.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/typography/code.py @@ -8,9 +8,9 @@ from reflex_components_core.core.breakpoints import Responsive from reflex_components_core.core.markdown_component_map import MarkdownComponentMap from reflex_components_core.el import elements +from reflex_core.components.component import field from reflex_core.vars.base import Var -from reflex.components.component import field from reflex_components_radix.themes.base import ( LiteralAccentColor, LiteralVariant, diff --git a/packages/reflex-components-radix/src/reflex_components_radix/themes/typography/heading.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/typography/heading.py index d957b7877a8..f2ec063d124 100644 --- a/packages/reflex-components-radix/src/reflex_components_radix/themes/typography/heading.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/typography/heading.py @@ -8,9 +8,9 @@ from reflex_components_core.core.breakpoints import Responsive from reflex_components_core.core.markdown_component_map import MarkdownComponentMap from reflex_components_core.el import elements +from reflex_core.components.component import field from reflex_core.vars.base import Var -from reflex.components.component import field from reflex_components_radix.themes.base import LiteralAccentColor, RadixThemesComponent from .base import LiteralTextAlign, LiteralTextSize, LiteralTextTrim, LiteralTextWeight diff --git a/packages/reflex-components-radix/src/reflex_components_radix/themes/typography/link.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/typography/link.py index 9ed1609bfa2..d88c8d68ac9 100644 --- a/packages/reflex-components-radix/src/reflex_components_radix/themes/typography/link.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/typography/link.py @@ -13,10 +13,10 @@ from reflex_components_core.core.markdown_component_map import MarkdownComponentMap from reflex_components_core.el.elements.inline import A from reflex_components_react_router.dom import ReactRouterLink +from reflex_core.components.component import Component, MemoizationLeaf, field from reflex_core.utils.imports import ImportDict, ImportVar from reflex_core.vars.base import Var -from reflex.components.component import Component, MemoizationLeaf, field from reflex_components_radix.themes.base import LiteralAccentColor, RadixThemesComponent from .base import LiteralTextSize, LiteralTextTrim, LiteralTextWeight diff --git a/packages/reflex-components-radix/src/reflex_components_radix/themes/typography/text.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/typography/text.py index 2850a31af78..c0adbd04b77 100644 --- a/packages/reflex-components-radix/src/reflex_components_radix/themes/typography/text.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/typography/text.py @@ -10,9 +10,9 @@ from reflex_components_core.core.breakpoints import Responsive from reflex_components_core.core.markdown_component_map import MarkdownComponentMap from reflex_components_core.el import elements +from reflex_core.components.component import ComponentNamespace, field from reflex_core.vars.base import Var -from reflex.components.component import ComponentNamespace, field from reflex_components_radix.themes.base import LiteralAccentColor, RadixThemesComponent from .base import LiteralTextAlign, LiteralTextSize, LiteralTextTrim, LiteralTextWeight diff --git a/packages/reflex-components-react-player/src/reflex_components_react_player/react_player.py b/packages/reflex-components-react-player/src/reflex_components_react_player/react_player.py index 0ed2b7251f2..345f1e4ee65 100644 --- a/packages/reflex-components-react-player/src/reflex_components_react_player/react_player.py +++ b/packages/reflex-components-react-player/src/reflex_components_react_player/react_player.py @@ -5,11 +5,11 @@ from typing import Any, TypedDict from reflex_components_core.core.cond import cond +from reflex_core.components.component import Component, field +from reflex_core.event import EventHandler, no_args_event_spec from reflex_core.vars.base import Var from reflex_core.vars.object import ObjectVar -from reflex.components.component import Component, field -from reflex.event import EventHandler, no_args_event_spec from reflex.utils import console ReactPlayerEvent = ObjectVar[dict[str, dict[str, dict[str, Any]]]] diff --git a/packages/reflex-components-react-router/src/reflex_components_react_router/dom.py b/packages/reflex-components-react-router/src/reflex_components_react_router/dom.py index c4d8c3ac457..3c299ecc4b3 100644 --- a/packages/reflex-components-react-router/src/reflex_components_react_router/dom.py +++ b/packages/reflex-components-react-router/src/reflex_components_react_router/dom.py @@ -5,10 +5,9 @@ from typing import ClassVar, Literal, TypedDict from reflex_components_core.el.elements.inline import A +from reflex_core.components.component import field from reflex_core.vars.base import Var -from reflex.components.component import field - LiteralLinkDiscover = Literal["none", "render"] diff --git a/packages/reflex-components-recharts/src/reflex_components_recharts/cartesian.py b/packages/reflex-components-recharts/src/reflex_components_recharts/cartesian.py index 81a7f976b6d..c75e0a5cc43 100644 --- a/packages/reflex-components-recharts/src/reflex_components_recharts/cartesian.py +++ b/packages/reflex-components-recharts/src/reflex_components_recharts/cartesian.py @@ -5,13 +5,12 @@ from collections.abc import Sequence from typing import Any, ClassVar, TypedDict +from reflex_core.components.component import field from reflex_core.constants import EventTriggers from reflex_core.constants.colors import Color +from reflex_core.event import EventHandler, no_args_event_spec from reflex_core.vars.base import LiteralVar, Var -from reflex.components.component import field -from reflex.event import EventHandler, no_args_event_spec - from .recharts import ( ACTIVE_DOT_TYPE, LiteralAnimationEasing, diff --git a/packages/reflex-components-recharts/src/reflex_components_recharts/charts.py b/packages/reflex-components-recharts/src/reflex_components_recharts/charts.py index f5802cf8a55..6df3ecccec3 100644 --- a/packages/reflex-components-recharts/src/reflex_components_recharts/charts.py +++ b/packages/reflex-components-recharts/src/reflex_components_recharts/charts.py @@ -5,12 +5,12 @@ from collections.abc import Sequence from typing import Any, ClassVar +from reflex_core.components.component import Component, field from reflex_core.constants import EventTriggers from reflex_core.constants.colors import Color +from reflex_core.event import EventHandler, no_args_event_spec from reflex_core.vars.base import Var -from reflex.components.component import Component, field -from reflex.event import EventHandler, no_args_event_spec from reflex_components_recharts.general import ResponsiveContainer from .recharts import ( diff --git a/packages/reflex-components-recharts/src/reflex_components_recharts/general.py b/packages/reflex-components-recharts/src/reflex_components_recharts/general.py index d1bc78d92fa..ab5ec1c1196 100644 --- a/packages/reflex-components-recharts/src/reflex_components_recharts/general.py +++ b/packages/reflex-components-recharts/src/reflex_components_recharts/general.py @@ -5,12 +5,11 @@ from collections.abc import Sequence from typing import Any, ClassVar +from reflex_core.components.component import MemoizationLeaf, field from reflex_core.constants.colors import Color +from reflex_core.event import EventHandler, no_args_event_spec from reflex_core.vars.base import LiteralVar, Var -from reflex.components.component import MemoizationLeaf, field -from reflex.event import EventHandler, no_args_event_spec - from .recharts import ( LiteralAnimationEasing, LiteralIconType, diff --git a/packages/reflex-components-recharts/src/reflex_components_recharts/polar.py b/packages/reflex-components-recharts/src/reflex_components_recharts/polar.py index 22083226fb7..bf5e871163c 100644 --- a/packages/reflex-components-recharts/src/reflex_components_recharts/polar.py +++ b/packages/reflex-components-recharts/src/reflex_components_recharts/polar.py @@ -5,13 +5,12 @@ from collections.abc import Sequence from typing import Any, ClassVar +from reflex_core.components.component import field from reflex_core.constants import EventTriggers from reflex_core.constants.colors import Color +from reflex_core.event import EventHandler, no_args_event_spec from reflex_core.vars.base import LiteralVar, Var -from reflex.components.component import field -from reflex.event import EventHandler, no_args_event_spec - from .recharts import ( ACTIVE_DOT_TYPE, LiteralAnimationEasing, diff --git a/packages/reflex-components-recharts/src/reflex_components_recharts/recharts.py b/packages/reflex-components-recharts/src/reflex_components_recharts/recharts.py index e1a4dfa8abf..02ed370f8c2 100644 --- a/packages/reflex-components-recharts/src/reflex_components_recharts/recharts.py +++ b/packages/reflex-components-recharts/src/reflex_components_recharts/recharts.py @@ -2,7 +2,7 @@ from typing import Any, Literal -from reflex.components.component import Component, MemoizationLeaf, NoSSRComponent +from reflex_core.components.component import Component, MemoizationLeaf, NoSSRComponent class Recharts(Component): diff --git a/packages/reflex-components-sonner/src/reflex_components_sonner/toast.py b/packages/reflex-components-sonner/src/reflex_components_sonner/toast.py index 265e4d5c29e..c51ce203fab 100644 --- a/packages/reflex-components-sonner/src/reflex_components_sonner/toast.py +++ b/packages/reflex-components-sonner/src/reflex_components_sonner/toast.py @@ -6,7 +6,11 @@ from typing import Any, Literal from reflex_components_lucide.icon import Icon +from reflex_core.components.component import Component, ComponentNamespace, field +from reflex_core.components.props import NoExtrasAllowedProps from reflex_core.constants.base import Dirs +from reflex_core.event import EventSpec, run_script +from reflex_core.style import Style, resolved_color_mode from reflex_core.utils.imports import ImportVar from reflex_core.vars import VarData from reflex_core.vars.base import LiteralVar, Var @@ -14,10 +18,6 @@ from reflex_core.vars.number import ternary_operation from reflex_core.vars.object import ObjectVar -from reflex.components.component import Component, ComponentNamespace, field -from reflex.components.props import NoExtrasAllowedProps -from reflex.event import EventSpec, run_script -from reflex.style import Style, resolved_color_mode from reflex.utils import format from reflex.utils.serializers import serializer diff --git a/packages/reflex-docgen/src/reflex_docgen/_component.py b/packages/reflex-docgen/src/reflex_docgen/_component.py index 315f7a0002d..1e1df8e9a2b 100644 --- a/packages/reflex-docgen/src/reflex_docgen/_component.py +++ b/packages/reflex-docgen/src/reflex_docgen/_component.py @@ -3,8 +3,8 @@ from dataclasses import dataclass from typing import Any -from reflex.components.component import DEFAULT_TRIGGERS_AND_DESC, Component -from reflex.event import EventHandler +from reflex_core.components.component import DEFAULT_TRIGGERS_AND_DESC, Component +from reflex_core.event import EventHandler @dataclass(frozen=True, slots=True, kw_only=True) diff --git a/pyi_hashes.json b/pyi_hashes.json index a3e83041afa..af2781e4051 100644 --- a/pyi_hashes.json +++ b/pyi_hashes.json @@ -1,122 +1,122 @@ { - "packages/reflex-components-code/src/reflex_components_code/code.pyi": "ee909f77bbd0fb2e3f1a47906756d143", + "packages/reflex-components-code/src/reflex_components_code/code.pyi": "3a169db4ce67e1b8891beba47089474a", "packages/reflex-components-code/src/reflex_components_code/shiki_code_block.pyi": "2c24228690a74c84e47dca765e0ac252", "packages/reflex-components-core/src/reflex_components_core/__init__.pyi": "6c3ceff429117483dd0035e7a21930f4", "packages/reflex-components-core/src/reflex_components_core/base/__init__.pyi": "4beb5ba739680b7974c37241f3f6791c", - "packages/reflex-components-core/src/reflex_components_core/base/app_wrap.pyi": "900f0927a8fbaea6d70a6c5f79ffe390", + "packages/reflex-components-core/src/reflex_components_core/base/app_wrap.pyi": "c0bd031d4a3bcebd7e739185f2b70bdf", "packages/reflex-components-core/src/reflex_components_core/base/body.pyi": "d05d36b0049a1ca9f48828de0a780264", - "packages/reflex-components-core/src/reflex_components_core/base/document.pyi": "abff8b40e875b1fd5a49adc602c0b3cb", - "packages/reflex-components-core/src/reflex_components_core/base/error_boundary.pyi": "8c6128003fc030ae3cad6a3c603f9600", - "packages/reflex-components-core/src/reflex_components_core/base/fragment.pyi": "203b060bc2ed6a1ee4f5139f3643a41b", - "packages/reflex-components-core/src/reflex_components_core/base/link.pyi": "560d2c9c4a21d5a99ecdaf7f220874d3", - "packages/reflex-components-core/src/reflex_components_core/base/meta.pyi": "d094461c8a1d56ba02102fce4940eff6", + "packages/reflex-components-core/src/reflex_components_core/base/document.pyi": "0b7eaf52f99c8f6f785f0ea506c01a8e", + "packages/reflex-components-core/src/reflex_components_core/base/error_boundary.pyi": "38f172c18f8fdeccedda67ad4f92399e", + "packages/reflex-components-core/src/reflex_components_core/base/fragment.pyi": "75455acfdbc65bffb4bf88343ac67807", + "packages/reflex-components-core/src/reflex_components_core/base/link.pyi": "fffd6706dfae3f97e51173ea59e5f43f", + "packages/reflex-components-core/src/reflex_components_core/base/meta.pyi": "26148bf0e78785982608dbec72992b53", "packages/reflex-components-core/src/reflex_components_core/base/script.pyi": "5d8adfa6c03b3cac1d51ac0b6bb1c015", - "packages/reflex-components-core/src/reflex_components_core/base/strict_mode.pyi": "490676ec4319d738aee31c3975361591", + "packages/reflex-components-core/src/reflex_components_core/base/strict_mode.pyi": "b0f92e71d1b3df1595d4f6d43199d0ca", "packages/reflex-components-core/src/reflex_components_core/core/__init__.pyi": "17f79762cef09c69acd9df227cf3bb35", "packages/reflex-components-core/src/reflex_components_core/core/auto_scroll.pyi": "74dbe0be2d13b0e56ab601a1018641ac", "packages/reflex-components-core/src/reflex_components_core/core/banner.pyi": "cbaf7af560c335460cb0f5248574a3f9", - "packages/reflex-components-core/src/reflex_components_core/core/clipboard.pyi": "4c8bca4b16aa8a5e06b7a0ce367e087e", - "packages/reflex-components-core/src/reflex_components_core/core/debounce.pyi": "883c94a3e37a4a85c8478ce341d3f873", - "packages/reflex-components-core/src/reflex_components_core/core/helmet.pyi": "bb1b4d7468cb1496bb301ade46354d3e", - "packages/reflex-components-core/src/reflex_components_core/core/html.pyi": "690ef52cd0c56fcc397826156a79c1e7", - "packages/reflex-components-core/src/reflex_components_core/core/sticky.pyi": "9e817ab33107cb59402e3aebf12c50cc", + "packages/reflex-components-core/src/reflex_components_core/core/clipboard.pyi": "94bc06be97b50d180efa409b37a8ea21", + "packages/reflex-components-core/src/reflex_components_core/core/debounce.pyi": "6a6c2cbffa849f28e8c9f943258dc865", + "packages/reflex-components-core/src/reflex_components_core/core/helmet.pyi": "0325ece6e0553dfa9914a6fed67ec3e7", + "packages/reflex-components-core/src/reflex_components_core/core/html.pyi": "4d2b918dc1bf189699120217958e0158", + "packages/reflex-components-core/src/reflex_components_core/core/sticky.pyi": "a88098e93ec116bf1f27ba1a07f573fb", "packages/reflex-components-core/src/reflex_components_core/core/upload.pyi": "fc802e5e985bd850e02aae2bc7265fd7", - "packages/reflex-components-core/src/reflex_components_core/core/window_events.pyi": "66593daba42db6ffa072f705cd208ad7", + "packages/reflex-components-core/src/reflex_components_core/core/window_events.pyi": "f0922feff9af64672591e52e9049ee39", "packages/reflex-components-core/src/reflex_components_core/datadisplay/__init__.pyi": "73e27e0bfd7e914c4baba7b166e75736", "packages/reflex-components-core/src/reflex_components_core/el/__init__.pyi": "a1d4d65ddc73b5fbba3726f4bcf6d6f0", - "packages/reflex-components-core/src/reflex_components_core/el/element.pyi": "071bd8e46b6b65806c938d343ec739cd", + "packages/reflex-components-core/src/reflex_components_core/el/element.pyi": "e724b6718d7658093729fd864ba3bf2f", "packages/reflex-components-core/src/reflex_components_core/el/elements/__init__.pyi": "ef8c4981b144e16b57e2fb805bf6dad5", - "packages/reflex-components-core/src/reflex_components_core/el/elements/base.pyi": "b004e6bfbb7de0789a6581274959dbb3", - "packages/reflex-components-core/src/reflex_components_core/el/elements/forms.pyi": "4ffd680ba7d473a1836b078c1523c855", - "packages/reflex-components-core/src/reflex_components_core/el/elements/inline.pyi": "a7d7c110149e0df7fd5798d29789d194", - "packages/reflex-components-core/src/reflex_components_core/el/elements/media.pyi": "91c5dfa222ac448f3734e34f780c6e31", - "packages/reflex-components-core/src/reflex_components_core/el/elements/metadata.pyi": "be4af13583cd210538a5bf401aee4653", - "packages/reflex-components-core/src/reflex_components_core/el/elements/other.pyi": "b38e41dbbc6ab30e9ee5fbb32cf675b9", - "packages/reflex-components-core/src/reflex_components_core/el/elements/scripts.pyi": "6391432c86072d5da87a1a54f4e861e9", + "packages/reflex-components-core/src/reflex_components_core/el/elements/base.pyi": "449ebb073d99a00a5de4ed1cf15a4e45", + "packages/reflex-components-core/src/reflex_components_core/el/elements/forms.pyi": "f44148daeb87118f31110b9d63da621c", + "packages/reflex-components-core/src/reflex_components_core/el/elements/inline.pyi": "1c527256bf880aa176a0bc9d734fac1f", + "packages/reflex-components-core/src/reflex_components_core/el/elements/media.pyi": "e47cd555937c6302a610d3d3101f606c", + "packages/reflex-components-core/src/reflex_components_core/el/elements/metadata.pyi": "b570e8a8008ac724c8a3db04da1adbc6", + "packages/reflex-components-core/src/reflex_components_core/el/elements/other.pyi": "0ac451f7246836ef59f9870570990299", + "packages/reflex-components-core/src/reflex_components_core/el/elements/scripts.pyi": "8914ccb733a9bab90ca03c2224941e2a", "packages/reflex-components-core/src/reflex_components_core/el/elements/sectioning.pyi": "6cd334cc7c5999ee4ae4c03455688313", - "packages/reflex-components-core/src/reflex_components_core/el/elements/tables.pyi": "28162491f97aeb8af88ec3b3d5faa275", - "packages/reflex-components-core/src/reflex_components_core/el/elements/typography.pyi": "80368be4e5e781a6ece17e452d37719b", + "packages/reflex-components-core/src/reflex_components_core/el/elements/tables.pyi": "528741e7b4a6a3c231bb91de4caa5fe4", + "packages/reflex-components-core/src/reflex_components_core/el/elements/typography.pyi": "97cc78a8b98273d0c6117793a5df47f5", "packages/reflex-components-dataeditor/src/reflex_components_dataeditor/dataeditor.pyi": "707afe3ba888c8c6b02bdf727354866e", - "packages/reflex-components-gridjs/src/reflex_components_gridjs/datatable.pyi": "7ac73744df1f5453c1dea3a8f192d460", - "packages/reflex-components-lucide/src/reflex_components_lucide/icon.pyi": "97469d8dc6b343d02c94ae3bc0c734e1", - "packages/reflex-components-markdown/src/reflex_components_markdown/markdown.pyi": "6e8d87e26ec0ed358f7f47ec25f0620f", - "packages/reflex-components-moment/src/reflex_components_moment/moment.pyi": "0d1cfd7fbb72efd4b79490fab739ee92", - "packages/reflex-components-plotly/src/reflex_components_plotly/plotly.pyi": "d8cb28a35e62d5d434f80fbc0be9f8a8", + "packages/reflex-components-gridjs/src/reflex_components_gridjs/datatable.pyi": "9d5e0cc9f10732ec198401e411efafde", + "packages/reflex-components-lucide/src/reflex_components_lucide/icon.pyi": "584c9aabc0e8a0ed5ccf1a9b8675fa50", + "packages/reflex-components-markdown/src/reflex_components_markdown/markdown.pyi": "567a4e0461c40603ae2ef3e8ccf7e854", + "packages/reflex-components-moment/src/reflex_components_moment/moment.pyi": "0d568e627f13dcbb53ce7c838cdb86eb", + "packages/reflex-components-plotly/src/reflex_components_plotly/plotly.pyi": "a108fc904e1d6230e8a5297800f51d1b", "packages/reflex-components-radix/src/reflex_components_radix/__init__.pyi": "899d63be42eb21c187519920518bf32e", "packages/reflex-components-radix/src/reflex_components_radix/primitives/__init__.pyi": "11bc17db31334f11d7fcde315b29cae1", - "packages/reflex-components-radix/src/reflex_components_radix/primitives/accordion.pyi": "4ddaa4093bd4a9225544cda8d4a847d8", - "packages/reflex-components-radix/src/reflex_components_radix/primitives/base.pyi": "c5d92880310ab703ea93d4b1a056eb86", - "packages/reflex-components-radix/src/reflex_components_radix/primitives/dialog.pyi": "064336aa87a72bc65327cc3154a29a45", - "packages/reflex-components-radix/src/reflex_components_radix/primitives/drawer.pyi": "1bcfb807f8629214f6a872c84c954eb7", - "packages/reflex-components-radix/src/reflex_components_radix/primitives/form.pyi": "a066a50b11c2b9507eb0544d0df3f668", - "packages/reflex-components-radix/src/reflex_components_radix/primitives/progress.pyi": "ac977b63b3640eeaa21204e6e9dcf40c", - "packages/reflex-components-radix/src/reflex_components_radix/primitives/slider.pyi": "a4d76b1220aeb40a53b5f6033f67ff04", + "packages/reflex-components-radix/src/reflex_components_radix/primitives/accordion.pyi": "78ee6a43956dba85266068212dedfd60", + "packages/reflex-components-radix/src/reflex_components_radix/primitives/base.pyi": "317062b2a792e345ef84ffdf2697e045", + "packages/reflex-components-radix/src/reflex_components_radix/primitives/dialog.pyi": "3289e8cf039ac7b59bc8207bc0dd946d", + "packages/reflex-components-radix/src/reflex_components_radix/primitives/drawer.pyi": "b380d9a0da1d6b580d21a2fb7a17df02", + "packages/reflex-components-radix/src/reflex_components_radix/primitives/form.pyi": "221eb2d8b4cdaa85a70b6c1c2fd72acf", + "packages/reflex-components-radix/src/reflex_components_radix/primitives/progress.pyi": "0a4d68580ee8ebc6ce26b79672dd5c5c", + "packages/reflex-components-radix/src/reflex_components_radix/primitives/slider.pyi": "2d3ce47fa10cfe94c56a4a6353dfb3c1", "packages/reflex-components-radix/src/reflex_components_radix/themes/__init__.pyi": "a152450345e7da9124e954843224e936", - "packages/reflex-components-radix/src/reflex_components_radix/themes/base.pyi": "aa534530d2947e6ac46205d5ccb3888b", - "packages/reflex-components-radix/src/reflex_components_radix/themes/color_mode.pyi": "5f41e358cde825fbdf589bf798b0bf00", + "packages/reflex-components-radix/src/reflex_components_radix/themes/base.pyi": "581e9a092afd222eeb7dfc1ce6c7f37d", + "packages/reflex-components-radix/src/reflex_components_radix/themes/color_mode.pyi": "cc2f97d2bece1cd27fd9cdd9364e271f", "packages/reflex-components-radix/src/reflex_components_radix/themes/components/__init__.pyi": "67d45aa96a97359ee556725641bb583f", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/alert_dialog.pyi": "1cd18bb9e1223b305f97e33f200563c1", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/aspect_ratio.pyi": "f6a1f6388abca0317671e336dcabfc4b", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/avatar.pyi": "99024032ec9f36c918174a3dd13d195e", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/badge.pyi": "04c9570befb04530ceb6f6a1de2dd581", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/button.pyi": "580e9f0b9ad5f5473e11adfdc6513166", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/callout.pyi": "a332c402424d159ab7e9f7a8a2145be1", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/card.pyi": "589ded8d2c453fbfd0c3c5a70f3f9826", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/checkbox.pyi": "6ca7329b87a775ff90cfceade116b28e", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/checkbox_cards.pyi": "aca084957873b350361996cd1e26ff28", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/checkbox_group.pyi": "4af3f42fd55c2a5810aad89f2077f03d", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/context_menu.pyi": "d68c3406e1e0cd77c88cb4294f58ffa3", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/data_list.pyi": "d526eb5152b616f93214f68670a0f4a9", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/dialog.pyi": "0c98db910a1da7029b9e37a077827613", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/dropdown_menu.pyi": "cedf9b90b2bc5590251eef1a40d02192", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/hover_card.pyi": "c6f94e10b03fb3ed02e993a6dfb405ec", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/icon_button.pyi": "e357ea5ef2c2c6d59decf528676c768d", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/inset.pyi": "d8ea4c7d2e69dc6fd27d98c3697b95c2", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/popover.pyi": "6343eea4f955dfbe9149735b6c74fcc0", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/progress.pyi": "cef3d099f95eb9ac18745df3cb57e9f6", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/radio.pyi": "117da2df15b89b430cd2d6c693d7eb30", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/radio_cards.pyi": "17c13d2209eaf47f43baab0c63574785", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/radio_group.pyi": "4501692b53bf26a18ddf968d4e212b0c", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/scroll_area.pyi": "2e3084f01527cb2a6be0db0cbac27237", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/segmented_control.pyi": "e16843a5243ae03a69c3c13f88910b74", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/select.pyi": "605eef88c8e9ce47c1ce0ed1771fb5ed", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/separator.pyi": "445408b7ee359bceddd897f164656e9b", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/skeleton.pyi": "eb5eb682a0d9875ecd3fd880cb283687", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/slider.pyi": "41a518be074ba740d995cbc0956ddf52", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/spinner.pyi": "4856ba4446ad21985a070d04dd6293d8", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/switch.pyi": "45fb1de0f43bf272ce5bfff9802839e8", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/table.pyi": "9aed7860e0541a82f69ae0ac37a44444", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/tabs.pyi": "4b16f26ee6a8f8bef43bc9338921c3ae", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/text_area.pyi": "a7689a98975afd666422cf4eec3a3eed", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/text_field.pyi": "743552036a6ae8143d8ce8d23b0d6fc5", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/tooltip.pyi": "da70fa40026aa2a1726e5140890da7b1", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/alert_dialog.pyi": "5bbe097edac2e111bdf65e23acf66bd8", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/aspect_ratio.pyi": "cd3955a5ccbf4a11aae1a0c2095c6d44", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/avatar.pyi": "c08892505373db7c19e866690ee78729", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/badge.pyi": "2bc0e5959b4bed487287afcbcb0a932b", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/button.pyi": "d4bf410164ee11479d5314f98d54f30a", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/callout.pyi": "fb3daca9a05decedc085f5e237859761", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/card.pyi": "38c32cdda959e32f713eb3712e414694", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/checkbox.pyi": "0fc9f6955a3d7ba4f44dc626fe3eedf2", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/checkbox_cards.pyi": "533c4565102f388358cc0685fdde965c", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/checkbox_group.pyi": "1309b694cc7413806f16fa214e0066d7", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/context_menu.pyi": "9e7c85a0c2c836d791b43d7e91ad5f53", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/data_list.pyi": "84f08ca6f06dfb4cc9075affa14afa27", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/dialog.pyi": "57cec7903d1b86c32ef977864bb36f57", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/dropdown_menu.pyi": "67b699b5b6ffb378689332dd2acf6cdb", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/hover_card.pyi": "8bf3ade8415908aad11da736dd23ad18", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/icon_button.pyi": "94664faf3a019aa6d9c901d59c16ed9e", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/inset.pyi": "7f11b97ca89644c0fa72dddf24d9bfc1", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/popover.pyi": "a3e999dd58187f97d47f3e25e6e8730e", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/progress.pyi": "f217303c45eb80d60807f72d230384a9", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/radio.pyi": "1dbb0c928b5cb7dc4d1fe77178423fc1", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/radio_cards.pyi": "512beb694d1a561855e3dd942e2d8ef9", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/radio_group.pyi": "586ef32f5faf8dd5aadd06097b086ce5", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/scroll_area.pyi": "0e1595623e30f81f9e123a1d4a6d66fd", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/segmented_control.pyi": "133c29bb43db5525134040e33f1b68d5", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/select.pyi": "28f8a6b2ea2bf4218f01c791f1a5c79c", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/separator.pyi": "373b43e18f3cb82d6d8e7a773f2744c4", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/skeleton.pyi": "2da5b95690cce684946f2801ff05efe6", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/slider.pyi": "60e2d765ba6264316eb224b36dcf080a", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/spinner.pyi": "283509251b927c9d33f776028b498e48", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/switch.pyi": "e94e1bbb4af7f59ccc808298ac6e001e", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/table.pyi": "ce54dd03efc4909aaf5271bc085dc3b0", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/tabs.pyi": "b1509e88841ffc75e1bd75d247eec167", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/text_area.pyi": "a0e31e2f2efdc2835db411fd6fad5255", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/text_field.pyi": "e1cd369c151987d3bb3d2f34c6a7a8b3", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/tooltip.pyi": "eced582469120eea433189b406230a0e", "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/__init__.pyi": "b7dbdecab770a7fc1435ad417d7f91c1", - "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/base.pyi": "b541c3e56e4b9afdb208db97b3c7846d", + "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/base.pyi": "a4634ccd1cc51c1faea3304fa8455bdd", "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/box.pyi": "744e6e83ab08c3f151715c5e459a03d2", "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/center.pyi": "c80413d4051b478e37c10a5e6e132f25", - "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/container.pyi": "b86f337365de46582e71f32135609ad2", - "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/flex.pyi": "4d5128e75445be5cc15d73006abbb1ec", - "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/grid.pyi": "ab58ab7820e10612a3fd1fddb0729003", - "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/list.pyi": "ba6f514524b94cb5e58e3543ac87d4a8", - "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/section.pyi": "295bf854ef0d0feadda6fbfdb7788244", + "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/container.pyi": "1d875cd301433c0ac2037afe0edf0490", + "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/flex.pyi": "a3f46d350c5e39505193fb6cecdeb0b4", + "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/grid.pyi": "d9dd4b2baa4e2d93017f5b1f5acac76a", + "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/list.pyi": "33cb4b45304ecfe335638a6b2fcde48d", + "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/section.pyi": "f3a2bddcb76c710bdaedcf360194c96a", "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/spacer.pyi": "02275e5a87cf57b880f6ee0cdde9ac94", - "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/stack.pyi": "b8e21fbae89af6a5d72beec914e196c4", + "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/stack.pyi": "46a9ab812ca9d8efea3c4600e237e07c", "packages/reflex-components-radix/src/reflex_components_radix/themes/typography/__init__.pyi": "15b13877fc52b82182fc42acd8adb027", - "packages/reflex-components-radix/src/reflex_components_radix/themes/typography/blockquote.pyi": "f03e588746d92248a8b2510cc19e43a6", - "packages/reflex-components-radix/src/reflex_components_radix/themes/typography/code.pyi": "ac3723aef4c9574578c67316e3ec7ebb", - "packages/reflex-components-radix/src/reflex_components_radix/themes/typography/heading.pyi": "b8961f85b0df2f70d345db45d138ea7b", - "packages/reflex-components-radix/src/reflex_components_radix/themes/typography/link.pyi": "677f87119fdbada1734e016c4e80347f", - "packages/reflex-components-radix/src/reflex_components_radix/themes/typography/text.pyi": "dea370cc7f7e4a75cb671d7ec0ccd949", + "packages/reflex-components-radix/src/reflex_components_radix/themes/typography/blockquote.pyi": "d16ce407e74de4d0326a601806e61525", + "packages/reflex-components-radix/src/reflex_components_radix/themes/typography/code.pyi": "348bde77a9bbf79b376792adcd9878e2", + "packages/reflex-components-radix/src/reflex_components_radix/themes/typography/heading.pyi": "bcfba34856a9ce0377b2c8b03fd5e244", + "packages/reflex-components-radix/src/reflex_components_radix/themes/typography/link.pyi": "7620c22dad86ba0613f7d612234171c4", + "packages/reflex-components-radix/src/reflex_components_radix/themes/typography/text.pyi": "01e2911337135e59c836e616e6888a75", "packages/reflex-components-react-player/src/reflex_components_react_player/audio.pyi": "551894a3f295deac9fed3fffb3f171b7", - "packages/reflex-components-react-player/src/reflex_components_react_player/react_player.pyi": "4961725c725fec0538b9e3bdbeafbcfe", + "packages/reflex-components-react-player/src/reflex_components_react_player/react_player.pyi": "df829c5cd25203086b775b552f711ed0", "packages/reflex-components-react-player/src/reflex_components_react_player/video.pyi": "4ca94af43518e83e1b9ee5da55a4843b", - "packages/reflex-components-react-router/src/reflex_components_react_router/dom.pyi": "5c13be18f98b23ed957e53c3a26c6216", + "packages/reflex-components-react-router/src/reflex_components_react_router/dom.pyi": "9fc7643e6212632a42efbc03421ef9fd", "packages/reflex-components-recharts/src/reflex_components_recharts/__init__.pyi": "1f04ab9f482261f18010fbf728d7b946", - "packages/reflex-components-recharts/src/reflex_components_recharts/cartesian.pyi": "d5f15d0a45eecec8eb8a74d44a97b523", - "packages/reflex-components-recharts/src/reflex_components_recharts/charts.pyi": "9c3e409cd5188487150223af7a9c0b8d", - "packages/reflex-components-recharts/src/reflex_components_recharts/general.pyi": "3d4b81db2bfa294fb18df151ed72d4f9", - "packages/reflex-components-recharts/src/reflex_components_recharts/polar.pyi": "6406446ea9c0c04ddfab4d6e7ed91df3", - "packages/reflex-components-recharts/src/reflex_components_recharts/recharts.pyi": "fa3dfbba1e25242fdb9787e280b43d1d", + "packages/reflex-components-recharts/src/reflex_components_recharts/cartesian.pyi": "3209b25c994aa92e0723cead757eec2e", + "packages/reflex-components-recharts/src/reflex_components_recharts/charts.pyi": "938791518f7dbbb04a4d4e3f9b69c1bc", + "packages/reflex-components-recharts/src/reflex_components_recharts/general.pyi": "c96fca4d56d0701d0f99a549b92f8ee2", + "packages/reflex-components-recharts/src/reflex_components_recharts/polar.pyi": "9e68127976b9f6a1129a08d9dfa5b254", + "packages/reflex-components-recharts/src/reflex_components_recharts/recharts.pyi": "c2cf67744cb2e92d5f82ed7aa09a7de0", "packages/reflex-components-sonner/src/reflex_components_sonner/toast.pyi": "6d0f9dba8d0c3c5ff2212766800e9f4d", "reflex/__init__.pyi": "c8154c3dde9c519bff7b6da72805b563", "reflex/components/__init__.pyi": "d620d52015f908cda828d231c1064236", diff --git a/reflex/_upload.py b/reflex/_upload.py index efc1f21a6da..4b8820433c1 100644 --- a/reflex/_upload.py +++ b/reflex/_upload.py @@ -23,10 +23,11 @@ from reflex.utils import exceptions if TYPE_CHECKING: + from reflex_core.event import EventHandler + from reflex_core.utils.types import Receive, Scope, Send + from reflex.app import App - from reflex.event import EventHandler from reflex.state import BaseState - from reflex.utils.types import Receive, Scope, Send @dataclasses.dataclass(frozen=True) @@ -504,10 +505,9 @@ async def _upload_buffered_file( Returns: A streaming response for the buffered upload. """ + from reflex_core.event import Event from reflex_core.utils.exceptions import UploadValueError - from reflex.event import Event - try: form_data = await request.form() except ClientDisconnect: @@ -610,7 +610,7 @@ async def _upload_chunk_file( Returns: The streaming upload response. """ - from reflex.event import Event + from reflex_core.event import Event chunk_iter = UploadChunkIterator(maxsize=8) event = Event( @@ -683,7 +683,7 @@ async def upload_file(request: Request): UploadTypeError: If a non-streaming upload is wired to a background task. HTTPException: when the request does not include token / handler headers. """ - from reflex.event import ( + from reflex_core.event import ( resolve_upload_chunk_handler_param, resolve_upload_handler_param, ) diff --git a/reflex/app.py b/reflex/app.py index 75dc5a573c6..89182659eb7 100644 --- a/reflex/app.py +++ b/reflex/app.py @@ -44,6 +44,12 @@ from reflex_components_radix import themes from reflex_components_sonner.toast import toast from reflex_core import constants +from reflex_core.components.component import ( + CUSTOM_COMPONENTS, + Component, + ComponentStyle, + evaluate_style_namespaces, +) from reflex_core.config import get_config from reflex_core.environment import ExecutorType, environment from reflex_core.event import ( @@ -56,6 +62,7 @@ noop, ) from reflex_core.utils.imports import ImportVar +from reflex_core.utils.types import ASGIApp, Message, Receive, Scope, Send from rich.progress import MofNCompleteColumn, Progress, TimeElapsedColumn from socketio import ASGIApp as EngineIOApp from socketio import AsyncNamespace, AsyncServer @@ -77,12 +84,6 @@ compile_theme, readable_name_from_component, ) -from reflex.components.component import ( - CUSTOM_COMPONENTS, - Component, - ComponentStyle, - evaluate_style_namespaces, -) from reflex.experimental.memo import EXPERIMENTAL_MEMOS from reflex.istate.manager import StateModificationContext from reflex.istate.proxy import StateProxy @@ -121,7 +122,6 @@ ) from reflex.utils.misc import run_in_thread from reflex.utils.token_manager import RedisTokenManager, TokenManager -from reflex.utils.types import ASGIApp, Message, Receive, Scope, Send if TYPE_CHECKING: from reflex_core.vars import Var @@ -211,7 +211,7 @@ def default_overlay_component() -> Component: Returns: The default overlay_component, which is a connection_modal. """ - from reflex.components.component import memo + from reflex_core.components.component import memo def default_overlay_components(): return Fragment.create( @@ -1058,7 +1058,7 @@ def _setup_overlay_component(self): def _setup_sticky_badge(self): """Add the sticky badge to the app.""" - from reflex.components.component import memo + from reflex_core.components.component import memo @memo def memoized_badge(): @@ -1258,7 +1258,7 @@ def get_compilation_time() -> str: all_imports = {} if (toaster := self.toaster) is not None: - from reflex.components.component import memo + from reflex_core.components.component import memo @memo def memoized_toast_provider(): diff --git a/reflex/app_mixins/middleware.py b/reflex/app_mixins/middleware.py index b78b96ec2dd..e999fcc4c31 100644 --- a/reflex/app_mixins/middleware.py +++ b/reflex/app_mixins/middleware.py @@ -5,7 +5,8 @@ import dataclasses import inspect -from reflex.event import Event +from reflex_core.event import Event + from reflex.middleware import HydrateMiddleware, Middleware from reflex.state import BaseState, StateUpdate diff --git a/reflex/compiler/compiler.py b/reflex/compiler/compiler.py index e51402fccc8..d17a8baf012 100644 --- a/reflex/compiler/compiler.py +++ b/reflex/compiler/compiler.py @@ -10,30 +10,30 @@ from reflex_components_core.base.fragment import Fragment from reflex_core import constants +from reflex_core.components.component import ( + BaseComponent, + Component, + ComponentStyle, + CustomComponent, + StatefulComponent, +) from reflex_core.config import get_config from reflex_core.constants.compiler import PageNames, ResetStylesheet from reflex_core.constants.state import FIELD_MARKER from reflex_core.environment import environment +from reflex_core.style import SYSTEM_COLOR_MODE from reflex_core.utils.exceptions import ReflexError from reflex_core.utils.format import to_title_case from reflex_core.utils.imports import ImportVar, ParsedImportDict from reflex_core.vars.base import LiteralVar, Var from reflex.compiler import templates, utils -from reflex.components.component import ( - BaseComponent, - Component, - ComponentStyle, - CustomComponent, - StatefulComponent, -) from reflex.experimental.memo import ( ExperimentalMemoComponentDefinition, ExperimentalMemoDefinition, ExperimentalMemoFunctionDefinition, ) from reflex.state import BaseState -from reflex.style import SYSTEM_COLOR_MODE from reflex.utils import console, path_ops from reflex.utils.exec import is_prod_mode from reflex.utils.prerequisites import get_web_dir @@ -88,7 +88,7 @@ def _compile_app(app_root: Component) -> str: Returns: The compiled app. """ - from reflex.components.dynamic import bundled_libraries + from reflex_core.components.dynamic import bundled_libraries window_libraries = [ (_normalize_library_name(name), name) for name in bundled_libraries diff --git a/reflex/compiler/utils.py b/reflex/compiler/utils.py index d3d28f5f09a..2694d82b39c 100644 --- a/reflex/compiler/utils.py +++ b/reflex/compiler/utils.py @@ -20,19 +20,19 @@ from reflex_components_core.el.elements.other import Html from reflex_components_core.el.elements.sectioning import Body from reflex_core import constants +from reflex_core.components.component import Component, ComponentStyle, CustomComponent from reflex_core.constants.state import CAMEL_CASE_MEMO_MARKER, FIELD_MARKER +from reflex_core.style import Style from reflex_core.utils.imports import ImportVar, ParsedImportDict from reflex_core.vars.base import Field, Var, VarData from reflex_core.vars.function import DestructuredArg -from reflex.components.component import Component, ComponentStyle, CustomComponent from reflex.experimental.memo import ( ExperimentalMemoComponentDefinition, ExperimentalMemoFunctionDefinition, ) from reflex.istate.storage import Cookie, LocalStorage, SessionStorage from reflex.state import BaseState, _resolve_delta -from reflex.style import Style from reflex.utils import format, imports, path_ops from reflex.utils.prerequisites import get_web_dir diff --git a/reflex/components/__init__.py b/reflex/components/__init__.py index b44e3e4fdfd..77b6acb14c3 100644 --- a/reflex/components/__init__.py +++ b/reflex/components/__init__.py @@ -2,7 +2,7 @@ Components have been split across multiple packages. This module installs an import redirect hook so that -``from reflex.components. import X`` continues to work +``from reflex_core.components. import X`` continues to work by delegating to the appropriate package. """ diff --git a/reflex/components/props.py b/reflex/components/props.py index 0d5a078a641..6af1651b320 100644 --- a/reflex/components/props.py +++ b/reflex/components/props.py @@ -6,15 +6,15 @@ from dataclasses import _MISSING_TYPE, MISSING from typing import Any, TypeVar, get_args, get_origin +from reflex_core.components.field import BaseField, FieldBasedMeta +from reflex_core.event import EventChain, args_specs_from_fields from reflex_core.utils.exceptions import InvalidPropValueError +from reflex_core.utils.types import is_union from reflex_core.vars.object import LiteralObjectVar from typing_extensions import dataclass_transform -from reflex.components.field import BaseField, FieldBasedMeta -from reflex.event import EventChain, args_specs_from_fields from reflex.utils import format from reflex.utils.serializers import serializer -from reflex.utils.types import is_union PROPS_FIELD_TYPE = TypeVar("PROPS_FIELD_TYPE") @@ -28,7 +28,7 @@ def _get_props_subclass(field_type: Any) -> type | None: Returns: The Props subclass if found, None otherwise. """ - from reflex.utils.types import typehint_issubclass + from reflex_core.utils.types import typehint_issubclass # For direct class types, we can return them directly if they're Props subclasses if isinstance(field_type, type): diff --git a/reflex/custom_components/custom_components.py b/reflex/custom_components/custom_components.py index e6dbc1a7ec5..11e6c5fc487 100644 --- a/reflex/custom_components/custom_components.py +++ b/reflex/custom_components/custom_components.py @@ -102,7 +102,7 @@ def _source_template(component_class_name: str, module_name: str) -> str: # This is because they they may not be compatible with Server-Side Rendering (SSR). # To handle this in Reflex, all you need to do is subclass `NoSSRComponent` instead. # For example: -# from reflex.components.component import NoSSRComponent +# from reflex_core.components.component import NoSSRComponent # class {component_class_name}(NoSSRComponent): # pass @@ -613,7 +613,7 @@ def _run_commands_in_subprocess(cmds: list[str]) -> bool: def _make_pyi_files(): """Create pyi files for the custom component.""" - from reflex.utils.pyi_generator import PyiGenerator + from reflex_core.utils.pyi_generator import PyiGenerator for top_level_dir in Path.cwd().iterdir(): if not top_level_dir.is_dir() or top_level_dir.name.startswith("."): diff --git a/reflex/experimental/client_state.py b/reflex/experimental/client_state.py index 6098c2bf76e..8f5b6565612 100644 --- a/reflex/experimental/client_state.py +++ b/reflex/experimental/client_state.py @@ -8,13 +8,12 @@ from typing import Any from reflex_core import constants +from reflex_core.event import EventChain, EventHandler, EventSpec, run_script from reflex_core.utils.imports import ImportVar from reflex_core.vars import VarData, get_unique_variable_name from reflex_core.vars.base import LiteralVar, Var from reflex_core.vars.function import ArgsFunctionOperationBuilder, FunctionVar -from reflex.event import EventChain, EventHandler, EventSpec, run_script - NoValue = object() diff --git a/reflex/experimental/memo.py b/reflex/experimental/memo.py index bcc0a2692a8..dbb8b5ad3e1 100644 --- a/reflex/experimental/memo.py +++ b/reflex/experimental/memo.py @@ -11,6 +11,8 @@ from reflex_components_core.base.bare import Bare from reflex_components_core.base.fragment import Fragment from reflex_core import constants +from reflex_core.components.component import Component +from reflex_core.components.dynamic import bundled_libraries from reflex_core.constants.compiler import SpecialAttributes from reflex_core.constants.state import CAMEL_CASE_MEMO_MARKER from reflex_core.utils.imports import ImportVar @@ -25,8 +27,6 @@ ) from reflex_core.vars.object import RestProp -from reflex.components.component import Component -from reflex.components.dynamic import bundled_libraries from reflex.utils import format from reflex.utils import types as type_utils diff --git a/reflex/istate/manager/__init__.py b/reflex/istate/manager/__init__.py index 023b021f396..cde9ca5d32d 100644 --- a/reflex/istate/manager/__init__.py +++ b/reflex/istate/manager/__init__.py @@ -8,10 +8,10 @@ from reflex_core import constants from reflex_core.config import get_config +from reflex_core.event import Event from reflex_core.utils.exceptions import InvalidStateManagerModeError from typing_extensions import ReadOnly, Unpack -from reflex.event import Event from reflex.state import BaseState from reflex.utils import console, prerequisites diff --git a/reflex/istate/proxy.py b/reflex/istate/proxy.py index cad7cdead96..75391b6732c 100644 --- a/reflex/istate/proxy.py +++ b/reflex/istate/proxy.py @@ -15,11 +15,11 @@ from typing import TYPE_CHECKING, Any, SupportsIndex, TypeVar import wrapt +from reflex_core.event import Event from reflex_core.utils.exceptions import ImmutableStateError from reflex_core.vars.base import Var from typing_extensions import Self -from reflex.event import Event from reflex.utils import prerequisites from reflex.utils.serializers import can_serialize, serialize, serializer diff --git a/reflex/istate/shared.py b/reflex/istate/shared.py index 16f031b17a2..b62056167f0 100644 --- a/reflex/istate/shared.py +++ b/reflex/istate/shared.py @@ -6,10 +6,10 @@ from typing import Self, TypeVar from reflex_core.constants import ROUTER_DATA +from reflex_core.event import Event, get_hydrate_event from reflex_core.utils import console from reflex_core.utils.exceptions import ReflexRuntimeError -from reflex.event import Event, get_hydrate_event from reflex.state import BaseState, State, _override_base_method, _substate_key UPDATE_OTHER_CLIENT_TASKS: set[asyncio.Task] = set() diff --git a/reflex/middleware/hydrate_middleware.py b/reflex/middleware/hydrate_middleware.py index 0e0b6970717..f5b75057050 100644 --- a/reflex/middleware/hydrate_middleware.py +++ b/reflex/middleware/hydrate_middleware.py @@ -6,8 +6,8 @@ from typing import TYPE_CHECKING from reflex_core import constants +from reflex_core.event import Event, get_hydrate_event -from reflex.event import Event, get_hydrate_event from reflex.middleware.middleware import Middleware from reflex.state import BaseState, StateUpdate, _resolve_delta diff --git a/reflex/middleware/middleware.py b/reflex/middleware/middleware.py index 6a5843b181e..f31761011cc 100644 --- a/reflex/middleware/middleware.py +++ b/reflex/middleware/middleware.py @@ -5,7 +5,8 @@ from abc import ABC, abstractmethod from typing import TYPE_CHECKING -from reflex.event import Event +from reflex_core.event import Event + from reflex.state import BaseState, StateUpdate if TYPE_CHECKING: diff --git a/reflex/page.py b/reflex/page.py index 257e7d469db..13ca05f803d 100644 --- a/reflex/page.py +++ b/reflex/page.py @@ -10,7 +10,7 @@ from collections.abc import Callable from typing import Any - from reflex.event import EventType + from reflex_core.event import EventType DECORATED_PAGES: dict[str, list] = defaultdict(list) diff --git a/reflex/state.py b/reflex/state.py index 068b98a6c3f..7f652267c15 100644 --- a/reflex/state.py +++ b/reflex/state.py @@ -35,6 +35,15 @@ from reflex_core import constants from reflex_core.constants.state import FIELD_MARKER from reflex_core.environment import PerformanceMode, environment +from reflex_core.event import ( + BACKGROUND_TASK_MARKER, + EVENT_ACTIONS_MARKER, + Event, + EventHandler, + EventSpec, + call_script, + fix_events, +) from reflex_core.utils.exceptions import ( ComputedVarShadowsBaseVarsError, ComputedVarShadowsStateVarError, @@ -66,15 +75,6 @@ import reflex.istate.dynamic from reflex import event -from reflex.event import ( - BACKGROUND_TASK_MARKER, - EVENT_ACTIONS_MARKER, - Event, - EventHandler, - EventSpec, - call_script, - fix_events, -) from reflex.istate import HANDLED_PICKLE_ERRORS, debug_failed_pickles from reflex.istate.data import RouterData from reflex.istate.proxy import ImmutableMutableProxy as ImmutableMutableProxy @@ -85,7 +85,7 @@ from reflex.utils.exec import is_testing_env if TYPE_CHECKING: - from reflex.components.component import Component + from reflex_core.components.component import Component Delta = dict[str, Any] @@ -723,7 +723,7 @@ def _evaluate(cls, f: Callable[[Self], Any], of_type: type | None = None) -> Var console.warn( "The _evaluate method is experimental and may be removed in future versions." ) - from reflex.components.component import Component + from reflex_core.components.component import Component of_type = of_type or Component diff --git a/reflex/testing.py b/reflex/testing.py index 092d6ab7bfe..c8d94b0bc2b 100644 --- a/reflex/testing.py +++ b/reflex/testing.py @@ -26,8 +26,10 @@ from typing import TYPE_CHECKING, Any, Literal, TypeVar import uvicorn +from reflex_core.components.component import CUSTOM_COMPONENTS, CustomComponent from reflex_core.config import get_config from reflex_core.environment import environment +from reflex_core.utils.types import ASGIApp from typing_extensions import Self import reflex @@ -36,7 +38,6 @@ import reflex.utils.format import reflex.utils.prerequisites import reflex.utils.processes -from reflex.components.component import CUSTOM_COMPONENTS, CustomComponent from reflex.experimental.memo import EXPERIMENTAL_MEMOS from reflex.istate.manager.disk import StateManagerDisk from reflex.istate.manager.memory import StateManagerMemory @@ -50,7 +51,6 @@ from reflex.utils import console, js_runtimes from reflex.utils.export import export from reflex.utils.token_manager import TokenManager -from reflex.utils.types import ASGIApp try: from selenium import webdriver diff --git a/reflex/utils/codespaces.py b/reflex/utils/codespaces.py index ec12ea641c3..aeb53925d7e 100644 --- a/reflex/utils/codespaces.py +++ b/reflex/utils/codespaces.py @@ -7,13 +7,12 @@ from reflex_components_core.base.script import Script from reflex_components_core.core.banner import has_connection_errors from reflex_components_core.core.cond import cond +from reflex_core.components.component import Component from reflex_core.constants import Endpoint +from reflex_core.utils.decorator import once from starlette.requests import Request from starlette.responses import HTMLResponse -from reflex.components.component import Component -from reflex.utils.decorator import once - @once def redirect_script() -> str: diff --git a/reflex/utils/exec.py b/reflex/utils/exec.py index 06b9a2bf2d8..e8e01d4bcba 100644 --- a/reflex/utils/exec.py +++ b/reflex/utils/exec.py @@ -20,9 +20,9 @@ from reflex_core.constants.base import LogLevel from reflex_core.environment import environment from reflex_core.utils import console +from reflex_core.utils.decorator import once from reflex.utils import path_ops -from reflex.utils.decorator import once from reflex.utils.misc import get_module_path from reflex.utils.prerequisites import get_web_dir diff --git a/reflex/utils/js_runtimes.py b/reflex/utils/js_runtimes.py index 022ad94ab7b..b031ac7d681 100644 --- a/reflex/utils/js_runtimes.py +++ b/reflex/utils/js_runtimes.py @@ -10,10 +10,10 @@ from reflex_core import constants from reflex_core.config import Config, get_config from reflex_core.environment import environment +from reflex_core.utils.decorator import cached_procedure, once from reflex_core.utils.exceptions import SystemPackageMissingError from reflex.utils import console, net, path_ops, processes -from reflex.utils.decorator import cached_procedure, once from reflex.utils.prerequisites import get_web_dir, windows_check_onedrive_in_path diff --git a/reflex/utils/net.py b/reflex/utils/net.py index 67ddd715ba1..17f7bd5de22 100644 --- a/reflex/utils/net.py +++ b/reflex/utils/net.py @@ -5,8 +5,8 @@ from collections.abc import Callable from typing import ParamSpec, TypeVar -from reflex.utils.decorator import once -from reflex.utils.types import Unset +from reflex_core.utils.decorator import once +from reflex_core.utils.types import Unset from . import console diff --git a/reflex/utils/prerequisites.py b/reflex/utils/prerequisites.py index 7e840c3676b..c91c62c8f91 100644 --- a/reflex/utils/prerequisites.py +++ b/reflex/utils/prerequisites.py @@ -21,10 +21,10 @@ from reflex_core import constants from reflex_core.config import Config, get_config from reflex_core.environment import environment +from reflex_core.utils.decorator import once from reflex import model from reflex.utils import console, net, path_ops -from reflex.utils.decorator import once from reflex.utils.misc import get_module_path if typing.TYPE_CHECKING: diff --git a/reflex/utils/registry.py b/reflex/utils/registry.py index 1c96c82161d..80e3af95b64 100644 --- a/reflex/utils/registry.py +++ b/reflex/utils/registry.py @@ -3,9 +3,9 @@ from pathlib import Path from reflex_core.environment import environment +from reflex_core.utils.decorator import cache_result_in_disk, once from reflex.utils import console, net -from reflex.utils.decorator import cache_result_in_disk, once def latency(registry: str) -> int: diff --git a/reflex/utils/telemetry.py b/reflex/utils/telemetry.py index 7d938e92134..e5ec1dacf02 100644 --- a/reflex/utils/telemetry.py +++ b/reflex/utils/telemetry.py @@ -13,10 +13,10 @@ from reflex_core import constants from reflex_core.environment import environment +from reflex_core.utils.decorator import once, once_unless_none from reflex_core.utils.exceptions import ReflexError from reflex.utils import console, processes -from reflex.utils.decorator import once, once_unless_none from reflex.utils.js_runtimes import get_bun_version, get_node_version from reflex.utils.prerequisites import ensure_reflex_installation_id, get_project_hash diff --git a/tests/benchmarks/test_compilation.py b/tests/benchmarks/test_compilation.py index 96fbf7e9d2a..e1188ed081b 100644 --- a/tests/benchmarks/test_compilation.py +++ b/tests/benchmarks/test_compilation.py @@ -1,7 +1,7 @@ from pytest_codspeed import BenchmarkFixture +from reflex_core.components.component import Component from reflex.compiler.compiler import _compile_page, _compile_stateful_components -from reflex.components.component import Component def import_templates(): diff --git a/tests/benchmarks/test_evaluate.py b/tests/benchmarks/test_evaluate.py index 4d348afb8b0..d12f9facf79 100644 --- a/tests/benchmarks/test_evaluate.py +++ b/tests/benchmarks/test_evaluate.py @@ -1,8 +1,7 @@ from collections.abc import Callable from pytest_codspeed import BenchmarkFixture - -from reflex.components.component import Component +from reflex_core.components.component import Component def test_evaluate_page( diff --git a/tests/integration/tests_playwright/test_appearance.py b/tests/integration/tests_playwright/test_appearance.py index 9af5fa38b73..6c847778494 100644 --- a/tests/integration/tests_playwright/test_appearance.py +++ b/tests/integration/tests_playwright/test_appearance.py @@ -7,8 +7,9 @@ def DefaultLightModeApp(): + from reflex_core.style import color_mode + import reflex as rx - from reflex.style import color_mode app = rx.App(theme=rx.theme(appearance="light")) @@ -18,8 +19,9 @@ def index(): def DefaultDarkModeApp(): + from reflex_core.style import color_mode + import reflex as rx - from reflex.style import color_mode app = rx.App(theme=rx.theme(appearance="dark")) @@ -29,8 +31,9 @@ def index(): def DefaultSystemModeApp(): + from reflex_core.style import color_mode + import reflex as rx - from reflex.style import color_mode app = rx.App() @@ -40,8 +43,9 @@ def index(): def ColorToggleApp(): + from reflex_core.style import color_mode, resolved_color_mode, set_color_mode + import reflex as rx - from reflex.style import color_mode, resolved_color_mode, set_color_mode app = rx.App(theme=rx.theme(appearance="light")) diff --git a/tests/units/components/core/test_foreach.py b/tests/units/components/core/test_foreach.py index 7aa15daba7a..71ea1efe494 100644 --- a/tests/units/components/core/test_foreach.py +++ b/tests/units/components/core/test_foreach.py @@ -8,13 +8,13 @@ ) from reflex_components_radix.themes.layout.box import box from reflex_components_radix.themes.typography.text import text +from reflex_core.components.component import Component from reflex_core.constants.state import FIELD_MARKER from reflex_core.vars.number import NumberVar from reflex_core.vars.sequence import ArrayVar import reflex as rx from reflex import el -from reflex.components.component import Component from reflex.state import BaseState, ComponentState diff --git a/tests/units/components/core/test_match.py b/tests/units/components/core/test_match.py index 04bf909c1cb..b8877523888 100644 --- a/tests/units/components/core/test_match.py +++ b/tests/units/components/core/test_match.py @@ -2,12 +2,12 @@ import pytest from reflex_components_core.core.match import Match +from reflex_core.components.component import Component from reflex_core.constants.state import FIELD_MARKER from reflex_core.utils.exceptions import MatchTypeError from reflex_core.vars.base import Var import reflex as rx -from reflex.components.component import Component from reflex.state import BaseState diff --git a/tests/units/components/core/test_upload.py b/tests/units/components/core/test_upload.py index b907697fc5d..f7ce6b6c0c5 100644 --- a/tests/units/components/core/test_upload.py +++ b/tests/units/components/core/test_upload.py @@ -9,11 +9,11 @@ cancel_upload, get_upload_url, ) +from reflex_core.event import EventChain, EventHandler, EventSpec from reflex_core.vars.base import LiteralVar, Var import reflex as rx from reflex import event -from reflex.event import EventChain, EventHandler, EventSpec from reflex.state import State diff --git a/tests/units/components/datadisplay/test_shiki_code.py b/tests/units/components/datadisplay/test_shiki_code.py index 5a7fa93c5ee..690765ea3e7 100644 --- a/tests/units/components/datadisplay/test_shiki_code.py +++ b/tests/units/components/datadisplay/test_shiki_code.py @@ -8,11 +8,10 @@ from reflex_components_core.el.elements.forms import Button from reflex_components_lucide.icon import Icon from reflex_components_radix.themes.layout.box import Box +from reflex_core.style import Style from reflex_core.vars import Var from reflex_core.vars.base import LiteralVar -from reflex.style import Style - @pytest.mark.parametrize( ("library", "fns", "expected_output", "raises_exception"), diff --git a/tests/units/components/forms/test_form.py b/tests/units/components/forms/test_form.py index 492f6ba3e62..d5b636711e1 100644 --- a/tests/units/components/forms/test_form.py +++ b/tests/units/components/forms/test_form.py @@ -1,8 +1,7 @@ from reflex_components_radix.primitives.form import Form +from reflex_core.event import EventChain, prevent_default from reflex_core.vars.base import Var -from reflex.event import EventChain, prevent_default - def test_render_on_submit(): """Test that on_submit event chain is rendered as a separate function.""" diff --git a/tests/units/components/markdown/test_markdown.py b/tests/units/components/markdown/test_markdown.py index 76100922a08..c56a8e67457 100644 --- a/tests/units/components/markdown/test_markdown.py +++ b/tests/units/components/markdown/test_markdown.py @@ -5,10 +5,9 @@ from reflex_components_markdown.markdown import Markdown from reflex_components_radix.themes.layout.box import Box from reflex_components_radix.themes.typography.heading import Heading +from reflex_core.components.component import Component, memo from reflex_core.vars.base import Var -from reflex.components.component import Component, memo - class CustomMarkdownComponent(Component, MarkdownComponentMap): """A custom markdown component.""" diff --git a/tests/units/components/radix/test_icon_button.py b/tests/units/components/radix/test_icon_button.py index 123c12db264..5622b6d1dc7 100644 --- a/tests/units/components/radix/test_icon_button.py +++ b/tests/units/components/radix/test_icon_button.py @@ -1,10 +1,9 @@ import pytest from reflex_components_lucide.icon import Icon from reflex_components_radix.themes.components.icon_button import IconButton +from reflex_core.style import Style from reflex_core.vars.base import LiteralVar -from reflex.style import Style - def test_icon_button(): ib1 = IconButton.create("activity") diff --git a/tests/units/components/test_component.py b/tests/units/components/test_component.py index 66cebc78045..bdd7f95bfa5 100644 --- a/tests/units/components/test_component.py +++ b/tests/units/components/test_component.py @@ -6,8 +6,25 @@ from reflex_components_core.base.bare import Bare from reflex_components_core.base.fragment import Fragment from reflex_components_radix.themes.layout.box import Box +from reflex_core.components.component import ( + CUSTOM_COMPONENTS, + Component, + CustomComponent, + StatefulComponent, + custom_component, +) from reflex_core.constants import EventTriggers from reflex_core.constants.state import FIELD_MARKER +from reflex_core.event import ( + EventChain, + EventHandler, + JavascriptInputEvent, + input_event, + no_args_event_spec, + parse_args_spec, + passthrough_event_spec, +) +from reflex_core.style import Style from reflex_core.utils.exceptions import ( ChildrenTypeError, EventFnArgMismatchError, @@ -25,24 +42,7 @@ import reflex as rx from reflex.compiler.utils import compile_custom_component -from reflex.components.component import ( - CUSTOM_COMPONENTS, - Component, - CustomComponent, - StatefulComponent, - custom_component, -) -from reflex.event import ( - EventChain, - EventHandler, - JavascriptInputEvent, - input_event, - no_args_event_spec, - parse_args_spec, - passthrough_event_spec, -) from reflex.state import BaseState -from reflex.style import Style from reflex.utils import imports diff --git a/tests/units/components/test_component_future_annotations.py b/tests/units/components/test_component_future_annotations.py index 29f6e821cad..56d7565b1c1 100644 --- a/tests/units/components/test_component_future_annotations.py +++ b/tests/units/components/test_component_future_annotations.py @@ -2,8 +2,8 @@ from typing import Any -from reflex.components.component import Component -from reflex.event import EventHandler, input_event, no_args_event_spec +from reflex_core.components.component import Component +from reflex_core.event import EventHandler, input_event, no_args_event_spec # This is a repeat of its namesake in test_component.py. diff --git a/tests/units/components/test_props.py b/tests/units/components/test_props.py index a81b4156106..cc437d7476d 100644 --- a/tests/units/components/test_props.py +++ b/tests/units/components/test_props.py @@ -1,16 +1,16 @@ from __future__ import annotations import pytest -from reflex_core.utils.exceptions import InvalidPropValueError - -from reflex.components.props import NoExtrasAllowedProps, PropsBase -from reflex.event import ( +from reflex_core.components.props import NoExtrasAllowedProps, PropsBase +from reflex_core.event import ( EventChain, EventHandler, event, no_args_event_spec, passthrough_event_spec, ) +from reflex_core.utils.exceptions import InvalidPropValueError + from reflex.state import State diff --git a/tests/units/components/test_tag.py b/tests/units/components/test_tag.py index 303f0d21f01..822c8c951b0 100644 --- a/tests/units/components/test_tag.py +++ b/tests/units/components/test_tag.py @@ -1,8 +1,7 @@ import pytest +from reflex_core.components.tags import CondTag, Tag, tagless from reflex_core.vars.base import LiteralVar, Var -from reflex.components.tags import CondTag, Tag, tagless - @pytest.mark.parametrize( ("props", "test_props"), diff --git a/tests/units/conftest.py b/tests/units/conftest.py index 1399832d8be..d330d50d19c 100644 --- a/tests/units/conftest.py +++ b/tests/units/conftest.py @@ -6,10 +6,10 @@ from unittest import mock import pytest +from reflex_core.components.component import CUSTOM_COMPONENTS +from reflex_core.event import EventSpec from reflex.app import App -from reflex.components.component import CUSTOM_COMPONENTS -from reflex.event import EventSpec from reflex.experimental.memo import EXPERIMENTAL_MEMOS from reflex.model import ModelRegistry from reflex.testing import chdir diff --git a/tests/units/docgen/test_class_and_component.py b/tests/units/docgen/test_class_and_component.py index 62787c110f3..3a2f67067a4 100644 --- a/tests/units/docgen/test_class_and_component.py +++ b/tests/units/docgen/test_class_and_component.py @@ -5,21 +5,20 @@ import sys import pytest +from reflex_core.components.component import ( + DEFAULT_TRIGGERS_AND_DESC, + Component, + TriggerDefinition, + field, +) from reflex_core.constants import EventTriggers +from reflex_core.event import EventHandler, no_args_event_spec from reflex_docgen import ( generate_class_documentation, generate_documentation, get_component_event_handlers, ) -from reflex.components.component import ( - DEFAULT_TRIGGERS_AND_DESC, - Component, - TriggerDefinition, - field, -) -from reflex.event import EventHandler, no_args_event_spec - def test_default_triggers_have_descriptions(): """Every entry in DEFAULT_TRIGGERS should carry a non-empty description.""" diff --git a/tests/units/experimental/test_memo.py b/tests/units/experimental/test_memo.py index cb68633769d..103c1c56c0e 100644 --- a/tests/units/experimental/test_memo.py +++ b/tests/units/experimental/test_memo.py @@ -6,6 +6,8 @@ from typing import Any import pytest +from reflex_core.components.component import CUSTOM_COMPONENTS, Component +from reflex_core.style import Style from reflex_core.utils.imports import ImportVar from reflex_core.vars import VarData from reflex_core.vars.base import Var @@ -14,14 +16,12 @@ import reflex as rx from reflex.compiler import compiler from reflex.compiler import utils as compiler_utils -from reflex.components.component import CUSTOM_COMPONENTS, Component from reflex.experimental.memo import ( EXPERIMENTAL_MEMOS, ExperimentalMemoComponent, ExperimentalMemoComponentDefinition, ExperimentalMemoFunctionDefinition, ) -from reflex.style import Style @pytest.fixture(autouse=True) diff --git a/tests/units/middleware/conftest.py b/tests/units/middleware/conftest.py index d786db6521d..559d62c20cb 100644 --- a/tests/units/middleware/conftest.py +++ b/tests/units/middleware/conftest.py @@ -1,6 +1,6 @@ import pytest +from reflex_core.event import Event -from reflex.event import Event from reflex.state import State diff --git a/tests/units/test_app.py b/tests/units/test_app.py index aa2c9e3fa51..d0166f37ed0 100644 --- a/tests/units/test_app.py +++ b/tests/units/test_app.py @@ -20,7 +20,10 @@ from reflex_components_core.base.fragment import Fragment from reflex_components_core.core.cond import Cond from reflex_components_radix.themes.typography.text import Text +from reflex_core.components.component import Component from reflex_core.constants.state import FIELD_MARKER +from reflex_core.event import Event +from reflex_core.style import Style from reflex_core.vars.base import computed_var from starlette.applications import Starlette from starlette.datastructures import FormData, Headers, UploadFile @@ -35,9 +38,7 @@ process, upload, ) -from reflex.components import Component from reflex.environment import environment -from reflex.event import Event from reflex.istate.manager.disk import StateManagerDisk from reflex.istate.manager.memory import StateManagerMemory from reflex.istate.manager.redis import StateManagerRedis @@ -51,7 +52,6 @@ StateUpdate, _substate_key, ) -from reflex.style import Style from reflex.utils import console, exceptions, format from .conftest import chdir diff --git a/tests/units/test_attribute_access_type.py b/tests/units/test_attribute_access_type.py index b8dd4431b2a..8f360f434a0 100644 --- a/tests/units/test_attribute_access_type.py +++ b/tests/units/test_attribute_access_type.py @@ -4,9 +4,9 @@ import attrs import pytest +from reflex_core.utils.types import GenericType, get_attribute_access_type import reflex as rx -from reflex.utils.types import GenericType, get_attribute_access_type pytest.importorskip("sqlalchemy") pytest.importorskip("sqlmodel") diff --git a/tests/units/test_event.py b/tests/units/test_event.py index 22f87f2f281..fdf07e69101 100644 --- a/tests/units/test_event.py +++ b/tests/units/test_event.py @@ -4,10 +4,7 @@ import pytest from reflex_core.constants.compiler import Hooks, Imports -from reflex_core.vars.base import Field, LiteralVar, Var, VarData, field - -import reflex as rx -from reflex.event import ( +from reflex_core.event import ( BACKGROUND_TASK_MARKER, Event, EventChain, @@ -19,6 +16,9 @@ event, fix_events, ) +from reflex_core.vars.base import Field, LiteralVar, Var, VarData, field + +import reflex as rx from reflex.state import BaseState from reflex.utils import format diff --git a/tests/units/test_prerequisites.py b/tests/units/test_prerequisites.py index d02596c4e3c..ee528ef9957 100644 --- a/tests/units/test_prerequisites.py +++ b/tests/units/test_prerequisites.py @@ -6,10 +6,10 @@ from click.testing import CliRunner from reflex_core.config import Config from reflex_core.constants.installer import PackageJson +from reflex_core.utils.decorator import cached_procedure from reflex.reflex import cli from reflex.testing import chdir -from reflex.utils.decorator import cached_procedure from reflex.utils.frontend_skeleton import ( _compile_package_json, _compile_vite_config, diff --git a/tests/units/test_state.py b/tests/units/test_state.py index 376d4a47f0c..1b5fb1a9bcd 100644 --- a/tests/units/test_state.py +++ b/tests/units/test_state.py @@ -24,6 +24,7 @@ from reflex_core import constants from reflex_core.constants import CompileVars, RouteVar, SocketEvent from reflex_core.constants.state import FIELD_MARKER +from reflex_core.event import Event, EventHandler from reflex_core.utils.exceptions import ( InvalidLockWarningThresholdError, LockExpiredError, @@ -38,7 +39,6 @@ import reflex as rx from reflex.app import App from reflex.environment import environment -from reflex.event import Event, EventHandler from reflex.istate.data import HeaderData, _FrozenDictStrStr from reflex.istate.manager import StateManager from reflex.istate.manager.disk import StateManagerDisk diff --git a/tests/units/test_style.py b/tests/units/test_style.py index 10b4541b28c..cbd27eaf0bc 100644 --- a/tests/units/test_style.py +++ b/tests/units/test_style.py @@ -3,14 +3,14 @@ from typing import Any import pytest +from reflex_core.components.component import evaluate_style_namespaces +from reflex_core.style import Style from reflex_core.utils.exceptions import ReflexError from reflex_core.vars import VarData from reflex_core.vars.base import LiteralVar, Var import reflex as rx from reflex import style -from reflex.components.component import evaluate_style_namespaces -from reflex.style import Style style_var = rx.Var.create({"height": "42px"}) diff --git a/tests/units/test_testing.py b/tests/units/test_testing.py index e34e8d26e38..6d29351cbc9 100644 --- a/tests/units/test_testing.py +++ b/tests/units/test_testing.py @@ -6,12 +6,12 @@ import pytest import reflex_core.config +from reflex_core.components.component import CUSTOM_COMPONENTS from reflex_core.constants import IS_WINDOWS import reflex.reflex as reflex_cli import reflex.testing as reflex_testing import reflex.utils.prerequisites -from reflex.components.component import CUSTOM_COMPONENTS from reflex.experimental.memo import EXPERIMENTAL_MEMOS from reflex.testing import AppHarness diff --git a/tests/units/test_var.py b/tests/units/test_var.py index 97635d39301..09cadb85c81 100644 --- a/tests/units/test_var.py +++ b/tests/units/test_var.py @@ -16,6 +16,7 @@ UntypedComputedVarError, ) from reflex_core.utils.imports import ImportVar +from reflex_core.utils.types import get_default_value_for_type from reflex_core.vars import VarData from reflex_core.vars.base import ( ComputedVar, @@ -42,7 +43,6 @@ import reflex as rx from reflex.environment import PerformanceMode from reflex.state import BaseState -from reflex.utils.types import get_default_value_for_type test_vars = [ Var(_js_expr="prop1", _var_type=int), diff --git a/tests/units/utils/test_format.py b/tests/units/utils/test_format.py index d0900cb6347..c6d043f2ec9 100644 --- a/tests/units/utils/test_format.py +++ b/tests/units/utils/test_format.py @@ -6,21 +6,21 @@ import plotly.graph_objects as go import pytest +from reflex_core.components.tags.tag import Tag from reflex_core.constants.state import FIELD_MARKER -from reflex_core.utils import format -from reflex_core.vars.base import LiteralVar, Var -from reflex_core.vars.function import FunctionStringVar -from reflex_core.vars.object import ObjectVar - -from reflex.components.tags.tag import Tag -from reflex.event import ( +from reflex_core.event import ( EventChain, EventHandler, EventSpec, JavascriptInputEvent, no_args_event_spec, ) -from reflex.style import Style +from reflex_core.style import Style +from reflex_core.utils import format +from reflex_core.vars.base import LiteralVar, Var +from reflex_core.vars.function import FunctionStringVar +from reflex_core.vars.object import ObjectVar + from reflex.utils.serializers import serialize_figure pytest.importorskip("pydantic") diff --git a/tests/units/utils/test_utils.py b/tests/units/utils/test_utils.py index 554eeee7215..81294299ce3 100644 --- a/tests/units/utils/test_utils.py +++ b/tests/units/utils/test_utils.py @@ -9,11 +9,11 @@ from packaging import version from pytest_mock import MockerFixture from reflex_core import constants +from reflex_core.event import EventHandler from reflex_core.utils.exceptions import ReflexError, SystemPackageMissingError from reflex_core.vars.base import Var from reflex.environment import environment -from reflex.event import EventHandler from reflex.state import BaseState from reflex.utils import exec as utils_exec from reflex.utils import frontend_skeleton, js_runtimes, prerequisites, templates, types diff --git a/tests/units/vars/test_object.py b/tests/units/vars/test_object.py index acccebcf602..6867711e6fe 100644 --- a/tests/units/vars/test_object.py +++ b/tests/units/vars/test_object.py @@ -3,13 +3,13 @@ import pydantic import pytest +from reflex_core.utils.types import GenericType from reflex_core.vars.base import Var from reflex_core.vars.object import LiteralObjectVar, ObjectVar from reflex_core.vars.sequence import ArrayVar from typing_extensions import assert_type import reflex as rx -from reflex.utils.types import GenericType pytest.importorskip("sqlalchemy") pytest.importorskip("pydantic") From 3ae1dbfa31a70367258f5cc4d2fac0be292a0d33 Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Thu, 26 Mar 2026 15:57:35 -0700 Subject: [PATCH 075/113] move reflex util import changes --- .../src/reflex_components_radix/primitives/base.py | 3 +-- .../src/reflex_components_radix/themes/components/tooltip.py | 2 +- .../src/reflex_components_sonner/toast.py | 2 +- pyi_hashes.json | 4 ++-- reflex/compiler/utils.py | 3 ++- reflex/components/props.py | 2 +- reflex/experimental/memo.py | 2 +- reflex/istate/storage.py | 2 +- tests/integration/test_form_submit.py | 2 +- tests/units/components/lucide/test_icon.py | 3 +-- tests/units/test_event.py | 2 +- tests/units/test_state.py | 3 ++- 12 files changed, 15 insertions(+), 15 deletions(-) diff --git a/packages/reflex-components-radix/src/reflex_components_radix/primitives/base.py b/packages/reflex-components-radix/src/reflex_components_radix/primitives/base.py index a1e32bd6d63..57c8725cfc6 100644 --- a/packages/reflex-components-radix/src/reflex_components_radix/primitives/base.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/primitives/base.py @@ -4,10 +4,9 @@ from reflex_core.components.component import Component, field from reflex_core.components.tags.tag import Tag +from reflex_core.utils import format from reflex_core.vars.base import Var -from reflex.utils import format - class RadixPrimitiveComponent(Component): """Basic component for radix Primitives.""" diff --git a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/tooltip.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/tooltip.py index 74e5a2d9dad..051985dbc19 100644 --- a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/tooltip.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/tooltip.py @@ -5,9 +5,9 @@ from reflex_core.components.component import Component, field from reflex_core.constants.compiler import MemoizationMode from reflex_core.event import EventHandler, no_args_event_spec, passthrough_event_spec +from reflex_core.utils import format from reflex_core.vars.base import Var -from reflex.utils import format from reflex_components_radix.themes.base import RadixThemesComponent LiteralSideType = Literal[ diff --git a/packages/reflex-components-sonner/src/reflex_components_sonner/toast.py b/packages/reflex-components-sonner/src/reflex_components_sonner/toast.py index c51ce203fab..e74512ed717 100644 --- a/packages/reflex-components-sonner/src/reflex_components_sonner/toast.py +++ b/packages/reflex-components-sonner/src/reflex_components_sonner/toast.py @@ -11,6 +11,7 @@ from reflex_core.constants.base import Dirs from reflex_core.event import EventSpec, run_script from reflex_core.style import Style, resolved_color_mode +from reflex_core.utils import format from reflex_core.utils.imports import ImportVar from reflex_core.vars import VarData from reflex_core.vars.base import LiteralVar, Var @@ -18,7 +19,6 @@ from reflex_core.vars.number import ternary_operation from reflex_core.vars.object import ObjectVar -from reflex.utils import format from reflex.utils.serializers import serializer LiteralPosition = Literal[ diff --git a/pyi_hashes.json b/pyi_hashes.json index af2781e4051..d58a5e29cb1 100644 --- a/pyi_hashes.json +++ b/pyi_hashes.json @@ -45,7 +45,7 @@ "packages/reflex-components-radix/src/reflex_components_radix/__init__.pyi": "899d63be42eb21c187519920518bf32e", "packages/reflex-components-radix/src/reflex_components_radix/primitives/__init__.pyi": "11bc17db31334f11d7fcde315b29cae1", "packages/reflex-components-radix/src/reflex_components_radix/primitives/accordion.pyi": "78ee6a43956dba85266068212dedfd60", - "packages/reflex-components-radix/src/reflex_components_radix/primitives/base.pyi": "317062b2a792e345ef84ffdf2697e045", + "packages/reflex-components-radix/src/reflex_components_radix/primitives/base.pyi": "98eac310dc0ddc5db75945955d606470", "packages/reflex-components-radix/src/reflex_components_radix/primitives/dialog.pyi": "3289e8cf039ac7b59bc8207bc0dd946d", "packages/reflex-components-radix/src/reflex_components_radix/primitives/drawer.pyi": "b380d9a0da1d6b580d21a2fb7a17df02", "packages/reflex-components-radix/src/reflex_components_radix/primitives/form.pyi": "221eb2d8b4cdaa85a70b6c1c2fd72acf", @@ -89,7 +89,7 @@ "packages/reflex-components-radix/src/reflex_components_radix/themes/components/tabs.pyi": "b1509e88841ffc75e1bd75d247eec167", "packages/reflex-components-radix/src/reflex_components_radix/themes/components/text_area.pyi": "a0e31e2f2efdc2835db411fd6fad5255", "packages/reflex-components-radix/src/reflex_components_radix/themes/components/text_field.pyi": "e1cd369c151987d3bb3d2f34c6a7a8b3", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/tooltip.pyi": "eced582469120eea433189b406230a0e", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/tooltip.pyi": "5ee3130487817e083408a6042ba6e31c", "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/__init__.pyi": "b7dbdecab770a7fc1435ad417d7f91c1", "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/base.pyi": "a4634ccd1cc51c1faea3304fa8455bdd", "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/box.pyi": "744e6e83ab08c3f151715c5e459a03d2", diff --git a/reflex/compiler/utils.py b/reflex/compiler/utils.py index 2694d82b39c..fd56b2e079a 100644 --- a/reflex/compiler/utils.py +++ b/reflex/compiler/utils.py @@ -23,6 +23,7 @@ from reflex_core.components.component import Component, ComponentStyle, CustomComponent from reflex_core.constants.state import CAMEL_CASE_MEMO_MARKER, FIELD_MARKER from reflex_core.style import Style +from reflex_core.utils import format, imports from reflex_core.utils.imports import ImportVar, ParsedImportDict from reflex_core.vars.base import Field, Var, VarData from reflex_core.vars.function import DestructuredArg @@ -33,7 +34,7 @@ ) from reflex.istate.storage import Cookie, LocalStorage, SessionStorage from reflex.state import BaseState, _resolve_delta -from reflex.utils import format, imports, path_ops +from reflex.utils import path_ops from reflex.utils.prerequisites import get_web_dir # To re-export this function. diff --git a/reflex/components/props.py b/reflex/components/props.py index 6af1651b320..5bd9c2f59cc 100644 --- a/reflex/components/props.py +++ b/reflex/components/props.py @@ -8,12 +8,12 @@ from reflex_core.components.field import BaseField, FieldBasedMeta from reflex_core.event import EventChain, args_specs_from_fields +from reflex_core.utils import format from reflex_core.utils.exceptions import InvalidPropValueError from reflex_core.utils.types import is_union from reflex_core.vars.object import LiteralObjectVar from typing_extensions import dataclass_transform -from reflex.utils import format from reflex.utils.serializers import serializer PROPS_FIELD_TYPE = TypeVar("PROPS_FIELD_TYPE") diff --git a/reflex/experimental/memo.py b/reflex/experimental/memo.py index dbb8b5ad3e1..c145f85cc1d 100644 --- a/reflex/experimental/memo.py +++ b/reflex/experimental/memo.py @@ -15,6 +15,7 @@ from reflex_core.components.dynamic import bundled_libraries from reflex_core.constants.compiler import SpecialAttributes from reflex_core.constants.state import CAMEL_CASE_MEMO_MARKER +from reflex_core.utils import format from reflex_core.utils.imports import ImportVar from reflex_core.vars import VarData from reflex_core.vars.base import LiteralVar, Var @@ -27,7 +28,6 @@ ) from reflex_core.vars.object import RestProp -from reflex.utils import format from reflex.utils import types as type_utils diff --git a/reflex/istate/storage.py b/reflex/istate/storage.py index a3dd98bafbf..c0cfb1b775a 100644 --- a/reflex/istate/storage.py +++ b/reflex/istate/storage.py @@ -4,7 +4,7 @@ from typing import Any -from reflex.utils import format +from reflex_core.utils import format class ClientStorageBase: diff --git a/tests/integration/test_form_submit.py b/tests/integration/test_form_submit.py index a064c1842fb..efe5708758e 100644 --- a/tests/integration/test_form_submit.py +++ b/tests/integration/test_form_submit.py @@ -5,11 +5,11 @@ from collections.abc import Generator import pytest +from reflex_core.utils import format from selenium.webdriver.common.by import By from selenium.webdriver.common.keys import Keys from reflex.testing import AppHarness -from reflex.utils import format def FormSubmit(form_component): diff --git a/tests/units/components/lucide/test_icon.py b/tests/units/components/lucide/test_icon.py index 71d5ab32a63..5412f05c17f 100644 --- a/tests/units/components/lucide/test_icon.py +++ b/tests/units/components/lucide/test_icon.py @@ -4,8 +4,7 @@ LUCIDE_ICON_MAPPING_OVERRIDE, Icon, ) - -from reflex.utils import format +from reflex_core.utils import format @pytest.mark.parametrize("tag", LUCIDE_ICON_LIST) diff --git a/tests/units/test_event.py b/tests/units/test_event.py index fdf07e69101..9cd80487895 100644 --- a/tests/units/test_event.py +++ b/tests/units/test_event.py @@ -16,11 +16,11 @@ event, fix_events, ) +from reflex_core.utils import format from reflex_core.vars.base import Field, LiteralVar, Var, VarData, field import reflex as rx from reflex.state import BaseState -from reflex.utils import format def make_var(value) -> Var: diff --git a/tests/units/test_state.py b/tests/units/test_state.py index 1b5fb1a9bcd..cc6b3ac5df4 100644 --- a/tests/units/test_state.py +++ b/tests/units/test_state.py @@ -25,6 +25,7 @@ from reflex_core.constants import CompileVars, RouteVar, SocketEvent from reflex_core.constants.state import FIELD_MARKER from reflex_core.event import Event, EventHandler +from reflex_core.utils import format, types from reflex_core.utils.exceptions import ( InvalidLockWarningThresholdError, LockExpiredError, @@ -57,7 +58,7 @@ _substate_key, ) from reflex.testing import chdir -from reflex.utils import format, prerequisites, types +from reflex.utils import prerequisites from reflex.utils.token_manager import SocketRecord from tests.units.mock_redis import mock_redis From 28c876312a2944cc01d382af98ca0a937c2a037d Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Thu, 26 Mar 2026 16:01:30 -0700 Subject: [PATCH 076/113] minimize util imports in packages --- .../src/reflex_components_gridjs/datatable.py | 3 +-- .../src/reflex_components_lucide/icon.py | 3 +-- .../src/reflex_components_markdown/markdown.py | 3 +-- .../src/reflex_components_plotly/plotly.py | 3 +-- .../themes/components/radio_group.py | 2 +- .../reflex_components_react_player/react_player.py | 3 +-- .../src/reflex_components_sonner/toast.py | 3 +-- packages/reflex-core/src/reflex_core/config.py | 2 +- pyi_hashes.json | 12 ++++++------ reflex/components/props.py | 3 +-- reflex/experimental/__init__.py | 2 +- reflex/istate/data.py | 5 ++--- reflex/istate/proxy.py | 2 +- reflex/model.py | 2 +- reflex/reflex.py | 2 +- reflex/state.py | 2 +- reflex/utils/redir.py | 2 +- reflex/utils/tasks.py | 2 +- tests/units/components/datadisplay/test_datatable.py | 2 +- tests/units/components/graphing/test_plotly.py | 2 +- tests/units/components/media/test_image.py | 2 +- tests/units/test_app.py | 2 +- tests/units/test_sqlalchemy.py | 2 +- tests/units/utils/test_format.py | 3 +-- 24 files changed, 30 insertions(+), 39 deletions(-) diff --git a/packages/reflex-components-gridjs/src/reflex_components_gridjs/datatable.py b/packages/reflex-components-gridjs/src/reflex_components_gridjs/datatable.py index b25cd12336a..9725e8d94f1 100644 --- a/packages/reflex-components-gridjs/src/reflex_components_gridjs/datatable.py +++ b/packages/reflex-components-gridjs/src/reflex_components_gridjs/datatable.py @@ -9,10 +9,9 @@ from reflex_core.components.tags import Tag from reflex_core.utils import types from reflex_core.utils.imports import ImportDict +from reflex_core.utils.serializers import serialize from reflex_core.vars.base import LiteralVar, Var, is_computed_var -from reflex.utils.serializers import serialize - class Gridjs(NoSSRComponent): """A component that wraps a nivo bar component.""" diff --git a/packages/reflex-components-lucide/src/reflex_components_lucide/icon.py b/packages/reflex-components-lucide/src/reflex_components_lucide/icon.py index 42cc7cb8b8c..beb3eb13b86 100644 --- a/packages/reflex-components-lucide/src/reflex_components_lucide/icon.py +++ b/packages/reflex-components-lucide/src/reflex_components_lucide/icon.py @@ -1,12 +1,11 @@ """Lucide Icon component.""" from reflex_core.components.component import Component, field +from reflex_core.utils import console, format from reflex_core.utils.imports import ImportVar from reflex_core.vars.base import LiteralVar, Var from reflex_core.vars.sequence import LiteralStringVar, StringVar -from reflex.utils import console, format - LUCIDE_LIBRARY = "lucide-react@0.577.0" diff --git a/packages/reflex-components-markdown/src/reflex_components_markdown/markdown.py b/packages/reflex-components-markdown/src/reflex_components_markdown/markdown.py index 8ab7ab68a2d..6e90c93390a 100644 --- a/packages/reflex-components-markdown/src/reflex_components_markdown/markdown.py +++ b/packages/reflex-components-markdown/src/reflex_components_markdown/markdown.py @@ -19,13 +19,12 @@ field, ) from reflex_core.components.tags.tag import Tag +from reflex_core.utils import console from reflex_core.utils.imports import ImportDict, ImportTypes, ImportVar from reflex_core.vars.base import LiteralVar, Var, VarData from reflex_core.vars.number import ternary_operation from reflex_core.vars.sequence import LiteralArrayVar -from reflex.utils import console - # Special vars used in the component map. _CHILDREN = Var(_js_expr="children", _var_type=str) _PROPS = Var(_js_expr="props") diff --git a/packages/reflex-components-plotly/src/reflex_components_plotly/plotly.py b/packages/reflex-components-plotly/src/reflex_components_plotly/plotly.py index db3009121e7..38dcd233bb6 100644 --- a/packages/reflex-components-plotly/src/reflex_components_plotly/plotly.py +++ b/packages/reflex-components-plotly/src/reflex_components_plotly/plotly.py @@ -7,11 +7,10 @@ from reflex_components_core.core.cond import color_mode_cond from reflex_core.components.component import Component, NoSSRComponent, field from reflex_core.event import EventHandler, no_args_event_spec +from reflex_core.utils import console from reflex_core.utils.imports import ImportDict, ImportVar from reflex_core.vars.base import LiteralVar, Var -from reflex.utils import console - try: from plotly.graph_objs import Figure from plotly.graph_objs.layout import Template diff --git a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/radio_group.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/radio_group.py index 6f8854b207f..8b6e7da1aed 100644 --- a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/radio_group.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/radio_group.py @@ -8,11 +8,11 @@ from reflex_components_core.core.breakpoints import Responsive from reflex_core.components.component import Component, ComponentNamespace, field from reflex_core.event import EventHandler, passthrough_event_spec +from reflex_core.utils import types from reflex_core.vars.base import LiteralVar, Var from reflex_core.vars.sequence import StringVar import reflex as rx -from reflex.utils import types from reflex_components_radix.themes.base import ( LiteralAccentColor, LiteralSpacing, diff --git a/packages/reflex-components-react-player/src/reflex_components_react_player/react_player.py b/packages/reflex-components-react-player/src/reflex_components_react_player/react_player.py index 345f1e4ee65..c117be99a3f 100644 --- a/packages/reflex-components-react-player/src/reflex_components_react_player/react_player.py +++ b/packages/reflex-components-react-player/src/reflex_components_react_player/react_player.py @@ -7,11 +7,10 @@ from reflex_components_core.core.cond import cond from reflex_core.components.component import Component, field from reflex_core.event import EventHandler, no_args_event_spec +from reflex_core.utils import console from reflex_core.vars.base import Var from reflex_core.vars.object import ObjectVar -from reflex.utils import console - ReactPlayerEvent = ObjectVar[dict[str, dict[str, dict[str, Any]]]] diff --git a/packages/reflex-components-sonner/src/reflex_components_sonner/toast.py b/packages/reflex-components-sonner/src/reflex_components_sonner/toast.py index e74512ed717..f967c93b8e8 100644 --- a/packages/reflex-components-sonner/src/reflex_components_sonner/toast.py +++ b/packages/reflex-components-sonner/src/reflex_components_sonner/toast.py @@ -13,14 +13,13 @@ from reflex_core.style import Style, resolved_color_mode from reflex_core.utils import format from reflex_core.utils.imports import ImportVar +from reflex_core.utils.serializers import serializer from reflex_core.vars import VarData from reflex_core.vars.base import LiteralVar, Var from reflex_core.vars.function import FunctionVar from reflex_core.vars.number import ternary_operation from reflex_core.vars.object import ObjectVar -from reflex.utils.serializers import serializer - LiteralPosition = Literal[ "top-left", "top-center", diff --git a/packages/reflex-core/src/reflex_core/config.py b/packages/reflex-core/src/reflex_core/config.py index 1607caf1467..b598b9d039a 100644 --- a/packages/reflex-core/src/reflex_core/config.py +++ b/packages/reflex-core/src/reflex_core/config.py @@ -457,7 +457,7 @@ def json(self) -> str: """ import json - from reflex.utils.serializers import serialize + from reflex_core.utils.serializers import serialize return json.dumps(self, default=serialize) diff --git a/pyi_hashes.json b/pyi_hashes.json index d58a5e29cb1..050fbd516c5 100644 --- a/pyi_hashes.json +++ b/pyi_hashes.json @@ -37,11 +37,11 @@ "packages/reflex-components-core/src/reflex_components_core/el/elements/tables.pyi": "528741e7b4a6a3c231bb91de4caa5fe4", "packages/reflex-components-core/src/reflex_components_core/el/elements/typography.pyi": "97cc78a8b98273d0c6117793a5df47f5", "packages/reflex-components-dataeditor/src/reflex_components_dataeditor/dataeditor.pyi": "707afe3ba888c8c6b02bdf727354866e", - "packages/reflex-components-gridjs/src/reflex_components_gridjs/datatable.pyi": "9d5e0cc9f10732ec198401e411efafde", - "packages/reflex-components-lucide/src/reflex_components_lucide/icon.pyi": "584c9aabc0e8a0ed5ccf1a9b8675fa50", - "packages/reflex-components-markdown/src/reflex_components_markdown/markdown.pyi": "567a4e0461c40603ae2ef3e8ccf7e854", + "packages/reflex-components-gridjs/src/reflex_components_gridjs/datatable.pyi": "2631fe0dd1606c83cb4bb76ea3f301e7", + "packages/reflex-components-lucide/src/reflex_components_lucide/icon.pyi": "d55482d15dee014c74f5585fdf63cd7e", + "packages/reflex-components-markdown/src/reflex_components_markdown/markdown.pyi": "5f9c263166f5c77f2b5c7567fafb8944", "packages/reflex-components-moment/src/reflex_components_moment/moment.pyi": "0d568e627f13dcbb53ce7c838cdb86eb", - "packages/reflex-components-plotly/src/reflex_components_plotly/plotly.pyi": "a108fc904e1d6230e8a5297800f51d1b", + "packages/reflex-components-plotly/src/reflex_components_plotly/plotly.pyi": "0d37990e3c915a3b0d9794981e2a2f51", "packages/reflex-components-radix/src/reflex_components_radix/__init__.pyi": "899d63be42eb21c187519920518bf32e", "packages/reflex-components-radix/src/reflex_components_radix/primitives/__init__.pyi": "11bc17db31334f11d7fcde315b29cae1", "packages/reflex-components-radix/src/reflex_components_radix/primitives/accordion.pyi": "78ee6a43956dba85266068212dedfd60", @@ -76,7 +76,7 @@ "packages/reflex-components-radix/src/reflex_components_radix/themes/components/progress.pyi": "f217303c45eb80d60807f72d230384a9", "packages/reflex-components-radix/src/reflex_components_radix/themes/components/radio.pyi": "1dbb0c928b5cb7dc4d1fe77178423fc1", "packages/reflex-components-radix/src/reflex_components_radix/themes/components/radio_cards.pyi": "512beb694d1a561855e3dd942e2d8ef9", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/radio_group.pyi": "586ef32f5faf8dd5aadd06097b086ce5", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/radio_group.pyi": "749eaed376ad1e43bb1ddc4784014f96", "packages/reflex-components-radix/src/reflex_components_radix/themes/components/scroll_area.pyi": "0e1595623e30f81f9e123a1d4a6d66fd", "packages/reflex-components-radix/src/reflex_components_radix/themes/components/segmented_control.pyi": "133c29bb43db5525134040e33f1b68d5", "packages/reflex-components-radix/src/reflex_components_radix/themes/components/select.pyi": "28f8a6b2ea2bf4218f01c791f1a5c79c", @@ -108,7 +108,7 @@ "packages/reflex-components-radix/src/reflex_components_radix/themes/typography/link.pyi": "7620c22dad86ba0613f7d612234171c4", "packages/reflex-components-radix/src/reflex_components_radix/themes/typography/text.pyi": "01e2911337135e59c836e616e6888a75", "packages/reflex-components-react-player/src/reflex_components_react_player/audio.pyi": "551894a3f295deac9fed3fffb3f171b7", - "packages/reflex-components-react-player/src/reflex_components_react_player/react_player.pyi": "df829c5cd25203086b775b552f711ed0", + "packages/reflex-components-react-player/src/reflex_components_react_player/react_player.pyi": "1bf51a767d1a3a4e0e109c08e4b3877e", "packages/reflex-components-react-player/src/reflex_components_react_player/video.pyi": "4ca94af43518e83e1b9ee5da55a4843b", "packages/reflex-components-react-router/src/reflex_components_react_router/dom.pyi": "9fc7643e6212632a42efbc03421ef9fd", "packages/reflex-components-recharts/src/reflex_components_recharts/__init__.pyi": "1f04ab9f482261f18010fbf728d7b946", diff --git a/reflex/components/props.py b/reflex/components/props.py index 5bd9c2f59cc..22f3396915b 100644 --- a/reflex/components/props.py +++ b/reflex/components/props.py @@ -10,12 +10,11 @@ from reflex_core.event import EventChain, args_specs_from_fields from reflex_core.utils import format from reflex_core.utils.exceptions import InvalidPropValueError +from reflex_core.utils.serializers import serializer from reflex_core.utils.types import is_union from reflex_core.vars.object import LiteralObjectVar from typing_extensions import dataclass_transform -from reflex.utils.serializers import serializer - PROPS_FIELD_TYPE = TypeVar("PROPS_FIELD_TYPE") diff --git a/reflex/experimental/__init__.py b/reflex/experimental/__init__.py index 0291883c6c9..1dbaf20ae88 100644 --- a/reflex/experimental/__init__.py +++ b/reflex/experimental/__init__.py @@ -3,8 +3,8 @@ from types import SimpleNamespace from reflex_components_code.shiki_code_block import code_block as code_block +from reflex_core.utils.console import warn -from reflex.utils.console import warn from reflex.utils.misc import run_in_thread from . import hooks as hooks diff --git a/reflex/istate/data.py b/reflex/istate/data.py index 4e86370ab73..0ff2fd0c6b9 100644 --- a/reflex/istate/data.py +++ b/reflex/istate/data.py @@ -7,9 +7,8 @@ from urllib.parse import _NetlocResultMixinStr, parse_qsl, urlsplit from reflex_core import constants - -from reflex.utils import console, format -from reflex.utils.serializers import serializer +from reflex_core.utils import console, format +from reflex_core.utils.serializers import serializer @dataclasses.dataclass(frozen=True, init=False) diff --git a/reflex/istate/proxy.py b/reflex/istate/proxy.py index 75391b6732c..df1feeb162d 100644 --- a/reflex/istate/proxy.py +++ b/reflex/istate/proxy.py @@ -17,11 +17,11 @@ import wrapt from reflex_core.event import Event from reflex_core.utils.exceptions import ImmutableStateError +from reflex_core.utils.serializers import can_serialize, serialize, serializer from reflex_core.vars.base import Var from typing_extensions import Self from reflex.utils import prerequisites -from reflex.utils.serializers import can_serialize, serialize, serializer if TYPE_CHECKING: from reflex.state import BaseState, StateUpdate diff --git a/reflex/model.py b/reflex/model.py index 37717ba1655..95650842a11 100644 --- a/reflex/model.py +++ b/reflex/model.py @@ -11,9 +11,9 @@ from reflex_core.config import get_config from reflex_core.environment import environment from reflex_core.utils import console +from reflex_core.utils.serializers import serializer from reflex.utils.compat import sqlmodel_field_has_primary_key -from reflex.utils.serializers import serializer if TYPE_CHECKING: from typing import TypeVar diff --git a/reflex/reflex.py b/reflex/reflex.py index c449378c21b..b605400e1bf 100644 --- a/reflex/reflex.py +++ b/reflex/reflex.py @@ -11,9 +11,9 @@ from reflex_core import constants from reflex_core.config import get_config from reflex_core.environment import environment +from reflex_core.utils import console from reflex.custom_components.custom_components import custom_components_cli -from reflex.utils import console if TYPE_CHECKING: from reflex_cli.constants.base import LogLevel as HostingLogLevel diff --git a/reflex/state.py b/reflex/state.py index 7f652267c15..d420702c5b4 100644 --- a/reflex/state.py +++ b/reflex/state.py @@ -2174,7 +2174,7 @@ def get_value(self, key: str) -> Any: """ if isinstance(key, MutableProxy): # Legacy behavior from v0.7.14: handle non-string keys with deprecation warning - from reflex.utils import console + from reflex_core.utils import console console.deprecate( feature_name="Non-string keys in get_value", diff --git a/reflex/utils/redir.py b/reflex/utils/redir.py index 76333bc93fa..a12b7efb078 100644 --- a/reflex/utils/redir.py +++ b/reflex/utils/redir.py @@ -14,7 +14,7 @@ def open_browser(target_url: "SplitResult") -> None: """ import webbrowser - from reflex.utils import console + from reflex_core.utils import console if not webbrowser.open(target_url.geturl()): console.warn( diff --git a/reflex/utils/tasks.py b/reflex/utils/tasks.py index ec51d94af22..6bb47fe60ae 100644 --- a/reflex/utils/tasks.py +++ b/reflex/utils/tasks.py @@ -5,7 +5,7 @@ from collections.abc import Callable, Coroutine from typing import Any -from reflex.utils import console +from reflex_core.utils import console async def _run_forever( diff --git a/tests/units/components/datadisplay/test_datatable.py b/tests/units/components/datadisplay/test_datatable.py index 6243bd93631..84b6c3a0f8a 100644 --- a/tests/units/components/datadisplay/test_datatable.py +++ b/tests/units/components/datadisplay/test_datatable.py @@ -3,10 +3,10 @@ from reflex_components_gridjs.datatable import DataTable from reflex_core.constants.state import FIELD_MARKER from reflex_core.utils.exceptions import UntypedComputedVarError +from reflex_core.utils.serializers import serialize, serialize_dataframe import reflex as rx from reflex.utils import types -from reflex.utils.serializers import serialize, serialize_dataframe @pytest.mark.parametrize( diff --git a/tests/units/components/graphing/test_plotly.py b/tests/units/components/graphing/test_plotly.py index d060a0fd3d3..46d70a20e9c 100644 --- a/tests/units/components/graphing/test_plotly.py +++ b/tests/units/components/graphing/test_plotly.py @@ -1,9 +1,9 @@ import numpy as np import plotly.graph_objects as go import pytest +from reflex_core.utils.serializers import serialize, serialize_figure import reflex as rx -from reflex.utils.serializers import serialize, serialize_figure @pytest.fixture diff --git a/tests/units/components/media/test_image.py b/tests/units/components/media/test_image.py index 3ab74eb688f..103b6fb135c 100644 --- a/tests/units/components/media/test_image.py +++ b/tests/units/components/media/test_image.py @@ -2,9 +2,9 @@ import PIL import pytest from PIL.Image import Image as Img +from reflex_core.utils.serializers import serialize, serialize_image import reflex as rx -from reflex.utils.serializers import serialize, serialize_image @pytest.fixture diff --git a/tests/units/test_app.py b/tests/units/test_app.py index d0166f37ed0..d34cb93283d 100644 --- a/tests/units/test_app.py +++ b/tests/units/test_app.py @@ -24,6 +24,7 @@ from reflex_core.constants.state import FIELD_MARKER from reflex_core.event import Event from reflex_core.style import Style +from reflex_core.utils import console, exceptions, format from reflex_core.vars.base import computed_var from starlette.applications import Starlette from starlette.datastructures import FormData, Headers, UploadFile @@ -52,7 +53,6 @@ StateUpdate, _substate_key, ) -from reflex.utils import console, exceptions, format from .conftest import chdir from .states import GenState diff --git a/tests/units/test_sqlalchemy.py b/tests/units/test_sqlalchemy.py index ca8e663cdfa..4f6c14b2dbc 100644 --- a/tests/units/test_sqlalchemy.py +++ b/tests/units/test_sqlalchemy.py @@ -3,12 +3,12 @@ from unittest import mock import pytest +from reflex_core.utils.serializers import serializer import reflex.constants import reflex.model from reflex.model import Model, ModelRegistry, sqla_session from reflex.state import MutableProxy -from reflex.utils.serializers import serializer pytest.importorskip("sqlalchemy") pytest.importorskip("sqlmodel") diff --git a/tests/units/utils/test_format.py b/tests/units/utils/test_format.py index c6d043f2ec9..38cebccad72 100644 --- a/tests/units/utils/test_format.py +++ b/tests/units/utils/test_format.py @@ -17,12 +17,11 @@ ) from reflex_core.style import Style from reflex_core.utils import format +from reflex_core.utils.serializers import serialize_figure from reflex_core.vars.base import LiteralVar, Var from reflex_core.vars.function import FunctionStringVar from reflex_core.vars.object import ObjectVar -from reflex.utils.serializers import serialize_figure - pytest.importorskip("pydantic") From 5ee6a0d39fae5dc2b052aab3a26cfc14b0af6666 Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Thu, 26 Mar 2026 16:02:42 -0700 Subject: [PATCH 077/113] move props --- .../reflex-core/src/reflex_core}/components/props.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename {reflex => packages/reflex-core/src/reflex_core}/components/props.py (100%) diff --git a/reflex/components/props.py b/packages/reflex-core/src/reflex_core/components/props.py similarity index 100% rename from reflex/components/props.py rename to packages/reflex-core/src/reflex_core/components/props.py From ab0ab54affa513e1c73def19d8778b18af89b95a Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Thu, 26 Mar 2026 16:03:46 -0700 Subject: [PATCH 078/113] more import changes --- .../src/reflex_core/components/props.py | 3 +- .../src/reflex_core/utils/pyi_generator.py | 4 +- pyi_hashes.json | 212 +++++++++--------- reflex/components/props.py | 3 + 4 files changed, 113 insertions(+), 109 deletions(-) create mode 100644 reflex/components/props.py diff --git a/packages/reflex-core/src/reflex_core/components/props.py b/packages/reflex-core/src/reflex_core/components/props.py index 22f3396915b..2ce49c4f7b8 100644 --- a/packages/reflex-core/src/reflex_core/components/props.py +++ b/packages/reflex-core/src/reflex_core/components/props.py @@ -6,6 +6,8 @@ from dataclasses import _MISSING_TYPE, MISSING from typing import Any, TypeVar, get_args, get_origin +from typing_extensions import dataclass_transform + from reflex_core.components.field import BaseField, FieldBasedMeta from reflex_core.event import EventChain, args_specs_from_fields from reflex_core.utils import format @@ -13,7 +15,6 @@ from reflex_core.utils.serializers import serializer from reflex_core.utils.types import is_union from reflex_core.vars.object import LiteralObjectVar -from typing_extensions import dataclass_transform PROPS_FIELD_TYPE = TypeVar("PROPS_FIELD_TYPE") diff --git a/packages/reflex-core/src/reflex_core/utils/pyi_generator.py b/packages/reflex-core/src/reflex_core/utils/pyi_generator.py index 0f4fcb0f6a8..3e6e5dd7906 100644 --- a/packages/reflex-core/src/reflex_core/utils/pyi_generator.py +++ b/packages/reflex-core/src/reflex_core/utils/pyi_generator.py @@ -110,7 +110,7 @@ def _safe_issubclass(cls: Any, cls_check: Any | tuple[Any, ...]) -> bool: DEFAULT_IMPORTS = { "typing": sorted(DEFAULT_TYPING_IMPORTS), "reflex_components_core.core.breakpoints": ["Breakpoints"], - "reflex.event": [ + "reflex_core.event": [ "EventChain", "EventHandler", "EventSpec", @@ -118,7 +118,7 @@ def _safe_issubclass(cls: Any, cls_check: Any | tuple[Any, ...]) -> bool: "KeyInputInfo", "PointerEventInfo", ], - "reflex.style": ["Style"], + "reflex_core.style": ["Style"], "reflex_core.vars.base": ["Var"], } diff --git a/pyi_hashes.json b/pyi_hashes.json index 050fbd516c5..aa34787f5fd 100644 --- a/pyi_hashes.json +++ b/pyi_hashes.json @@ -1,124 +1,124 @@ { - "packages/reflex-components-code/src/reflex_components_code/code.pyi": "3a169db4ce67e1b8891beba47089474a", - "packages/reflex-components-code/src/reflex_components_code/shiki_code_block.pyi": "2c24228690a74c84e47dca765e0ac252", + "packages/reflex-components-code/src/reflex_components_code/code.pyi": "059b4808aff73cc99cd50c979e79602c", + "packages/reflex-components-code/src/reflex_components_code/shiki_code_block.pyi": "82bcf9a9c792b82783f5c54cb7d99b9d", "packages/reflex-components-core/src/reflex_components_core/__init__.pyi": "6c3ceff429117483dd0035e7a21930f4", "packages/reflex-components-core/src/reflex_components_core/base/__init__.pyi": "4beb5ba739680b7974c37241f3f6791c", - "packages/reflex-components-core/src/reflex_components_core/base/app_wrap.pyi": "c0bd031d4a3bcebd7e739185f2b70bdf", - "packages/reflex-components-core/src/reflex_components_core/base/body.pyi": "d05d36b0049a1ca9f48828de0a780264", - "packages/reflex-components-core/src/reflex_components_core/base/document.pyi": "0b7eaf52f99c8f6f785f0ea506c01a8e", - "packages/reflex-components-core/src/reflex_components_core/base/error_boundary.pyi": "38f172c18f8fdeccedda67ad4f92399e", - "packages/reflex-components-core/src/reflex_components_core/base/fragment.pyi": "75455acfdbc65bffb4bf88343ac67807", - "packages/reflex-components-core/src/reflex_components_core/base/link.pyi": "fffd6706dfae3f97e51173ea59e5f43f", - "packages/reflex-components-core/src/reflex_components_core/base/meta.pyi": "26148bf0e78785982608dbec72992b53", - "packages/reflex-components-core/src/reflex_components_core/base/script.pyi": "5d8adfa6c03b3cac1d51ac0b6bb1c015", - "packages/reflex-components-core/src/reflex_components_core/base/strict_mode.pyi": "b0f92e71d1b3df1595d4f6d43199d0ca", + "packages/reflex-components-core/src/reflex_components_core/base/app_wrap.pyi": "8a0b6dcdf622b96be65311b7803c8ce9", + "packages/reflex-components-core/src/reflex_components_core/base/body.pyi": "aa734326f57b0fee9caed75bd318762e", + "packages/reflex-components-core/src/reflex_components_core/base/document.pyi": "8edb8967aa628329c4d1b7cfa3705f3a", + "packages/reflex-components-core/src/reflex_components_core/base/error_boundary.pyi": "8ea2e9080e809db757a5e0978798dbee", + "packages/reflex-components-core/src/reflex_components_core/base/fragment.pyi": "ba9a750fa1036dd4f454e7f3235aa4aa", + "packages/reflex-components-core/src/reflex_components_core/base/link.pyi": "fbae966c13c0da651a1e35f7045799c1", + "packages/reflex-components-core/src/reflex_components_core/base/meta.pyi": "9c1df9038ff6394cac77dbac6b3175c5", + "packages/reflex-components-core/src/reflex_components_core/base/script.pyi": "434ca63fb809077642112d53879380f5", + "packages/reflex-components-core/src/reflex_components_core/base/strict_mode.pyi": "4002f8ac81d1b38177c3b837cbc3b44d", "packages/reflex-components-core/src/reflex_components_core/core/__init__.pyi": "17f79762cef09c69acd9df227cf3bb35", - "packages/reflex-components-core/src/reflex_components_core/core/auto_scroll.pyi": "74dbe0be2d13b0e56ab601a1018641ac", - "packages/reflex-components-core/src/reflex_components_core/core/banner.pyi": "cbaf7af560c335460cb0f5248574a3f9", - "packages/reflex-components-core/src/reflex_components_core/core/clipboard.pyi": "94bc06be97b50d180efa409b37a8ea21", - "packages/reflex-components-core/src/reflex_components_core/core/debounce.pyi": "6a6c2cbffa849f28e8c9f943258dc865", - "packages/reflex-components-core/src/reflex_components_core/core/helmet.pyi": "0325ece6e0553dfa9914a6fed67ec3e7", - "packages/reflex-components-core/src/reflex_components_core/core/html.pyi": "4d2b918dc1bf189699120217958e0158", - "packages/reflex-components-core/src/reflex_components_core/core/sticky.pyi": "a88098e93ec116bf1f27ba1a07f573fb", - "packages/reflex-components-core/src/reflex_components_core/core/upload.pyi": "fc802e5e985bd850e02aae2bc7265fd7", - "packages/reflex-components-core/src/reflex_components_core/core/window_events.pyi": "f0922feff9af64672591e52e9049ee39", + "packages/reflex-components-core/src/reflex_components_core/core/auto_scroll.pyi": "3f8b625f5b38a9351b01201c7adb2ca0", + "packages/reflex-components-core/src/reflex_components_core/core/banner.pyi": "2041580964e872f09de6e0c2bb29aa6f", + "packages/reflex-components-core/src/reflex_components_core/core/clipboard.pyi": "4164c841934cab71b1c4b132d15663f5", + "packages/reflex-components-core/src/reflex_components_core/core/debounce.pyi": "d01e9934bcfd81b5fc969d82e362ac20", + "packages/reflex-components-core/src/reflex_components_core/core/helmet.pyi": "3bf7bee5665293f7583009f651ea3cb1", + "packages/reflex-components-core/src/reflex_components_core/core/html.pyi": "7209d1607545e412ed38dbe2a129321c", + "packages/reflex-components-core/src/reflex_components_core/core/sticky.pyi": "8241c75ca16a0960b7dea6d6e7aff52e", + "packages/reflex-components-core/src/reflex_components_core/core/upload.pyi": "54ba94e554dc3a310e67c44ef1a7be98", + "packages/reflex-components-core/src/reflex_components_core/core/window_events.pyi": "6051d938c93aaa40101550811a19731c", "packages/reflex-components-core/src/reflex_components_core/datadisplay/__init__.pyi": "73e27e0bfd7e914c4baba7b166e75736", "packages/reflex-components-core/src/reflex_components_core/el/__init__.pyi": "a1d4d65ddc73b5fbba3726f4bcf6d6f0", - "packages/reflex-components-core/src/reflex_components_core/el/element.pyi": "e724b6718d7658093729fd864ba3bf2f", + "packages/reflex-components-core/src/reflex_components_core/el/element.pyi": "6add8b77380ea3702031b07330fc7d60", "packages/reflex-components-core/src/reflex_components_core/el/elements/__init__.pyi": "ef8c4981b144e16b57e2fb805bf6dad5", - "packages/reflex-components-core/src/reflex_components_core/el/elements/base.pyi": "449ebb073d99a00a5de4ed1cf15a4e45", - "packages/reflex-components-core/src/reflex_components_core/el/elements/forms.pyi": "f44148daeb87118f31110b9d63da621c", - "packages/reflex-components-core/src/reflex_components_core/el/elements/inline.pyi": "1c527256bf880aa176a0bc9d734fac1f", - "packages/reflex-components-core/src/reflex_components_core/el/elements/media.pyi": "e47cd555937c6302a610d3d3101f606c", - "packages/reflex-components-core/src/reflex_components_core/el/elements/metadata.pyi": "b570e8a8008ac724c8a3db04da1adbc6", - "packages/reflex-components-core/src/reflex_components_core/el/elements/other.pyi": "0ac451f7246836ef59f9870570990299", - "packages/reflex-components-core/src/reflex_components_core/el/elements/scripts.pyi": "8914ccb733a9bab90ca03c2224941e2a", - "packages/reflex-components-core/src/reflex_components_core/el/elements/sectioning.pyi": "6cd334cc7c5999ee4ae4c03455688313", - "packages/reflex-components-core/src/reflex_components_core/el/elements/tables.pyi": "528741e7b4a6a3c231bb91de4caa5fe4", - "packages/reflex-components-core/src/reflex_components_core/el/elements/typography.pyi": "97cc78a8b98273d0c6117793a5df47f5", + "packages/reflex-components-core/src/reflex_components_core/el/elements/base.pyi": "7c427fcd82fc6ccf86b4d2b5c4756426", + "packages/reflex-components-core/src/reflex_components_core/el/elements/forms.pyi": "5912179a169da4dc3b152042558be2cf", + "packages/reflex-components-core/src/reflex_components_core/el/elements/inline.pyi": "6bf366f345e14a556dbb3c0f230e1355", + "packages/reflex-components-core/src/reflex_components_core/el/elements/media.pyi": "b50ff4c79bb55c2c0e2aba6f1613040a", + "packages/reflex-components-core/src/reflex_components_core/el/elements/metadata.pyi": "20d803fcc05d4c378547ceaa0e1bcc70", + "packages/reflex-components-core/src/reflex_components_core/el/elements/other.pyi": "1cf906cbc2751f87adbcd85e03b72d2e", + "packages/reflex-components-core/src/reflex_components_core/el/elements/scripts.pyi": "b4b5bb69e6ce8d08c0df51301e132af4", + "packages/reflex-components-core/src/reflex_components_core/el/elements/sectioning.pyi": "4ce119b25459a01d128bdb5b79b0d128", + "packages/reflex-components-core/src/reflex_components_core/el/elements/tables.pyi": "9e58353a97dc006d37d2c7c50506fac4", + "packages/reflex-components-core/src/reflex_components_core/el/elements/typography.pyi": "3325f8a4af0aadb70cbfc50558e2f3b2", "packages/reflex-components-dataeditor/src/reflex_components_dataeditor/dataeditor.pyi": "707afe3ba888c8c6b02bdf727354866e", - "packages/reflex-components-gridjs/src/reflex_components_gridjs/datatable.pyi": "2631fe0dd1606c83cb4bb76ea3f301e7", - "packages/reflex-components-lucide/src/reflex_components_lucide/icon.pyi": "d55482d15dee014c74f5585fdf63cd7e", - "packages/reflex-components-markdown/src/reflex_components_markdown/markdown.pyi": "5f9c263166f5c77f2b5c7567fafb8944", - "packages/reflex-components-moment/src/reflex_components_moment/moment.pyi": "0d568e627f13dcbb53ce7c838cdb86eb", - "packages/reflex-components-plotly/src/reflex_components_plotly/plotly.pyi": "0d37990e3c915a3b0d9794981e2a2f51", + "packages/reflex-components-gridjs/src/reflex_components_gridjs/datatable.pyi": "32880736442800061a39ce4b55267eaf", + "packages/reflex-components-lucide/src/reflex_components_lucide/icon.pyi": "e55c023c9ecc907321f163955f4c4875", + "packages/reflex-components-markdown/src/reflex_components_markdown/markdown.pyi": "bacf19a5b6904281d7238dbd51e6fc1c", + "packages/reflex-components-moment/src/reflex_components_moment/moment.pyi": "b997bdd994844f0e6ca923bbb2dc34a1", + "packages/reflex-components-plotly/src/reflex_components_plotly/plotly.pyi": "65a93d778a0fde06975dac9244f51bb3", "packages/reflex-components-radix/src/reflex_components_radix/__init__.pyi": "899d63be42eb21c187519920518bf32e", "packages/reflex-components-radix/src/reflex_components_radix/primitives/__init__.pyi": "11bc17db31334f11d7fcde315b29cae1", - "packages/reflex-components-radix/src/reflex_components_radix/primitives/accordion.pyi": "78ee6a43956dba85266068212dedfd60", - "packages/reflex-components-radix/src/reflex_components_radix/primitives/base.pyi": "98eac310dc0ddc5db75945955d606470", - "packages/reflex-components-radix/src/reflex_components_radix/primitives/dialog.pyi": "3289e8cf039ac7b59bc8207bc0dd946d", - "packages/reflex-components-radix/src/reflex_components_radix/primitives/drawer.pyi": "b380d9a0da1d6b580d21a2fb7a17df02", - "packages/reflex-components-radix/src/reflex_components_radix/primitives/form.pyi": "221eb2d8b4cdaa85a70b6c1c2fd72acf", - "packages/reflex-components-radix/src/reflex_components_radix/primitives/progress.pyi": "0a4d68580ee8ebc6ce26b79672dd5c5c", - "packages/reflex-components-radix/src/reflex_components_radix/primitives/slider.pyi": "2d3ce47fa10cfe94c56a4a6353dfb3c1", + "packages/reflex-components-radix/src/reflex_components_radix/primitives/accordion.pyi": "6dd30847af62ad7d50d5c5daf6c4a1d7", + "packages/reflex-components-radix/src/reflex_components_radix/primitives/base.pyi": "6c12ef3d9f82926bf17d410b774d56f5", + "packages/reflex-components-radix/src/reflex_components_radix/primitives/dialog.pyi": "810fa8c626b79035cdbb04f43b5bc5ad", + "packages/reflex-components-radix/src/reflex_components_radix/primitives/drawer.pyi": "cb67e835f9be41f70ee2bae0f8c0a764", + "packages/reflex-components-radix/src/reflex_components_radix/primitives/form.pyi": "ba31009535c078df0bc5a26bce6dfd2b", + "packages/reflex-components-radix/src/reflex_components_radix/primitives/progress.pyi": "2356caa9e23f9c8888cccbbb41b57985", + "packages/reflex-components-radix/src/reflex_components_radix/primitives/slider.pyi": "f694033992ef188f2da04e865d5a7d77", "packages/reflex-components-radix/src/reflex_components_radix/themes/__init__.pyi": "a152450345e7da9124e954843224e936", - "packages/reflex-components-radix/src/reflex_components_radix/themes/base.pyi": "581e9a092afd222eeb7dfc1ce6c7f37d", - "packages/reflex-components-radix/src/reflex_components_radix/themes/color_mode.pyi": "cc2f97d2bece1cd27fd9cdd9364e271f", + "packages/reflex-components-radix/src/reflex_components_radix/themes/base.pyi": "d7c20bd180f28fdb4affcba37e2aa1ff", + "packages/reflex-components-radix/src/reflex_components_radix/themes/color_mode.pyi": "ba0ff3b00289cd1896e327fa2be99563", "packages/reflex-components-radix/src/reflex_components_radix/themes/components/__init__.pyi": "67d45aa96a97359ee556725641bb583f", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/alert_dialog.pyi": "5bbe097edac2e111bdf65e23acf66bd8", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/aspect_ratio.pyi": "cd3955a5ccbf4a11aae1a0c2095c6d44", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/avatar.pyi": "c08892505373db7c19e866690ee78729", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/badge.pyi": "2bc0e5959b4bed487287afcbcb0a932b", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/button.pyi": "d4bf410164ee11479d5314f98d54f30a", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/callout.pyi": "fb3daca9a05decedc085f5e237859761", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/card.pyi": "38c32cdda959e32f713eb3712e414694", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/checkbox.pyi": "0fc9f6955a3d7ba4f44dc626fe3eedf2", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/checkbox_cards.pyi": "533c4565102f388358cc0685fdde965c", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/checkbox_group.pyi": "1309b694cc7413806f16fa214e0066d7", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/context_menu.pyi": "9e7c85a0c2c836d791b43d7e91ad5f53", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/data_list.pyi": "84f08ca6f06dfb4cc9075affa14afa27", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/dialog.pyi": "57cec7903d1b86c32ef977864bb36f57", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/dropdown_menu.pyi": "67b699b5b6ffb378689332dd2acf6cdb", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/hover_card.pyi": "8bf3ade8415908aad11da736dd23ad18", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/icon_button.pyi": "94664faf3a019aa6d9c901d59c16ed9e", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/inset.pyi": "7f11b97ca89644c0fa72dddf24d9bfc1", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/popover.pyi": "a3e999dd58187f97d47f3e25e6e8730e", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/progress.pyi": "f217303c45eb80d60807f72d230384a9", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/radio.pyi": "1dbb0c928b5cb7dc4d1fe77178423fc1", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/radio_cards.pyi": "512beb694d1a561855e3dd942e2d8ef9", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/radio_group.pyi": "749eaed376ad1e43bb1ddc4784014f96", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/scroll_area.pyi": "0e1595623e30f81f9e123a1d4a6d66fd", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/segmented_control.pyi": "133c29bb43db5525134040e33f1b68d5", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/select.pyi": "28f8a6b2ea2bf4218f01c791f1a5c79c", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/separator.pyi": "373b43e18f3cb82d6d8e7a773f2744c4", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/skeleton.pyi": "2da5b95690cce684946f2801ff05efe6", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/slider.pyi": "60e2d765ba6264316eb224b36dcf080a", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/spinner.pyi": "283509251b927c9d33f776028b498e48", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/switch.pyi": "e94e1bbb4af7f59ccc808298ac6e001e", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/table.pyi": "ce54dd03efc4909aaf5271bc085dc3b0", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/tabs.pyi": "b1509e88841ffc75e1bd75d247eec167", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/text_area.pyi": "a0e31e2f2efdc2835db411fd6fad5255", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/text_field.pyi": "e1cd369c151987d3bb3d2f34c6a7a8b3", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/tooltip.pyi": "5ee3130487817e083408a6042ba6e31c", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/alert_dialog.pyi": "fad40b463a8ebb0d3ca3900dc8a91679", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/aspect_ratio.pyi": "0d22969c5407592a0bb36768e149f2b5", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/avatar.pyi": "6a6e9b8f6ca3428c45d62bd0e7f94693", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/badge.pyi": "d96b2048ae17a558d9eb3378ae98524e", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/button.pyi": "1c7f518e1881e98614eadff952da0844", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/callout.pyi": "377ca214324f6841e4113b4405e6018e", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/card.pyi": "b56fa19913ed15d9e630951e70479b36", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/checkbox.pyi": "f33d86a3bb176e3144570198ce5f93ae", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/checkbox_cards.pyi": "10af49cf574b738d616803df2c055ad0", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/checkbox_group.pyi": "3edbddceb585fd80d9e7959977ff276e", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/context_menu.pyi": "0a06f6fa5cf8a2590c302f618451ca65", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/data_list.pyi": "82508b83193afde0b3bc06911cb78f87", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/dialog.pyi": "8f21ba52183221d4cf0b8beaacd8e006", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/dropdown_menu.pyi": "b167c32571142878305d98c0bd656b09", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/hover_card.pyi": "8009f36c543c1407e2aa7ead41178ceb", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/icon_button.pyi": "3814bb2950e2bcc454d186d50d123e9f", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/inset.pyi": "31af9b53ec38736ab7457ea731642869", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/popover.pyi": "2ca6dfe4f9e00f2647f0ad4fd131e6d3", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/progress.pyi": "7a874fa512ce2d8a490aa41531f5814b", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/radio.pyi": "465a6d6e9525ac909b4f193d2d788682", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/radio_cards.pyi": "ddb2835ecbeaf90681e4030a14d74604", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/radio_group.pyi": "c67cadf189a6b0b937a170833469fd05", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/scroll_area.pyi": "6b2d881a8ecdf4dd169b341418a703db", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/segmented_control.pyi": "79764047f53543d673d6e1b2c929d9b8", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/select.pyi": "37bce63b12836813d0a84b784cac4806", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/separator.pyi": "f8420b5196edb74275d2119d780d0031", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/skeleton.pyi": "574407d03b311ca9cdf0f98ab53a6fbe", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/slider.pyi": "9e26688af77fab944635e16e0bf7283f", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/spinner.pyi": "3477cc5e00146eaa2cde5d35f9459ad6", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/switch.pyi": "37e0c8dc43c5a24bdba03429e3ca9052", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/table.pyi": "c910ebd02d7a78627f884e3431426552", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/tabs.pyi": "ead639a106a76cc0e3fd2c8f093f9f23", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/text_area.pyi": "1a03d9525a1544816392067499c3354d", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/text_field.pyi": "c2fbd8547de4993e03017844e8c4b477", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/tooltip.pyi": "4545b70fd0802f19993419ab0163d595", "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/__init__.pyi": "b7dbdecab770a7fc1435ad417d7f91c1", - "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/base.pyi": "a4634ccd1cc51c1faea3304fa8455bdd", - "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/box.pyi": "744e6e83ab08c3f151715c5e459a03d2", - "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/center.pyi": "c80413d4051b478e37c10a5e6e132f25", - "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/container.pyi": "1d875cd301433c0ac2037afe0edf0490", - "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/flex.pyi": "a3f46d350c5e39505193fb6cecdeb0b4", - "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/grid.pyi": "d9dd4b2baa4e2d93017f5b1f5acac76a", - "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/list.pyi": "33cb4b45304ecfe335638a6b2fcde48d", - "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/section.pyi": "f3a2bddcb76c710bdaedcf360194c96a", - "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/spacer.pyi": "02275e5a87cf57b880f6ee0cdde9ac94", - "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/stack.pyi": "46a9ab812ca9d8efea3c4600e237e07c", + "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/base.pyi": "3195e198d92ff43644a09c277303b83b", + "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/box.pyi": "69d12b6c918a476ac4557f42fef73c27", + "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/center.pyi": "88880d197ff7347ec7d3f81d6e57de8e", + "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/container.pyi": "f329387a5d4a988bc195e6a487ff44db", + "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/flex.pyi": "855c9d0c3c2e79e7d3811cfec74d6379", + "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/grid.pyi": "bd2c31d4e3d61743b72327f071969e05", + "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/list.pyi": "2c67a8ab89ec0c931ce321712d11d8dc", + "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/section.pyi": "1aa57142797597d65d840eb1d3cc7de7", + "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/spacer.pyi": "c774f0a1384f983e6d73bde603c341ca", + "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/stack.pyi": "894dcd5945123c1c8aa34cb77602fead", "packages/reflex-components-radix/src/reflex_components_radix/themes/typography/__init__.pyi": "15b13877fc52b82182fc42acd8adb027", - "packages/reflex-components-radix/src/reflex_components_radix/themes/typography/blockquote.pyi": "d16ce407e74de4d0326a601806e61525", - "packages/reflex-components-radix/src/reflex_components_radix/themes/typography/code.pyi": "348bde77a9bbf79b376792adcd9878e2", - "packages/reflex-components-radix/src/reflex_components_radix/themes/typography/heading.pyi": "bcfba34856a9ce0377b2c8b03fd5e244", - "packages/reflex-components-radix/src/reflex_components_radix/themes/typography/link.pyi": "7620c22dad86ba0613f7d612234171c4", - "packages/reflex-components-radix/src/reflex_components_radix/themes/typography/text.pyi": "01e2911337135e59c836e616e6888a75", - "packages/reflex-components-react-player/src/reflex_components_react_player/audio.pyi": "551894a3f295deac9fed3fffb3f171b7", - "packages/reflex-components-react-player/src/reflex_components_react_player/react_player.pyi": "1bf51a767d1a3a4e0e109c08e4b3877e", - "packages/reflex-components-react-player/src/reflex_components_react_player/video.pyi": "4ca94af43518e83e1b9ee5da55a4843b", - "packages/reflex-components-react-router/src/reflex_components_react_router/dom.pyi": "9fc7643e6212632a42efbc03421ef9fd", + "packages/reflex-components-radix/src/reflex_components_radix/themes/typography/blockquote.pyi": "305a8932078e4af48e44489e7ce74060", + "packages/reflex-components-radix/src/reflex_components_radix/themes/typography/code.pyi": "fad43053747fb84229cc35296c7028b5", + "packages/reflex-components-radix/src/reflex_components_radix/themes/typography/heading.pyi": "5901c7202a5b135f60cc1407878b4859", + "packages/reflex-components-radix/src/reflex_components_radix/themes/typography/link.pyi": "3df3dfc470322f791eb065c9a39d1d95", + "packages/reflex-components-radix/src/reflex_components_radix/themes/typography/text.pyi": "76f100da40d0e18ad4f7b3387dec1d4a", + "packages/reflex-components-react-player/src/reflex_components_react_player/audio.pyi": "a981a6031015c3a384e6255be88885f1", + "packages/reflex-components-react-player/src/reflex_components_react_player/react_player.pyi": "1f66ea4fa34e8a8fa7473d312daf84b8", + "packages/reflex-components-react-player/src/reflex_components_react_player/video.pyi": "8c1ea5bf4ec27ec6ff2dce462021b094", + "packages/reflex-components-react-router/src/reflex_components_react_router/dom.pyi": "4690df14b7c3893ba3c416fd07d21e91", "packages/reflex-components-recharts/src/reflex_components_recharts/__init__.pyi": "1f04ab9f482261f18010fbf728d7b946", - "packages/reflex-components-recharts/src/reflex_components_recharts/cartesian.pyi": "3209b25c994aa92e0723cead757eec2e", - "packages/reflex-components-recharts/src/reflex_components_recharts/charts.pyi": "938791518f7dbbb04a4d4e3f9b69c1bc", - "packages/reflex-components-recharts/src/reflex_components_recharts/general.pyi": "c96fca4d56d0701d0f99a549b92f8ee2", - "packages/reflex-components-recharts/src/reflex_components_recharts/polar.pyi": "9e68127976b9f6a1129a08d9dfa5b254", - "packages/reflex-components-recharts/src/reflex_components_recharts/recharts.pyi": "c2cf67744cb2e92d5f82ed7aa09a7de0", - "packages/reflex-components-sonner/src/reflex_components_sonner/toast.pyi": "6d0f9dba8d0c3c5ff2212766800e9f4d", + "packages/reflex-components-recharts/src/reflex_components_recharts/cartesian.pyi": "597b9eb86c57f5293c13c128fb972c27", + "packages/reflex-components-recharts/src/reflex_components_recharts/charts.pyi": "303d4b1dc72c08339154907b9b095365", + "packages/reflex-components-recharts/src/reflex_components_recharts/general.pyi": "6e9371bddea95f8e2491d9b3c7e250cd", + "packages/reflex-components-recharts/src/reflex_components_recharts/polar.pyi": "1ce679c002336c7bdbdd6c8ff6f2413c", + "packages/reflex-components-recharts/src/reflex_components_recharts/recharts.pyi": "1b92135de4ea79cb7d94eaaec55b9ab7", + "packages/reflex-components-sonner/src/reflex_components_sonner/toast.pyi": "f09c503c4ab880c13c13d6fa67d708b8", "reflex/__init__.pyi": "c8154c3dde9c519bff7b6da72805b563", "reflex/components/__init__.pyi": "d620d52015f908cda828d231c1064236", - "reflex/experimental/memo.pyi": "702b5693c5ee3ede31fe602643e470bc" + "reflex/experimental/memo.pyi": "d16eccf33993c781e2f8bc2dd8bbd4d4" } diff --git a/reflex/components/props.py b/reflex/components/props.py new file mode 100644 index 00000000000..a139804455f --- /dev/null +++ b/reflex/components/props.py @@ -0,0 +1,3 @@ +"""Re-export from reflex_core.components.props.""" + +from reflex_core.components.props import * From 11c7ce8dddff1a3dea4d9681b856d15dc267aa03 Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Thu, 26 Mar 2026 16:04:59 -0700 Subject: [PATCH 079/113] move literals --- .../reflex-core/src/reflex_core}/components/literals.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename {reflex => packages/reflex-core/src/reflex_core}/components/literals.py (100%) diff --git a/reflex/components/literals.py b/packages/reflex-core/src/reflex_core/components/literals.py similarity index 100% rename from reflex/components/literals.py rename to packages/reflex-core/src/reflex_core/components/literals.py From 9990de68630070d9c17219a3bbfcbf25f4924a82 Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Thu, 26 Mar 2026 16:05:18 -0700 Subject: [PATCH 080/113] insert shim --- pyi_hashes.json | 2 +- reflex/components/literals.py | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) create mode 100644 reflex/components/literals.py diff --git a/pyi_hashes.json b/pyi_hashes.json index aa34787f5fd..024b8cbc669 100644 --- a/pyi_hashes.json +++ b/pyi_hashes.json @@ -36,7 +36,7 @@ "packages/reflex-components-core/src/reflex_components_core/el/elements/sectioning.pyi": "4ce119b25459a01d128bdb5b79b0d128", "packages/reflex-components-core/src/reflex_components_core/el/elements/tables.pyi": "9e58353a97dc006d37d2c7c50506fac4", "packages/reflex-components-core/src/reflex_components_core/el/elements/typography.pyi": "3325f8a4af0aadb70cbfc50558e2f3b2", - "packages/reflex-components-dataeditor/src/reflex_components_dataeditor/dataeditor.pyi": "707afe3ba888c8c6b02bdf727354866e", + "packages/reflex-components-dataeditor/src/reflex_components_dataeditor/dataeditor.pyi": "caa83be6f97faa95588bfa9ae9e9331e", "packages/reflex-components-gridjs/src/reflex_components_gridjs/datatable.pyi": "32880736442800061a39ce4b55267eaf", "packages/reflex-components-lucide/src/reflex_components_lucide/icon.pyi": "e55c023c9ecc907321f163955f4c4875", "packages/reflex-components-markdown/src/reflex_components_markdown/markdown.pyi": "bacf19a5b6904281d7238dbd51e6fc1c", diff --git a/reflex/components/literals.py b/reflex/components/literals.py new file mode 100644 index 00000000000..974b23f9304 --- /dev/null +++ b/reflex/components/literals.py @@ -0,0 +1,3 @@ +"""Re-export from reflex_core.components.literals.""" + +from reflex_core.components.literals import * From ccca366dcce681a1c5dd0dfa7d683882c168b8d6 Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Thu, 26 Mar 2026 16:09:06 -0700 Subject: [PATCH 081/113] add lazy loader --- .../reflex-core/src/reflex_core}/utils/lazy_loader.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename {reflex => packages/reflex-core/src/reflex_core}/utils/lazy_loader.py (100%) diff --git a/reflex/utils/lazy_loader.py b/packages/reflex-core/src/reflex_core/utils/lazy_loader.py similarity index 100% rename from reflex/utils/lazy_loader.py rename to packages/reflex-core/src/reflex_core/utils/lazy_loader.py From 43d581224bc784a800fde9a5fd5ab4c01e2431be Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Thu, 26 Mar 2026 16:09:10 -0700 Subject: [PATCH 082/113] fix imports --- .../src/reflex_components_core/__init__.py | 2 +- .../reflex_components_core/base/__init__.py | 2 +- .../reflex_components_core/core/__init__.py | 2 +- .../datadisplay/__init__.py | 2 +- .../src/reflex_components_core/el/__init__.py | 2 +- .../el/elements/__init__.py | 2 +- .../src/reflex_components_radix/__init__.py | 3 +- .../primitives/__init__.py | 3 +- .../themes/__init__.py | 2 +- .../themes/components/__init__.py | 3 +- .../themes/layout/__init__.py | 3 +- .../themes/typography/__init__.py | 3 +- .../reflex_components_recharts/__init__.py | 2 +- pyi_hashes.json | 123 +----------------- reflex/__init__.py | 2 +- reflex/components/__init__.py | 2 +- reflex/utils/lazy_loader.py | 3 + 17 files changed, 25 insertions(+), 136 deletions(-) create mode 100644 reflex/utils/lazy_loader.py diff --git a/packages/reflex-components-core/src/reflex_components_core/__init__.py b/packages/reflex-components-core/src/reflex_components_core/__init__.py index 87727b9eb05..b202eddb5ba 100644 --- a/packages/reflex-components-core/src/reflex_components_core/__init__.py +++ b/packages/reflex-components-core/src/reflex_components_core/__init__.py @@ -2,7 +2,7 @@ from __future__ import annotations -from reflex.utils import lazy_loader +from reflex_core.utils import lazy_loader _SUBMODULES: set[str] = { "base", diff --git a/packages/reflex-components-core/src/reflex_components_core/base/__init__.py b/packages/reflex-components-core/src/reflex_components_core/base/__init__.py index 6e4cfd66b3c..38e538497e1 100644 --- a/packages/reflex-components-core/src/reflex_components_core/base/__init__.py +++ b/packages/reflex-components-core/src/reflex_components_core/base/__init__.py @@ -2,7 +2,7 @@ from __future__ import annotations -from reflex.utils import lazy_loader +from reflex_core.utils import lazy_loader _SUBMODULES: set[str] = {"app_wrap", "bare"} diff --git a/packages/reflex-components-core/src/reflex_components_core/core/__init__.py b/packages/reflex-components-core/src/reflex_components_core/core/__init__.py index c9babf294a1..e31071804c4 100644 --- a/packages/reflex-components-core/src/reflex_components_core/core/__init__.py +++ b/packages/reflex-components-core/src/reflex_components_core/core/__init__.py @@ -2,7 +2,7 @@ from __future__ import annotations -from reflex.utils import lazy_loader +from reflex_core.utils import lazy_loader _SUBMODULES: set[str] = {"layout"} diff --git a/packages/reflex-components-core/src/reflex_components_core/datadisplay/__init__.py b/packages/reflex-components-core/src/reflex_components_core/datadisplay/__init__.py index 1aff69676cc..33e01746acf 100644 --- a/packages/reflex-components-core/src/reflex_components_core/datadisplay/__init__.py +++ b/packages/reflex-components-core/src/reflex_components_core/datadisplay/__init__.py @@ -2,7 +2,7 @@ from __future__ import annotations -from reflex.utils import lazy_loader +from reflex_core.utils import lazy_loader _SUBMOD_ATTRS: dict[str, list[str]] = { "code": [ diff --git a/packages/reflex-components-core/src/reflex_components_core/el/__init__.py b/packages/reflex-components-core/src/reflex_components_core/el/__init__.py index f0f414fe860..1ab7f65235c 100644 --- a/packages/reflex-components-core/src/reflex_components_core/el/__init__.py +++ b/packages/reflex-components-core/src/reflex_components_core/el/__init__.py @@ -2,7 +2,7 @@ from __future__ import annotations -from reflex.utils import lazy_loader +from reflex_core.utils import lazy_loader from . import elements diff --git a/packages/reflex-components-core/src/reflex_components_core/el/elements/__init__.py b/packages/reflex-components-core/src/reflex_components_core/el/elements/__init__.py index 75f39f7e53b..2b4494bcb35 100644 --- a/packages/reflex-components-core/src/reflex_components_core/el/elements/__init__.py +++ b/packages/reflex-components-core/src/reflex_components_core/el/elements/__init__.py @@ -2,7 +2,7 @@ from __future__ import annotations -from reflex.utils import lazy_loader +from reflex_core.utils import lazy_loader _MAPPING = { "forms": [ diff --git a/packages/reflex-components-radix/src/reflex_components_radix/__init__.py b/packages/reflex-components-radix/src/reflex_components_radix/__init__.py index 6b1673b8887..59a263804fa 100644 --- a/packages/reflex-components-radix/src/reflex_components_radix/__init__.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/__init__.py @@ -2,8 +2,9 @@ from __future__ import annotations +from reflex_core.utils import lazy_loader + from reflex import RADIX_MAPPING -from reflex.utils import lazy_loader _SUBMODULES: set[str] = {"themes", "primitives"} diff --git a/packages/reflex-components-radix/src/reflex_components_radix/primitives/__init__.py b/packages/reflex-components-radix/src/reflex_components_radix/primitives/__init__.py index 7b6cd7b607d..f144c57ecdf 100644 --- a/packages/reflex-components-radix/src/reflex_components_radix/primitives/__init__.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/primitives/__init__.py @@ -2,8 +2,9 @@ from __future__ import annotations +from reflex_core.utils import lazy_loader + from reflex import RADIX_PRIMITIVES_MAPPING -from reflex.utils import lazy_loader _SUBMOD_ATTRS: dict[str, list[str]] = { "".join(k.split("components.radix.primitives.")[-1]): v diff --git a/packages/reflex-components-radix/src/reflex_components_radix/themes/__init__.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/__init__.py index 0afd5aa5d5e..c2cf4b11b88 100644 --- a/packages/reflex-components-radix/src/reflex_components_radix/themes/__init__.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/__init__.py @@ -2,7 +2,7 @@ from __future__ import annotations -from reflex.utils import lazy_loader +from reflex_core.utils import lazy_loader _SUBMODULES: set[str] = {"components", "layout", "typography"} _SUBMOD_ATTRS: dict[str, list[str]] = { diff --git a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/__init__.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/__init__.py index ffb7a580654..5faaf6b4c6f 100644 --- a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/__init__.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/__init__.py @@ -2,8 +2,9 @@ from __future__ import annotations +from reflex_core.utils import lazy_loader + from reflex import RADIX_THEMES_COMPONENTS_MAPPING -from reflex.utils import lazy_loader _SUBMOD_ATTRS: dict[str, list[str]] = { "".join(k.split("components.radix.themes.components.")[-1]): v diff --git a/packages/reflex-components-radix/src/reflex_components_radix/themes/layout/__init__.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/layout/__init__.py index b141146bd16..d72e774af1f 100644 --- a/packages/reflex-components-radix/src/reflex_components_radix/themes/layout/__init__.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/layout/__init__.py @@ -2,8 +2,9 @@ from __future__ import annotations +from reflex_core.utils import lazy_loader + from reflex import RADIX_THEMES_LAYOUT_MAPPING -from reflex.utils import lazy_loader _SUBMOD_ATTRS: dict[str, list[str]] = { "".join(k.split("components.radix.themes.layout.")[-1]): v diff --git a/packages/reflex-components-radix/src/reflex_components_radix/themes/typography/__init__.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/typography/__init__.py index 0ea695ac5ee..50f6544afe4 100644 --- a/packages/reflex-components-radix/src/reflex_components_radix/themes/typography/__init__.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/typography/__init__.py @@ -2,8 +2,9 @@ from __future__ import annotations +from reflex_core.utils import lazy_loader + from reflex import RADIX_THEMES_TYPOGRAPHY_MAPPING -from reflex.utils import lazy_loader _SUBMOD_ATTRS: dict[str, list[str]] = { "".join(k.split("components.radix.themes.typography.")[-1]): v diff --git a/packages/reflex-components-recharts/src/reflex_components_recharts/__init__.py b/packages/reflex-components-recharts/src/reflex_components_recharts/__init__.py index 6b4b358095b..a2a654006e4 100644 --- a/packages/reflex-components-recharts/src/reflex_components_recharts/__init__.py +++ b/packages/reflex-components-recharts/src/reflex_components_recharts/__init__.py @@ -2,7 +2,7 @@ from __future__ import annotations -from reflex.utils import lazy_loader +from reflex_core.utils import lazy_loader _SUBMOD_ATTRS: dict = { "cartesian": [ diff --git a/pyi_hashes.json b/pyi_hashes.json index 024b8cbc669..f378e9040d0 100644 --- a/pyi_hashes.json +++ b/pyi_hashes.json @@ -1,124 +1,5 @@ { - "packages/reflex-components-code/src/reflex_components_code/code.pyi": "059b4808aff73cc99cd50c979e79602c", - "packages/reflex-components-code/src/reflex_components_code/shiki_code_block.pyi": "82bcf9a9c792b82783f5c54cb7d99b9d", - "packages/reflex-components-core/src/reflex_components_core/__init__.pyi": "6c3ceff429117483dd0035e7a21930f4", - "packages/reflex-components-core/src/reflex_components_core/base/__init__.pyi": "4beb5ba739680b7974c37241f3f6791c", - "packages/reflex-components-core/src/reflex_components_core/base/app_wrap.pyi": "8a0b6dcdf622b96be65311b7803c8ce9", - "packages/reflex-components-core/src/reflex_components_core/base/body.pyi": "aa734326f57b0fee9caed75bd318762e", - "packages/reflex-components-core/src/reflex_components_core/base/document.pyi": "8edb8967aa628329c4d1b7cfa3705f3a", - "packages/reflex-components-core/src/reflex_components_core/base/error_boundary.pyi": "8ea2e9080e809db757a5e0978798dbee", - "packages/reflex-components-core/src/reflex_components_core/base/fragment.pyi": "ba9a750fa1036dd4f454e7f3235aa4aa", - "packages/reflex-components-core/src/reflex_components_core/base/link.pyi": "fbae966c13c0da651a1e35f7045799c1", - "packages/reflex-components-core/src/reflex_components_core/base/meta.pyi": "9c1df9038ff6394cac77dbac6b3175c5", - "packages/reflex-components-core/src/reflex_components_core/base/script.pyi": "434ca63fb809077642112d53879380f5", - "packages/reflex-components-core/src/reflex_components_core/base/strict_mode.pyi": "4002f8ac81d1b38177c3b837cbc3b44d", - "packages/reflex-components-core/src/reflex_components_core/core/__init__.pyi": "17f79762cef09c69acd9df227cf3bb35", - "packages/reflex-components-core/src/reflex_components_core/core/auto_scroll.pyi": "3f8b625f5b38a9351b01201c7adb2ca0", - "packages/reflex-components-core/src/reflex_components_core/core/banner.pyi": "2041580964e872f09de6e0c2bb29aa6f", - "packages/reflex-components-core/src/reflex_components_core/core/clipboard.pyi": "4164c841934cab71b1c4b132d15663f5", - "packages/reflex-components-core/src/reflex_components_core/core/debounce.pyi": "d01e9934bcfd81b5fc969d82e362ac20", - "packages/reflex-components-core/src/reflex_components_core/core/helmet.pyi": "3bf7bee5665293f7583009f651ea3cb1", - "packages/reflex-components-core/src/reflex_components_core/core/html.pyi": "7209d1607545e412ed38dbe2a129321c", - "packages/reflex-components-core/src/reflex_components_core/core/sticky.pyi": "8241c75ca16a0960b7dea6d6e7aff52e", - "packages/reflex-components-core/src/reflex_components_core/core/upload.pyi": "54ba94e554dc3a310e67c44ef1a7be98", - "packages/reflex-components-core/src/reflex_components_core/core/window_events.pyi": "6051d938c93aaa40101550811a19731c", - "packages/reflex-components-core/src/reflex_components_core/datadisplay/__init__.pyi": "73e27e0bfd7e914c4baba7b166e75736", - "packages/reflex-components-core/src/reflex_components_core/el/__init__.pyi": "a1d4d65ddc73b5fbba3726f4bcf6d6f0", - "packages/reflex-components-core/src/reflex_components_core/el/element.pyi": "6add8b77380ea3702031b07330fc7d60", - "packages/reflex-components-core/src/reflex_components_core/el/elements/__init__.pyi": "ef8c4981b144e16b57e2fb805bf6dad5", - "packages/reflex-components-core/src/reflex_components_core/el/elements/base.pyi": "7c427fcd82fc6ccf86b4d2b5c4756426", - "packages/reflex-components-core/src/reflex_components_core/el/elements/forms.pyi": "5912179a169da4dc3b152042558be2cf", - "packages/reflex-components-core/src/reflex_components_core/el/elements/inline.pyi": "6bf366f345e14a556dbb3c0f230e1355", - "packages/reflex-components-core/src/reflex_components_core/el/elements/media.pyi": "b50ff4c79bb55c2c0e2aba6f1613040a", - "packages/reflex-components-core/src/reflex_components_core/el/elements/metadata.pyi": "20d803fcc05d4c378547ceaa0e1bcc70", - "packages/reflex-components-core/src/reflex_components_core/el/elements/other.pyi": "1cf906cbc2751f87adbcd85e03b72d2e", - "packages/reflex-components-core/src/reflex_components_core/el/elements/scripts.pyi": "b4b5bb69e6ce8d08c0df51301e132af4", - "packages/reflex-components-core/src/reflex_components_core/el/elements/sectioning.pyi": "4ce119b25459a01d128bdb5b79b0d128", - "packages/reflex-components-core/src/reflex_components_core/el/elements/tables.pyi": "9e58353a97dc006d37d2c7c50506fac4", - "packages/reflex-components-core/src/reflex_components_core/el/elements/typography.pyi": "3325f8a4af0aadb70cbfc50558e2f3b2", - "packages/reflex-components-dataeditor/src/reflex_components_dataeditor/dataeditor.pyi": "caa83be6f97faa95588bfa9ae9e9331e", - "packages/reflex-components-gridjs/src/reflex_components_gridjs/datatable.pyi": "32880736442800061a39ce4b55267eaf", - "packages/reflex-components-lucide/src/reflex_components_lucide/icon.pyi": "e55c023c9ecc907321f163955f4c4875", - "packages/reflex-components-markdown/src/reflex_components_markdown/markdown.pyi": "bacf19a5b6904281d7238dbd51e6fc1c", - "packages/reflex-components-moment/src/reflex_components_moment/moment.pyi": "b997bdd994844f0e6ca923bbb2dc34a1", - "packages/reflex-components-plotly/src/reflex_components_plotly/plotly.pyi": "65a93d778a0fde06975dac9244f51bb3", - "packages/reflex-components-radix/src/reflex_components_radix/__init__.pyi": "899d63be42eb21c187519920518bf32e", - "packages/reflex-components-radix/src/reflex_components_radix/primitives/__init__.pyi": "11bc17db31334f11d7fcde315b29cae1", - "packages/reflex-components-radix/src/reflex_components_radix/primitives/accordion.pyi": "6dd30847af62ad7d50d5c5daf6c4a1d7", - "packages/reflex-components-radix/src/reflex_components_radix/primitives/base.pyi": "6c12ef3d9f82926bf17d410b774d56f5", - "packages/reflex-components-radix/src/reflex_components_radix/primitives/dialog.pyi": "810fa8c626b79035cdbb04f43b5bc5ad", - "packages/reflex-components-radix/src/reflex_components_radix/primitives/drawer.pyi": "cb67e835f9be41f70ee2bae0f8c0a764", - "packages/reflex-components-radix/src/reflex_components_radix/primitives/form.pyi": "ba31009535c078df0bc5a26bce6dfd2b", - "packages/reflex-components-radix/src/reflex_components_radix/primitives/progress.pyi": "2356caa9e23f9c8888cccbbb41b57985", - "packages/reflex-components-radix/src/reflex_components_radix/primitives/slider.pyi": "f694033992ef188f2da04e865d5a7d77", - "packages/reflex-components-radix/src/reflex_components_radix/themes/__init__.pyi": "a152450345e7da9124e954843224e936", - "packages/reflex-components-radix/src/reflex_components_radix/themes/base.pyi": "d7c20bd180f28fdb4affcba37e2aa1ff", - "packages/reflex-components-radix/src/reflex_components_radix/themes/color_mode.pyi": "ba0ff3b00289cd1896e327fa2be99563", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/__init__.pyi": "67d45aa96a97359ee556725641bb583f", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/alert_dialog.pyi": "fad40b463a8ebb0d3ca3900dc8a91679", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/aspect_ratio.pyi": "0d22969c5407592a0bb36768e149f2b5", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/avatar.pyi": "6a6e9b8f6ca3428c45d62bd0e7f94693", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/badge.pyi": "d96b2048ae17a558d9eb3378ae98524e", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/button.pyi": "1c7f518e1881e98614eadff952da0844", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/callout.pyi": "377ca214324f6841e4113b4405e6018e", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/card.pyi": "b56fa19913ed15d9e630951e70479b36", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/checkbox.pyi": "f33d86a3bb176e3144570198ce5f93ae", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/checkbox_cards.pyi": "10af49cf574b738d616803df2c055ad0", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/checkbox_group.pyi": "3edbddceb585fd80d9e7959977ff276e", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/context_menu.pyi": "0a06f6fa5cf8a2590c302f618451ca65", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/data_list.pyi": "82508b83193afde0b3bc06911cb78f87", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/dialog.pyi": "8f21ba52183221d4cf0b8beaacd8e006", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/dropdown_menu.pyi": "b167c32571142878305d98c0bd656b09", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/hover_card.pyi": "8009f36c543c1407e2aa7ead41178ceb", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/icon_button.pyi": "3814bb2950e2bcc454d186d50d123e9f", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/inset.pyi": "31af9b53ec38736ab7457ea731642869", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/popover.pyi": "2ca6dfe4f9e00f2647f0ad4fd131e6d3", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/progress.pyi": "7a874fa512ce2d8a490aa41531f5814b", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/radio.pyi": "465a6d6e9525ac909b4f193d2d788682", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/radio_cards.pyi": "ddb2835ecbeaf90681e4030a14d74604", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/radio_group.pyi": "c67cadf189a6b0b937a170833469fd05", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/scroll_area.pyi": "6b2d881a8ecdf4dd169b341418a703db", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/segmented_control.pyi": "79764047f53543d673d6e1b2c929d9b8", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/select.pyi": "37bce63b12836813d0a84b784cac4806", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/separator.pyi": "f8420b5196edb74275d2119d780d0031", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/skeleton.pyi": "574407d03b311ca9cdf0f98ab53a6fbe", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/slider.pyi": "9e26688af77fab944635e16e0bf7283f", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/spinner.pyi": "3477cc5e00146eaa2cde5d35f9459ad6", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/switch.pyi": "37e0c8dc43c5a24bdba03429e3ca9052", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/table.pyi": "c910ebd02d7a78627f884e3431426552", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/tabs.pyi": "ead639a106a76cc0e3fd2c8f093f9f23", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/text_area.pyi": "1a03d9525a1544816392067499c3354d", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/text_field.pyi": "c2fbd8547de4993e03017844e8c4b477", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/tooltip.pyi": "4545b70fd0802f19993419ab0163d595", - "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/__init__.pyi": "b7dbdecab770a7fc1435ad417d7f91c1", - "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/base.pyi": "3195e198d92ff43644a09c277303b83b", - "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/box.pyi": "69d12b6c918a476ac4557f42fef73c27", - "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/center.pyi": "88880d197ff7347ec7d3f81d6e57de8e", - "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/container.pyi": "f329387a5d4a988bc195e6a487ff44db", - "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/flex.pyi": "855c9d0c3c2e79e7d3811cfec74d6379", - "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/grid.pyi": "bd2c31d4e3d61743b72327f071969e05", - "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/list.pyi": "2c67a8ab89ec0c931ce321712d11d8dc", - "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/section.pyi": "1aa57142797597d65d840eb1d3cc7de7", - "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/spacer.pyi": "c774f0a1384f983e6d73bde603c341ca", - "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/stack.pyi": "894dcd5945123c1c8aa34cb77602fead", - "packages/reflex-components-radix/src/reflex_components_radix/themes/typography/__init__.pyi": "15b13877fc52b82182fc42acd8adb027", - "packages/reflex-components-radix/src/reflex_components_radix/themes/typography/blockquote.pyi": "305a8932078e4af48e44489e7ce74060", - "packages/reflex-components-radix/src/reflex_components_radix/themes/typography/code.pyi": "fad43053747fb84229cc35296c7028b5", - "packages/reflex-components-radix/src/reflex_components_radix/themes/typography/heading.pyi": "5901c7202a5b135f60cc1407878b4859", - "packages/reflex-components-radix/src/reflex_components_radix/themes/typography/link.pyi": "3df3dfc470322f791eb065c9a39d1d95", - "packages/reflex-components-radix/src/reflex_components_radix/themes/typography/text.pyi": "76f100da40d0e18ad4f7b3387dec1d4a", - "packages/reflex-components-react-player/src/reflex_components_react_player/audio.pyi": "a981a6031015c3a384e6255be88885f1", - "packages/reflex-components-react-player/src/reflex_components_react_player/react_player.pyi": "1f66ea4fa34e8a8fa7473d312daf84b8", - "packages/reflex-components-react-player/src/reflex_components_react_player/video.pyi": "8c1ea5bf4ec27ec6ff2dce462021b094", - "packages/reflex-components-react-router/src/reflex_components_react_router/dom.pyi": "4690df14b7c3893ba3c416fd07d21e91", - "packages/reflex-components-recharts/src/reflex_components_recharts/__init__.pyi": "1f04ab9f482261f18010fbf728d7b946", - "packages/reflex-components-recharts/src/reflex_components_recharts/cartesian.pyi": "597b9eb86c57f5293c13c128fb972c27", - "packages/reflex-components-recharts/src/reflex_components_recharts/charts.pyi": "303d4b1dc72c08339154907b9b095365", - "packages/reflex-components-recharts/src/reflex_components_recharts/general.pyi": "6e9371bddea95f8e2491d9b3c7e250cd", - "packages/reflex-components-recharts/src/reflex_components_recharts/polar.pyi": "1ce679c002336c7bdbdd6c8ff6f2413c", - "packages/reflex-components-recharts/src/reflex_components_recharts/recharts.pyi": "1b92135de4ea79cb7d94eaaec55b9ab7", - "packages/reflex-components-sonner/src/reflex_components_sonner/toast.pyi": "f09c503c4ab880c13c13d6fa67d708b8", - "reflex/__init__.pyi": "c8154c3dde9c519bff7b6da72805b563", - "reflex/components/__init__.pyi": "d620d52015f908cda828d231c1064236", + "reflex/__init__.pyi": "e2e675167f52ca535830860e84384103", + "reflex/components/__init__.pyi": "55bb242d5e5428db329b88b4923c2ba5", "reflex/experimental/memo.pyi": "d16eccf33993c781e2f8bc2dd8bbd4d4" } diff --git a/reflex/__init__.py b/reflex/__init__.py index c23f7dc4ea1..b7cabe6f481 100644 --- a/reflex/__init__.py +++ b/reflex/__init__.py @@ -86,7 +86,7 @@ import sys -from reflex.utils import lazy_loader +from reflex_core.utils import lazy_loader if sys.version_info < (3, 11): from reflex_core.utils import console diff --git a/reflex/components/__init__.py b/reflex/components/__init__.py index 77b6acb14c3..d3f33831fc2 100644 --- a/reflex/components/__init__.py +++ b/reflex/components/__init__.py @@ -14,7 +14,7 @@ import sys from types import ModuleType -from reflex.utils import lazy_loader +from reflex_core.utils import lazy_loader # Mapping from subpackage name to the target top-level package. _SUBPACKAGE_TARGETS: dict[str, str] = { diff --git a/reflex/utils/lazy_loader.py b/reflex/utils/lazy_loader.py new file mode 100644 index 00000000000..faf48f398b0 --- /dev/null +++ b/reflex/utils/lazy_loader.py @@ -0,0 +1,3 @@ +"""Re-export from reflex_core.utils.lazy_loader.""" + +from reflex_core.utils.lazy_loader import * From ddd28887b5efd0cb008dbb03d76b303bf16b68ed Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Thu, 26 Mar 2026 16:09:46 -0700 Subject: [PATCH 083/113] update hashes --- pyi_hashes.json | 119 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 119 insertions(+) diff --git a/pyi_hashes.json b/pyi_hashes.json index f378e9040d0..c9759dbcc2c 100644 --- a/pyi_hashes.json +++ b/pyi_hashes.json @@ -1,4 +1,123 @@ { + "packages/reflex-components-code/src/reflex_components_code/code.pyi": "059b4808aff73cc99cd50c979e79602c", + "packages/reflex-components-code/src/reflex_components_code/shiki_code_block.pyi": "82bcf9a9c792b82783f5c54cb7d99b9d", + "packages/reflex-components-core/src/reflex_components_core/__init__.pyi": "e4f253225cf70b62900e25d0a5c16436", + "packages/reflex-components-core/src/reflex_components_core/base/__init__.pyi": "407342f78a72e87489c8b22e40de68b9", + "packages/reflex-components-core/src/reflex_components_core/base/app_wrap.pyi": "8a0b6dcdf622b96be65311b7803c8ce9", + "packages/reflex-components-core/src/reflex_components_core/base/body.pyi": "aa734326f57b0fee9caed75bd318762e", + "packages/reflex-components-core/src/reflex_components_core/base/document.pyi": "8edb8967aa628329c4d1b7cfa3705f3a", + "packages/reflex-components-core/src/reflex_components_core/base/error_boundary.pyi": "8ea2e9080e809db757a5e0978798dbee", + "packages/reflex-components-core/src/reflex_components_core/base/fragment.pyi": "ba9a750fa1036dd4f454e7f3235aa4aa", + "packages/reflex-components-core/src/reflex_components_core/base/link.pyi": "fbae966c13c0da651a1e35f7045799c1", + "packages/reflex-components-core/src/reflex_components_core/base/meta.pyi": "9c1df9038ff6394cac77dbac6b3175c5", + "packages/reflex-components-core/src/reflex_components_core/base/script.pyi": "434ca63fb809077642112d53879380f5", + "packages/reflex-components-core/src/reflex_components_core/base/strict_mode.pyi": "4002f8ac81d1b38177c3b837cbc3b44d", + "packages/reflex-components-core/src/reflex_components_core/core/__init__.pyi": "3c22950d97f6017b8e6cc6a6c83cb4b3", + "packages/reflex-components-core/src/reflex_components_core/core/auto_scroll.pyi": "3f8b625f5b38a9351b01201c7adb2ca0", + "packages/reflex-components-core/src/reflex_components_core/core/banner.pyi": "2041580964e872f09de6e0c2bb29aa6f", + "packages/reflex-components-core/src/reflex_components_core/core/clipboard.pyi": "4164c841934cab71b1c4b132d15663f5", + "packages/reflex-components-core/src/reflex_components_core/core/debounce.pyi": "d01e9934bcfd81b5fc969d82e362ac20", + "packages/reflex-components-core/src/reflex_components_core/core/helmet.pyi": "3bf7bee5665293f7583009f651ea3cb1", + "packages/reflex-components-core/src/reflex_components_core/core/html.pyi": "7209d1607545e412ed38dbe2a129321c", + "packages/reflex-components-core/src/reflex_components_core/core/sticky.pyi": "8241c75ca16a0960b7dea6d6e7aff52e", + "packages/reflex-components-core/src/reflex_components_core/core/upload.pyi": "54ba94e554dc3a310e67c44ef1a7be98", + "packages/reflex-components-core/src/reflex_components_core/core/window_events.pyi": "6051d938c93aaa40101550811a19731c", + "packages/reflex-components-core/src/reflex_components_core/datadisplay/__init__.pyi": "aaab42816119ac0f308841dc5482b3f1", + "packages/reflex-components-core/src/reflex_components_core/el/__init__.pyi": "4ddf8457f8c48e7381e04e20dfa656e5", + "packages/reflex-components-core/src/reflex_components_core/el/element.pyi": "6add8b77380ea3702031b07330fc7d60", + "packages/reflex-components-core/src/reflex_components_core/el/elements/__init__.pyi": "721b328e94510f8328728be1657abbb8", + "packages/reflex-components-core/src/reflex_components_core/el/elements/base.pyi": "7c427fcd82fc6ccf86b4d2b5c4756426", + "packages/reflex-components-core/src/reflex_components_core/el/elements/forms.pyi": "5912179a169da4dc3b152042558be2cf", + "packages/reflex-components-core/src/reflex_components_core/el/elements/inline.pyi": "6bf366f345e14a556dbb3c0f230e1355", + "packages/reflex-components-core/src/reflex_components_core/el/elements/media.pyi": "b50ff4c79bb55c2c0e2aba6f1613040a", + "packages/reflex-components-core/src/reflex_components_core/el/elements/metadata.pyi": "20d803fcc05d4c378547ceaa0e1bcc70", + "packages/reflex-components-core/src/reflex_components_core/el/elements/other.pyi": "1cf906cbc2751f87adbcd85e03b72d2e", + "packages/reflex-components-core/src/reflex_components_core/el/elements/scripts.pyi": "b4b5bb69e6ce8d08c0df51301e132af4", + "packages/reflex-components-core/src/reflex_components_core/el/elements/sectioning.pyi": "4ce119b25459a01d128bdb5b79b0d128", + "packages/reflex-components-core/src/reflex_components_core/el/elements/tables.pyi": "9e58353a97dc006d37d2c7c50506fac4", + "packages/reflex-components-core/src/reflex_components_core/el/elements/typography.pyi": "3325f8a4af0aadb70cbfc50558e2f3b2", + "packages/reflex-components-dataeditor/src/reflex_components_dataeditor/dataeditor.pyi": "caa83be6f97faa95588bfa9ae9e9331e", + "packages/reflex-components-gridjs/src/reflex_components_gridjs/datatable.pyi": "32880736442800061a39ce4b55267eaf", + "packages/reflex-components-lucide/src/reflex_components_lucide/icon.pyi": "e55c023c9ecc907321f163955f4c4875", + "packages/reflex-components-markdown/src/reflex_components_markdown/markdown.pyi": "bacf19a5b6904281d7238dbd51e6fc1c", + "packages/reflex-components-moment/src/reflex_components_moment/moment.pyi": "b997bdd994844f0e6ca923bbb2dc34a1", + "packages/reflex-components-plotly/src/reflex_components_plotly/plotly.pyi": "65a93d778a0fde06975dac9244f51bb3", + "packages/reflex-components-radix/src/reflex_components_radix/__init__.pyi": "a4b84a5b7b26f8f9193e96f0079c3a9e", + "packages/reflex-components-radix/src/reflex_components_radix/primitives/__init__.pyi": "380078ada483194e7c143afbd6fdfbc6", + "packages/reflex-components-radix/src/reflex_components_radix/primitives/accordion.pyi": "6dd30847af62ad7d50d5c5daf6c4a1d7", + "packages/reflex-components-radix/src/reflex_components_radix/primitives/base.pyi": "6c12ef3d9f82926bf17d410b774d56f5", + "packages/reflex-components-radix/src/reflex_components_radix/primitives/dialog.pyi": "810fa8c626b79035cdbb04f43b5bc5ad", + "packages/reflex-components-radix/src/reflex_components_radix/primitives/drawer.pyi": "cb67e835f9be41f70ee2bae0f8c0a764", + "packages/reflex-components-radix/src/reflex_components_radix/primitives/form.pyi": "ba31009535c078df0bc5a26bce6dfd2b", + "packages/reflex-components-radix/src/reflex_components_radix/primitives/progress.pyi": "2356caa9e23f9c8888cccbbb41b57985", + "packages/reflex-components-radix/src/reflex_components_radix/primitives/slider.pyi": "f694033992ef188f2da04e865d5a7d77", + "packages/reflex-components-radix/src/reflex_components_radix/themes/__init__.pyi": "5d9a06872953d3e3df99e1ff154a4e0c", + "packages/reflex-components-radix/src/reflex_components_radix/themes/base.pyi": "d7c20bd180f28fdb4affcba37e2aa1ff", + "packages/reflex-components-radix/src/reflex_components_radix/themes/color_mode.pyi": "ba0ff3b00289cd1896e327fa2be99563", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/__init__.pyi": "545c186b400ebac8a7b3d7e7a17de232", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/alert_dialog.pyi": "fad40b463a8ebb0d3ca3900dc8a91679", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/aspect_ratio.pyi": "0d22969c5407592a0bb36768e149f2b5", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/avatar.pyi": "6a6e9b8f6ca3428c45d62bd0e7f94693", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/badge.pyi": "d96b2048ae17a558d9eb3378ae98524e", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/button.pyi": "1c7f518e1881e98614eadff952da0844", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/callout.pyi": "377ca214324f6841e4113b4405e6018e", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/card.pyi": "b56fa19913ed15d9e630951e70479b36", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/checkbox.pyi": "f33d86a3bb176e3144570198ce5f93ae", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/checkbox_cards.pyi": "10af49cf574b738d616803df2c055ad0", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/checkbox_group.pyi": "3edbddceb585fd80d9e7959977ff276e", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/context_menu.pyi": "0a06f6fa5cf8a2590c302f618451ca65", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/data_list.pyi": "82508b83193afde0b3bc06911cb78f87", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/dialog.pyi": "8f21ba52183221d4cf0b8beaacd8e006", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/dropdown_menu.pyi": "b167c32571142878305d98c0bd656b09", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/hover_card.pyi": "8009f36c543c1407e2aa7ead41178ceb", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/icon_button.pyi": "3814bb2950e2bcc454d186d50d123e9f", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/inset.pyi": "31af9b53ec38736ab7457ea731642869", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/popover.pyi": "2ca6dfe4f9e00f2647f0ad4fd131e6d3", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/progress.pyi": "7a874fa512ce2d8a490aa41531f5814b", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/radio.pyi": "465a6d6e9525ac909b4f193d2d788682", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/radio_cards.pyi": "ddb2835ecbeaf90681e4030a14d74604", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/radio_group.pyi": "c67cadf189a6b0b937a170833469fd05", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/scroll_area.pyi": "6b2d881a8ecdf4dd169b341418a703db", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/segmented_control.pyi": "79764047f53543d673d6e1b2c929d9b8", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/select.pyi": "37bce63b12836813d0a84b784cac4806", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/separator.pyi": "f8420b5196edb74275d2119d780d0031", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/skeleton.pyi": "574407d03b311ca9cdf0f98ab53a6fbe", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/slider.pyi": "9e26688af77fab944635e16e0bf7283f", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/spinner.pyi": "3477cc5e00146eaa2cde5d35f9459ad6", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/switch.pyi": "37e0c8dc43c5a24bdba03429e3ca9052", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/table.pyi": "c910ebd02d7a78627f884e3431426552", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/tabs.pyi": "ead639a106a76cc0e3fd2c8f093f9f23", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/text_area.pyi": "1a03d9525a1544816392067499c3354d", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/text_field.pyi": "c2fbd8547de4993e03017844e8c4b477", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/tooltip.pyi": "4545b70fd0802f19993419ab0163d595", + "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/__init__.pyi": "d53df0885dc31e227726ca715df4b926", + "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/base.pyi": "3195e198d92ff43644a09c277303b83b", + "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/box.pyi": "69d12b6c918a476ac4557f42fef73c27", + "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/center.pyi": "88880d197ff7347ec7d3f81d6e57de8e", + "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/container.pyi": "f329387a5d4a988bc195e6a487ff44db", + "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/flex.pyi": "855c9d0c3c2e79e7d3811cfec74d6379", + "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/grid.pyi": "bd2c31d4e3d61743b72327f071969e05", + "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/list.pyi": "2c67a8ab89ec0c931ce321712d11d8dc", + "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/section.pyi": "1aa57142797597d65d840eb1d3cc7de7", + "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/spacer.pyi": "c774f0a1384f983e6d73bde603c341ca", + "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/stack.pyi": "894dcd5945123c1c8aa34cb77602fead", + "packages/reflex-components-radix/src/reflex_components_radix/themes/typography/__init__.pyi": "4db383f8b76312d82d67152dde97b4e6", + "packages/reflex-components-radix/src/reflex_components_radix/themes/typography/blockquote.pyi": "305a8932078e4af48e44489e7ce74060", + "packages/reflex-components-radix/src/reflex_components_radix/themes/typography/code.pyi": "fad43053747fb84229cc35296c7028b5", + "packages/reflex-components-radix/src/reflex_components_radix/themes/typography/heading.pyi": "5901c7202a5b135f60cc1407878b4859", + "packages/reflex-components-radix/src/reflex_components_radix/themes/typography/link.pyi": "3df3dfc470322f791eb065c9a39d1d95", + "packages/reflex-components-radix/src/reflex_components_radix/themes/typography/text.pyi": "76f100da40d0e18ad4f7b3387dec1d4a", + "packages/reflex-components-react-player/src/reflex_components_react_player/audio.pyi": "a981a6031015c3a384e6255be88885f1", + "packages/reflex-components-react-player/src/reflex_components_react_player/react_player.pyi": "1f66ea4fa34e8a8fa7473d312daf84b8", + "packages/reflex-components-react-player/src/reflex_components_react_player/video.pyi": "8c1ea5bf4ec27ec6ff2dce462021b094", + "packages/reflex-components-react-router/src/reflex_components_react_router/dom.pyi": "4690df14b7c3893ba3c416fd07d21e91", + "packages/reflex-components-recharts/src/reflex_components_recharts/__init__.pyi": "2610c28416f80e2254bd10dde8c29bdf", + "packages/reflex-components-recharts/src/reflex_components_recharts/cartesian.pyi": "597b9eb86c57f5293c13c128fb972c27", + "packages/reflex-components-recharts/src/reflex_components_recharts/charts.pyi": "303d4b1dc72c08339154907b9b095365", + "packages/reflex-components-recharts/src/reflex_components_recharts/general.pyi": "6e9371bddea95f8e2491d9b3c7e250cd", + "packages/reflex-components-recharts/src/reflex_components_recharts/polar.pyi": "1ce679c002336c7bdbdd6c8ff6f2413c", + "packages/reflex-components-recharts/src/reflex_components_recharts/recharts.pyi": "1b92135de4ea79cb7d94eaaec55b9ab7", + "packages/reflex-components-sonner/src/reflex_components_sonner/toast.pyi": "f09c503c4ab880c13c13d6fa67d708b8", "reflex/__init__.pyi": "e2e675167f52ca535830860e84384103", "reflex/components/__init__.pyi": "55bb242d5e5428db329b88b4923c2ba5", "reflex/experimental/memo.pyi": "d16eccf33993c781e2f8bc2dd8bbd4d4" From 742e4a462f0f9162a9f8350e6418d58e2906498f Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Thu, 26 Mar 2026 16:26:28 -0700 Subject: [PATCH 084/113] add "reflex-components-core" to build deps and rewrite banner and logo to use on html elements --- .../reflex-components-code/pyproject.toml | 2 +- .../src/reflex_components_core/core/banner.py | 226 +++++++++--------- .../core/window_events.py | 21 +- .../datadisplay/logo.py | 29 ++- .../src/reflex_components_core/el/__init__.py | 2 +- .../pyproject.toml | 2 +- .../reflex-components-markdown/pyproject.toml | 2 +- .../reflex-components-plotly/pyproject.toml | 2 +- .../reflex-components-radix/pyproject.toml | 2 +- .../themes/components/callout.py | 4 +- .../themes/components/radio_group.py | 6 +- .../themes/components/select.py | 18 +- .../pyproject.toml | 2 +- .../pyproject.toml | 2 +- pyi_hashes.json | 10 +- 15 files changed, 170 insertions(+), 160 deletions(-) diff --git a/packages/reflex-components-code/pyproject.toml b/packages/reflex-components-code/pyproject.toml index 44c6b05beb5..728762ea10c 100644 --- a/packages/reflex-components-code/pyproject.toml +++ b/packages/reflex-components-code/pyproject.toml @@ -24,7 +24,7 @@ targets.sdist.artifacts = ["*.pyi"] targets.wheel.artifacts = ["*.pyi"] [tool.hatch.build.hooks.reflex-pyi] -dependencies = ["ruff", "reflex-core"] +dependencies = ["ruff", "reflex-core", "reflex-components-core"] [build-system] requires = ["hatchling", "uv-dynamic-versioning", "hatch-reflex-pyi"] diff --git a/packages/reflex-components-core/src/reflex_components_core/core/banner.py b/packages/reflex-components-core/src/reflex_components_core/core/banner.py index 3699bc6146f..bd5cc3d9dfd 100644 --- a/packages/reflex-components-core/src/reflex_components_core/core/banner.py +++ b/packages/reflex-components-core/src/reflex_components_core/core/banner.py @@ -16,11 +16,9 @@ from reflex_core.vars.number import BooleanVar from reflex_core.vars.sequence import LiteralArrayVar +from reflex_components_core import el from reflex_components_core.base.fragment import Fragment from reflex_components_core.core.cond import cond -from reflex_components_core.el.elements.inline import Span -from reflex_components_core.el.elements.other import Dialog -from reflex_components_core.el.elements.sectioning import H2 from reflex_components_core.el.elements.typography import Div connect_error_var_data: VarData = VarData( @@ -207,8 +205,8 @@ def create(cls, comp: Component | None = None) -> Component: The connection banner component. """ if not comp: - comp = Div.create( - Span.create( + comp = el.div( + el.span( *default_connection_error(), color="black", font_size="1.125rem", @@ -238,11 +236,11 @@ def create(cls, comp: Component | None = None) -> Component: The connection banner component. """ if not comp: - comp = Span.create(*default_connection_error()) + comp = el.span(*default_connection_error()) return cond( has_too_many_connection_errors, - Dialog.create( - H2.create("Connection Error"), + el.dialog( + el.h2("Connection Error"), comp, open=has_too_many_connection_errors, z_index=9999, @@ -326,7 +324,7 @@ def create(cls, **props) -> Component: Returns: The backend disabled component. """ - import reflex as rx + from reflex_components_core.core.colors import color is_backend_disabled = Var( "backendDisabled", @@ -344,118 +342,128 @@ def create(cls, **props) -> Component: ), ) + warning_icon = el.svg( + el.path( + d="M6.90816 1.34341C7.61776 1.10786 8.38256 1.10786 9.09216 1.34341C9.7989 1.57799 10.3538 2.13435 10.9112 2.91605C11.4668 3.69515 12.0807 4.78145 12.872 6.18175L12.9031 6.23672C13.6946 7.63721 14.3085 8.72348 14.6911 9.60441C15.0755 10.4896 15.267 11.2539 15.1142 11.9881C14.9604 12.7275 14.5811 13.3997 14.0287 13.9079C13.4776 14.4147 12.7273 14.6286 11.7826 14.7313C10.8432 14.8334 9.6143 14.8334 8.0327 14.8334H7.9677C6.38604 14.8334 5.15719 14.8334 4.21778 14.7313C3.27301 14.6286 2.52269 14.4147 1.97164 13.9079C1.41924 13.3997 1.03995 12.7275 0.88613 11.9881C0.733363 11.2539 0.92483 10.4896 1.30926 9.60441C1.69184 8.72348 2.30573 7.63721 3.09722 6.23671L3.12828 6.18175C3.91964 4.78146 4.53355 3.69515 5.08914 2.91605C5.64658 2.13435 6.20146 1.57799 6.90816 1.34341ZM7.3335 11.3334C7.3335 10.9652 7.63063 10.6667 7.99716 10.6667H8.00316C8.3697 10.6667 8.66683 10.9652 8.66683 11.3334C8.66683 11.7016 8.3697 12.0001 8.00316 12.0001H7.99716C7.63063 12.0001 7.3335 11.7016 7.3335 11.3334ZM7.3335 8.66675C7.3335 9.03495 7.63196 9.33341 8.00016 9.33341C8.36836 9.33341 8.66683 9.03495 8.66683 8.66675V6.00008C8.66683 5.63189 8.36836 5.33341 8.00016 5.33341C7.63196 5.33341 7.3335 5.63189 7.3335 6.00008V8.66675Z", + fill_rule="evenodd", + clip_rule="evenodd", + fill=color("amber", 11), + ), + width="16", + height="16", + viewBox="0 0 16 16", + fill="none", + xmlns="http://www.w3.org/2000/svg", + margin_top="0.125rem", + flex_shrink="0", + ) + + info_message = el.div( + el.span( + "If you are the owner of this app, visit ", + el.a( + "Reflex Cloud", + color=color("amber", 11), + text_decoration="underline", + _hover={ + "color": color("amber", 11), + "text_decoration_color": color("amber", 11), + }, + text_decoration_color=color("amber", 10), + href="https://cloud.reflex.dev/", + font_weight="600", + target="_blank", + ), + " for more information on how to resume your app.", + font_size="0.875rem", + font_weight="500", + line_height="1.25rem", + letter_spacing="-0.01094rem", + color=color("amber", 11), + ), + display="flex", + align_items="start", + gap="0.625rem", + border_radius="0.75rem", + border_width="1px", + border_color=color("amber", 5), + background_color=color("amber", 3), + padding="0.625rem", + ) + # Prepend warning icon into info_message children + info_message.children.insert(0, warning_icon) + + resume_button = el.a( + el.button( + "Resume app", + color="rgba(252, 252, 253, 1)", + font_size="0.875rem", + font_weight="600", + line_height="1.25rem", + letter_spacing="-0.01094rem", + height="2.5rem", + padding="0rem 0.75rem", + width="100%", + border_radius="0.75rem", + background=f"linear-gradient(180deg, {color('violet', 9)} 0%, {color('violet', 10)} 100%)", + _hover={ + "background": f"linear-gradient(180deg, {color('violet', 10)} 0%, {color('violet', 10)} 100%)", + }, + ), + width="100%", + text_decoration="none", + href="https://cloud.reflex.dev/", + target="_blank", + ) + + card = el.div( + el.div( + el.div( + "This app is paused", + font_size="1.5rem", + font_weight="600", + line_height="1.25rem", + letter_spacing="-0.0375rem", + ), + info_message, + resume_button, + display="flex", + flex_direction="column", + gap="1rem", + ), + font_family='"Instrument Sans", "Helvetica", "Arial", sans-serif', + position="fixed", + top="50%", + left="50%", + transform="translate(-50%, -50%)", + width="60ch", + max_width="90vw", + border_radius="0.75rem", + border_width="1px", + border_color=color("slate", 4), + padding="1.5rem", + background_color=color("slate", 1), + box_shadow="0px 2px 5px 0px light-dark(rgba(28, 32, 36, 0.03), rgba(0, 0, 0, 0.00))", + ) + return super().create( - rx.cond( + cond( is_backend_disabled, - rx.box( - rx.el.link( + el.div( + el.link( rel="preconnect", href="https://fonts.googleapis.com", ), - rx.el.link( + el.link( rel="preconnect", href="https://fonts.gstatic.com", crossorigin="", ), - rx.el.link( + el.link( href="https://fonts.googleapis.com/css2?family=Instrument+Sans:ital,wght@0,500;0,600&display=swap", rel="stylesheet", ), - rx.box( - rx.vstack( - rx.text( - "This app is paused", - font_size="1.5rem", - font_weight="600", - line_height="1.25rem", - letter_spacing="-0.0375rem", - ), - rx.hstack( - rx.el.svg( - rx.el.path( - d="M6.90816 1.34341C7.61776 1.10786 8.38256 1.10786 9.09216 1.34341C9.7989 1.57799 10.3538 2.13435 10.9112 2.91605C11.4668 3.69515 12.0807 4.78145 12.872 6.18175L12.9031 6.23672C13.6946 7.63721 14.3085 8.72348 14.6911 9.60441C15.0755 10.4896 15.267 11.2539 15.1142 11.9881C14.9604 12.7275 14.5811 13.3997 14.0287 13.9079C13.4776 14.4147 12.7273 14.6286 11.7826 14.7313C10.8432 14.8334 9.6143 14.8334 8.0327 14.8334H7.9677C6.38604 14.8334 5.15719 14.8334 4.21778 14.7313C3.27301 14.6286 2.52269 14.4147 1.97164 13.9079C1.41924 13.3997 1.03995 12.7275 0.88613 11.9881C0.733363 11.2539 0.92483 10.4896 1.30926 9.60441C1.69184 8.72348 2.30573 7.63721 3.09722 6.23671L3.12828 6.18175C3.91964 4.78146 4.53355 3.69515 5.08914 2.91605C5.64658 2.13435 6.20146 1.57799 6.90816 1.34341ZM7.3335 11.3334C7.3335 10.9652 7.63063 10.6667 7.99716 10.6667H8.00316C8.3697 10.6667 8.66683 10.9652 8.66683 11.3334C8.66683 11.7016 8.3697 12.0001 8.00316 12.0001H7.99716C7.63063 12.0001 7.3335 11.7016 7.3335 11.3334ZM7.3335 8.66675C7.3335 9.03495 7.63196 9.33341 8.00016 9.33341C8.36836 9.33341 8.66683 9.03495 8.66683 8.66675V6.00008C8.66683 5.63189 8.36836 5.33341 8.00016 5.33341C7.63196 5.33341 7.3335 5.63189 7.3335 6.00008V8.66675Z", - fill_rule="evenodd", - clip_rule="evenodd", - fill=rx.color("amber", 11), - ), - width="16", - height="16", - viewBox="0 0 16 16", - fill="none", - xmlns="http://www.w3.org/2000/svg", - margin_top="0.125rem", - flex_shrink="0", - ), - rx.text( - "If you are the owner of this app, visit ", - rx.link( - "Reflex Cloud", - color=rx.color("amber", 11), - underline="always", - _hover={ - "color": rx.color("amber", 11), - "text_decoration_color": rx.color( - "amber", 11 - ), - }, - text_decoration_color=rx.color("amber", 10), - href="https://cloud.reflex.dev/", - font_weight="600", - is_external=True, - ), - " for more information on how to resume your app.", - font_size="0.875rem", - font_weight="500", - line_height="1.25rem", - letter_spacing="-0.01094rem", - color=rx.color("amber", 11), - ), - align="start", - gap="0.625rem", - border_radius="0.75rem", - border_width="1px", - border_color=rx.color("amber", 5), - background_color=rx.color("amber", 3), - padding="0.625rem", - ), - rx.link( - rx.el.button( - "Resume app", - color="rgba(252, 252, 253, 1)", - font_size="0.875rem", - font_weight="600", - line_height="1.25rem", - letter_spacing="-0.01094rem", - height="2.5rem", - padding="0rem 0.75rem", - width="100%", - border_radius="0.75rem", - background=f"linear-gradient(180deg, {rx.color('violet', 9)} 0%, {rx.color('violet', 10)} 100%)", - _hover={ - "background": f"linear-gradient(180deg, {rx.color('violet', 10)} 0%, {rx.color('violet', 10)} 100%)", - }, - ), - width="100%", - underline="none", - href="https://cloud.reflex.dev/", - is_external=True, - ), - gap="1rem", - ), - font_family='"Instrument Sans", "Helvetica", "Arial", sans-serif', - position="fixed", - top="50%", - left="50%", - transform="translate(-50%, -50%)", - width="60ch", - max_width="90vw", - border_radius="0.75rem", - border_width="1px", - border_color=rx.color("slate", 4), - padding="1.5rem", - background_color=rx.color("slate", 1), - box_shadow="0px 2px 5px 0px light-dark(rgba(28, 32, 36, 0.03), rgba(0, 0, 0, 0.00))", - ), + card, position="fixed", z_index=9999, backdrop_filter="grayscale(1) blur(5px)", diff --git a/packages/reflex-components-core/src/reflex_components_core/core/window_events.py b/packages/reflex-components-core/src/reflex_components_core/core/window_events.py index 8fec63ddbe1..7ba473f6cfa 100644 --- a/packages/reflex-components-core/src/reflex_components_core/core/window_events.py +++ b/packages/reflex-components-core/src/reflex_components_core/core/window_events.py @@ -6,11 +6,10 @@ from reflex_core.components.component import StatefulComponent, field from reflex_core.constants.compiler import Hooks -from reflex_core.event import key_event, no_args_event_spec +from reflex_core.event import EventHandler, key_event, no_args_event_spec from reflex_core.vars.base import Var, VarData from reflex_core.vars.object import ObjectVar -import reflex as rx from reflex_components_core.base.fragment import Fragment @@ -56,31 +55,31 @@ def _on_storage_spec(e: ObjectVar) -> tuple[Var[str], Var[str], Var[str], Var[st class WindowEventListener(Fragment): """A component that listens for window events.""" - on_resize: rx.EventHandler[_on_resize_spec] = field( + on_resize: EventHandler[_on_resize_spec] = field( doc="Triggered when the browser window is resized. Receives the new width and height in pixels." ) - on_scroll: rx.EventHandler[_on_scroll_spec] = field( + on_scroll: EventHandler[_on_scroll_spec] = field( doc="Triggered when the user scrolls the page. Receives the current horizontal and vertical scroll positions." ) - on_focus: rx.EventHandler[no_args_event_spec] = field( + on_focus: EventHandler[no_args_event_spec] = field( doc="Triggered when the browser tab or window gains focus (e.g. user switches back to the tab)." ) - on_blur: rx.EventHandler[no_args_event_spec] = field( + on_blur: EventHandler[no_args_event_spec] = field( doc="Triggered when the browser tab or window loses focus (e.g. user switches to another tab)." ) - on_visibility_change: rx.EventHandler[_on_visibility_change_spec] = field( + on_visibility_change: EventHandler[_on_visibility_change_spec] = field( doc="Triggered when the page becomes visible or hidden (e.g. tab switch or minimize). Receives a boolean indicating whether the document is hidden." ) - on_before_unload: rx.EventHandler[no_args_event_spec] = field( + on_before_unload: EventHandler[no_args_event_spec] = field( doc="Triggered just before the user navigates away from or closes the page. Useful for cleanup or prompting unsaved-changes warnings." ) - on_key_down: rx.EventHandler[key_event] = field( + on_key_down: EventHandler[key_event] = field( doc="Triggered when a key is pressed anywhere on the page. Receives the key name and active modifier keys (shift, ctrl, alt, meta)." ) - on_popstate: rx.EventHandler[no_args_event_spec] = field( + on_popstate: EventHandler[no_args_event_spec] = field( doc="Triggered when the user navigates back or forward via the browser history buttons." ) - on_storage: rx.EventHandler[_on_storage_spec] = field( + on_storage: EventHandler[_on_storage_spec] = field( doc="Triggered when localStorage or sessionStorage is modified in another tab. Receives the key, old value, new value, and the URL of the document that changed the storage." ) diff --git a/packages/reflex-components-core/src/reflex_components_core/datadisplay/logo.py b/packages/reflex-components-core/src/reflex_components_core/datadisplay/logo.py index 235ed91c2e2..fb1721875bd 100644 --- a/packages/reflex-components-core/src/reflex_components_core/datadisplay/logo.py +++ b/packages/reflex-components-core/src/reflex_components_core/datadisplay/logo.py @@ -1,11 +1,14 @@ """A Reflex logo component.""" -import reflex as rx +from reflex_core.vars import Var -SVG_COLOR = rx.color_mode_cond("#110F1F", "white") +from reflex_components_core import el +from reflex_components_core.core import color_mode_cond +SVG_COLOR = color_mode_cond("#110F1F", "white") -def svg_logo(color: str | rx.Var[str] = SVG_COLOR, **props): + +def svg_logo(color: str | Var[str] = SVG_COLOR, **props): """A Reflex logo SVG. Args: @@ -17,7 +20,7 @@ def svg_logo(color: str | rx.Var[str] = SVG_COLOR, **props): """ def logo_path(d: str): - return rx.el.path(d=d) + return el.path(d=d) paths = [ "M0 11.5999V0.399902H8.96V4.8799H6.72V2.6399H2.24V4.8799H6.72V7.1199H2.24V11.5999H0ZM6.72 11.5999V7.1199H8.96V11.5999H6.72Z", @@ -28,9 +31,9 @@ def logo_path(d: str): "M47.04 4.8799V0.399902H49.28V4.8799H47.04ZM53.76 4.8799V0.399902H56V4.8799H53.76ZM49.28 7.1199V4.8799H53.76V7.1199H49.28ZM47.04 11.5999V7.1199H49.28V11.5999H47.04ZM53.76 11.5999V7.1199H56V11.5999H53.76Z", ] - return rx.el.svg( + return el.svg( *[logo_path(d) for d in paths], - rx.el.title("Reflex"), + el.title("Reflex"), aria_label="Reflex", role="img", width=props.pop("width", "56"), @@ -50,18 +53,20 @@ def logo(**props): Returns: The logo component. """ - return rx.center( - rx.link( - rx.hstack( + return el.div( + el.a( + el.div( "Built with ", svg_logo(), - text_align="center", - align="center", + display="flex", + align_items="center", + gap="0.5em", padding="1em", ), href="https://reflex.dev", - size="3", ), + display="flex", + justify_content="center", width=props.pop("width", "100%"), **props, ) diff --git a/packages/reflex-components-core/src/reflex_components_core/el/__init__.py b/packages/reflex-components-core/src/reflex_components_core/el/__init__.py index 1ab7f65235c..ab397000458 100644 --- a/packages/reflex-components-core/src/reflex_components_core/el/__init__.py +++ b/packages/reflex-components-core/src/reflex_components_core/el/__init__.py @@ -8,7 +8,7 @@ _SUBMODULES: set[str] = {"elements"} _SUBMOD_ATTRS: dict[str, list[str]] = { - # rx.el.a is replaced by React Router's Link. + # el.a is replaced by React Router's Link. f"elements.{k}": [attr for attr in attrs if attr != "a"] for k, attrs in elements._MAPPING.items() } diff --git a/packages/reflex-components-dataeditor/pyproject.toml b/packages/reflex-components-dataeditor/pyproject.toml index fa2614256b2..ce27e3b9770 100644 --- a/packages/reflex-components-dataeditor/pyproject.toml +++ b/packages/reflex-components-dataeditor/pyproject.toml @@ -22,7 +22,7 @@ targets.sdist.artifacts = ["*.pyi"] targets.wheel.artifacts = ["*.pyi"] [tool.hatch.build.hooks.reflex-pyi] -dependencies = ["ruff", "reflex-core"] +dependencies = ["ruff", "reflex-core", "reflex-components-core"] [build-system] requires = ["hatchling", "uv-dynamic-versioning", "hatch-reflex-pyi"] diff --git a/packages/reflex-components-markdown/pyproject.toml b/packages/reflex-components-markdown/pyproject.toml index 08a5078a9d5..98578c8f14d 100644 --- a/packages/reflex-components-markdown/pyproject.toml +++ b/packages/reflex-components-markdown/pyproject.toml @@ -24,7 +24,7 @@ targets.sdist.artifacts = ["*.pyi"] targets.wheel.artifacts = ["*.pyi"] [tool.hatch.build.hooks.reflex-pyi] -dependencies = ["ruff", "reflex-core"] +dependencies = ["ruff", "reflex-core", "reflex-components-core"] [build-system] requires = ["hatchling", "uv-dynamic-versioning", "hatch-reflex-pyi"] diff --git a/packages/reflex-components-plotly/pyproject.toml b/packages/reflex-components-plotly/pyproject.toml index 6fc5f4f8859..9a35c298896 100644 --- a/packages/reflex-components-plotly/pyproject.toml +++ b/packages/reflex-components-plotly/pyproject.toml @@ -22,7 +22,7 @@ targets.sdist.artifacts = ["*.pyi"] targets.wheel.artifacts = ["*.pyi"] [tool.hatch.build.hooks.reflex-pyi] -dependencies = ["ruff", "reflex-core"] +dependencies = ["ruff", "reflex-core", "reflex-components-core"] [build-system] requires = ["hatchling", "uv-dynamic-versioning", "hatch-reflex-pyi"] diff --git a/packages/reflex-components-radix/pyproject.toml b/packages/reflex-components-radix/pyproject.toml index 3d17aedd1b2..0d081b3507f 100644 --- a/packages/reflex-components-radix/pyproject.toml +++ b/packages/reflex-components-radix/pyproject.toml @@ -24,7 +24,7 @@ targets.sdist.artifacts = ["*.pyi"] targets.wheel.artifacts = ["*.pyi"] [tool.hatch.build.hooks.reflex-pyi] -dependencies = ["ruff", "reflex-core"] +dependencies = ["ruff", "reflex-core", "reflex-components-core"] [build-system] requires = ["hatchling", "uv-dynamic-versioning", "hatch-reflex-pyi"] diff --git a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/callout.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/callout.py index 4269ae2bf46..60c7c4a256f 100644 --- a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/callout.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/callout.py @@ -2,13 +2,13 @@ from typing import Literal +from reflex_components_core.base import fragment from reflex_components_core.core.breakpoints import Responsive from reflex_components_core.el import elements from reflex_components_lucide.icon import Icon from reflex_core.components.component import Component, ComponentNamespace, field from reflex_core.vars.base import Var -import reflex as rx from reflex_components_radix.themes.base import LiteralAccentColor, RadixThemesComponent CalloutVariant = Literal["soft", "surface", "outline"] @@ -70,7 +70,7 @@ def create(cls, text: str | Var[str], **props) -> Component: ( CalloutIcon.create(Icon.create(tag=props["icon"])) if "icon" in props - else rx.fragment() + else fragment() ), CalloutText.create(text), **props, diff --git a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/radio_group.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/radio_group.py index 8b6e7da1aed..c33bdfc6cbb 100644 --- a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/radio_group.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/radio_group.py @@ -5,6 +5,7 @@ from collections.abc import Sequence from typing import Literal +from reflex_components_core.core import cond, foreach from reflex_components_core.core.breakpoints import Responsive from reflex_core.components.component import Component, ComponentNamespace, field from reflex_core.event import EventHandler, passthrough_event_spec @@ -12,7 +13,6 @@ from reflex_core.vars.base import LiteralVar, Var from reflex_core.vars.sequence import StringVar -import reflex as rx from reflex_components_radix.themes.base import ( LiteralAccentColor, LiteralSpacing, @@ -178,7 +178,7 @@ def create( default_value = LiteralVar.create(default_value).to_string() def radio_group_item(value: Var) -> Component: - item_value = rx.cond( + item_value = cond( value.js_type() == "string", value, value.to_string(), @@ -198,7 +198,7 @@ def radio_group_item(value: Var) -> Component: ) children = [ - rx.foreach( + foreach( items, radio_group_item, ) diff --git a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/select.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/select.py index 43737b145d0..38336b3830c 100644 --- a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/select.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/select.py @@ -3,13 +3,13 @@ from collections.abc import Sequence from typing import ClassVar, Literal +from reflex_components_core.core import foreach from reflex_components_core.core.breakpoints import Responsive from reflex_core.components.component import Component, ComponentNamespace, field from reflex_core.constants.compiler import MemoizationMode -from reflex_core.event import no_args_event_spec, passthrough_event_spec +from reflex_core.event import EventHandler, no_args_event_spec, passthrough_event_spec from reflex_core.vars.base import Var -import reflex as rx from reflex_components_radix.themes.base import ( LiteralAccentColor, LiteralRadius, @@ -57,11 +57,11 @@ class SelectRoot(RadixThemesComponent): # Props to rename _rename_props = {"onChange": "onValueChange"} - on_change: rx.EventHandler[passthrough_event_spec(str)] = field( + on_change: EventHandler[passthrough_event_spec(str)] = field( doc="Fired when the value of the select changes." ) - on_open_change: rx.EventHandler[passthrough_event_spec(bool)] = field( + on_open_change: EventHandler[passthrough_event_spec(bool)] = field( doc="Fired when the select is opened or closed." ) @@ -121,15 +121,15 @@ class SelectContent(RadixThemesComponent): doc="The vertical distance in pixels from the anchor. Only available when position is set to popper." ) - on_close_auto_focus: rx.EventHandler[no_args_event_spec] = field( + on_close_auto_focus: EventHandler[no_args_event_spec] = field( doc="Fired when the select content is closed." ) - on_escape_key_down: rx.EventHandler[no_args_event_spec] = field( + on_escape_key_down: EventHandler[no_args_event_spec] = field( doc="Fired when the escape key is pressed." ) - on_pointer_down_outside: rx.EventHandler[no_args_event_spec] = field( + on_pointer_down_outside: EventHandler[no_args_event_spec] = field( doc="Fired when a pointer down event happens outside the select content." ) @@ -237,9 +237,7 @@ def create(cls, items: list[str] | Var[list[str]], **props) -> Component: label = props.pop("label", None) if isinstance(items, Var): - child = [ - rx.foreach(items, lambda item: SelectItem.create(item, value=item)) - ] + child = [foreach(items, lambda item: SelectItem.create(item, value=item))] else: child = [SelectItem.create(item, value=item) for item in items] diff --git a/packages/reflex-components-react-player/pyproject.toml b/packages/reflex-components-react-player/pyproject.toml index 01f643e6831..c5abd0581b0 100644 --- a/packages/reflex-components-react-player/pyproject.toml +++ b/packages/reflex-components-react-player/pyproject.toml @@ -22,7 +22,7 @@ targets.sdist.artifacts = ["*.pyi"] targets.wheel.artifacts = ["*.pyi"] [tool.hatch.build.hooks.reflex-pyi] -dependencies = ["ruff", "reflex-core"] +dependencies = ["ruff", "reflex-core", "reflex-components-core"] [build-system] requires = ["hatchling", "uv-dynamic-versioning", "hatch-reflex-pyi"] diff --git a/packages/reflex-components-react-router/pyproject.toml b/packages/reflex-components-react-router/pyproject.toml index 64d6b021862..fcc61f5fadd 100644 --- a/packages/reflex-components-react-router/pyproject.toml +++ b/packages/reflex-components-react-router/pyproject.toml @@ -22,7 +22,7 @@ targets.sdist.artifacts = ["*.pyi"] targets.wheel.artifacts = ["*.pyi"] [tool.hatch.build.hooks.reflex-pyi] -dependencies = ["ruff", "reflex-core"] +dependencies = ["ruff", "reflex-core", "reflex-components-core"] [build-system] requires = ["hatchling", "uv-dynamic-versioning", "hatch-reflex-pyi"] diff --git a/pyi_hashes.json b/pyi_hashes.json index c9759dbcc2c..7294b9b0fb1 100644 --- a/pyi_hashes.json +++ b/pyi_hashes.json @@ -14,14 +14,14 @@ "packages/reflex-components-core/src/reflex_components_core/base/strict_mode.pyi": "4002f8ac81d1b38177c3b837cbc3b44d", "packages/reflex-components-core/src/reflex_components_core/core/__init__.pyi": "3c22950d97f6017b8e6cc6a6c83cb4b3", "packages/reflex-components-core/src/reflex_components_core/core/auto_scroll.pyi": "3f8b625f5b38a9351b01201c7adb2ca0", - "packages/reflex-components-core/src/reflex_components_core/core/banner.pyi": "2041580964e872f09de6e0c2bb29aa6f", + "packages/reflex-components-core/src/reflex_components_core/core/banner.pyi": "b2e4b26b13f33d8900550fedd2d5f447", "packages/reflex-components-core/src/reflex_components_core/core/clipboard.pyi": "4164c841934cab71b1c4b132d15663f5", "packages/reflex-components-core/src/reflex_components_core/core/debounce.pyi": "d01e9934bcfd81b5fc969d82e362ac20", "packages/reflex-components-core/src/reflex_components_core/core/helmet.pyi": "3bf7bee5665293f7583009f651ea3cb1", "packages/reflex-components-core/src/reflex_components_core/core/html.pyi": "7209d1607545e412ed38dbe2a129321c", "packages/reflex-components-core/src/reflex_components_core/core/sticky.pyi": "8241c75ca16a0960b7dea6d6e7aff52e", "packages/reflex-components-core/src/reflex_components_core/core/upload.pyi": "54ba94e554dc3a310e67c44ef1a7be98", - "packages/reflex-components-core/src/reflex_components_core/core/window_events.pyi": "6051d938c93aaa40101550811a19731c", + "packages/reflex-components-core/src/reflex_components_core/core/window_events.pyi": "4407cecb1825dc359bcc7b2bea011a8e", "packages/reflex-components-core/src/reflex_components_core/datadisplay/__init__.pyi": "aaab42816119ac0f308841dc5482b3f1", "packages/reflex-components-core/src/reflex_components_core/el/__init__.pyi": "4ddf8457f8c48e7381e04e20dfa656e5", "packages/reflex-components-core/src/reflex_components_core/el/element.pyi": "6add8b77380ea3702031b07330fc7d60", @@ -60,7 +60,7 @@ "packages/reflex-components-radix/src/reflex_components_radix/themes/components/avatar.pyi": "6a6e9b8f6ca3428c45d62bd0e7f94693", "packages/reflex-components-radix/src/reflex_components_radix/themes/components/badge.pyi": "d96b2048ae17a558d9eb3378ae98524e", "packages/reflex-components-radix/src/reflex_components_radix/themes/components/button.pyi": "1c7f518e1881e98614eadff952da0844", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/callout.pyi": "377ca214324f6841e4113b4405e6018e", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/callout.pyi": "b1cf893a60440b431e44bdeab3654fd9", "packages/reflex-components-radix/src/reflex_components_radix/themes/components/card.pyi": "b56fa19913ed15d9e630951e70479b36", "packages/reflex-components-radix/src/reflex_components_radix/themes/components/checkbox.pyi": "f33d86a3bb176e3144570198ce5f93ae", "packages/reflex-components-radix/src/reflex_components_radix/themes/components/checkbox_cards.pyi": "10af49cf574b738d616803df2c055ad0", @@ -76,10 +76,10 @@ "packages/reflex-components-radix/src/reflex_components_radix/themes/components/progress.pyi": "7a874fa512ce2d8a490aa41531f5814b", "packages/reflex-components-radix/src/reflex_components_radix/themes/components/radio.pyi": "465a6d6e9525ac909b4f193d2d788682", "packages/reflex-components-radix/src/reflex_components_radix/themes/components/radio_cards.pyi": "ddb2835ecbeaf90681e4030a14d74604", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/radio_group.pyi": "c67cadf189a6b0b937a170833469fd05", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/radio_group.pyi": "29e8f508cd30a3d27c7ee3debc986d82", "packages/reflex-components-radix/src/reflex_components_radix/themes/components/scroll_area.pyi": "6b2d881a8ecdf4dd169b341418a703db", "packages/reflex-components-radix/src/reflex_components_radix/themes/components/segmented_control.pyi": "79764047f53543d673d6e1b2c929d9b8", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/select.pyi": "37bce63b12836813d0a84b784cac4806", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/select.pyi": "0cb2e8d2102d33c400d874d306dcbaa7", "packages/reflex-components-radix/src/reflex_components_radix/themes/components/separator.pyi": "f8420b5196edb74275d2119d780d0031", "packages/reflex-components-radix/src/reflex_components_radix/themes/components/skeleton.pyi": "574407d03b311ca9cdf0f98ab53a6fbe", "packages/reflex-components-radix/src/reflex_components_radix/themes/components/slider.pyi": "9e26688af77fab944635e16e0bf7283f", From 72c26adec37575e9f3054b23030128454430ecd8 Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Thu, 26 Mar 2026 16:32:15 -0700 Subject: [PATCH 085/113] does this even work --- packages/reflex-components-core/pyproject.toml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/reflex-components-core/pyproject.toml b/packages/reflex-components-core/pyproject.toml index 407bc59919e..a719d8fc18e 100644 --- a/packages/reflex-components-core/pyproject.toml +++ b/packages/reflex-components-core/pyproject.toml @@ -25,6 +25,10 @@ targets.wheel.artifacts = ["*.pyi"] [tool.hatch.build.hooks.reflex-pyi] dependencies = ["ruff", "reflex-core"] +[tool.uv.sources] +reflex-components-lucide = { workspace = true } +reflex-components-sonner = { workspace = true } + [build-system] requires = ["hatchling", "uv-dynamic-versioning", "hatch-reflex-pyi"] build-backend = "hatchling.build" From 714bd253dfc75382c8f909b365094e44cc1000c9 Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Thu, 26 Mar 2026 16:43:43 -0700 Subject: [PATCH 086/113] maybe --- packages/reflex-components-core/pyproject.toml | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/packages/reflex-components-core/pyproject.toml b/packages/reflex-components-core/pyproject.toml index a719d8fc18e..f1a0af131f5 100644 --- a/packages/reflex-components-core/pyproject.toml +++ b/packages/reflex-components-core/pyproject.toml @@ -6,10 +6,7 @@ readme = "README.md" authors = [{ name = "Khaleel Al-Adhami", email = "khaleel@reflex.dev" }] maintainers = [{ name = "Khaleel Al-Adhami", email = "khaleel@reflex.dev" }] requires-python = ">=3.10" -dependencies = [ - "reflex-components-lucide", - "reflex-components-sonner", -] +dependencies = ["reflex-components-lucide", "reflex-components-sonner"] [tool.hatch.version] source = "uv-dynamic-versioning" @@ -23,11 +20,13 @@ targets.sdist.artifacts = ["*.pyi"] targets.wheel.artifacts = ["*.pyi"] [tool.hatch.build.hooks.reflex-pyi] -dependencies = ["ruff", "reflex-core"] +dependencies = [ + "ruff", + "reflex-core", + "reflex-components-lucide", + "reflex-components-sonner", +] -[tool.uv.sources] -reflex-components-lucide = { workspace = true } -reflex-components-sonner = { workspace = true } [build-system] requires = ["hatchling", "uv-dynamic-versioning", "hatch-reflex-pyi"] From 0f2a1f80399a4fca73e2dea90e8fba24e24d9eb5 Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Thu, 26 Mar 2026 17:00:28 -0700 Subject: [PATCH 087/113] get it to combile --- packages/reflex-components-code/pyproject.toml | 10 +++++++++- .../src/reflex_components_code/code.py | 3 ++- .../src/reflex_components_code/shiki_code_block.py | 3 ++- packages/reflex-components-dataeditor/pyproject.toml | 12 ++++++++---- packages/reflex-components-markdown/pyproject.toml | 8 +++++++- packages/reflex-components-plotly/pyproject.toml | 12 ++++++++---- packages/reflex-components-radix/pyproject.toml | 9 ++++++++- .../themes/components/callout.py | 3 ++- .../reflex_components_radix/themes/layout/list.py | 3 ++- .../reflex-components-react-player/pyproject.toml | 12 ++++++++---- .../reflex-components-react-router/pyproject.toml | 12 ++++++++---- packages/reflex-components-sonner/pyproject.toml | 2 +- pyi_hashes.json | 8 ++++---- uv.lock | 2 -- 14 files changed, 69 insertions(+), 30 deletions(-) diff --git a/packages/reflex-components-code/pyproject.toml b/packages/reflex-components-code/pyproject.toml index 728762ea10c..d9b6908ae65 100644 --- a/packages/reflex-components-code/pyproject.toml +++ b/packages/reflex-components-code/pyproject.toml @@ -24,7 +24,15 @@ targets.sdist.artifacts = ["*.pyi"] targets.wheel.artifacts = ["*.pyi"] [tool.hatch.build.hooks.reflex-pyi] -dependencies = ["ruff", "reflex-core", "reflex-components-core"] +dependencies = [ + "ruff", + "reflex-core", + "reflex-components-core", + "reflex-components-lucide", + "reflex-components-radix", + "reflex-components-sonner", + "reflex-components-react-router", +] [build-system] requires = ["hatchling", "uv-dynamic-versioning", "hatch-reflex-pyi"] diff --git a/packages/reflex-components-code/src/reflex_components_code/code.py b/packages/reflex-components-code/src/reflex_components_code/code.py index 9dbbcb9a65f..86f0ace171e 100644 --- a/packages/reflex-components-code/src/reflex_components_code/code.py +++ b/packages/reflex-components-code/src/reflex_components_code/code.py @@ -7,7 +7,6 @@ from reflex_components_core.core.cond import color_mode_cond from reflex_components_core.core.markdown_component_map import MarkdownComponentMap -from reflex_components_lucide.icon import Icon from reflex_components_radix.themes.components.button import Button from reflex_components_radix.themes.layout.box import Box from reflex_core.components.component import Component, ComponentNamespace, field @@ -443,6 +442,8 @@ def create( Returns: The text component. """ + from reflex_components_lucide.icon import Icon + # This component handles style in a special prop. custom_style = props.pop("custom_style", {}) can_copy = props.pop("can_copy", False) diff --git a/packages/reflex-components-code/src/reflex_components_code/shiki_code_block.py b/packages/reflex-components-code/src/reflex_components_code/shiki_code_block.py index 52e79d6f242..a299ea66b7e 100644 --- a/packages/reflex-components-code/src/reflex_components_code/shiki_code_block.py +++ b/packages/reflex-components-code/src/reflex_components_code/shiki_code_block.py @@ -12,7 +12,6 @@ from reflex_components_core.core.cond import color_mode_cond from reflex_components_core.core.markdown_component_map import MarkdownComponentMap from reflex_components_core.el.elements.forms import Button -from reflex_components_lucide.icon import Icon from reflex_components_radix.themes.layout.box import Box from reflex_core.components.component import Component, ComponentNamespace, field from reflex_core.components.props import NoExtrasAllowedProps @@ -751,6 +750,8 @@ def create( Returns: The code block component. """ + from reflex_components_lucide.icon import Icon + use_transformers = props.pop("use_transformers", False) show_line_numbers = props.pop("show_line_numbers", False) language = props.pop("language", None) diff --git a/packages/reflex-components-dataeditor/pyproject.toml b/packages/reflex-components-dataeditor/pyproject.toml index ce27e3b9770..a0420e0c9ee 100644 --- a/packages/reflex-components-dataeditor/pyproject.toml +++ b/packages/reflex-components-dataeditor/pyproject.toml @@ -6,9 +6,7 @@ readme = "README.md" authors = [{ name = "Khaleel Al-Adhami", email = "khaleel@reflex.dev" }] maintainers = [{ name = "Khaleel Al-Adhami", email = "khaleel@reflex.dev" }] requires-python = ">=3.10" -dependencies = [ - "reflex-components-core", -] +dependencies = ["reflex-components-core"] [tool.hatch.version] source = "uv-dynamic-versioning" @@ -22,7 +20,13 @@ targets.sdist.artifacts = ["*.pyi"] targets.wheel.artifacts = ["*.pyi"] [tool.hatch.build.hooks.reflex-pyi] -dependencies = ["ruff", "reflex-core", "reflex-components-core"] +dependencies = [ + "ruff", + "reflex-core", + "reflex-components-core", + "reflex-components-lucide", + "reflex-components-sonner", +] [build-system] requires = ["hatchling", "uv-dynamic-versioning", "hatch-reflex-pyi"] diff --git a/packages/reflex-components-markdown/pyproject.toml b/packages/reflex-components-markdown/pyproject.toml index 98578c8f14d..b85165c2bf4 100644 --- a/packages/reflex-components-markdown/pyproject.toml +++ b/packages/reflex-components-markdown/pyproject.toml @@ -24,7 +24,13 @@ targets.sdist.artifacts = ["*.pyi"] targets.wheel.artifacts = ["*.pyi"] [tool.hatch.build.hooks.reflex-pyi] -dependencies = ["ruff", "reflex-core", "reflex-components-core"] +dependencies = [ + "ruff", + "reflex-core", + "reflex-components-core", + "reflex-components-lucide", + "reflex-components-sonner", +] [build-system] requires = ["hatchling", "uv-dynamic-versioning", "hatch-reflex-pyi"] diff --git a/packages/reflex-components-plotly/pyproject.toml b/packages/reflex-components-plotly/pyproject.toml index 9a35c298896..aac8d08152d 100644 --- a/packages/reflex-components-plotly/pyproject.toml +++ b/packages/reflex-components-plotly/pyproject.toml @@ -6,9 +6,7 @@ readme = "README.md" authors = [{ name = "Khaleel Al-Adhami", email = "khaleel@reflex.dev" }] maintainers = [{ name = "Khaleel Al-Adhami", email = "khaleel@reflex.dev" }] requires-python = ">=3.10" -dependencies = [ - "reflex-components-core", -] +dependencies = ["reflex-components-core"] [tool.hatch.version] source = "uv-dynamic-versioning" @@ -22,7 +20,13 @@ targets.sdist.artifacts = ["*.pyi"] targets.wheel.artifacts = ["*.pyi"] [tool.hatch.build.hooks.reflex-pyi] -dependencies = ["ruff", "reflex-core", "reflex-components-core"] +dependencies = [ + "ruff", + "reflex-core", + "reflex-components-core", + "reflex-components-lucide", + "reflex-components-sonner", +] [build-system] requires = ["hatchling", "uv-dynamic-versioning", "hatch-reflex-pyi"] diff --git a/packages/reflex-components-radix/pyproject.toml b/packages/reflex-components-radix/pyproject.toml index 0d081b3507f..358329693de 100644 --- a/packages/reflex-components-radix/pyproject.toml +++ b/packages/reflex-components-radix/pyproject.toml @@ -24,7 +24,14 @@ targets.sdist.artifacts = ["*.pyi"] targets.wheel.artifacts = ["*.pyi"] [tool.hatch.build.hooks.reflex-pyi] -dependencies = ["ruff", "reflex-core", "reflex-components-core"] +dependencies = [ + "ruff", + "reflex-core", + "reflex-components-core", + "reflex-components-lucide", + "reflex-components-sonner", + "reflex-components-react-router", +] [build-system] requires = ["hatchling", "uv-dynamic-versioning", "hatch-reflex-pyi"] diff --git a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/callout.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/callout.py index 60c7c4a256f..0d78d2bfd86 100644 --- a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/callout.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/callout.py @@ -5,7 +5,6 @@ from reflex_components_core.base import fragment from reflex_components_core.core.breakpoints import Responsive from reflex_components_core.el import elements -from reflex_components_lucide.icon import Icon from reflex_core.components.component import Component, ComponentNamespace, field from reflex_core.vars.base import Var @@ -66,6 +65,8 @@ def create(cls, text: str | Var[str], **props) -> Component: Returns: The callout component. """ + from reflex_components_lucide.icon import Icon + return CalloutRoot.create( ( CalloutIcon.create(Icon.create(tag=props["icon"])) diff --git a/packages/reflex-components-radix/src/reflex_components_radix/themes/layout/list.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/layout/list.py index 5dd8d25c002..46c1a37de94 100644 --- a/packages/reflex-components-radix/src/reflex_components_radix/themes/layout/list.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/layout/list.py @@ -9,7 +9,6 @@ from reflex_components_core.core.markdown_component_map import MarkdownComponentMap from reflex_components_core.el.elements.base import BaseHTML from reflex_components_core.el.elements.typography import Li, Ol, Ul -from reflex_components_lucide.icon import Icon from reflex_core.components.component import ComponentNamespace, field from reflex_core.vars.base import Var @@ -171,6 +170,8 @@ def create(cls, *children, **props): Returns: The list item component. """ + from reflex_components_lucide.icon import Icon + for child in children: if isinstance(child, Text): child.as_ = "span" # pyright: ignore[reportAttributeAccessIssue] diff --git a/packages/reflex-components-react-player/pyproject.toml b/packages/reflex-components-react-player/pyproject.toml index c5abd0581b0..b8518aff675 100644 --- a/packages/reflex-components-react-player/pyproject.toml +++ b/packages/reflex-components-react-player/pyproject.toml @@ -6,9 +6,7 @@ readme = "README.md" authors = [{ name = "Khaleel Al-Adhami", email = "khaleel@reflex.dev" }] maintainers = [{ name = "Khaleel Al-Adhami", email = "khaleel@reflex.dev" }] requires-python = ">=3.10" -dependencies = [ - "reflex-components-core", -] +dependencies = ["reflex-components-core"] [tool.hatch.version] source = "uv-dynamic-versioning" @@ -22,7 +20,13 @@ targets.sdist.artifacts = ["*.pyi"] targets.wheel.artifacts = ["*.pyi"] [tool.hatch.build.hooks.reflex-pyi] -dependencies = ["ruff", "reflex-core", "reflex-components-core"] +dependencies = [ + "ruff", + "reflex-core", + "reflex-components-core", + "reflex-components-lucide", + "reflex-components-sonner", +] [build-system] requires = ["hatchling", "uv-dynamic-versioning", "hatch-reflex-pyi"] diff --git a/packages/reflex-components-react-router/pyproject.toml b/packages/reflex-components-react-router/pyproject.toml index fcc61f5fadd..53b513c7e53 100644 --- a/packages/reflex-components-react-router/pyproject.toml +++ b/packages/reflex-components-react-router/pyproject.toml @@ -6,9 +6,7 @@ readme = "README.md" authors = [{ name = "Khaleel Al-Adhami", email = "khaleel@reflex.dev" }] maintainers = [{ name = "Khaleel Al-Adhami", email = "khaleel@reflex.dev" }] requires-python = ">=3.10" -dependencies = [ - "reflex-components-core", -] +dependencies = ["reflex-components-core"] [tool.hatch.version] source = "uv-dynamic-versioning" @@ -22,7 +20,13 @@ targets.sdist.artifacts = ["*.pyi"] targets.wheel.artifacts = ["*.pyi"] [tool.hatch.build.hooks.reflex-pyi] -dependencies = ["ruff", "reflex-core", "reflex-components-core"] +dependencies = [ + "ruff", + "reflex-core", + "reflex-components-core", + "reflex-components-lucide", + "reflex-components-sonner", +] [build-system] requires = ["hatchling", "uv-dynamic-versioning", "hatch-reflex-pyi"] diff --git a/packages/reflex-components-sonner/pyproject.toml b/packages/reflex-components-sonner/pyproject.toml index 8fae4b5992a..37099de6214 100644 --- a/packages/reflex-components-sonner/pyproject.toml +++ b/packages/reflex-components-sonner/pyproject.toml @@ -22,7 +22,7 @@ targets.sdist.artifacts = ["*.pyi"] targets.wheel.artifacts = ["*.pyi"] [tool.hatch.build.hooks.reflex-pyi] -dependencies = ["ruff", "reflex-core"] +dependencies = ["ruff", "reflex-core", "reflex-components-lucide"] [build-system] requires = ["hatchling", "uv-dynamic-versioning", "hatch-reflex-pyi"] diff --git a/pyi_hashes.json b/pyi_hashes.json index 7294b9b0fb1..549e9718ebb 100644 --- a/pyi_hashes.json +++ b/pyi_hashes.json @@ -1,6 +1,6 @@ { - "packages/reflex-components-code/src/reflex_components_code/code.pyi": "059b4808aff73cc99cd50c979e79602c", - "packages/reflex-components-code/src/reflex_components_code/shiki_code_block.pyi": "82bcf9a9c792b82783f5c54cb7d99b9d", + "packages/reflex-components-code/src/reflex_components_code/code.pyi": "a252d3efb9c621216c3ac32327158a83", + "packages/reflex-components-code/src/reflex_components_code/shiki_code_block.pyi": "2ae0bc697886c5a735afbe232a84f022", "packages/reflex-components-core/src/reflex_components_core/__init__.pyi": "e4f253225cf70b62900e25d0a5c16436", "packages/reflex-components-core/src/reflex_components_core/base/__init__.pyi": "407342f78a72e87489c8b22e40de68b9", "packages/reflex-components-core/src/reflex_components_core/base/app_wrap.pyi": "8a0b6dcdf622b96be65311b7803c8ce9", @@ -60,7 +60,7 @@ "packages/reflex-components-radix/src/reflex_components_radix/themes/components/avatar.pyi": "6a6e9b8f6ca3428c45d62bd0e7f94693", "packages/reflex-components-radix/src/reflex_components_radix/themes/components/badge.pyi": "d96b2048ae17a558d9eb3378ae98524e", "packages/reflex-components-radix/src/reflex_components_radix/themes/components/button.pyi": "1c7f518e1881e98614eadff952da0844", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/callout.pyi": "b1cf893a60440b431e44bdeab3654fd9", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/callout.pyi": "be245c1c3796f695ac4b2d77c3b88a3a", "packages/reflex-components-radix/src/reflex_components_radix/themes/components/card.pyi": "b56fa19913ed15d9e630951e70479b36", "packages/reflex-components-radix/src/reflex_components_radix/themes/components/checkbox.pyi": "f33d86a3bb176e3144570198ce5f93ae", "packages/reflex-components-radix/src/reflex_components_radix/themes/components/checkbox_cards.pyi": "10af49cf574b738d616803df2c055ad0", @@ -97,7 +97,7 @@ "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/container.pyi": "f329387a5d4a988bc195e6a487ff44db", "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/flex.pyi": "855c9d0c3c2e79e7d3811cfec74d6379", "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/grid.pyi": "bd2c31d4e3d61743b72327f071969e05", - "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/list.pyi": "2c67a8ab89ec0c931ce321712d11d8dc", + "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/list.pyi": "856e0dd916fa69f152b3ec557871a133", "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/section.pyi": "1aa57142797597d65d840eb1d3cc7de7", "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/spacer.pyi": "c774f0a1384f983e6d73bde603c341ca", "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/stack.pyi": "894dcd5945123c1c8aa34cb77602fead", diff --git a/uv.lock b/uv.lock index f0cdeb6f16b..d216a147ade 100644 --- a/uv.lock +++ b/uv.lock @@ -2059,7 +2059,6 @@ dependencies = [ { name = "granian", extra = ["reload"] }, { name = "httpx" }, { name = "packaging" }, - { name = "platformdirs" }, { name = "psutil", marker = "sys_platform == 'win32'" }, { name = "pydantic" }, { name = "python-multipart" }, @@ -2139,7 +2138,6 @@ requires-dist = [ { name = "granian", extras = ["reload"], specifier = ">=2.5.5" }, { name = "httpx", specifier = ">=0.23.3,<1.0" }, { name = "packaging", specifier = ">=24.2,<27" }, - { name = "platformdirs", specifier = ">=4.3.7,<5.0" }, { name = "psutil", marker = "sys_platform == 'win32'", specifier = ">=7.0.0,<8.0" }, { name = "pydantic", specifier = ">=2.12.0,<3.0" }, { name = "pydantic", marker = "extra == 'db'", specifier = ">=2.12.0,<3.0" }, From e5291b210a325a6cc57f7b6ba0601585311056e3 Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Thu, 26 Mar 2026 17:09:29 -0700 Subject: [PATCH 088/113] fix reflex import --- .../src/reflex_components_core/base/error_boundary.py | 3 ++- .../src/reflex_components_core/core/foreach.py | 3 ++- pyi_hashes.json | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/reflex-components-core/src/reflex_components_core/base/error_boundary.py b/packages/reflex-components-core/src/reflex_components_core/base/error_boundary.py index 497aa9f3641..35432a91810 100644 --- a/packages/reflex-components-core/src/reflex_components_core/base/error_boundary.py +++ b/packages/reflex-components-core/src/reflex_components_core/base/error_boundary.py @@ -8,7 +8,6 @@ from reflex_core.vars.function import ArgsFunctionOperation from reflex_core.vars.object import ObjectVar -from reflex.state import FrontendEventExceptionState from reflex_components_core.datadisplay.logo import svg_logo from reflex_components_core.el import a, button, div, h2, hr, p, pre, svg @@ -59,6 +58,8 @@ def create(cls, *children, **props): Returns: The ErrorBoundary component. """ + from reflex.state import FrontendEventExceptionState + if "on_error" not in props: props["on_error"] = FrontendEventExceptionState.handle_frontend_exception if "fallback_render" not in props: diff --git a/packages/reflex-components-core/src/reflex_components_core/core/foreach.py b/packages/reflex-components-core/src/reflex_components_core/core/foreach.py index 10e80ce4255..6e36051577d 100644 --- a/packages/reflex-components-core/src/reflex_components_core/core/foreach.py +++ b/packages/reflex-components-core/src/reflex_components_core/core/foreach.py @@ -16,7 +16,6 @@ from reflex_core.utils.exceptions import UntypedVarError from reflex_core.vars.base import LiteralVar, Var -from reflex.state import ComponentState from reflex_components_core.base.fragment import Fragment from reflex_components_core.core.cond import cond @@ -67,6 +66,8 @@ def create( """ from reflex_core.vars import ArrayVar, ObjectVar, StringVar + from reflex.state import ComponentState + iterable = ( LiteralVar.create(iterable).guess_type() if not isinstance(iterable, Var) diff --git a/pyi_hashes.json b/pyi_hashes.json index 549e9718ebb..5e8c968587a 100644 --- a/pyi_hashes.json +++ b/pyi_hashes.json @@ -6,7 +6,7 @@ "packages/reflex-components-core/src/reflex_components_core/base/app_wrap.pyi": "8a0b6dcdf622b96be65311b7803c8ce9", "packages/reflex-components-core/src/reflex_components_core/base/body.pyi": "aa734326f57b0fee9caed75bd318762e", "packages/reflex-components-core/src/reflex_components_core/base/document.pyi": "8edb8967aa628329c4d1b7cfa3705f3a", - "packages/reflex-components-core/src/reflex_components_core/base/error_boundary.pyi": "8ea2e9080e809db757a5e0978798dbee", + "packages/reflex-components-core/src/reflex_components_core/base/error_boundary.pyi": "c02999fa5d121904a242a83d2221f069", "packages/reflex-components-core/src/reflex_components_core/base/fragment.pyi": "ba9a750fa1036dd4f454e7f3235aa4aa", "packages/reflex-components-core/src/reflex_components_core/base/link.pyi": "fbae966c13c0da651a1e35f7045799c1", "packages/reflex-components-core/src/reflex_components_core/base/meta.pyi": "9c1df9038ff6394cac77dbac6b3175c5", From 4af9fc473c6457ade7fad2c24fbe3529e0711026 Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Thu, 26 Mar 2026 17:23:31 -0700 Subject: [PATCH 089/113] move upload --- .../src/reflex_components_upload}/_upload.py | 0 .../src/reflex_components_upload}/upload.py | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename {reflex => packages/reflex-components-upload/src/reflex_components_upload}/_upload.py (100%) rename packages/{reflex-components-core/src/reflex_components_core/core => reflex-components-upload/src/reflex_components_upload}/upload.py (100%) diff --git a/reflex/_upload.py b/packages/reflex-components-upload/src/reflex_components_upload/_upload.py similarity index 100% rename from reflex/_upload.py rename to packages/reflex-components-upload/src/reflex_components_upload/_upload.py diff --git a/packages/reflex-components-core/src/reflex_components_core/core/upload.py b/packages/reflex-components-upload/src/reflex_components_upload/upload.py similarity index 100% rename from packages/reflex-components-core/src/reflex_components_core/core/upload.py rename to packages/reflex-components-upload/src/reflex_components_upload/upload.py From 5deae8e6b568483dcc95be6181398664415f496d Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Thu, 26 Mar 2026 17:23:53 -0700 Subject: [PATCH 090/113] update imports --- .../src/reflex_components_core/core/upload.py | 3 +++ packages/reflex-components-upload/README.md | 3 +++ .../reflex-components-upload/pyproject.toml | 27 +++++++++++++++++++ .../src/reflex_components_upload/__init__.py | 1 + .../src/reflex_components_upload/_upload.py | 3 +-- .../src/reflex_components_upload/upload.py | 10 +++---- packages/reflex-core/src/reflex_core/event.py | 6 +++-- pyi_hashes.json | 2 +- pyproject.toml | 2 ++ reflex/_upload.py | 3 +++ scripts/make_pyi.py | 1 + uv.lock | 7 +++++ 12 files changed, 58 insertions(+), 10 deletions(-) create mode 100644 packages/reflex-components-core/src/reflex_components_core/core/upload.py create mode 100644 packages/reflex-components-upload/README.md create mode 100644 packages/reflex-components-upload/pyproject.toml create mode 100644 packages/reflex-components-upload/src/reflex_components_upload/__init__.py create mode 100644 reflex/_upload.py diff --git a/packages/reflex-components-core/src/reflex_components_core/core/upload.py b/packages/reflex-components-core/src/reflex_components_core/core/upload.py new file mode 100644 index 00000000000..44d483bddba --- /dev/null +++ b/packages/reflex-components-core/src/reflex_components_core/core/upload.py @@ -0,0 +1,3 @@ +"""Re-export from reflex_components_upload.upload.""" + +from reflex_components_upload.upload import * diff --git a/packages/reflex-components-upload/README.md b/packages/reflex-components-upload/README.md new file mode 100644 index 00000000000..7fb771e8d96 --- /dev/null +++ b/packages/reflex-components-upload/README.md @@ -0,0 +1,3 @@ +# Reflex Upload Components + +File upload components and backend helpers for Reflex apps. diff --git a/packages/reflex-components-upload/pyproject.toml b/packages/reflex-components-upload/pyproject.toml new file mode 100644 index 00000000000..1ef1fa77d51 --- /dev/null +++ b/packages/reflex-components-upload/pyproject.toml @@ -0,0 +1,27 @@ +[project] +name = "reflex-components-upload" +dynamic = ["version"] +description = "Reflex upload components." +readme = "README.md" +authors = [{ name = "Khaleel Al-Adhami", email = "khaleel@reflex.dev" }] +maintainers = [{ name = "Khaleel Al-Adhami", email = "khaleel@reflex.dev" }] +requires-python = ">=3.10" +dependencies = [] + +[tool.hatch.version] +source = "uv-dynamic-versioning" + +[tool.uv-dynamic-versioning] +pattern-prefix = "reflex-components-upload-" +fallback-version = "0.0.0dev0" + +[tool.hatch.build] +targets.sdist.artifacts = ["*.pyi"] +targets.wheel.artifacts = ["*.pyi"] + +[tool.hatch.build.hooks.reflex-pyi] +dependencies = ["ruff", "reflex-core"] + +[build-system] +requires = ["hatchling", "uv-dynamic-versioning", "hatch-reflex-pyi"] +build-backend = "hatchling.build" diff --git a/packages/reflex-components-upload/src/reflex_components_upload/__init__.py b/packages/reflex-components-upload/src/reflex_components_upload/__init__.py new file mode 100644 index 00000000000..6764bebfb4c --- /dev/null +++ b/packages/reflex-components-upload/src/reflex_components_upload/__init__.py @@ -0,0 +1 @@ +"""Reflex upload components.""" diff --git a/packages/reflex-components-upload/src/reflex_components_upload/_upload.py b/packages/reflex-components-upload/src/reflex_components_upload/_upload.py index 4b8820433c1..b096a03cd8a 100644 --- a/packages/reflex-components-upload/src/reflex_components_upload/_upload.py +++ b/packages/reflex-components-upload/src/reflex_components_upload/_upload.py @@ -12,6 +12,7 @@ from python_multipart.multipart import MultipartParser, parse_options_header from reflex_core import constants +from reflex_core.utils import exceptions from starlette.datastructures import Headers from starlette.datastructures import UploadFile as StarletteUploadFile from starlette.exceptions import HTTPException @@ -20,8 +21,6 @@ from starlette.responses import JSONResponse, Response, StreamingResponse from typing_extensions import Self -from reflex.utils import exceptions - if TYPE_CHECKING: from reflex_core.event import EventHandler from reflex_core.utils.types import Receive, Scope, Send diff --git a/packages/reflex-components-upload/src/reflex_components_upload/upload.py b/packages/reflex-components-upload/src/reflex_components_upload/upload.py index 71a3d2031ef..df323e51b03 100644 --- a/packages/reflex-components-upload/src/reflex_components_upload/upload.py +++ b/packages/reflex-components-upload/src/reflex_components_upload/upload.py @@ -6,6 +6,10 @@ from pathlib import Path from typing import Any, ClassVar +from reflex_components_core.base.fragment import Fragment +from reflex_components_core.core.cond import cond +from reflex_components_core.el.elements.forms import Input +from reflex_components_core.el.elements.typography import Div from reflex_components_sonner.toast import toast from reflex_core.components.component import ( Component, @@ -38,11 +42,7 @@ from reflex_core.vars.object import ObjectVar from reflex_core.vars.sequence import ArrayVar, LiteralStringVar -from reflex._upload import UploadChunkIterator, UploadFile -from reflex_components_core.base.fragment import Fragment -from reflex_components_core.core.cond import cond -from reflex_components_core.el.elements.forms import Input -from reflex_components_core.el.elements.typography import Div +from reflex_components_upload._upload import UploadChunkIterator, UploadFile DEFAULT_UPLOAD_ID: str = "default" diff --git a/packages/reflex-core/src/reflex_core/event.py b/packages/reflex-core/src/reflex_core/event.py index 2204ee93140..9140568aaa9 100644 --- a/packages/reflex-core/src/reflex_core/event.py +++ b/packages/reflex-core/src/reflex_core/event.py @@ -128,7 +128,8 @@ def resolve_upload_handler_param(handler: EventHandler) -> tuple[str, Any]: UploadTypeError: If the handler is a background task. UploadValueError: If the handler does not accept ``list[rx.UploadFile]``. """ - from reflex._upload import UploadFile + from reflex_components_upload._upload import UploadFile + from reflex_core.utils.exceptions import UploadTypeError, UploadValueError handler_name = _handler_name(handler) @@ -167,7 +168,8 @@ def resolve_upload_chunk_handler_param(handler: EventHandler) -> tuple[str, type UploadTypeError: If the handler is not a background task. UploadValueError: If the handler does not accept an UploadChunkIterator. """ - from reflex._upload import UploadChunkIterator + from reflex_components_upload._upload import UploadChunkIterator + from reflex_core.utils.exceptions import UploadTypeError, UploadValueError handler_name = _handler_name(handler) diff --git a/pyi_hashes.json b/pyi_hashes.json index 5e8c968587a..4c8ad523916 100644 --- a/pyi_hashes.json +++ b/pyi_hashes.json @@ -20,7 +20,6 @@ "packages/reflex-components-core/src/reflex_components_core/core/helmet.pyi": "3bf7bee5665293f7583009f651ea3cb1", "packages/reflex-components-core/src/reflex_components_core/core/html.pyi": "7209d1607545e412ed38dbe2a129321c", "packages/reflex-components-core/src/reflex_components_core/core/sticky.pyi": "8241c75ca16a0960b7dea6d6e7aff52e", - "packages/reflex-components-core/src/reflex_components_core/core/upload.pyi": "54ba94e554dc3a310e67c44ef1a7be98", "packages/reflex-components-core/src/reflex_components_core/core/window_events.pyi": "4407cecb1825dc359bcc7b2bea011a8e", "packages/reflex-components-core/src/reflex_components_core/datadisplay/__init__.pyi": "aaab42816119ac0f308841dc5482b3f1", "packages/reflex-components-core/src/reflex_components_core/el/__init__.pyi": "4ddf8457f8c48e7381e04e20dfa656e5", @@ -118,6 +117,7 @@ "packages/reflex-components-recharts/src/reflex_components_recharts/polar.pyi": "1ce679c002336c7bdbdd6c8ff6f2413c", "packages/reflex-components-recharts/src/reflex_components_recharts/recharts.pyi": "1b92135de4ea79cb7d94eaaec55b9ab7", "packages/reflex-components-sonner/src/reflex_components_sonner/toast.pyi": "f09c503c4ab880c13c13d6fa67d708b8", + "packages/reflex-components-upload/src/reflex_components_upload/upload.pyi": "bb0977d2e9ab51344b84d3da2b23a984", "reflex/__init__.pyi": "e2e675167f52ca535830860e84384103", "reflex/components/__init__.pyi": "55bb242d5e5428db329b88b4923c2ba5", "reflex/experimental/memo.pyi": "d16eccf33993c781e2f8bc2dd8bbd4d4" diff --git a/pyproject.toml b/pyproject.toml index ea8a4964450..0f2e177cf2a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -50,6 +50,7 @@ dependencies = [ "reflex-components-react-router", "reflex-components-recharts", "reflex-components-sonner", + "reflex-components-upload", ] classifiers = [ @@ -345,6 +346,7 @@ reflex-components-react-player.workspace = true reflex-components-react-router.workspace = true reflex-components-recharts.workspace = true reflex-components-sonner.workspace = true +reflex-components-upload.workspace = true reflex-docgen.workspace = true [tool.uv.workspace] diff --git a/reflex/_upload.py b/reflex/_upload.py new file mode 100644 index 00000000000..af35cc948f0 --- /dev/null +++ b/reflex/_upload.py @@ -0,0 +1,3 @@ +"""Re-export from reflex_components_upload._upload.""" + +from reflex_components_upload._upload import * diff --git a/scripts/make_pyi.py b/scripts/make_pyi.py index 81c20bb4ac5..51ee14f8fe8 100644 --- a/scripts/make_pyi.py +++ b/scripts/make_pyi.py @@ -29,6 +29,7 @@ "packages/reflex-components-react-router/src/reflex_components_react_router", "packages/reflex-components-recharts/src/reflex_components_recharts", "packages/reflex-components-sonner/src/reflex_components_sonner", + "packages/reflex-components-upload/src/reflex_components_upload", ] diff --git a/uv.lock b/uv.lock index d216a147ade..f7a36eedbd0 100644 --- a/uv.lock +++ b/uv.lock @@ -28,6 +28,7 @@ members = [ "reflex-components-react-router", "reflex-components-recharts", "reflex-components-sonner", + "reflex-components-upload", "reflex-core", "reflex-docgen", ] @@ -2077,6 +2078,7 @@ dependencies = [ { name = "reflex-components-react-router" }, { name = "reflex-components-recharts" }, { name = "reflex-components-sonner" }, + { name = "reflex-components-upload" }, { name = "reflex-core" }, { name = "reflex-hosting-cli" }, { name = "rich" }, @@ -2157,6 +2159,7 @@ requires-dist = [ { name = "reflex-components-react-router", editable = "packages/reflex-components-react-router" }, { name = "reflex-components-recharts", editable = "packages/reflex-components-recharts" }, { name = "reflex-components-sonner", editable = "packages/reflex-components-sonner" }, + { name = "reflex-components-upload", editable = "packages/reflex-components-upload" }, { name = "reflex-core", editable = "packages/reflex-core" }, { name = "reflex-hosting-cli", specifier = ">=0.1.61" }, { name = "rich", specifier = ">=13,<15" }, @@ -2331,6 +2334,10 @@ dependencies = [ [package.metadata] requires-dist = [{ name = "reflex-components-lucide", editable = "packages/reflex-components-lucide" }] +[[package]] +name = "reflex-components-upload" +source = { editable = "packages/reflex-components-upload" } + [[package]] name = "reflex-core" source = { editable = "packages/reflex-core" } From 407fc9ef02307cc250e00bbb5084fc66266424ea Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Thu, 26 Mar 2026 17:26:27 -0700 Subject: [PATCH 091/113] no need for shim --- .../src/reflex_components_core/core/upload.py | 3 --- packages/reflex-core/src/reflex_core/event.py | 2 +- 2 files changed, 1 insertion(+), 4 deletions(-) delete mode 100644 packages/reflex-components-core/src/reflex_components_core/core/upload.py diff --git a/packages/reflex-components-core/src/reflex_components_core/core/upload.py b/packages/reflex-components-core/src/reflex_components_core/core/upload.py deleted file mode 100644 index 44d483bddba..00000000000 --- a/packages/reflex-components-core/src/reflex_components_core/core/upload.py +++ /dev/null @@ -1,3 +0,0 @@ -"""Re-export from reflex_components_upload.upload.""" - -from reflex_components_upload.upload import * diff --git a/packages/reflex-core/src/reflex_core/event.py b/packages/reflex-core/src/reflex_core/event.py index 9140568aaa9..893b7dca03a 100644 --- a/packages/reflex-core/src/reflex_core/event.py +++ b/packages/reflex-core/src/reflex_core/event.py @@ -984,7 +984,7 @@ def _as_event_spec( Raises: ValueError: If the on_upload_progress is not a valid event handler. """ - from reflex_components_core.core.upload import ( + from reflex_components_upload.upload import ( DEFAULT_UPLOAD_ID, upload_files_context_var_data, ) From 31680913c5b16332d06bdced53980e131deb42c6 Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Thu, 26 Mar 2026 17:30:06 -0700 Subject: [PATCH 092/113] fix upload --- packages/reflex-components-upload/pyproject.toml | 10 ++++++++-- uv.lock | 6 ++++++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/packages/reflex-components-upload/pyproject.toml b/packages/reflex-components-upload/pyproject.toml index 1ef1fa77d51..a91349201d9 100644 --- a/packages/reflex-components-upload/pyproject.toml +++ b/packages/reflex-components-upload/pyproject.toml @@ -6,7 +6,7 @@ readme = "README.md" authors = [{ name = "Khaleel Al-Adhami", email = "khaleel@reflex.dev" }] maintainers = [{ name = "Khaleel Al-Adhami", email = "khaleel@reflex.dev" }] requires-python = ">=3.10" -dependencies = [] +dependencies = ["reflex_components_core"] [tool.hatch.version] source = "uv-dynamic-versioning" @@ -20,7 +20,13 @@ targets.sdist.artifacts = ["*.pyi"] targets.wheel.artifacts = ["*.pyi"] [tool.hatch.build.hooks.reflex-pyi] -dependencies = ["ruff", "reflex-core"] +dependencies = [ + "ruff", + "reflex-core", + "reflex_components_core", + "reflex-components-lucide", + "reflex-components-sonner", +] [build-system] requires = ["hatchling", "uv-dynamic-versioning", "hatch-reflex-pyi"] diff --git a/uv.lock b/uv.lock index f7a36eedbd0..431344307d1 100644 --- a/uv.lock +++ b/uv.lock @@ -2337,6 +2337,12 @@ requires-dist = [{ name = "reflex-components-lucide", editable = "packages/refle [[package]] name = "reflex-components-upload" source = { editable = "packages/reflex-components-upload" } +dependencies = [ + { name = "reflex-components-core" }, +] + +[package.metadata] +requires-dist = [{ name = "reflex-components-core", editable = "packages/reflex-components-core" }] [[package]] name = "reflex-core" From 941f25c1368642aecc7132f0f4c037ea87ee68ab Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Thu, 26 Mar 2026 17:37:41 -0700 Subject: [PATCH 093/113] move react router into core --- .../react_router}/dom.py | 0 .../reflex-components-react-router/README.md | 3 -- .../pyproject.toml | 33 ------------------- .../__init__.py | 5 --- 4 files changed, 41 deletions(-) rename packages/{reflex-components-react-router/src/reflex_components_react_router => reflex-components-core/src/reflex_components_core/react_router}/dom.py (100%) delete mode 100644 packages/reflex-components-react-router/README.md delete mode 100644 packages/reflex-components-react-router/pyproject.toml delete mode 100644 packages/reflex-components-react-router/src/reflex_components_react_router/__init__.py diff --git a/packages/reflex-components-react-router/src/reflex_components_react_router/dom.py b/packages/reflex-components-core/src/reflex_components_core/react_router/dom.py similarity index 100% rename from packages/reflex-components-react-router/src/reflex_components_react_router/dom.py rename to packages/reflex-components-core/src/reflex_components_core/react_router/dom.py diff --git a/packages/reflex-components-react-router/README.md b/packages/reflex-components-react-router/README.md deleted file mode 100644 index f4465ac2200..00000000000 --- a/packages/reflex-components-react-router/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# reflex-components-react-router - -Reflex react-router components. diff --git a/packages/reflex-components-react-router/pyproject.toml b/packages/reflex-components-react-router/pyproject.toml deleted file mode 100644 index 53b513c7e53..00000000000 --- a/packages/reflex-components-react-router/pyproject.toml +++ /dev/null @@ -1,33 +0,0 @@ -[project] -name = "reflex-components-react-router" -dynamic = ["version"] -description = "Reflex react-router components." -readme = "README.md" -authors = [{ name = "Khaleel Al-Adhami", email = "khaleel@reflex.dev" }] -maintainers = [{ name = "Khaleel Al-Adhami", email = "khaleel@reflex.dev" }] -requires-python = ">=3.10" -dependencies = ["reflex-components-core"] - -[tool.hatch.version] -source = "uv-dynamic-versioning" - -[tool.uv-dynamic-versioning] -pattern-prefix = "reflex-components-react-router-" -fallback-version = "0.0.0dev0" - -[tool.hatch.build] -targets.sdist.artifacts = ["*.pyi"] -targets.wheel.artifacts = ["*.pyi"] - -[tool.hatch.build.hooks.reflex-pyi] -dependencies = [ - "ruff", - "reflex-core", - "reflex-components-core", - "reflex-components-lucide", - "reflex-components-sonner", -] - -[build-system] -requires = ["hatchling", "uv-dynamic-versioning", "hatch-reflex-pyi"] -build-backend = "hatchling.build" diff --git a/packages/reflex-components-react-router/src/reflex_components_react_router/__init__.py b/packages/reflex-components-react-router/src/reflex_components_react_router/__init__.py deleted file mode 100644 index 3f5a2f33158..00000000000 --- a/packages/reflex-components-react-router/src/reflex_components_react_router/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -"""React-router internal components.""" - -from .dom import ReactRouterLink - -link = ReactRouterLink.create From 5486c4d2a7f1b4ac379e052ec478f729d49a3907 Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Thu, 26 Mar 2026 17:37:46 -0700 Subject: [PATCH 094/113] update its imports --- packages/reflex-components-code/pyproject.toml | 1 - .../src/reflex_components_core/el/__init__.py | 2 +- .../react_router/__init__.py | 5 +++++ .../reflex_components_core/react_router/dom.py | 3 ++- packages/reflex-components-radix/pyproject.toml | 2 -- .../themes/typography/link.py | 2 +- .../src/reflex_core/utils/pyi_generator.py | 2 +- pyi_hashes.json | 6 +++--- pyproject.toml | 2 -- reflex/components/__init__.py | 2 +- scripts/make_pyi.py | 1 - uv.lock | 15 --------------- 12 files changed, 14 insertions(+), 29 deletions(-) create mode 100644 packages/reflex-components-core/src/reflex_components_core/react_router/__init__.py diff --git a/packages/reflex-components-code/pyproject.toml b/packages/reflex-components-code/pyproject.toml index d9b6908ae65..454ac263659 100644 --- a/packages/reflex-components-code/pyproject.toml +++ b/packages/reflex-components-code/pyproject.toml @@ -31,7 +31,6 @@ dependencies = [ "reflex-components-lucide", "reflex-components-radix", "reflex-components-sonner", - "reflex-components-react-router", ] [build-system] diff --git a/packages/reflex-components-core/src/reflex_components_core/el/__init__.py b/packages/reflex-components-core/src/reflex_components_core/el/__init__.py index ab397000458..49e9cb5bccc 100644 --- a/packages/reflex-components-core/src/reflex_components_core/el/__init__.py +++ b/packages/reflex-components-core/src/reflex_components_core/el/__init__.py @@ -13,7 +13,7 @@ for k, attrs in elements._MAPPING.items() } _EXTRA_MAPPINGS: dict[str, str] = { - "a": "reflex_components_react_router.link", + "a": "reflex_components_core.react_router.link", } __getattr__, __dir__, __all__ = lazy_loader.attach( diff --git a/packages/reflex-components-core/src/reflex_components_core/react_router/__init__.py b/packages/reflex-components-core/src/reflex_components_core/react_router/__init__.py new file mode 100644 index 00000000000..3f5a2f33158 --- /dev/null +++ b/packages/reflex-components-core/src/reflex_components_core/react_router/__init__.py @@ -0,0 +1,5 @@ +"""React-router internal components.""" + +from .dom import ReactRouterLink + +link = ReactRouterLink.create diff --git a/packages/reflex-components-core/src/reflex_components_core/react_router/dom.py b/packages/reflex-components-core/src/reflex_components_core/react_router/dom.py index 3c299ecc4b3..a5eb70b4290 100644 --- a/packages/reflex-components-core/src/reflex_components_core/react_router/dom.py +++ b/packages/reflex-components-core/src/reflex_components_core/react_router/dom.py @@ -4,10 +4,11 @@ from typing import ClassVar, Literal, TypedDict -from reflex_components_core.el.elements.inline import A from reflex_core.components.component import field from reflex_core.vars.base import Var +from reflex_components_core.el.elements.inline import A + LiteralLinkDiscover = Literal["none", "render"] diff --git a/packages/reflex-components-radix/pyproject.toml b/packages/reflex-components-radix/pyproject.toml index 358329693de..9634cc731ef 100644 --- a/packages/reflex-components-radix/pyproject.toml +++ b/packages/reflex-components-radix/pyproject.toml @@ -9,7 +9,6 @@ requires-python = ">=3.10" dependencies = [ "reflex-components-core", "reflex-components-lucide", - "reflex-components-react-router", ] [tool.hatch.version] @@ -30,7 +29,6 @@ dependencies = [ "reflex-components-core", "reflex-components-lucide", "reflex-components-sonner", - "reflex-components-react-router", ] [build-system] diff --git a/packages/reflex-components-radix/src/reflex_components_radix/themes/typography/link.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/typography/link.py index d88c8d68ac9..0317148246e 100644 --- a/packages/reflex-components-radix/src/reflex_components_radix/themes/typography/link.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/typography/link.py @@ -12,7 +12,7 @@ from reflex_components_core.core.cond import cond from reflex_components_core.core.markdown_component_map import MarkdownComponentMap from reflex_components_core.el.elements.inline import A -from reflex_components_react_router.dom import ReactRouterLink +from reflex_components_core.react_router.dom import ReactRouterLink from reflex_core.components.component import Component, MemoizationLeaf, field from reflex_core.utils.imports import ImportDict, ImportVar from reflex_core.vars.base import Var diff --git a/packages/reflex-core/src/reflex_core/utils/pyi_generator.py b/packages/reflex-core/src/reflex_core/utils/pyi_generator.py index 3e6e5dd7906..45b3ff0fabf 100644 --- a/packages/reflex-core/src/reflex_core/utils/pyi_generator.py +++ b/packages/reflex-core/src/reflex_core/utils/pyi_generator.py @@ -1324,7 +1324,7 @@ def _write_pyi_file(module_path: Path, source: str) -> str: "plotly": "reflex_components_plotly", "radix": "reflex_components_radix", "react_player": "reflex_components_react_player", - "react_router": "reflex_components_react_router", + "react_router": "reflex_components_core.react_router", "recharts": "reflex_components_recharts", "sonner": "reflex_components_sonner", } diff --git a/pyi_hashes.json b/pyi_hashes.json index 4c8ad523916..4caedf7f33c 100644 --- a/pyi_hashes.json +++ b/pyi_hashes.json @@ -22,7 +22,7 @@ "packages/reflex-components-core/src/reflex_components_core/core/sticky.pyi": "8241c75ca16a0960b7dea6d6e7aff52e", "packages/reflex-components-core/src/reflex_components_core/core/window_events.pyi": "4407cecb1825dc359bcc7b2bea011a8e", "packages/reflex-components-core/src/reflex_components_core/datadisplay/__init__.pyi": "aaab42816119ac0f308841dc5482b3f1", - "packages/reflex-components-core/src/reflex_components_core/el/__init__.pyi": "4ddf8457f8c48e7381e04e20dfa656e5", + "packages/reflex-components-core/src/reflex_components_core/el/__init__.pyi": "e27fddec8de079db37d6699e136411d1", "packages/reflex-components-core/src/reflex_components_core/el/element.pyi": "6add8b77380ea3702031b07330fc7d60", "packages/reflex-components-core/src/reflex_components_core/el/elements/__init__.pyi": "721b328e94510f8328728be1657abbb8", "packages/reflex-components-core/src/reflex_components_core/el/elements/base.pyi": "7c427fcd82fc6ccf86b4d2b5c4756426", @@ -35,6 +35,7 @@ "packages/reflex-components-core/src/reflex_components_core/el/elements/sectioning.pyi": "4ce119b25459a01d128bdb5b79b0d128", "packages/reflex-components-core/src/reflex_components_core/el/elements/tables.pyi": "9e58353a97dc006d37d2c7c50506fac4", "packages/reflex-components-core/src/reflex_components_core/el/elements/typography.pyi": "3325f8a4af0aadb70cbfc50558e2f3b2", + "packages/reflex-components-core/src/reflex_components_core/react_router/dom.pyi": "d77a80f688b29d2e1048007172d2b65f", "packages/reflex-components-dataeditor/src/reflex_components_dataeditor/dataeditor.pyi": "caa83be6f97faa95588bfa9ae9e9331e", "packages/reflex-components-gridjs/src/reflex_components_gridjs/datatable.pyi": "32880736442800061a39ce4b55267eaf", "packages/reflex-components-lucide/src/reflex_components_lucide/icon.pyi": "e55c023c9ecc907321f163955f4c4875", @@ -104,12 +105,11 @@ "packages/reflex-components-radix/src/reflex_components_radix/themes/typography/blockquote.pyi": "305a8932078e4af48e44489e7ce74060", "packages/reflex-components-radix/src/reflex_components_radix/themes/typography/code.pyi": "fad43053747fb84229cc35296c7028b5", "packages/reflex-components-radix/src/reflex_components_radix/themes/typography/heading.pyi": "5901c7202a5b135f60cc1407878b4859", - "packages/reflex-components-radix/src/reflex_components_radix/themes/typography/link.pyi": "3df3dfc470322f791eb065c9a39d1d95", + "packages/reflex-components-radix/src/reflex_components_radix/themes/typography/link.pyi": "d567c1242672d125015920f7ae6e6999", "packages/reflex-components-radix/src/reflex_components_radix/themes/typography/text.pyi": "76f100da40d0e18ad4f7b3387dec1d4a", "packages/reflex-components-react-player/src/reflex_components_react_player/audio.pyi": "a981a6031015c3a384e6255be88885f1", "packages/reflex-components-react-player/src/reflex_components_react_player/react_player.pyi": "1f66ea4fa34e8a8fa7473d312daf84b8", "packages/reflex-components-react-player/src/reflex_components_react_player/video.pyi": "8c1ea5bf4ec27ec6ff2dce462021b094", - "packages/reflex-components-react-router/src/reflex_components_react_router/dom.pyi": "4690df14b7c3893ba3c416fd07d21e91", "packages/reflex-components-recharts/src/reflex_components_recharts/__init__.pyi": "2610c28416f80e2254bd10dde8c29bdf", "packages/reflex-components-recharts/src/reflex_components_recharts/cartesian.pyi": "597b9eb86c57f5293c13c128fb972c27", "packages/reflex-components-recharts/src/reflex_components_recharts/charts.pyi": "303d4b1dc72c08339154907b9b095365", diff --git a/pyproject.toml b/pyproject.toml index 0f2e177cf2a..29ad7c52cc5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -47,7 +47,6 @@ dependencies = [ "reflex-components-plotly", "reflex-components-radix", "reflex-components-react-player", - "reflex-components-react-router", "reflex-components-recharts", "reflex-components-sonner", "reflex-components-upload", @@ -343,7 +342,6 @@ reflex-components-moment.workspace = true reflex-components-plotly.workspace = true reflex-components-radix.workspace = true reflex-components-react-player.workspace = true -reflex-components-react-router.workspace = true reflex-components-recharts.workspace = true reflex-components-sonner.workspace = true reflex-components-upload.workspace = true diff --git a/reflex/components/__init__.py b/reflex/components/__init__.py index d3f33831fc2..741709b0463 100644 --- a/reflex/components/__init__.py +++ b/reflex/components/__init__.py @@ -31,7 +31,7 @@ "plotly": "reflex_components_plotly", "radix": "reflex_components_radix", "react_player": "reflex_components_react_player", - "react_router": "reflex_components_react_router", + "react_router": "reflex_components_core.react_router", "recharts": "reflex_components_recharts", "sonner": "reflex_components_sonner", } diff --git a/scripts/make_pyi.py b/scripts/make_pyi.py index 51ee14f8fe8..e51ab2cd9b1 100644 --- a/scripts/make_pyi.py +++ b/scripts/make_pyi.py @@ -26,7 +26,6 @@ "packages/reflex-components-plotly/src/reflex_components_plotly", "packages/reflex-components-radix/src/reflex_components_radix", "packages/reflex-components-react-player/src/reflex_components_react_player", - "packages/reflex-components-react-router/src/reflex_components_react_router", "packages/reflex-components-recharts/src/reflex_components_recharts", "packages/reflex-components-sonner/src/reflex_components_sonner", "packages/reflex-components-upload/src/reflex_components_upload", diff --git a/uv.lock b/uv.lock index 431344307d1..a2efd837753 100644 --- a/uv.lock +++ b/uv.lock @@ -25,7 +25,6 @@ members = [ "reflex-components-plotly", "reflex-components-radix", "reflex-components-react-player", - "reflex-components-react-router", "reflex-components-recharts", "reflex-components-sonner", "reflex-components-upload", @@ -2075,7 +2074,6 @@ dependencies = [ { name = "reflex-components-plotly" }, { name = "reflex-components-radix" }, { name = "reflex-components-react-player" }, - { name = "reflex-components-react-router" }, { name = "reflex-components-recharts" }, { name = "reflex-components-sonner" }, { name = "reflex-components-upload" }, @@ -2156,7 +2154,6 @@ requires-dist = [ { name = "reflex-components-plotly", editable = "packages/reflex-components-plotly" }, { name = "reflex-components-radix", editable = "packages/reflex-components-radix" }, { name = "reflex-components-react-player", editable = "packages/reflex-components-react-player" }, - { name = "reflex-components-react-router", editable = "packages/reflex-components-react-router" }, { name = "reflex-components-recharts", editable = "packages/reflex-components-recharts" }, { name = "reflex-components-sonner", editable = "packages/reflex-components-sonner" }, { name = "reflex-components-upload", editable = "packages/reflex-components-upload" }, @@ -2290,14 +2287,12 @@ source = { editable = "packages/reflex-components-radix" } dependencies = [ { name = "reflex-components-core" }, { name = "reflex-components-lucide" }, - { name = "reflex-components-react-router" }, ] [package.metadata] requires-dist = [ { name = "reflex-components-core", editable = "packages/reflex-components-core" }, { name = "reflex-components-lucide", editable = "packages/reflex-components-lucide" }, - { name = "reflex-components-react-router", editable = "packages/reflex-components-react-router" }, ] [[package]] @@ -2310,16 +2305,6 @@ dependencies = [ [package.metadata] requires-dist = [{ name = "reflex-components-core", editable = "packages/reflex-components-core" }] -[[package]] -name = "reflex-components-react-router" -source = { editable = "packages/reflex-components-react-router" } -dependencies = [ - { name = "reflex-components-core" }, -] - -[package.metadata] -requires-dist = [{ name = "reflex-components-core", editable = "packages/reflex-components-core" }] - [[package]] name = "reflex-components-recharts" source = { editable = "packages/reflex-components-recharts" } From ed222102aa2f369b2f5f0e27d9ec58bb27519bc7 Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Thu, 26 Mar 2026 17:45:10 -0700 Subject: [PATCH 095/113] remove radix reliance on reflex import --- .../el/elements/media.py | 3 +- .../src/reflex_components_radix/__init__.py | 2 +- .../src/reflex_components_radix/mappings.py | 128 +++++++++++++++++ .../primitives/__init__.py | 2 +- .../themes/components/__init__.py | 2 +- .../themes/layout/__init__.py | 2 +- .../themes/typography/__init__.py | 2 +- pyi_hashes.json | 14 +- reflex/__init__.py | 135 ++---------------- tests/units/components/test_component.py | 3 +- 10 files changed, 152 insertions(+), 141 deletions(-) create mode 100644 packages/reflex-components-radix/src/reflex_components_radix/mappings.py diff --git a/packages/reflex-components-core/src/reflex_components_core/el/elements/media.py b/packages/reflex-components-core/src/reflex_components_core/el/elements/media.py index cbf43ee1d20..2e978426e81 100644 --- a/packages/reflex-components-core/src/reflex_components_core/el/elements/media.py +++ b/packages/reflex-components-core/src/reflex_components_core/el/elements/media.py @@ -2,11 +2,10 @@ from typing import Any, Literal -from reflex_core.components.component import field +from reflex_core.components.component import Component, ComponentNamespace, field from reflex_core.constants.colors import Color from reflex_core.vars.base import Var -from reflex import Component, ComponentNamespace from reflex_components_core.el.elements.inline import ReferrerPolicy from .base import BaseHTML diff --git a/packages/reflex-components-radix/src/reflex_components_radix/__init__.py b/packages/reflex-components-radix/src/reflex_components_radix/__init__.py index 59a263804fa..40b89dbcf61 100644 --- a/packages/reflex-components-radix/src/reflex_components_radix/__init__.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/__init__.py @@ -4,7 +4,7 @@ from reflex_core.utils import lazy_loader -from reflex import RADIX_MAPPING +from reflex_components_radix.mappings import RADIX_MAPPING _SUBMODULES: set[str] = {"themes", "primitives"} diff --git a/packages/reflex-components-radix/src/reflex_components_radix/mappings.py b/packages/reflex-components-radix/src/reflex_components_radix/mappings.py new file mode 100644 index 00000000000..bdd090a04e8 --- /dev/null +++ b/packages/reflex-components-radix/src/reflex_components_radix/mappings.py @@ -0,0 +1,128 @@ +"""Radix component mappings for lazy loading.""" + +RADIX_THEMES_MAPPING: dict = { + "components.radix.themes.base": ["color_mode", "theme", "theme_panel"], + "components.radix.themes.color_mode": ["color_mode"], +} +RADIX_THEMES_COMPONENTS_MAPPING: dict = { + **{ + f"components.radix.themes.components.{mod}": [mod] + for mod in [ + "alert_dialog", + "aspect_ratio", + "avatar", + "badge", + "button", + "callout", + "card", + "checkbox", + "context_menu", + "data_list", + "dialog", + "hover_card", + "icon_button", + "input", + "inset", + "popover", + "scroll_area", + "select", + "skeleton", + "slider", + "spinner", + "switch", + "table", + "tabs", + "text_area", + "tooltip", + "segmented_control", + "radio_cards", + "checkbox_cards", + "checkbox_group", + ] + }, + "components.radix.themes.components.text_field": ["text_field", "input"], + "components.radix.themes.components.radio_group": ["radio", "radio_group"], + "components.radix.themes.components.dropdown_menu": ["menu", "dropdown_menu"], + "components.radix.themes.components.separator": ["divider", "separator"], + "components.radix.themes.components.progress": ["progress"], +} + +RADIX_THEMES_LAYOUT_MAPPING: dict = { + "components.radix.themes.layout.box": [ + "box", + ], + "components.radix.themes.layout.center": [ + "center", + ], + "components.radix.themes.layout.container": [ + "container", + ], + "components.radix.themes.layout.flex": [ + "flex", + ], + "components.radix.themes.layout.grid": [ + "grid", + ], + "components.radix.themes.layout.section": [ + "section", + ], + "components.radix.themes.layout.spacer": [ + "spacer", + ], + "components.radix.themes.layout.stack": [ + "stack", + "hstack", + "vstack", + ], + "components.radix.themes.layout.list": [ + ("list_ns", "list"), + "list_item", + "ordered_list", + "unordered_list", + ], +} + +RADIX_THEMES_TYPOGRAPHY_MAPPING: dict = { + "components.radix.themes.typography.blockquote": [ + "blockquote", + ], + "components.radix.themes.typography.code": [ + "code", + ], + "components.radix.themes.typography.heading": [ + "heading", + ], + "components.radix.themes.typography.link": [ + "link", + ], + "components.radix.themes.typography.text": [ + "text", + ], +} + +RADIX_PRIMITIVES_MAPPING: dict = { + "components.radix.primitives.accordion": [ + "accordion", + ], + "components.radix.primitives.drawer": [ + "drawer", + ], + "components.radix.primitives.form": [ + "form", + ], + "components.radix.primitives.progress": [ + "progress", + ], +} + +RADIX_PRIMITIVES_SHORTCUT_MAPPING: dict = { + k: v for k, v in RADIX_PRIMITIVES_MAPPING.items() if "progress" not in k +} + +RADIX_MAPPING: dict = { + **RADIX_THEMES_MAPPING, + **RADIX_THEMES_COMPONENTS_MAPPING, + **RADIX_THEMES_TYPOGRAPHY_MAPPING, + **RADIX_THEMES_LAYOUT_MAPPING, + **RADIX_PRIMITIVES_SHORTCUT_MAPPING, +} diff --git a/packages/reflex-components-radix/src/reflex_components_radix/primitives/__init__.py b/packages/reflex-components-radix/src/reflex_components_radix/primitives/__init__.py index f144c57ecdf..bff9cbd1dc4 100644 --- a/packages/reflex-components-radix/src/reflex_components_radix/primitives/__init__.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/primitives/__init__.py @@ -4,7 +4,7 @@ from reflex_core.utils import lazy_loader -from reflex import RADIX_PRIMITIVES_MAPPING +from reflex_components_radix.mappings import RADIX_PRIMITIVES_MAPPING _SUBMOD_ATTRS: dict[str, list[str]] = { "".join(k.split("components.radix.primitives.")[-1]): v diff --git a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/__init__.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/__init__.py index 5faaf6b4c6f..396fd751bf8 100644 --- a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/__init__.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/__init__.py @@ -4,7 +4,7 @@ from reflex_core.utils import lazy_loader -from reflex import RADIX_THEMES_COMPONENTS_MAPPING +from reflex_components_radix.mappings import RADIX_THEMES_COMPONENTS_MAPPING _SUBMOD_ATTRS: dict[str, list[str]] = { "".join(k.split("components.radix.themes.components.")[-1]): v diff --git a/packages/reflex-components-radix/src/reflex_components_radix/themes/layout/__init__.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/layout/__init__.py index d72e774af1f..1aaf76f9496 100644 --- a/packages/reflex-components-radix/src/reflex_components_radix/themes/layout/__init__.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/layout/__init__.py @@ -4,7 +4,7 @@ from reflex_core.utils import lazy_loader -from reflex import RADIX_THEMES_LAYOUT_MAPPING +from reflex_components_radix.mappings import RADIX_THEMES_LAYOUT_MAPPING _SUBMOD_ATTRS: dict[str, list[str]] = { "".join(k.split("components.radix.themes.layout.")[-1]): v diff --git a/packages/reflex-components-radix/src/reflex_components_radix/themes/typography/__init__.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/typography/__init__.py index 50f6544afe4..2dff8bc32ec 100644 --- a/packages/reflex-components-radix/src/reflex_components_radix/themes/typography/__init__.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/typography/__init__.py @@ -4,7 +4,7 @@ from reflex_core.utils import lazy_loader -from reflex import RADIX_THEMES_TYPOGRAPHY_MAPPING +from reflex_components_radix.mappings import RADIX_THEMES_TYPOGRAPHY_MAPPING _SUBMOD_ATTRS: dict[str, list[str]] = { "".join(k.split("components.radix.themes.typography.")[-1]): v diff --git a/pyi_hashes.json b/pyi_hashes.json index 4caedf7f33c..3795ac87f1c 100644 --- a/pyi_hashes.json +++ b/pyi_hashes.json @@ -28,7 +28,7 @@ "packages/reflex-components-core/src/reflex_components_core/el/elements/base.pyi": "7c427fcd82fc6ccf86b4d2b5c4756426", "packages/reflex-components-core/src/reflex_components_core/el/elements/forms.pyi": "5912179a169da4dc3b152042558be2cf", "packages/reflex-components-core/src/reflex_components_core/el/elements/inline.pyi": "6bf366f345e14a556dbb3c0f230e1355", - "packages/reflex-components-core/src/reflex_components_core/el/elements/media.pyi": "b50ff4c79bb55c2c0e2aba6f1613040a", + "packages/reflex-components-core/src/reflex_components_core/el/elements/media.pyi": "f8d2a995e488ebc5e8633977151758ce", "packages/reflex-components-core/src/reflex_components_core/el/elements/metadata.pyi": "20d803fcc05d4c378547ceaa0e1bcc70", "packages/reflex-components-core/src/reflex_components_core/el/elements/other.pyi": "1cf906cbc2751f87adbcd85e03b72d2e", "packages/reflex-components-core/src/reflex_components_core/el/elements/scripts.pyi": "b4b5bb69e6ce8d08c0df51301e132af4", @@ -42,8 +42,8 @@ "packages/reflex-components-markdown/src/reflex_components_markdown/markdown.pyi": "bacf19a5b6904281d7238dbd51e6fc1c", "packages/reflex-components-moment/src/reflex_components_moment/moment.pyi": "b997bdd994844f0e6ca923bbb2dc34a1", "packages/reflex-components-plotly/src/reflex_components_plotly/plotly.pyi": "65a93d778a0fde06975dac9244f51bb3", - "packages/reflex-components-radix/src/reflex_components_radix/__init__.pyi": "a4b84a5b7b26f8f9193e96f0079c3a9e", - "packages/reflex-components-radix/src/reflex_components_radix/primitives/__init__.pyi": "380078ada483194e7c143afbd6fdfbc6", + "packages/reflex-components-radix/src/reflex_components_radix/__init__.pyi": "6786311e88184cd12f59f6bcb72b4271", + "packages/reflex-components-radix/src/reflex_components_radix/primitives/__init__.pyi": "bc25cae0eca01c8684443d5dfd7b6455", "packages/reflex-components-radix/src/reflex_components_radix/primitives/accordion.pyi": "6dd30847af62ad7d50d5c5daf6c4a1d7", "packages/reflex-components-radix/src/reflex_components_radix/primitives/base.pyi": "6c12ef3d9f82926bf17d410b774d56f5", "packages/reflex-components-radix/src/reflex_components_radix/primitives/dialog.pyi": "810fa8c626b79035cdbb04f43b5bc5ad", @@ -54,7 +54,7 @@ "packages/reflex-components-radix/src/reflex_components_radix/themes/__init__.pyi": "5d9a06872953d3e3df99e1ff154a4e0c", "packages/reflex-components-radix/src/reflex_components_radix/themes/base.pyi": "d7c20bd180f28fdb4affcba37e2aa1ff", "packages/reflex-components-radix/src/reflex_components_radix/themes/color_mode.pyi": "ba0ff3b00289cd1896e327fa2be99563", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/__init__.pyi": "545c186b400ebac8a7b3d7e7a17de232", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/__init__.pyi": "2b0f9f472ba6dcc743c2df17642c4a4b", "packages/reflex-components-radix/src/reflex_components_radix/themes/components/alert_dialog.pyi": "fad40b463a8ebb0d3ca3900dc8a91679", "packages/reflex-components-radix/src/reflex_components_radix/themes/components/aspect_ratio.pyi": "0d22969c5407592a0bb36768e149f2b5", "packages/reflex-components-radix/src/reflex_components_radix/themes/components/avatar.pyi": "6a6e9b8f6ca3428c45d62bd0e7f94693", @@ -90,7 +90,7 @@ "packages/reflex-components-radix/src/reflex_components_radix/themes/components/text_area.pyi": "1a03d9525a1544816392067499c3354d", "packages/reflex-components-radix/src/reflex_components_radix/themes/components/text_field.pyi": "c2fbd8547de4993e03017844e8c4b477", "packages/reflex-components-radix/src/reflex_components_radix/themes/components/tooltip.pyi": "4545b70fd0802f19993419ab0163d595", - "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/__init__.pyi": "d53df0885dc31e227726ca715df4b926", + "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/__init__.pyi": "94c16e4bf7e41939232ff1f19e7b3709", "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/base.pyi": "3195e198d92ff43644a09c277303b83b", "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/box.pyi": "69d12b6c918a476ac4557f42fef73c27", "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/center.pyi": "88880d197ff7347ec7d3f81d6e57de8e", @@ -101,7 +101,7 @@ "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/section.pyi": "1aa57142797597d65d840eb1d3cc7de7", "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/spacer.pyi": "c774f0a1384f983e6d73bde603c341ca", "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/stack.pyi": "894dcd5945123c1c8aa34cb77602fead", - "packages/reflex-components-radix/src/reflex_components_radix/themes/typography/__init__.pyi": "4db383f8b76312d82d67152dde97b4e6", + "packages/reflex-components-radix/src/reflex_components_radix/themes/typography/__init__.pyi": "3926877c04f74fc2acf4a398bee9da06", "packages/reflex-components-radix/src/reflex_components_radix/themes/typography/blockquote.pyi": "305a8932078e4af48e44489e7ce74060", "packages/reflex-components-radix/src/reflex_components_radix/themes/typography/code.pyi": "fad43053747fb84229cc35296c7028b5", "packages/reflex-components-radix/src/reflex_components_radix/themes/typography/heading.pyi": "5901c7202a5b135f60cc1407878b4859", @@ -118,7 +118,7 @@ "packages/reflex-components-recharts/src/reflex_components_recharts/recharts.pyi": "1b92135de4ea79cb7d94eaaec55b9ab7", "packages/reflex-components-sonner/src/reflex_components_sonner/toast.pyi": "f09c503c4ab880c13c13d6fa67d708b8", "packages/reflex-components-upload/src/reflex_components_upload/upload.pyi": "bb0977d2e9ab51344b84d3da2b23a984", - "reflex/__init__.pyi": "e2e675167f52ca535830860e84384103", + "reflex/__init__.pyi": "a13470178c515fb4ad0c1adf6d63efe2", "reflex/components/__init__.pyi": "55bb242d5e5428db329b88b4923c2ba5", "reflex/experimental/memo.pyi": "d16eccf33993c781e2f8bc2dd8bbd4d4" } diff --git a/reflex/__init__.py b/reflex/__init__.py index b7cabe6f481..8578a602ebe 100644 --- a/reflex/__init__.py +++ b/reflex/__init__.py @@ -97,124 +97,15 @@ del console del sys -RADIX_THEMES_MAPPING: dict = { - "components.radix.themes.base": ["color_mode", "theme", "theme_panel"], - "components.radix.themes.color_mode": ["color_mode"], -} -RADIX_THEMES_COMPONENTS_MAPPING: dict = { - **{ - f"components.radix.themes.components.{mod}": [mod] - for mod in [ - "alert_dialog", - "aspect_ratio", - "avatar", - "badge", - "button", - "callout", - "card", - "checkbox", - "context_menu", - "data_list", - "dialog", - "hover_card", - "icon_button", - "input", - "inset", - "popover", - "scroll_area", - "select", - "skeleton", - "slider", - "spinner", - "switch", - "table", - "tabs", - "text_area", - "tooltip", - "segmented_control", - "radio_cards", - "checkbox_cards", - "checkbox_group", - ] - }, - "components.radix.themes.components.text_field": ["text_field", "input"], - "components.radix.themes.components.radio_group": ["radio", "radio_group"], - "components.radix.themes.components.dropdown_menu": ["menu", "dropdown_menu"], - "components.radix.themes.components.separator": ["divider", "separator"], - "components.radix.themes.components.progress": ["progress"], -} - -RADIX_THEMES_LAYOUT_MAPPING: dict = { - "components.radix.themes.layout.box": [ - "box", - ], - "components.radix.themes.layout.center": [ - "center", - ], - "components.radix.themes.layout.container": [ - "container", - ], - "components.radix.themes.layout.flex": [ - "flex", - ], - "components.radix.themes.layout.grid": [ - "grid", - ], - "components.radix.themes.layout.section": [ - "section", - ], - "components.radix.themes.layout.spacer": [ - "spacer", - ], - "components.radix.themes.layout.stack": [ - "stack", - "hstack", - "vstack", - ], - "components.radix.themes.layout.list": [ - ("list_ns", "list"), - "list_item", - "ordered_list", - "unordered_list", - ], -} - -RADIX_THEMES_TYPOGRAPHY_MAPPING: dict = { - "components.radix.themes.typography.blockquote": [ - "blockquote", - ], - "components.radix.themes.typography.code": [ - "code", - ], - "components.radix.themes.typography.heading": [ - "heading", - ], - "components.radix.themes.typography.link": [ - "link", - ], - "components.radix.themes.typography.text": [ - "text", - ], -} - -RADIX_PRIMITIVES_MAPPING: dict = { - "components.radix.primitives.accordion": [ - "accordion", - ], - "components.radix.primitives.drawer": [ - "drawer", - ], - "components.radix.primitives.form": [ - "form", - ], - "components.radix.primitives.progress": [ - "progress", - ], -} - -RADIX_PRIMITIVES_SHORTCUT_MAPPING: dict = { - k: v for k, v in RADIX_PRIMITIVES_MAPPING.items() if "progress" not in k -} +from reflex_components_radix.mappings import ( # noqa: E402 + RADIX_MAPPING, + RADIX_PRIMITIVES_MAPPING, + RADIX_PRIMITIVES_SHORTCUT_MAPPING, + RADIX_THEMES_COMPONENTS_MAPPING, + RADIX_THEMES_LAYOUT_MAPPING, + RADIX_THEMES_MAPPING, + RADIX_THEMES_TYPOGRAPHY_MAPPING, +) COMPONENTS_CORE_MAPPING: dict = { "components.core.banner": [ @@ -253,14 +144,6 @@ "components.base.script": ["script", "Script"], } -RADIX_MAPPING: dict = { - **RADIX_THEMES_MAPPING, - **RADIX_THEMES_COMPONENTS_MAPPING, - **RADIX_THEMES_TYPOGRAPHY_MAPPING, - **RADIX_THEMES_LAYOUT_MAPPING, - **RADIX_PRIMITIVES_SHORTCUT_MAPPING, -} - _MAPPING: dict = { "experimental": ["_x"], "admin": ["AdminDash"], diff --git a/tests/units/components/test_component.py b/tests/units/components/test_component.py index bdd7f95bfa5..0b14d9c1071 100644 --- a/tests/units/components/test_component.py +++ b/tests/units/components/test_component.py @@ -5,6 +5,7 @@ import pytest from reflex_components_core.base.bare import Bare from reflex_components_core.base.fragment import Fragment +from reflex_components_radix.mappings import RADIX_MAPPING from reflex_components_radix.themes.layout.box import Box from reflex_core.components.component import ( CUSTOM_COMPONENTS, @@ -1473,7 +1474,7 @@ def test_instantiate_all_components(): "Thead", } component_nested_list = [ - *rx.RADIX_MAPPING.values(), + *RADIX_MAPPING.values(), *rx.COMPONENTS_BASE_MAPPING.values(), *rx.COMPONENTS_CORE_MAPPING.values(), ] From 11305284e33fa6bc593065847aeb3d9200e2f73a Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Thu, 26 Mar 2026 17:47:08 -0700 Subject: [PATCH 096/113] interesting --- tests/units/components/core/test_upload.py | 2 +- tests/units/test_app.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/units/components/core/test_upload.py b/tests/units/components/core/test_upload.py index f7ce6b6c0c5..7d9d9dbf785 100644 --- a/tests/units/components/core/test_upload.py +++ b/tests/units/components/core/test_upload.py @@ -1,7 +1,7 @@ from typing import Any, cast import pytest -from reflex_components_core.core.upload import ( +from reflex_components_upload.upload import ( StyledUpload, Upload, UploadNamespace, diff --git a/tests/units/test_app.py b/tests/units/test_app.py index d34cb93283d..b72e7548a0f 100644 --- a/tests/units/test_app.py +++ b/tests/units/test_app.py @@ -2186,7 +2186,7 @@ def test_call_app(): def test_app_with_optional_endpoints(): - from reflex_components_core.core.upload import Upload + from reflex_components_upload.upload import Upload app = App() Upload.is_used = True From 9327e9257cda38fbabbb371e779d60b37b85cfde Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Thu, 26 Mar 2026 17:50:59 -0700 Subject: [PATCH 097/113] specify all deps --- packages/reflex-components-upload/pyproject.toml | 10 +++++++++- reflex/app.py | 2 +- uv.lock | 10 +++++++++- 3 files changed, 19 insertions(+), 3 deletions(-) diff --git a/packages/reflex-components-upload/pyproject.toml b/packages/reflex-components-upload/pyproject.toml index a91349201d9..c4a1deb0347 100644 --- a/packages/reflex-components-upload/pyproject.toml +++ b/packages/reflex-components-upload/pyproject.toml @@ -6,7 +6,12 @@ readme = "README.md" authors = [{ name = "Khaleel Al-Adhami", email = "khaleel@reflex.dev" }] maintainers = [{ name = "Khaleel Al-Adhami", email = "khaleel@reflex.dev" }] requires-python = ">=3.10" -dependencies = ["reflex_components_core"] +dependencies = [ + "reflex_components_core", + "python_multipart", + "starlette", + "typing_extensions", +] [tool.hatch.version] source = "uv-dynamic-versioning" @@ -26,6 +31,9 @@ dependencies = [ "reflex_components_core", "reflex-components-lucide", "reflex-components-sonner", + "python_multipart", + "starlette", + "typing_extensions", ] [build-system] diff --git a/reflex/app.py b/reflex/app.py index 89182659eb7..49a702a8887 100644 --- a/reflex/app.py +++ b/reflex/app.py @@ -655,7 +655,7 @@ def _add_default_endpoints(self): def _add_optional_endpoints(self): """Add optional api endpoints (_upload).""" - from reflex_components_core.core.upload import Upload, get_upload_dir + from reflex_components_upload.upload import Upload, get_upload_dir if not self._api: return diff --git a/uv.lock b/uv.lock index a2efd837753..eca80b0662b 100644 --- a/uv.lock +++ b/uv.lock @@ -2323,11 +2323,19 @@ requires-dist = [{ name = "reflex-components-lucide", editable = "packages/refle name = "reflex-components-upload" source = { editable = "packages/reflex-components-upload" } dependencies = [ + { name = "python-multipart" }, { name = "reflex-components-core" }, + { name = "starlette" }, + { name = "typing-extensions" }, ] [package.metadata] -requires-dist = [{ name = "reflex-components-core", editable = "packages/reflex-components-core" }] +requires-dist = [ + { name = "python-multipart" }, + { name = "reflex-components-core", editable = "packages/reflex-components-core" }, + { name = "starlette" }, + { name = "typing-extensions" }, +] [[package]] name = "reflex-core" From e19138b192c96d2be5300bb55bc31bcae102cf90 Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Thu, 26 Mar 2026 18:00:28 -0700 Subject: [PATCH 098/113] move upload back here --- .../reflex_components_core/core}/_upload.py | 0 .../reflex_components_core/core}/upload.py | 0 packages/reflex-components-upload/README.md | 3 -- .../reflex-components-upload/pyproject.toml | 41 ------------------- .../src/reflex_components_upload/__init__.py | 1 - 5 files changed, 45 deletions(-) rename packages/{reflex-components-upload/src/reflex_components_upload => reflex-components-core/src/reflex_components_core/core}/_upload.py (100%) rename packages/{reflex-components-upload/src/reflex_components_upload => reflex-components-core/src/reflex_components_core/core}/upload.py (100%) delete mode 100644 packages/reflex-components-upload/README.md delete mode 100644 packages/reflex-components-upload/pyproject.toml delete mode 100644 packages/reflex-components-upload/src/reflex_components_upload/__init__.py diff --git a/packages/reflex-components-upload/src/reflex_components_upload/_upload.py b/packages/reflex-components-core/src/reflex_components_core/core/_upload.py similarity index 100% rename from packages/reflex-components-upload/src/reflex_components_upload/_upload.py rename to packages/reflex-components-core/src/reflex_components_core/core/_upload.py diff --git a/packages/reflex-components-upload/src/reflex_components_upload/upload.py b/packages/reflex-components-core/src/reflex_components_core/core/upload.py similarity index 100% rename from packages/reflex-components-upload/src/reflex_components_upload/upload.py rename to packages/reflex-components-core/src/reflex_components_core/core/upload.py diff --git a/packages/reflex-components-upload/README.md b/packages/reflex-components-upload/README.md deleted file mode 100644 index 7fb771e8d96..00000000000 --- a/packages/reflex-components-upload/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# Reflex Upload Components - -File upload components and backend helpers for Reflex apps. diff --git a/packages/reflex-components-upload/pyproject.toml b/packages/reflex-components-upload/pyproject.toml deleted file mode 100644 index c4a1deb0347..00000000000 --- a/packages/reflex-components-upload/pyproject.toml +++ /dev/null @@ -1,41 +0,0 @@ -[project] -name = "reflex-components-upload" -dynamic = ["version"] -description = "Reflex upload components." -readme = "README.md" -authors = [{ name = "Khaleel Al-Adhami", email = "khaleel@reflex.dev" }] -maintainers = [{ name = "Khaleel Al-Adhami", email = "khaleel@reflex.dev" }] -requires-python = ">=3.10" -dependencies = [ - "reflex_components_core", - "python_multipart", - "starlette", - "typing_extensions", -] - -[tool.hatch.version] -source = "uv-dynamic-versioning" - -[tool.uv-dynamic-versioning] -pattern-prefix = "reflex-components-upload-" -fallback-version = "0.0.0dev0" - -[tool.hatch.build] -targets.sdist.artifacts = ["*.pyi"] -targets.wheel.artifacts = ["*.pyi"] - -[tool.hatch.build.hooks.reflex-pyi] -dependencies = [ - "ruff", - "reflex-core", - "reflex_components_core", - "reflex-components-lucide", - "reflex-components-sonner", - "python_multipart", - "starlette", - "typing_extensions", -] - -[build-system] -requires = ["hatchling", "uv-dynamic-versioning", "hatch-reflex-pyi"] -build-backend = "hatchling.build" diff --git a/packages/reflex-components-upload/src/reflex_components_upload/__init__.py b/packages/reflex-components-upload/src/reflex_components_upload/__init__.py deleted file mode 100644 index 6764bebfb4c..00000000000 --- a/packages/reflex-components-upload/src/reflex_components_upload/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""Reflex upload components.""" From 4c0f58019eb1aa3ba88221154b589c7226361dfe Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Thu, 26 Mar 2026 18:00:35 -0700 Subject: [PATCH 099/113] update imports --- .../src/reflex_components_core/core/upload.py | 10 ++++----- packages/reflex-core/src/reflex_core/event.py | 6 +++--- pyi_hashes.json | 2 +- pyproject.toml | 2 -- reflex/_upload.py | 4 ++-- reflex/app.py | 2 +- scripts/make_pyi.py | 1 - tests/units/components/core/test_upload.py | 2 +- tests/units/test_app.py | 2 +- uv.lock | 21 ------------------- 10 files changed, 14 insertions(+), 38 deletions(-) diff --git a/packages/reflex-components-core/src/reflex_components_core/core/upload.py b/packages/reflex-components-core/src/reflex_components_core/core/upload.py index df323e51b03..ebe5019d526 100644 --- a/packages/reflex-components-core/src/reflex_components_core/core/upload.py +++ b/packages/reflex-components-core/src/reflex_components_core/core/upload.py @@ -6,10 +6,6 @@ from pathlib import Path from typing import Any, ClassVar -from reflex_components_core.base.fragment import Fragment -from reflex_components_core.core.cond import cond -from reflex_components_core.el.elements.forms import Input -from reflex_components_core.el.elements.typography import Div from reflex_components_sonner.toast import toast from reflex_core.components.component import ( Component, @@ -42,7 +38,11 @@ from reflex_core.vars.object import ObjectVar from reflex_core.vars.sequence import ArrayVar, LiteralStringVar -from reflex_components_upload._upload import UploadChunkIterator, UploadFile +from reflex_components_core.base.fragment import Fragment +from reflex_components_core.core._upload import UploadChunkIterator, UploadFile +from reflex_components_core.core.cond import cond +from reflex_components_core.el.elements.forms import Input +from reflex_components_core.el.elements.typography import Div DEFAULT_UPLOAD_ID: str = "default" diff --git a/packages/reflex-core/src/reflex_core/event.py b/packages/reflex-core/src/reflex_core/event.py index 893b7dca03a..4165c000c04 100644 --- a/packages/reflex-core/src/reflex_core/event.py +++ b/packages/reflex-core/src/reflex_core/event.py @@ -128,7 +128,7 @@ def resolve_upload_handler_param(handler: EventHandler) -> tuple[str, Any]: UploadTypeError: If the handler is a background task. UploadValueError: If the handler does not accept ``list[rx.UploadFile]``. """ - from reflex_components_upload._upload import UploadFile + from reflex_components_core.core._upload import UploadFile from reflex_core.utils.exceptions import UploadTypeError, UploadValueError @@ -168,7 +168,7 @@ def resolve_upload_chunk_handler_param(handler: EventHandler) -> tuple[str, type UploadTypeError: If the handler is not a background task. UploadValueError: If the handler does not accept an UploadChunkIterator. """ - from reflex_components_upload._upload import UploadChunkIterator + from reflex_components_core.core._upload import UploadChunkIterator from reflex_core.utils.exceptions import UploadTypeError, UploadValueError @@ -984,7 +984,7 @@ def _as_event_spec( Raises: ValueError: If the on_upload_progress is not a valid event handler. """ - from reflex_components_upload.upload import ( + from reflex_components_core.core.upload import ( DEFAULT_UPLOAD_ID, upload_files_context_var_data, ) diff --git a/pyi_hashes.json b/pyi_hashes.json index 3795ac87f1c..d526931c8dc 100644 --- a/pyi_hashes.json +++ b/pyi_hashes.json @@ -20,6 +20,7 @@ "packages/reflex-components-core/src/reflex_components_core/core/helmet.pyi": "3bf7bee5665293f7583009f651ea3cb1", "packages/reflex-components-core/src/reflex_components_core/core/html.pyi": "7209d1607545e412ed38dbe2a129321c", "packages/reflex-components-core/src/reflex_components_core/core/sticky.pyi": "8241c75ca16a0960b7dea6d6e7aff52e", + "packages/reflex-components-core/src/reflex_components_core/core/upload.pyi": "73e38c074d7e6ca2fda8eaad820f177e", "packages/reflex-components-core/src/reflex_components_core/core/window_events.pyi": "4407cecb1825dc359bcc7b2bea011a8e", "packages/reflex-components-core/src/reflex_components_core/datadisplay/__init__.pyi": "aaab42816119ac0f308841dc5482b3f1", "packages/reflex-components-core/src/reflex_components_core/el/__init__.pyi": "e27fddec8de079db37d6699e136411d1", @@ -117,7 +118,6 @@ "packages/reflex-components-recharts/src/reflex_components_recharts/polar.pyi": "1ce679c002336c7bdbdd6c8ff6f2413c", "packages/reflex-components-recharts/src/reflex_components_recharts/recharts.pyi": "1b92135de4ea79cb7d94eaaec55b9ab7", "packages/reflex-components-sonner/src/reflex_components_sonner/toast.pyi": "f09c503c4ab880c13c13d6fa67d708b8", - "packages/reflex-components-upload/src/reflex_components_upload/upload.pyi": "bb0977d2e9ab51344b84d3da2b23a984", "reflex/__init__.pyi": "a13470178c515fb4ad0c1adf6d63efe2", "reflex/components/__init__.pyi": "55bb242d5e5428db329b88b4923c2ba5", "reflex/experimental/memo.pyi": "d16eccf33993c781e2f8bc2dd8bbd4d4" diff --git a/pyproject.toml b/pyproject.toml index 29ad7c52cc5..f3ad60edc0d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -49,7 +49,6 @@ dependencies = [ "reflex-components-react-player", "reflex-components-recharts", "reflex-components-sonner", - "reflex-components-upload", ] classifiers = [ @@ -344,7 +343,6 @@ reflex-components-radix.workspace = true reflex-components-react-player.workspace = true reflex-components-recharts.workspace = true reflex-components-sonner.workspace = true -reflex-components-upload.workspace = true reflex-docgen.workspace = true [tool.uv.workspace] diff --git a/reflex/_upload.py b/reflex/_upload.py index af35cc948f0..6820782b940 100644 --- a/reflex/_upload.py +++ b/reflex/_upload.py @@ -1,3 +1,3 @@ -"""Re-export from reflex_components_upload._upload.""" +"""Re-export from reflex_components_core.core._upload.""" -from reflex_components_upload._upload import * +from reflex_components_core.core._upload import * diff --git a/reflex/app.py b/reflex/app.py index 49a702a8887..89182659eb7 100644 --- a/reflex/app.py +++ b/reflex/app.py @@ -655,7 +655,7 @@ def _add_default_endpoints(self): def _add_optional_endpoints(self): """Add optional api endpoints (_upload).""" - from reflex_components_upload.upload import Upload, get_upload_dir + from reflex_components_core.core.upload import Upload, get_upload_dir if not self._api: return diff --git a/scripts/make_pyi.py b/scripts/make_pyi.py index e51ab2cd9b1..5410d45302c 100644 --- a/scripts/make_pyi.py +++ b/scripts/make_pyi.py @@ -28,7 +28,6 @@ "packages/reflex-components-react-player/src/reflex_components_react_player", "packages/reflex-components-recharts/src/reflex_components_recharts", "packages/reflex-components-sonner/src/reflex_components_sonner", - "packages/reflex-components-upload/src/reflex_components_upload", ] diff --git a/tests/units/components/core/test_upload.py b/tests/units/components/core/test_upload.py index 7d9d9dbf785..f7ce6b6c0c5 100644 --- a/tests/units/components/core/test_upload.py +++ b/tests/units/components/core/test_upload.py @@ -1,7 +1,7 @@ from typing import Any, cast import pytest -from reflex_components_upload.upload import ( +from reflex_components_core.core.upload import ( StyledUpload, Upload, UploadNamespace, diff --git a/tests/units/test_app.py b/tests/units/test_app.py index b72e7548a0f..d34cb93283d 100644 --- a/tests/units/test_app.py +++ b/tests/units/test_app.py @@ -2186,7 +2186,7 @@ def test_call_app(): def test_app_with_optional_endpoints(): - from reflex_components_upload.upload import Upload + from reflex_components_core.core.upload import Upload app = App() Upload.is_used = True diff --git a/uv.lock b/uv.lock index eca80b0662b..e2d730997b6 100644 --- a/uv.lock +++ b/uv.lock @@ -27,7 +27,6 @@ members = [ "reflex-components-react-player", "reflex-components-recharts", "reflex-components-sonner", - "reflex-components-upload", "reflex-core", "reflex-docgen", ] @@ -2076,7 +2075,6 @@ dependencies = [ { name = "reflex-components-react-player" }, { name = "reflex-components-recharts" }, { name = "reflex-components-sonner" }, - { name = "reflex-components-upload" }, { name = "reflex-core" }, { name = "reflex-hosting-cli" }, { name = "rich" }, @@ -2156,7 +2154,6 @@ requires-dist = [ { name = "reflex-components-react-player", editable = "packages/reflex-components-react-player" }, { name = "reflex-components-recharts", editable = "packages/reflex-components-recharts" }, { name = "reflex-components-sonner", editable = "packages/reflex-components-sonner" }, - { name = "reflex-components-upload", editable = "packages/reflex-components-upload" }, { name = "reflex-core", editable = "packages/reflex-core" }, { name = "reflex-hosting-cli", specifier = ">=0.1.61" }, { name = "rich", specifier = ">=13,<15" }, @@ -2319,24 +2316,6 @@ dependencies = [ [package.metadata] requires-dist = [{ name = "reflex-components-lucide", editable = "packages/reflex-components-lucide" }] -[[package]] -name = "reflex-components-upload" -source = { editable = "packages/reflex-components-upload" } -dependencies = [ - { name = "python-multipart" }, - { name = "reflex-components-core" }, - { name = "starlette" }, - { name = "typing-extensions" }, -] - -[package.metadata] -requires-dist = [ - { name = "python-multipart" }, - { name = "reflex-components-core", editable = "packages/reflex-components-core" }, - { name = "starlette" }, - { name = "typing-extensions" }, -] - [[package]] name = "reflex-core" source = { editable = "packages/reflex-core" } From 25b049348bb85809643573cf77c0e05a1631725e Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Thu, 26 Mar 2026 18:01:17 -0700 Subject: [PATCH 100/113] update deps --- packages/reflex-components-core/pyproject.toml | 11 ++++++++++- uv.lock | 6 ++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/packages/reflex-components-core/pyproject.toml b/packages/reflex-components-core/pyproject.toml index f1a0af131f5..f61e5d74612 100644 --- a/packages/reflex-components-core/pyproject.toml +++ b/packages/reflex-components-core/pyproject.toml @@ -6,7 +6,13 @@ readme = "README.md" authors = [{ name = "Khaleel Al-Adhami", email = "khaleel@reflex.dev" }] maintainers = [{ name = "Khaleel Al-Adhami", email = "khaleel@reflex.dev" }] requires-python = ">=3.10" -dependencies = ["reflex-components-lucide", "reflex-components-sonner"] +dependencies = [ + "reflex-components-lucide", + "reflex-components-sonner", + "python_multipart", + "starlette", + "typing_extensions", +] [tool.hatch.version] source = "uv-dynamic-versioning" @@ -25,6 +31,9 @@ dependencies = [ "reflex-core", "reflex-components-lucide", "reflex-components-sonner", + "python_multipart", + "starlette", + "typing_extensions", ] diff --git a/uv.lock b/uv.lock index e2d730997b6..e7df6683183 100644 --- a/uv.lock +++ b/uv.lock @@ -2220,14 +2220,20 @@ requires-dist = [ name = "reflex-components-core" source = { editable = "packages/reflex-components-core" } dependencies = [ + { name = "python-multipart" }, { name = "reflex-components-lucide" }, { name = "reflex-components-sonner" }, + { name = "starlette" }, + { name = "typing-extensions" }, ] [package.metadata] requires-dist = [ + { name = "python-multipart" }, { name = "reflex-components-lucide", editable = "packages/reflex-components-lucide" }, { name = "reflex-components-sonner", editable = "packages/reflex-components-sonner" }, + { name = "starlette" }, + { name = "typing-extensions" }, ] [[package]] From 02841d7d19935ac56ba0d899fe37d8d8c5ab4690 Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Thu, 26 Mar 2026 18:10:02 -0700 Subject: [PATCH 101/113] fix the mocks --- reflex/app.py | 2 +- tests/units/test_state.py | 2 +- tests/units/test_testing.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/reflex/app.py b/reflex/app.py index 89182659eb7..6c4a127a649 100644 --- a/reflex/app.py +++ b/reflex/app.py @@ -61,6 +61,7 @@ get_hydrate_event, noop, ) +from reflex_core.utils import console from reflex_core.utils.imports import ImportVar from reflex_core.utils.types import ASGIApp, Message, Receive, Scope, Send from rich.progress import MofNCompleteColumn, Progress, TimeElapsedColumn @@ -106,7 +107,6 @@ ) from reflex.utils import ( codespaces, - console, exceptions, format, frontend_skeleton, diff --git a/tests/units/test_state.py b/tests/units/test_state.py index cc6b3ac5df4..341584df409 100644 --- a/tests/units/test_state.py +++ b/tests/units/test_state.py @@ -1979,7 +1979,7 @@ async def test_state_manager_lock_warning_threshold_contend( substate_token_redis: A token + substate name for looking up in state manager. mocker: Pytest mocker object. """ - console_warn = mocker.patch("reflex.utils.console.warn") + console_warn = mocker.patch("reflex_core.utils.console.warn") state_manager_redis.lock_expiration = LOCK_EXPIRATION state_manager_redis.lock_warning_threshold = LOCK_WARNING_THRESHOLD diff --git a/tests/units/test_testing.py b/tests/units/test_testing.py index 6d29351cbc9..0815326648c 100644 --- a/tests/units/test_testing.py +++ b/tests/units/test_testing.py @@ -69,7 +69,7 @@ def harness_mocks(monkeypatch): ) ) - monkeypatch.setattr(reflex_testing, "get_config", lambda: fake_config) + monkeypatch.setattr(reflex_testing, "get_config", lambda reload=False: fake_config) monkeypatch.setattr( reflex_core.config, "get_config", lambda reload=False: fake_config ) From 3eadcc593742da08fd3f939256f8cb918f940245 Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Thu, 26 Mar 2026 18:12:41 -0700 Subject: [PATCH 102/113] oh well --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index f3ad60edc0d..3bc21a784a5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -247,7 +247,7 @@ omit = [ [tool.coverage.report] show_missing = true # TODO bump back to 79 -fail_under = 60 +fail_under = 50 precision = 2 ignore_errors = true From bd6c6d913d7331017e13b4bf081b32fc3a9c21d3 Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Thu, 26 Mar 2026 18:21:38 -0700 Subject: [PATCH 103/113] fix module imports --- .../reflex_components_radix/themes/components/radio_group.py | 3 ++- .../src/reflex_components_radix/themes/components/select.py | 2 +- pyi_hashes.json | 4 ++-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/radio_group.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/radio_group.py index c33bdfc6cbb..537cdc6d114 100644 --- a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/radio_group.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/radio_group.py @@ -5,8 +5,9 @@ from collections.abc import Sequence from typing import Literal -from reflex_components_core.core import cond, foreach from reflex_components_core.core.breakpoints import Responsive +from reflex_components_core.core.cond import cond +from reflex_components_core.core.foreach import foreach from reflex_core.components.component import Component, ComponentNamespace, field from reflex_core.event import EventHandler, passthrough_event_spec from reflex_core.utils import types diff --git a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/select.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/select.py index 38336b3830c..583433e8010 100644 --- a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/select.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/select.py @@ -3,8 +3,8 @@ from collections.abc import Sequence from typing import ClassVar, Literal -from reflex_components_core.core import foreach from reflex_components_core.core.breakpoints import Responsive +from reflex_components_core.core.foreach import foreach from reflex_core.components.component import Component, ComponentNamespace, field from reflex_core.constants.compiler import MemoizationMode from reflex_core.event import EventHandler, no_args_event_spec, passthrough_event_spec diff --git a/pyi_hashes.json b/pyi_hashes.json index d526931c8dc..d3a527457f5 100644 --- a/pyi_hashes.json +++ b/pyi_hashes.json @@ -77,10 +77,10 @@ "packages/reflex-components-radix/src/reflex_components_radix/themes/components/progress.pyi": "7a874fa512ce2d8a490aa41531f5814b", "packages/reflex-components-radix/src/reflex_components_radix/themes/components/radio.pyi": "465a6d6e9525ac909b4f193d2d788682", "packages/reflex-components-radix/src/reflex_components_radix/themes/components/radio_cards.pyi": "ddb2835ecbeaf90681e4030a14d74604", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/radio_group.pyi": "29e8f508cd30a3d27c7ee3debc986d82", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/radio_group.pyi": "d5333b59e6ba9ad30923d2b60d0e382e", "packages/reflex-components-radix/src/reflex_components_radix/themes/components/scroll_area.pyi": "6b2d881a8ecdf4dd169b341418a703db", "packages/reflex-components-radix/src/reflex_components_radix/themes/components/segmented_control.pyi": "79764047f53543d673d6e1b2c929d9b8", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/select.pyi": "0cb2e8d2102d33c400d874d306dcbaa7", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/select.pyi": "f71a320b02ac8f1d6db07b9198b296ec", "packages/reflex-components-radix/src/reflex_components_radix/themes/components/separator.pyi": "f8420b5196edb74275d2119d780d0031", "packages/reflex-components-radix/src/reflex_components_radix/themes/components/skeleton.pyi": "574407d03b311ca9cdf0f98ab53a6fbe", "packages/reflex-components-radix/src/reflex_components_radix/themes/components/slider.pyi": "9e26688af77fab944635e16e0bf7283f", From a58e209d31114e5c2f355416ba17e049d42b7fd0 Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Fri, 27 Mar 2026 10:40:25 -0700 Subject: [PATCH 104/113] use string --- packages/reflex-core/src/reflex_core/event.py | 70 +++++++++---------- 1 file changed, 34 insertions(+), 36 deletions(-) diff --git a/packages/reflex-core/src/reflex_core/event.py b/packages/reflex-core/src/reflex_core/event.py index 4165c000c04..7828a60f44a 100644 --- a/packages/reflex-core/src/reflex_core/event.py +++ b/packages/reflex-core/src/reflex_core/event.py @@ -1,7 +1,5 @@ """Define event classes to connect the frontend and backend.""" -from __future__ import annotations - import dataclasses import inspect import sys @@ -101,7 +99,7 @@ def substate_token(self) -> str: UPLOAD_FILES_CLIENT_HANDLER = "uploadFiles" -def _handler_name(handler: EventHandler) -> str: +def _handler_name(handler: "EventHandler") -> str: """Get a stable fully qualified handler name for errors. Args: @@ -115,7 +113,7 @@ def _handler_name(handler: EventHandler) -> str: return handler.fn.__qualname__ -def resolve_upload_handler_param(handler: EventHandler) -> tuple[str, Any]: +def resolve_upload_handler_param(handler: "EventHandler") -> tuple[str, Any]: """Validate and resolve the UploadFile list parameter for a handler. Args: @@ -155,7 +153,7 @@ def resolve_upload_handler_param(handler: EventHandler) -> tuple[str, Any]: raise UploadValueError(msg) -def resolve_upload_chunk_handler_param(handler: EventHandler) -> tuple[str, type]: +def resolve_upload_chunk_handler_param(handler: "EventHandler") -> tuple[str, type]: """Validate and resolve the UploadChunkIterator parameter for a handler. Args: @@ -338,7 +336,7 @@ def is_background(self) -> bool: """ return getattr(self.fn, BACKGROUND_TASK_MARKER, False) - def __call__(self, *args: Any, **kwargs: Any) -> EventSpec: + def __call__(self, *args: Any, **kwargs: Any) -> "EventSpec": """Pass arguments to the handler to get an event spec. This method configures event handlers that take in arguments. @@ -448,7 +446,7 @@ def __init__( object.__setattr__(self, "client_handler_name", client_handler_name) object.__setattr__(self, "args", args or ()) - def with_args(self, args: tuple[tuple[Var, Var], ...]) -> EventSpec: + def with_args(self, args: tuple[tuple[Var, Var], ...]) -> "EventSpec": """Copy the event spec, with updated args. Args: @@ -464,7 +462,7 @@ def with_args(self, args: tuple[tuple[Var, Var], ...]) -> EventSpec: event_actions=self.event_actions.copy(), ) - def add_args(self, *args: Var) -> EventSpec: + def add_args(self, *args: Var) -> "EventSpec": """Add arguments to the event spec. Args: @@ -555,7 +553,7 @@ def __call__(self, *args, **kwargs) -> EventSpec: class EventChain(EventActionsMixin): """Container for a chain of events that will be executed in order.""" - events: Sequence[EventSpec | EventVar | FunctionVar | EventCallback] = ( + events: Sequence["EventSpec | EventVar | FunctionVar | EventCallback"] = ( dataclasses.field(default_factory=list) ) @@ -566,11 +564,11 @@ class EventChain(EventActionsMixin): @classmethod def create( cls, - value: EventType, + value: "EventType", args_spec: ArgsSpec | Sequence[ArgsSpec], key: str | None = None, **event_chain_kwargs, - ) -> EventChain | Var: + ) -> "EventChain | Var": """Create an event chain from a variety of input types. Args: @@ -1485,7 +1483,7 @@ def download( def call_script( javascript_code: str | Var[str], - callback: EventType[Any] | None = None, + callback: "EventType[Any] | None" = None, ) -> EventSpec: """Create an event handler that executes arbitrary javascript code. @@ -1526,7 +1524,7 @@ def call_script( def call_function( javascript_code: str | Var, - callback: EventType[Any] | None = None, + callback: "EventType[Any] | None" = None, ) -> EventSpec: """Create an event handler that executes arbitrary javascript code. @@ -1562,7 +1560,7 @@ def call_function( def run_script( javascript_code: str | Var, - callback: EventType[Any] | None = None, + callback: "EventType[Any] | None" = None, ) -> EventSpec: """Create an event handler that executes arbitrary javascript code. @@ -1580,7 +1578,7 @@ def run_script( return call_function(ArgsFunctionOperation.create((), javascript_code), callback) -def get_event(state: BaseState, event: str): +def get_event(state: "BaseState", event: str): """Get the event from the given state. Args: @@ -1593,7 +1591,7 @@ def get_event(state: BaseState, event: str): return f"{state.get_name()}.{event}" -def get_hydrate_event(state: BaseState) -> str: +def get_hydrate_event(state: "BaseState") -> str: """Get the name of the hydrate event for the state. Args: @@ -1946,7 +1944,7 @@ def call_event_fn( fn: Callable, arg_spec: ArgsSpec | Sequence[ArgsSpec], key: str | None = None, -) -> list[EventSpec | FunctionVar | EventVar]: +) -> list["EventSpec | FunctionVar | EventVar"]: """Call a function to a list of event specs. The function should return a single event-like value or a heterogeneous @@ -2153,7 +2151,7 @@ def create( cls, value: EventSpec | EventHandler, _var_data: VarData | None = None, - ) -> LiteralEventVar: + ) -> "LiteralEventVar": """Create a new LiteralEventVar instance. Args: @@ -2240,7 +2238,7 @@ def create( cls, value: EventChain, _var_data: VarData | None = None, - ) -> LiteralEventChainVar: + ) -> "LiteralEventChainVar": """Create a new LiteralEventChainVar instance. Args: @@ -2363,39 +2361,39 @@ def __init__(self, func: Callable[[Any, Unpack[P]], Any]): @overload def __call__( - self: EventCallback[Unpack[Q]], - ) -> EventCallback[Unpack[Q]]: ... + self: "EventCallback[Unpack[Q]]", + ) -> "EventCallback[Unpack[Q]]": ... @overload def __call__( - self: EventCallback[V, Unpack[Q]], value: V | Var[V] - ) -> EventCallback[Unpack[Q]]: ... + self: "EventCallback[V, Unpack[Q]]", value: V | Var[V] + ) -> "EventCallback[Unpack[Q]]": ... @overload def __call__( - self: EventCallback[V, V2, Unpack[Q]], + self: "EventCallback[V, V2, Unpack[Q]]", value: V | Var[V], value2: V2 | Var[V2], - ) -> EventCallback[Unpack[Q]]: ... + ) -> "EventCallback[Unpack[Q]]": ... @overload def __call__( - self: EventCallback[V, V2, V3, Unpack[Q]], + self: "EventCallback[V, V2, V3, Unpack[Q]]", value: V | Var[V], value2: V2 | Var[V2], value3: V3 | Var[V3], - ) -> EventCallback[Unpack[Q]]: ... + ) -> "EventCallback[Unpack[Q]]": ... @overload def __call__( - self: EventCallback[V, V2, V3, V4, Unpack[Q]], + self: "EventCallback[V, V2, V3, V4, Unpack[Q]]", value: V | Var[V], value2: V2 | Var[V2], value3: V3 | Var[V3], value4: V4 | Var[V4], - ) -> EventCallback[Unpack[Q]]: ... + ) -> "EventCallback[Unpack[Q]]": ... - def __call__(self, *values) -> EventCallback: # pyright: ignore [reportInconsistentOverload] + def __call__(self, *values) -> "EventCallback": # pyright: ignore [reportInconsistentOverload] """Call the function with the values. Args: @@ -2408,8 +2406,8 @@ def __call__(self, *values) -> EventCallback: # pyright: ignore [reportInconsis @overload def __get__( - self: EventCallback[Unpack[P]], instance: None, owner: Any - ) -> EventCallback[Unpack[P]]: ... + self: "EventCallback[Unpack[P]]", instance: None, owner: Any + ) -> "EventCallback[Unpack[P]]": ... @overload def __get__(self, instance: Any, owner: Any) -> Callable[[Unpack[P]]]: ... @@ -2436,19 +2434,19 @@ class LambdaEventCallback(Protocol[Unpack[P]]): __code__: types.CodeType @overload - def __call__(self: LambdaEventCallback[()]) -> Any: ... + def __call__(self: "LambdaEventCallback[()]") -> Any: ... @overload - def __call__(self: LambdaEventCallback[V], value: Var[V], /) -> Any: ... + def __call__(self: "LambdaEventCallback[V]", value: Var[V], /) -> Any: ... @overload def __call__( - self: LambdaEventCallback[V, V2], value: Var[V], value2: Var[V2], / + self: "LambdaEventCallback[V, V2]", value: Var[V], value2: Var[V2], / ) -> Any: ... @overload def __call__( - self: LambdaEventCallback[V, V2, V3], + self: "LambdaEventCallback[V, V2, V3]", value: Var[V], value2: Var[V2], value3: Var[V3], From 86914e0d76648c785f48ab3d960df4cc350d66e8 Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Fri, 27 Mar 2026 10:43:19 -0700 Subject: [PATCH 105/113] use strings --- packages/reflex-core/src/reflex_core/event.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/reflex-core/src/reflex_core/event.py b/packages/reflex-core/src/reflex_core/event.py index 7828a60f44a..71cc53baab5 100644 --- a/packages/reflex-core/src/reflex_core/event.py +++ b/packages/reflex-core/src/reflex_core/event.py @@ -2410,7 +2410,7 @@ def __get__( ) -> "EventCallback[Unpack[P]]": ... @overload - def __get__(self, instance: Any, owner: Any) -> Callable[[Unpack[P]]]: ... + def __get__(self, instance: Any, owner: Any) -> "Callable[[Unpack[P]]]": ... def __get__(self, instance: Any, owner: Any) -> Callable: """Get the function with the instance bound to it. @@ -2568,9 +2568,9 @@ def __new__( throttle: int | None = None, debounce: int | None = None, temporal: bool | None = None, - ) -> Callable[ - [Callable[[BASE_STATE, Unpack[P]], Any]], EventCallback[Unpack[P]] # pyright: ignore [reportInvalidTypeVarUse] - ]: ... + ) -> ( + "Callable[[Callable[[BASE_STATE, Unpack[P]], Any]], EventCallback[Unpack[P]]]" + ): ... @overload def __new__( From e80aece4805dc56ef62c6c1f37be96c221bf955d Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Fri, 27 Mar 2026 16:41:17 -0700 Subject: [PATCH 106/113] do imports better --- .../src/reflex_components_radix/mappings.py | 71 ++++++------- .../src/reflex_core/utils/lazy_loader.py | 7 +- .../src/reflex_core/utils/pyi_generator.py | 4 + pyi_hashes.json | 12 +-- reflex/__init__.py | 99 ++++++++++--------- 5 files changed, 105 insertions(+), 88 deletions(-) diff --git a/packages/reflex-components-radix/src/reflex_components_radix/mappings.py b/packages/reflex-components-radix/src/reflex_components_radix/mappings.py index bdd090a04e8..8e7494da37f 100644 --- a/packages/reflex-components-radix/src/reflex_components_radix/mappings.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/mappings.py @@ -1,12 +1,12 @@ """Radix component mappings for lazy loading.""" -RADIX_THEMES_MAPPING: dict = { - "components.radix.themes.base": ["color_mode", "theme", "theme_panel"], - "components.radix.themes.color_mode": ["color_mode"], +RADIX_THEMES_MAPPING: dict[str, list[str]] = { + "reflex_components_radix.themes.base": ["color_mode", "theme", "theme_panel"], + "reflex_components_radix.themes.color_mode": ["color_mode"], } -RADIX_THEMES_COMPONENTS_MAPPING: dict = { +RADIX_THEMES_COMPONENTS_MAPPING: dict[str, list[str]] = { **{ - f"components.radix.themes.components.{mod}": [mod] + f"reflex_components_radix.themes.components.{mod}": [mod] for mod in [ "alert_dialog", "aspect_ratio", @@ -40,86 +40,89 @@ "checkbox_group", ] }, - "components.radix.themes.components.text_field": ["text_field", "input"], - "components.radix.themes.components.radio_group": ["radio", "radio_group"], - "components.radix.themes.components.dropdown_menu": ["menu", "dropdown_menu"], - "components.radix.themes.components.separator": ["divider", "separator"], - "components.radix.themes.components.progress": ["progress"], + "reflex_components_radix.themes.components.text_field": ["text_field", "input"], + "reflex_components_radix.themes.components.radio_group": ["radio", "radio_group"], + "reflex_components_radix.themes.components.dropdown_menu": [ + "menu", + "dropdown_menu", + ], + "reflex_components_radix.themes.components.separator": ["divider", "separator"], + "reflex_components_radix.themes.components.progress": ["progress"], } -RADIX_THEMES_LAYOUT_MAPPING: dict = { - "components.radix.themes.layout.box": [ +RADIX_THEMES_LAYOUT_MAPPING: dict[str, list[str]] = { + "reflex_components_radix.themes.layout.box": [ "box", ], - "components.radix.themes.layout.center": [ + "reflex_components_radix.themes.layout.center": [ "center", ], - "components.radix.themes.layout.container": [ + "reflex_components_radix.themes.layout.container": [ "container", ], - "components.radix.themes.layout.flex": [ + "reflex_components_radix.themes.layout.flex": [ "flex", ], - "components.radix.themes.layout.grid": [ + "reflex_components_radix.themes.layout.grid": [ "grid", ], - "components.radix.themes.layout.section": [ + "reflex_components_radix.themes.layout.section": [ "section", ], - "components.radix.themes.layout.spacer": [ + "reflex_components_radix.themes.layout.spacer": [ "spacer", ], - "components.radix.themes.layout.stack": [ + "reflex_components_radix.themes.layout.stack": [ "stack", "hstack", "vstack", ], - "components.radix.themes.layout.list": [ - ("list_ns", "list"), + "reflex_components_radix.themes.layout.list": [ + "list_ns", "list_item", "ordered_list", "unordered_list", ], } -RADIX_THEMES_TYPOGRAPHY_MAPPING: dict = { - "components.radix.themes.typography.blockquote": [ +RADIX_THEMES_TYPOGRAPHY_MAPPING: dict[str, list[str]] = { + "reflex_components_radix.themes.typography.blockquote": [ "blockquote", ], - "components.radix.themes.typography.code": [ + "reflex_components_radix.themes.typography.code": [ "code", ], - "components.radix.themes.typography.heading": [ + "reflex_components_radix.themes.typography.heading": [ "heading", ], - "components.radix.themes.typography.link": [ + "reflex_components_radix.themes.typography.link": [ "link", ], - "components.radix.themes.typography.text": [ + "reflex_components_radix.themes.typography.text": [ "text", ], } -RADIX_PRIMITIVES_MAPPING: dict = { - "components.radix.primitives.accordion": [ +RADIX_PRIMITIVES_MAPPING: dict[str, list[str]] = { + "reflex_components_radix.primitives.accordion": [ "accordion", ], - "components.radix.primitives.drawer": [ + "reflex_components_radix.primitives.drawer": [ "drawer", ], - "components.radix.primitives.form": [ + "reflex_components_radix.primitives.form": [ "form", ], - "components.radix.primitives.progress": [ + "reflex_components_radix.primitives.progress": [ "progress", ], } -RADIX_PRIMITIVES_SHORTCUT_MAPPING: dict = { +RADIX_PRIMITIVES_SHORTCUT_MAPPING: dict[str, list[str]] = { k: v for k, v in RADIX_PRIMITIVES_MAPPING.items() if "progress" not in k } -RADIX_MAPPING: dict = { +RADIX_MAPPING: dict[str, list[str]] = { **RADIX_THEMES_MAPPING, **RADIX_THEMES_COMPONENTS_MAPPING, **RADIX_THEMES_TYPOGRAPHY_MAPPING, diff --git a/packages/reflex-core/src/reflex_core/utils/lazy_loader.py b/packages/reflex-core/src/reflex_core/utils/lazy_loader.py index 3cc2f83061e..ba157f0c2ae 100644 --- a/packages/reflex-core/src/reflex_core/utils/lazy_loader.py +++ b/packages/reflex-core/src/reflex_core/utils/lazy_loader.py @@ -27,7 +27,7 @@ def attach( package_name: str, submodules: set[str] | None = None, submod_attrs: dict[str, list[str]] | None = None, - **extra_mappings, + **extra_mappings: str, ): """Replaces a package's __getattr__, __dir__, and __all__ attributes using lazy.attach. The lazy loader __getattr__ doesn't support tuples as list values. We needed to add @@ -66,7 +66,10 @@ def attach( def __getattr__(name: str): # noqa: N807 if name in extra_mappings: - submod_path, attr = extra_mappings[name].rsplit(".", 1) + path = extra_mappings[name] + if "." in path: + return importlib.import_module(path) + submod_path, attr = path.rsplit(".", 1) submod = importlib.import_module(submod_path) return getattr(submod, attr) if name in submodules: diff --git a/packages/reflex-core/src/reflex_core/utils/pyi_generator.py b/packages/reflex-core/src/reflex_core/utils/pyi_generator.py index 45b3ff0fabf..ef54acd4ab9 100644 --- a/packages/reflex-core/src/reflex_core/utils/pyi_generator.py +++ b/packages/reflex-core/src/reflex_core/utils/pyi_generator.py @@ -1407,6 +1407,10 @@ def _get_init_lazy_imports(mod: tuple | ModuleType, new_tree: ast.AST): if extra_mappings: for alias, import_path in extra_mappings.items(): + if "." not in import_path: + # Handle simple module imports (e.g. "import reflex_components_markdown as markdown"). + extra_mappings_imports.append(f"import {import_path} as {alias}") + continue module_name, import_name = import_path.rsplit(".", 1) extra_mappings_imports.append( f"from {module_name} import {import_name} as {alias}" diff --git a/pyi_hashes.json b/pyi_hashes.json index d3a527457f5..a91629a9c5d 100644 --- a/pyi_hashes.json +++ b/pyi_hashes.json @@ -43,8 +43,8 @@ "packages/reflex-components-markdown/src/reflex_components_markdown/markdown.pyi": "bacf19a5b6904281d7238dbd51e6fc1c", "packages/reflex-components-moment/src/reflex_components_moment/moment.pyi": "b997bdd994844f0e6ca923bbb2dc34a1", "packages/reflex-components-plotly/src/reflex_components_plotly/plotly.pyi": "65a93d778a0fde06975dac9244f51bb3", - "packages/reflex-components-radix/src/reflex_components_radix/__init__.pyi": "6786311e88184cd12f59f6bcb72b4271", - "packages/reflex-components-radix/src/reflex_components_radix/primitives/__init__.pyi": "bc25cae0eca01c8684443d5dfd7b6455", + "packages/reflex-components-radix/src/reflex_components_radix/__init__.pyi": "f54e0eac69cbca68ef94720ab439b52f", + "packages/reflex-components-radix/src/reflex_components_radix/primitives/__init__.pyi": "289dfe0e5a59948b90b875ea8f75639c", "packages/reflex-components-radix/src/reflex_components_radix/primitives/accordion.pyi": "6dd30847af62ad7d50d5c5daf6c4a1d7", "packages/reflex-components-radix/src/reflex_components_radix/primitives/base.pyi": "6c12ef3d9f82926bf17d410b774d56f5", "packages/reflex-components-radix/src/reflex_components_radix/primitives/dialog.pyi": "810fa8c626b79035cdbb04f43b5bc5ad", @@ -55,7 +55,7 @@ "packages/reflex-components-radix/src/reflex_components_radix/themes/__init__.pyi": "5d9a06872953d3e3df99e1ff154a4e0c", "packages/reflex-components-radix/src/reflex_components_radix/themes/base.pyi": "d7c20bd180f28fdb4affcba37e2aa1ff", "packages/reflex-components-radix/src/reflex_components_radix/themes/color_mode.pyi": "ba0ff3b00289cd1896e327fa2be99563", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/__init__.pyi": "2b0f9f472ba6dcc743c2df17642c4a4b", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/__init__.pyi": "0bddcd536fbe99dc635474da32b4b633", "packages/reflex-components-radix/src/reflex_components_radix/themes/components/alert_dialog.pyi": "fad40b463a8ebb0d3ca3900dc8a91679", "packages/reflex-components-radix/src/reflex_components_radix/themes/components/aspect_ratio.pyi": "0d22969c5407592a0bb36768e149f2b5", "packages/reflex-components-radix/src/reflex_components_radix/themes/components/avatar.pyi": "6a6e9b8f6ca3428c45d62bd0e7f94693", @@ -91,7 +91,7 @@ "packages/reflex-components-radix/src/reflex_components_radix/themes/components/text_area.pyi": "1a03d9525a1544816392067499c3354d", "packages/reflex-components-radix/src/reflex_components_radix/themes/components/text_field.pyi": "c2fbd8547de4993e03017844e8c4b477", "packages/reflex-components-radix/src/reflex_components_radix/themes/components/tooltip.pyi": "4545b70fd0802f19993419ab0163d595", - "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/__init__.pyi": "94c16e4bf7e41939232ff1f19e7b3709", + "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/__init__.pyi": "f7e4de67a6e656417246321cd992fd95", "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/base.pyi": "3195e198d92ff43644a09c277303b83b", "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/box.pyi": "69d12b6c918a476ac4557f42fef73c27", "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/center.pyi": "88880d197ff7347ec7d3f81d6e57de8e", @@ -102,7 +102,7 @@ "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/section.pyi": "1aa57142797597d65d840eb1d3cc7de7", "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/spacer.pyi": "c774f0a1384f983e6d73bde603c341ca", "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/stack.pyi": "894dcd5945123c1c8aa34cb77602fead", - "packages/reflex-components-radix/src/reflex_components_radix/themes/typography/__init__.pyi": "3926877c04f74fc2acf4a398bee9da06", + "packages/reflex-components-radix/src/reflex_components_radix/themes/typography/__init__.pyi": "c0e39c5990d4ee43ab14a56a522678ae", "packages/reflex-components-radix/src/reflex_components_radix/themes/typography/blockquote.pyi": "305a8932078e4af48e44489e7ce74060", "packages/reflex-components-radix/src/reflex_components_radix/themes/typography/code.pyi": "fad43053747fb84229cc35296c7028b5", "packages/reflex-components-radix/src/reflex_components_radix/themes/typography/heading.pyi": "5901c7202a5b135f60cc1407878b4859", @@ -118,7 +118,7 @@ "packages/reflex-components-recharts/src/reflex_components_recharts/polar.pyi": "1ce679c002336c7bdbdd6c8ff6f2413c", "packages/reflex-components-recharts/src/reflex_components_recharts/recharts.pyi": "1b92135de4ea79cb7d94eaaec55b9ab7", "packages/reflex-components-sonner/src/reflex_components_sonner/toast.pyi": "f09c503c4ab880c13c13d6fa67d708b8", - "reflex/__init__.pyi": "a13470178c515fb4ad0c1adf6d63efe2", + "reflex/__init__.pyi": "0d2fdfbd6860edc301afc744f198391a", "reflex/components/__init__.pyi": "55bb242d5e5428db329b88b4923c2ba5", "reflex/experimental/memo.pyi": "d16eccf33993c781e2f8bc2dd8bbd4d4" } diff --git a/reflex/__init__.py b/reflex/__init__.py index 8578a602ebe..105f4c690c8 100644 --- a/reflex/__init__.py +++ b/reflex/__init__.py @@ -97,37 +97,29 @@ del console del sys -from reflex_components_radix.mappings import ( # noqa: E402 - RADIX_MAPPING, - RADIX_PRIMITIVES_MAPPING, - RADIX_PRIMITIVES_SHORTCUT_MAPPING, - RADIX_THEMES_COMPONENTS_MAPPING, - RADIX_THEMES_LAYOUT_MAPPING, - RADIX_THEMES_MAPPING, - RADIX_THEMES_TYPOGRAPHY_MAPPING, -) +from reflex_components_radix.mappings import RADIX_MAPPING # noqa: E402 -COMPONENTS_CORE_MAPPING: dict = { - "components.core.banner": [ +COMPONENTS_CORE_MAPPING: dict[str, list[str]] = { + "reflex_components_core.core.banner": [ "connection_banner", "connection_modal", ], - "components.core.cond": ["cond", "color_mode_cond"], - "components.core.foreach": ["foreach"], - "components.core.debounce": ["debounce_input"], - "components.core.html": ["html"], - "components.core.match": ["match"], - "components.core.clipboard": ["clipboard"], - "components.core.colors": ["color"], - "components.core.breakpoints": ["breakpoints"], - "components.core.responsive": [ + "reflex_components_core.core.cond": ["cond", "color_mode_cond"], + "reflex_components_core.core.foreach": ["foreach"], + "reflex_components_core.core.debounce": ["debounce_input"], + "reflex_components_core.core.html": ["html"], + "reflex_components_core.core.match": ["match"], + "reflex_components_core.core.clipboard": ["clipboard"], + "reflex_components_core.core.colors": ["color"], + "reflex_components_core.core.breakpoints": ["breakpoints"], + "reflex_components_core.core.responsive": [ "desktop_only", "mobile_and_tablet", "mobile_only", "tablet_and_desktop", "tablet_only", ], - "components.core.upload": [ + "reflex_components_core.core.upload": [ "cancel_upload", "clear_selected_files", "get_upload_dir", @@ -135,47 +127,60 @@ "selected_files", "upload", ], - "components.core.auto_scroll": ["auto_scroll"], - "components.core.window_events": ["window_event_listener"], + "reflex_components_core.core.auto_scroll": ["auto_scroll"], + "reflex_components_core.core.window_events": ["window_event_listener"], } -COMPONENTS_BASE_MAPPING: dict = { - "components.base.fragment": ["fragment", "Fragment"], - "components.base.script": ["script", "Script"], +COMPONENTS_BASE_MAPPING: dict[str, list[str]] = { + "reflex_components_core.base.fragment": ["fragment", "Fragment"], + "reflex_components_core.base.script": ["script", "Script"], } -_MAPPING: dict = { - "experimental": ["_x"], - "admin": ["AdminDash"], - "app": ["App", "UploadFile"], - "assets": ["asset"], - "components.component": [ +ALL_COMPONENTS_MAPPING: dict[str, list[str]] = { + "reflex_core.components.component": [ "Component", "NoSSRComponent", "memo", "ComponentNamespace", ], - "components.el.elements.media": ["image"], - "components.lucide": ["icon"], + "reflex_components_core.el.elements.media": ["image"], + "reflex_components_lucide": ["icon"], **COMPONENTS_BASE_MAPPING, - "components": ["el", "radix", "lucide", "recharts"], - "components.markdown": ["markdown"], + "reflex_components_core": ["el"], + "reflex_components_markdown.markdown": ["markdown"], **RADIX_MAPPING, - "components.plotly": ["plotly"], - "components.react_player": ["audio", "video"], + "reflex_components_plotly": ["plotly"], + "reflex_components_react_player": ["audio", "video"], **COMPONENTS_CORE_MAPPING, - "components.datadisplay.code": [ + "reflex_components_code.code": [ "code_block", ], - "components.datadisplay.dataeditor": [ + "reflex_components_dataeditor.dataeditor": [ "data_editor", "data_editor_theme", ], - "components.sonner.toast": ["toast"], - "components.props": ["PropsBase"], - "components.datadisplay.logo": ["logo"], - "components.gridjs": ["data_table"], - "components.moment": ["MomentDelta", "moment"], + "reflex_components_sonner.toast": ["toast"], + "reflex_core.components.props": ["PropsBase"], + "reflex_components_core.datadisplay.logo": ["logo"], + "reflex_components_gridjs": ["data_table"], + "reflex_components_moment": ["MomentDelta", "moment"], +} + +COMPONENT_NAME_TO_PATH: dict[str, str] = { + comp: path + "." + comp + for path, comps in ALL_COMPONENTS_MAPPING.items() + for comp in comps +} | { + "radix": "reflex_components_radix", + "lucide": "reflex_components_lucide", + "recharts": "reflex_components_recharts", +} + +_MAPPING: dict[str, list[str]] = { + "experimental": ["_x"], + "admin": ["AdminDash"], + "app": ["App", "UploadFile"], + "assets": ["asset"], "config": ["Config", "DBConfig"], "constants": ["Env"], "constants.colors": ["Color"], @@ -247,11 +252,13 @@ "compiler", "plugins", } -_SUBMOD_ATTRS: dict = _MAPPING +_SUBMOD_ATTRS: dict[str, list[str]] = _MAPPING +_EXTRA_MAPPINGS: dict[str, str] = COMPONENT_NAME_TO_PATH getattr, __dir__, __all__ = lazy_loader.attach( __name__, submodules=_SUBMODULES, submod_attrs=_SUBMOD_ATTRS, + **_EXTRA_MAPPINGS, ) From e81844fd6118f8400bfb8d4dcfcbc025f71d9935 Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Fri, 27 Mar 2026 16:54:32 -0700 Subject: [PATCH 107/113] i inverted the logic --- packages/reflex-core/src/reflex_core/utils/lazy_loader.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/reflex-core/src/reflex_core/utils/lazy_loader.py b/packages/reflex-core/src/reflex_core/utils/lazy_loader.py index ba157f0c2ae..3b1dd42798e 100644 --- a/packages/reflex-core/src/reflex_core/utils/lazy_loader.py +++ b/packages/reflex-core/src/reflex_core/utils/lazy_loader.py @@ -67,7 +67,7 @@ def attach( def __getattr__(name: str): # noqa: N807 if name in extra_mappings: path = extra_mappings[name] - if "." in path: + if "." not in path: return importlib.import_module(path) submod_path, attr = path.rsplit(".", 1) submod = importlib.import_module(submod_path) From d240423c1067c2b4f8c5513e4f01085f5f259756 Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Fri, 27 Mar 2026 17:00:45 -0700 Subject: [PATCH 108/113] fix list_ns --- .../src/reflex_components_radix/themes/components/tabs.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/tabs.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/tabs.py index 3c9dd20c641..1b17da2c698 100644 --- a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/tabs.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/tabs.py @@ -153,7 +153,7 @@ class Tabs(ComponentNamespace): """Set of content sections to be displayed one at a time.""" root = __call__ = staticmethod(TabsRoot.create) - list = staticmethod(TabsList.create) + list_ns = list = staticmethod(TabsList.create) trigger = staticmethod(TabsTrigger.create) content = staticmethod(TabsContent.create) From f48b10b5df0fe0fae9d2768e33c8212d8c5ef784 Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Fri, 27 Mar 2026 17:04:34 -0700 Subject: [PATCH 109/113] ig? --- .../src/reflex_components_radix/mappings.py | 2 +- .../src/reflex_components_radix/themes/components/tabs.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/reflex-components-radix/src/reflex_components_radix/mappings.py b/packages/reflex-components-radix/src/reflex_components_radix/mappings.py index 8e7494da37f..141dc6ac59c 100644 --- a/packages/reflex-components-radix/src/reflex_components_radix/mappings.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/mappings.py @@ -78,7 +78,7 @@ "vstack", ], "reflex_components_radix.themes.layout.list": [ - "list_ns", + "list", "list_item", "ordered_list", "unordered_list", diff --git a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/tabs.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/tabs.py index 1b17da2c698..3c9dd20c641 100644 --- a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/tabs.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/tabs.py @@ -153,7 +153,7 @@ class Tabs(ComponentNamespace): """Set of content sections to be displayed one at a time.""" root = __call__ = staticmethod(TabsRoot.create) - list_ns = list = staticmethod(TabsList.create) + list = staticmethod(TabsList.create) trigger = staticmethod(TabsTrigger.create) content = staticmethod(TabsContent.create) From 82bf4e5752de4506ddc4d7d3c916b5bfc993059f Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Fri, 27 Mar 2026 17:15:00 -0700 Subject: [PATCH 110/113] give up on list --- .../themes/layout/list.py | 18 +++--------------- pyi_hashes.json | 8 ++++---- reflex/__init__.py | 16 ++++++++-------- tests/units/components/test_component.py | 8 ++++++-- 4 files changed, 21 insertions(+), 29 deletions(-) diff --git a/packages/reflex-components-radix/src/reflex_components_radix/themes/layout/list.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/layout/list.py index 46c1a37de94..8c44dfe11a6 100644 --- a/packages/reflex-components-radix/src/reflex_components_radix/themes/layout/list.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/layout/list.py @@ -2,7 +2,7 @@ from __future__ import annotations -from collections.abc import Iterable +from collections.abc import Iterable, MutableSequence from typing import Any, Literal from reflex_components_core.core.foreach import Foreach @@ -94,7 +94,7 @@ def add_style(self) -> dict[str, Any] | None: "direction": "column", } - def _exclude_props(self) -> list[str]: + def _exclude_props(self) -> MutableSequence[str]: return ["items", "list_style_type"] @@ -189,19 +189,7 @@ class List(ComponentNamespace): __call__ = staticmethod(BaseList.create) -list_ns = List() +list = list_ns = List() list_item = list_ns.item ordered_list = list_ns.ordered unordered_list = list_ns.unordered - - -def __getattr__(name: Any): - # special case for when accessing list to avoid shadowing - # python's built in list object. - if name == "list": - return list_ns - try: - return globals()[name] - except KeyError: - msg = f"module '{__name__} has no attribute '{name}'" - raise AttributeError(msg) from None diff --git a/pyi_hashes.json b/pyi_hashes.json index a91629a9c5d..ddb059061d5 100644 --- a/pyi_hashes.json +++ b/pyi_hashes.json @@ -43,7 +43,7 @@ "packages/reflex-components-markdown/src/reflex_components_markdown/markdown.pyi": "bacf19a5b6904281d7238dbd51e6fc1c", "packages/reflex-components-moment/src/reflex_components_moment/moment.pyi": "b997bdd994844f0e6ca923bbb2dc34a1", "packages/reflex-components-plotly/src/reflex_components_plotly/plotly.pyi": "65a93d778a0fde06975dac9244f51bb3", - "packages/reflex-components-radix/src/reflex_components_radix/__init__.pyi": "f54e0eac69cbca68ef94720ab439b52f", + "packages/reflex-components-radix/src/reflex_components_radix/__init__.pyi": "7d5cd2fcd037c9b299c2c25b9a844db2", "packages/reflex-components-radix/src/reflex_components_radix/primitives/__init__.pyi": "289dfe0e5a59948b90b875ea8f75639c", "packages/reflex-components-radix/src/reflex_components_radix/primitives/accordion.pyi": "6dd30847af62ad7d50d5c5daf6c4a1d7", "packages/reflex-components-radix/src/reflex_components_radix/primitives/base.pyi": "6c12ef3d9f82926bf17d410b774d56f5", @@ -91,14 +91,14 @@ "packages/reflex-components-radix/src/reflex_components_radix/themes/components/text_area.pyi": "1a03d9525a1544816392067499c3354d", "packages/reflex-components-radix/src/reflex_components_radix/themes/components/text_field.pyi": "c2fbd8547de4993e03017844e8c4b477", "packages/reflex-components-radix/src/reflex_components_radix/themes/components/tooltip.pyi": "4545b70fd0802f19993419ab0163d595", - "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/__init__.pyi": "f7e4de67a6e656417246321cd992fd95", + "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/__init__.pyi": "49134dc875d5f3a8ef6215453ecd702b", "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/base.pyi": "3195e198d92ff43644a09c277303b83b", "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/box.pyi": "69d12b6c918a476ac4557f42fef73c27", "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/center.pyi": "88880d197ff7347ec7d3f81d6e57de8e", "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/container.pyi": "f329387a5d4a988bc195e6a487ff44db", "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/flex.pyi": "855c9d0c3c2e79e7d3811cfec74d6379", "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/grid.pyi": "bd2c31d4e3d61743b72327f071969e05", - "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/list.pyi": "856e0dd916fa69f152b3ec557871a133", + "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/list.pyi": "eee53b418ff0e0660c8cf9d8a0a59386", "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/section.pyi": "1aa57142797597d65d840eb1d3cc7de7", "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/spacer.pyi": "c774f0a1384f983e6d73bde603c341ca", "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/stack.pyi": "894dcd5945123c1c8aa34cb77602fead", @@ -118,7 +118,7 @@ "packages/reflex-components-recharts/src/reflex_components_recharts/polar.pyi": "1ce679c002336c7bdbdd6c8ff6f2413c", "packages/reflex-components-recharts/src/reflex_components_recharts/recharts.pyi": "1b92135de4ea79cb7d94eaaec55b9ab7", "packages/reflex-components-sonner/src/reflex_components_sonner/toast.pyi": "f09c503c4ab880c13c13d6fa67d708b8", - "reflex/__init__.pyi": "0d2fdfbd6860edc301afc744f198391a", + "reflex/__init__.pyi": "7696c38fd9c04a598518b49c5185c414", "reflex/components/__init__.pyi": "55bb242d5e5428db329b88b4923c2ba5", "reflex/experimental/memo.pyi": "d16eccf33993c781e2f8bc2dd8bbd4d4" } diff --git a/reflex/__init__.py b/reflex/__init__.py index 105f4c690c8..598cd878598 100644 --- a/reflex/__init__.py +++ b/reflex/__init__.py @@ -99,7 +99,7 @@ from reflex_components_radix.mappings import RADIX_MAPPING # noqa: E402 -COMPONENTS_CORE_MAPPING: dict[str, list[str]] = { +_COMPONENTS_CORE_MAPPING: dict[str, list[str]] = { "reflex_components_core.core.banner": [ "connection_banner", "connection_modal", @@ -131,12 +131,12 @@ "reflex_components_core.core.window_events": ["window_event_listener"], } -COMPONENTS_BASE_MAPPING: dict[str, list[str]] = { +_COMPONENTS_BASE_MAPPING: dict[str, list[str]] = { "reflex_components_core.base.fragment": ["fragment", "Fragment"], "reflex_components_core.base.script": ["script", "Script"], } -ALL_COMPONENTS_MAPPING: dict[str, list[str]] = { +_ALL_COMPONENTS_MAPPING: dict[str, list[str]] = { "reflex_core.components.component": [ "Component", "NoSSRComponent", @@ -145,13 +145,13 @@ ], "reflex_components_core.el.elements.media": ["image"], "reflex_components_lucide": ["icon"], - **COMPONENTS_BASE_MAPPING, + **_COMPONENTS_BASE_MAPPING, "reflex_components_core": ["el"], "reflex_components_markdown.markdown": ["markdown"], **RADIX_MAPPING, "reflex_components_plotly": ["plotly"], "reflex_components_react_player": ["audio", "video"], - **COMPONENTS_CORE_MAPPING, + **_COMPONENTS_CORE_MAPPING, "reflex_components_code.code": [ "code_block", ], @@ -166,9 +166,9 @@ "reflex_components_moment": ["MomentDelta", "moment"], } -COMPONENT_NAME_TO_PATH: dict[str, str] = { +_COMPONENT_NAME_TO_PATH: dict[str, str] = { comp: path + "." + comp - for path, comps in ALL_COMPONENTS_MAPPING.items() + for path, comps in _ALL_COMPONENTS_MAPPING.items() for comp in comps } | { "radix": "reflex_components_radix", @@ -253,7 +253,7 @@ "plugins", } _SUBMOD_ATTRS: dict[str, list[str]] = _MAPPING -_EXTRA_MAPPINGS: dict[str, str] = COMPONENT_NAME_TO_PATH +_EXTRA_MAPPINGS: dict[str, str] = _COMPONENT_NAME_TO_PATH getattr, __dir__, __all__ = lazy_loader.attach( __name__, submodules=_SUBMODULES, diff --git a/tests/units/components/test_component.py b/tests/units/components/test_component.py index 0b14d9c1071..98df3eb85a7 100644 --- a/tests/units/components/test_component.py +++ b/tests/units/components/test_component.py @@ -42,6 +42,10 @@ from reflex_core.vars.object import ObjectVar import reflex as rx +from reflex import ( + _COMPONENTS_BASE_MAPPING, # pyright: ignore[reportAttributeAccessIssue] + _COMPONENTS_CORE_MAPPING, # pyright: ignore[reportAttributeAccessIssue] +) from reflex.compiler.utils import compile_custom_component from reflex.state import BaseState from reflex.utils import imports @@ -1475,8 +1479,8 @@ def test_instantiate_all_components(): } component_nested_list = [ *RADIX_MAPPING.values(), - *rx.COMPONENTS_BASE_MAPPING.values(), - *rx.COMPONENTS_CORE_MAPPING.values(), + *_COMPONENTS_BASE_MAPPING.values(), + *_COMPONENTS_CORE_MAPPING.values(), ] for component_name in [ comp_name From 1f3676072eb61213153f2a8546027dc93993ad99 Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Fri, 27 Mar 2026 17:26:14 -0700 Subject: [PATCH 111/113] export modules --- packages/reflex-core/src/reflex_core/vars/__init__.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/packages/reflex-core/src/reflex_core/vars/__init__.py b/packages/reflex-core/src/reflex_core/vars/__init__.py index c1989e5733d..4c0ebe85c94 100644 --- a/packages/reflex-core/src/reflex_core/vars/__init__.py +++ b/packages/reflex-core/src/reflex_core/vars/__init__.py @@ -1,5 +1,6 @@ """Immutable-Based Var System.""" +from . import base, color, datetime, function, number, object, sequence from .base import ( BaseStateMeta, EvenMoreBasicBaseState, @@ -51,9 +52,16 @@ "Var", "VarData", "VarOperationCall", + "base", + "color", + "datetime", "field", + "function", "get_unique_variable_name", "get_uuid_string_var", + "number", + "object", + "sequence", "var_operation", "var_operation_return", ] From 92dd67dde5b3d5e1e63ea912100617f9ad934880 Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Fri, 27 Mar 2026 17:32:28 -0700 Subject: [PATCH 112/113] fix radix imports --- .../src/reflex_components_radix/__init__.py | 3 ++- .../src/reflex_components_radix/primitives/__init__.py | 2 +- .../themes/components/__init__.py | 2 +- .../reflex_components_radix/themes/layout/__init__.py | 2 +- .../themes/typography/__init__.py | 2 +- pyi_hashes.json | 10 +++++----- 6 files changed, 11 insertions(+), 10 deletions(-) diff --git a/packages/reflex-components-radix/src/reflex_components_radix/__init__.py b/packages/reflex-components-radix/src/reflex_components_radix/__init__.py index 40b89dbcf61..e2e8541abae 100644 --- a/packages/reflex-components-radix/src/reflex_components_radix/__init__.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/__init__.py @@ -9,7 +9,8 @@ _SUBMODULES: set[str] = {"themes", "primitives"} _SUBMOD_ATTRS: dict[str, list[str]] = { - "".join(k.split("components.radix.")[-1]): v for k, v in RADIX_MAPPING.items() + "".join(k.split("reflex_components_radix.")[-1]): v + for k, v in RADIX_MAPPING.items() } __getattr__, __dir__, __all__ = lazy_loader.attach( __name__, diff --git a/packages/reflex-components-radix/src/reflex_components_radix/primitives/__init__.py b/packages/reflex-components-radix/src/reflex_components_radix/primitives/__init__.py index bff9cbd1dc4..799113be297 100644 --- a/packages/reflex-components-radix/src/reflex_components_radix/primitives/__init__.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/primitives/__init__.py @@ -7,7 +7,7 @@ from reflex_components_radix.mappings import RADIX_PRIMITIVES_MAPPING _SUBMOD_ATTRS: dict[str, list[str]] = { - "".join(k.split("components.radix.primitives.")[-1]): v + "".join(k.split("reflex_components_radix.primitives.")[-1]): v for k, v in RADIX_PRIMITIVES_MAPPING.items() } | {"dialog": ["dialog"]} diff --git a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/__init__.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/__init__.py index 396fd751bf8..bbb5061ee7a 100644 --- a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/__init__.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/__init__.py @@ -7,7 +7,7 @@ from reflex_components_radix.mappings import RADIX_THEMES_COMPONENTS_MAPPING _SUBMOD_ATTRS: dict[str, list[str]] = { - "".join(k.split("components.radix.themes.components.")[-1]): v + "".join(k.split("reflex_components_radix.themes.components.")[-1]): v for k, v in RADIX_THEMES_COMPONENTS_MAPPING.items() } diff --git a/packages/reflex-components-radix/src/reflex_components_radix/themes/layout/__init__.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/layout/__init__.py index 1aaf76f9496..b2b6a0a02d0 100644 --- a/packages/reflex-components-radix/src/reflex_components_radix/themes/layout/__init__.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/layout/__init__.py @@ -7,7 +7,7 @@ from reflex_components_radix.mappings import RADIX_THEMES_LAYOUT_MAPPING _SUBMOD_ATTRS: dict[str, list[str]] = { - "".join(k.split("components.radix.themes.layout.")[-1]): v + "".join(k.split("reflex_components_radix.themes.layout.")[-1]): v for k, v in RADIX_THEMES_LAYOUT_MAPPING.items() } diff --git a/packages/reflex-components-radix/src/reflex_components_radix/themes/typography/__init__.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/typography/__init__.py index 2dff8bc32ec..87330b5e825 100644 --- a/packages/reflex-components-radix/src/reflex_components_radix/themes/typography/__init__.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/typography/__init__.py @@ -7,7 +7,7 @@ from reflex_components_radix.mappings import RADIX_THEMES_TYPOGRAPHY_MAPPING _SUBMOD_ATTRS: dict[str, list[str]] = { - "".join(k.split("components.radix.themes.typography.")[-1]): v + "".join(k.split("reflex_components_radix.themes.typography.")[-1]): v for k, v in RADIX_THEMES_TYPOGRAPHY_MAPPING.items() } diff --git a/pyi_hashes.json b/pyi_hashes.json index ddb059061d5..62611f50a6d 100644 --- a/pyi_hashes.json +++ b/pyi_hashes.json @@ -43,8 +43,8 @@ "packages/reflex-components-markdown/src/reflex_components_markdown/markdown.pyi": "bacf19a5b6904281d7238dbd51e6fc1c", "packages/reflex-components-moment/src/reflex_components_moment/moment.pyi": "b997bdd994844f0e6ca923bbb2dc34a1", "packages/reflex-components-plotly/src/reflex_components_plotly/plotly.pyi": "65a93d778a0fde06975dac9244f51bb3", - "packages/reflex-components-radix/src/reflex_components_radix/__init__.pyi": "7d5cd2fcd037c9b299c2c25b9a844db2", - "packages/reflex-components-radix/src/reflex_components_radix/primitives/__init__.pyi": "289dfe0e5a59948b90b875ea8f75639c", + "packages/reflex-components-radix/src/reflex_components_radix/__init__.pyi": "4843dd071acb073dc30028322c3d4023", + "packages/reflex-components-radix/src/reflex_components_radix/primitives/__init__.pyi": "bc25cae0eca01c8684443d5dfd7b6455", "packages/reflex-components-radix/src/reflex_components_radix/primitives/accordion.pyi": "6dd30847af62ad7d50d5c5daf6c4a1d7", "packages/reflex-components-radix/src/reflex_components_radix/primitives/base.pyi": "6c12ef3d9f82926bf17d410b774d56f5", "packages/reflex-components-radix/src/reflex_components_radix/primitives/dialog.pyi": "810fa8c626b79035cdbb04f43b5bc5ad", @@ -55,7 +55,7 @@ "packages/reflex-components-radix/src/reflex_components_radix/themes/__init__.pyi": "5d9a06872953d3e3df99e1ff154a4e0c", "packages/reflex-components-radix/src/reflex_components_radix/themes/base.pyi": "d7c20bd180f28fdb4affcba37e2aa1ff", "packages/reflex-components-radix/src/reflex_components_radix/themes/color_mode.pyi": "ba0ff3b00289cd1896e327fa2be99563", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/__init__.pyi": "0bddcd536fbe99dc635474da32b4b633", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/__init__.pyi": "2b0f9f472ba6dcc743c2df17642c4a4b", "packages/reflex-components-radix/src/reflex_components_radix/themes/components/alert_dialog.pyi": "fad40b463a8ebb0d3ca3900dc8a91679", "packages/reflex-components-radix/src/reflex_components_radix/themes/components/aspect_ratio.pyi": "0d22969c5407592a0bb36768e149f2b5", "packages/reflex-components-radix/src/reflex_components_radix/themes/components/avatar.pyi": "6a6e9b8f6ca3428c45d62bd0e7f94693", @@ -91,7 +91,7 @@ "packages/reflex-components-radix/src/reflex_components_radix/themes/components/text_area.pyi": "1a03d9525a1544816392067499c3354d", "packages/reflex-components-radix/src/reflex_components_radix/themes/components/text_field.pyi": "c2fbd8547de4993e03017844e8c4b477", "packages/reflex-components-radix/src/reflex_components_radix/themes/components/tooltip.pyi": "4545b70fd0802f19993419ab0163d595", - "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/__init__.pyi": "49134dc875d5f3a8ef6215453ecd702b", + "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/__init__.pyi": "417e490adc15e93dc2cdb854ee0361d2", "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/base.pyi": "3195e198d92ff43644a09c277303b83b", "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/box.pyi": "69d12b6c918a476ac4557f42fef73c27", "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/center.pyi": "88880d197ff7347ec7d3f81d6e57de8e", @@ -102,7 +102,7 @@ "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/section.pyi": "1aa57142797597d65d840eb1d3cc7de7", "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/spacer.pyi": "c774f0a1384f983e6d73bde603c341ca", "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/stack.pyi": "894dcd5945123c1c8aa34cb77602fead", - "packages/reflex-components-radix/src/reflex_components_radix/themes/typography/__init__.pyi": "c0e39c5990d4ee43ab14a56a522678ae", + "packages/reflex-components-radix/src/reflex_components_radix/themes/typography/__init__.pyi": "3926877c04f74fc2acf4a398bee9da06", "packages/reflex-components-radix/src/reflex_components_radix/themes/typography/blockquote.pyi": "305a8932078e4af48e44489e7ce74060", "packages/reflex-components-radix/src/reflex_components_radix/themes/typography/code.pyi": "fad43053747fb84229cc35296c7028b5", "packages/reflex-components-radix/src/reflex_components_radix/themes/typography/heading.pyi": "5901c7202a5b135f60cc1407878b4859", From 8462f356d16663cc19239334e8543fe9aa39b84c Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Fri, 27 Mar 2026 17:46:44 -0700 Subject: [PATCH 113/113] add fetch tags to actions --- .github/workflows/check_node_latest.yml | 3 +++ .github/workflows/check_outdated_dependencies.yml | 10 ++++++++-- .github/workflows/integration_app_harness.yml | 3 +++ .github/workflows/integration_tests.yml | 12 ++++++++++++ .github/workflows/performance.yml | 3 +++ .github/workflows/pre-commit.yml | 5 +++++ .github/workflows/publish.yml | 1 + .github/workflows/reflex_init_in_docker_test.yml | 3 +++ .github/workflows/unit_tests.yml | 6 ++++++ 9 files changed, 44 insertions(+), 2 deletions(-) diff --git a/.github/workflows/check_node_latest.yml b/.github/workflows/check_node_latest.yml index 3f7afe71980..452c360ed44 100644 --- a/.github/workflows/check_node_latest.yml +++ b/.github/workflows/check_node_latest.yml @@ -24,6 +24,9 @@ jobs: steps: - uses: actions/checkout@v4 + with: + fetch-tags: true + fetch-depth: 0 - uses: ./.github/actions/setup_build_env with: python-version: 3.13 diff --git a/.github/workflows/check_outdated_dependencies.yml b/.github/workflows/check_outdated_dependencies.yml index 0e7c3ba1615..9e96870214a 100644 --- a/.github/workflows/check_outdated_dependencies.yml +++ b/.github/workflows/check_outdated_dependencies.yml @@ -14,8 +14,10 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v3 - + uses: actions/checkout@v4 + with: + fetch-tags: true + fetch-depth: 0 - uses: ./.github/actions/setup_build_env with: python-version: 3.13 @@ -43,6 +45,10 @@ jobs: steps: - name: Checkout code uses: actions/checkout@v4 + with: + fetch-tags: true + fetch-depth: 0 + - uses: ./.github/actions/setup_build_env with: python-version: 3.13 diff --git a/.github/workflows/integration_app_harness.yml b/.github/workflows/integration_app_harness.yml index 606503d5ad9..a567141fa7f 100644 --- a/.github/workflows/integration_app_harness.yml +++ b/.github/workflows/integration_app_harness.yml @@ -45,6 +45,9 @@ jobs: - 6379:6379 steps: - uses: actions/checkout@v4 + with: + fetch-tags: true + fetch-depth: 0 - uses: ./.github/actions/setup_build_env with: python-version: ${{ matrix.python-version }} diff --git a/.github/workflows/integration_tests.yml b/.github/workflows/integration_tests.yml index c0c9bd3ae34..bec8b4ce448 100644 --- a/.github/workflows/integration_tests.yml +++ b/.github/workflows/integration_tests.yml @@ -48,6 +48,9 @@ jobs: runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 + with: + fetch-tags: true + fetch-depth: 0 - uses: ./.github/actions/setup_build_env with: python-version: ${{ matrix.python-version }} @@ -111,6 +114,9 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + with: + fetch-tags: true + fetch-depth: 0 - uses: ./.github/actions/setup_build_env with: python-version: ${{ matrix.python-version }} @@ -147,6 +153,9 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + with: + fetch-tags: true + fetch-depth: 0 - uses: ./.github/actions/setup_build_env with: python-version: 3.14 @@ -179,6 +188,9 @@ jobs: runs-on: macos-latest steps: - uses: actions/checkout@v4 + with: + fetch-tags: true + fetch-depth: 0 - uses: ./.github/actions/setup_build_env with: python-version: ${{ matrix.python-version }} diff --git a/.github/workflows/performance.yml b/.github/workflows/performance.yml index 3f1bd391890..e6cc29dd98a 100644 --- a/.github/workflows/performance.yml +++ b/.github/workflows/performance.yml @@ -24,6 +24,9 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + with: + fetch-tags: true + fetch-depth: 0 - name: Install uv uses: astral-sh/setup-uv@v6 diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml index d94bfdd7181..06ae79a0914 100644 --- a/.github/workflows/pre-commit.yml +++ b/.github/workflows/pre-commit.yml @@ -21,6 +21,9 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + with: + fetch-tags: true + fetch-depth: 0 - uses: ./.github/actions/setup_build_env with: python-version: 3.14 @@ -28,4 +31,6 @@ jobs: - uses: actions/checkout@v4 with: clean: false + fetch-tags: true + fetch-depth: 0 - run: uv run pre-commit run --all-files --show-diff-on-failure diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index a9c453eeed9..043739b9bd2 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -21,6 +21,7 @@ jobs: - name: Checkout uses: actions/checkout@v6 with: + fetch-tags: true fetch-depth: 0 - name: Install uv diff --git a/.github/workflows/reflex_init_in_docker_test.yml b/.github/workflows/reflex_init_in_docker_test.yml index 1b36786183b..7e33623fde6 100644 --- a/.github/workflows/reflex_init_in_docker_test.yml +++ b/.github/workflows/reflex_init_in_docker_test.yml @@ -24,6 +24,9 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + with: + fetch-tags: true + fetch-depth: 0 - shell: bash run: | diff --git a/.github/workflows/unit_tests.yml b/.github/workflows/unit_tests.yml index 731bbeb04a4..5b3e339ea4a 100644 --- a/.github/workflows/unit_tests.yml +++ b/.github/workflows/unit_tests.yml @@ -47,6 +47,9 @@ jobs: - 6379:6379 steps: - uses: actions/checkout@v4 + with: + fetch-tags: true + fetch-depth: 0 - uses: ./.github/actions/setup_build_env with: python-version: ${{ matrix.python-version }} @@ -82,6 +85,9 @@ jobs: runs-on: macos-latest steps: - uses: actions/checkout@v4 + with: + fetch-tags: true + fetch-depth: 0 - uses: ./.github/actions/setup_build_env with: python-version: ${{ matrix.python-version }}

zo~61yOg<8l*E{Q1VC_AaYLA*8Q`b%7k7%?QOpb}y}L*xtm8{qbq&_r2IY0AM0WFt`9J4ehPC_vX1 z28&!Gd03bP8A9aDU6hP1KrhVXZtM$)FNgC|*EPgDF<-;O-? zv_*lRWYC?jiF^>?wYm}JZXimlWSIc2i`cYwM5>O2*o zj8o@&munh8H|BG@E9zUqDm8|6U4|Rv z;TJ4i?`|cGbR!GSyNyegB<=8kYHv*>c~>E0c6o$$HDFuOC;%NQ zx|NXFuWqn*;;nsZ`Kj3a;MnX5B1#DAag}N(h{&+3E~0D#>gt z?g&L-)5S++iCUj!*waKb<+VCUQg{T%kl8ktUn1F4yC z-bTKhRs_h)-8UMD?2>^xhLn(5?>YeLIf@Tsr3|l@#2QhgCkRdSfaIok>Iq$;G)rv7 z?}7%Gx6a<=r{L)Z-=4_%=~+n)jk1KQ>Ir11ETN)+eX|0EPe}qVYKXK=k5uTr|72L; zWTM5?Qy@G{6@Cz;k)}9UAI@hUibFXvYj0=CWY4j1L{*66_aOz1s*$0)>>>)bY^^f~J@0X?EH| z%QWR&rSvd-@teJFN`+b~;kIWvs-161i{u zjhBL2aV{G{4y$^(CbE{Tn71uCGHcOeNXbn!qdCV(*-+vUlNs1?laI-tN=F7-MjG5k z@?ZlDy$g0m^uy-O3i9f6INyrJ$bG*O*sSUowW4dA(j^$3*L*cyRoTv`${Hl7B zo}$hcyVX74PakLFlMwbc)tPFcI-yp?3N;=styd~N2Uba?Qce%b574 z;{FJ#*5rC5FmDI-J`0eZ{IZf7I;oqrxlV=>A;9i=Tcm77T1p<{klw~uYsb5ldig? zYjSXY`S{B{wTy?LnT@fooanu-dgSig)ll;jkS5Y|y&CINl*G7hY*N%6bY`ZDt$hqj z7TUBQEKA1Gqh^>zo8M@{32C_vh4sra(68H~jH5n0TD2}r?*Fw;Q^&AF0hvs8=aC=?~q zWvBi~T&!znW$lx(V{w#N8tvHoH?ZBwKlz)~)T7<$wD}cmTL?0 zVIW@{+!7N6)$vEXrr9Vx?H?ZQomYks2dq1C{w{ljq3XQaH+^L(vs zo5-iigZYk!Ng}HjtEfQq_%m78Bzws{x{)6jjxYE@W*0ke>gl-k7iemdX4X4dQdf`^ zR7Bxfj-;C&a3WNf={YO{$H|OsaQpR33jqR^i4~WqBZ+u%P%?uZ)P5W~W}<{0>O-V0 z!49(Ngh{O?rL*ISY2e85(^xyb-K!%Mx+9@yr@1%6zh7}~Gu80?f#;#muC|MJsw6DF zzVwq9q}{L-6vUy6)DP&GI7~AR#ocs_*6&hYJS^Ww>g}u8fB&$ zRY&_4pS#$g8a^xI@M0FSUU0lWlNZ)mTo^*5Mj>_ovW8s=p!hJ9&XytHplhOo_{#5K zRz2&2h8&7hfTnTg#S6Y3GoWS*)Xe)}06Z?teXMDVHV+$`itFesB zy;C~(*q6D-KHuMNfDU%rSVc!Te>#Ee^pwrD#wLkGidgVk`AlLQ%=ivm5?t&zq$5ek z)}a(JN>;X6x4jPMn|~Vjqij4cyO#gHvV7x04pr&iFg9SWn{j_-u)UBvgyV53>_RQz zGROl<%#V?6Wc<@gf0NV4io#lN*mMr^~BS#tS*vg6}AhjR9aGBN`{&`eR#)w zYLxM}F6hVEV=>>O7RWy{-Nddso`-dM=?h*}pPu>2f}XYNKPwnVTOVF75nD!N{t@IT zxra-X}R7R=F zz`X+_w=A)B?LvH!uphQJ+mZ8TDJAw@bmnam-%2k0Gniu)+|`FuTuZp7*rW5<0@J#r+-25i=+#ItWayqH`YM>=%*q2&LZ`bgUf7&T;sf@-$2^aQ3@Y ziE00}yZfKV#Zfjt#Yr4;3m_q7$5!r^)p-=R?7j&TH_U!Ckm3kWf59|43O;vQVBx@_ zsn%C2l)M0ni5{pu`@Ms(oxb}M zJDun7rng!ODQcZS{f|&N#J`&k*6%l;_Zf;pj1yNT7Z`qf33u3flE@^Ul>D@P zdgb=iL!XAYiP_8QW@@1rB^=kX1(Zv^GiM8Gc{A`WX`xl@k45z>nXeJzvgXS|lI%gU|L6Y>O*_&VjF?0OBXGa$$ zLVz)GnpRc8z40_gM@PfsOSWlU2oG18>->SddMxOIb-7YUMHw#e6GL4W%`68<6L!PX zt5PYKPaiToJ}RD$E+dP=Q*t|9)h$(i#YSF}R@rE$LXI#>ht+qse#Aua=)$Z3^Ou+D zE(DI_{V;sYucy8yEB#@T_kZ_S7FmYl`enO?J)SAxI?}$BMGI;H@#W@-j@s4{O${Neb9TTSROUYL`j{?L?Oz~2}TTGremzkiS zJ=IHNNB~NTe?*K=?3VcVYZ}}8XE#09b4mj=uCpt)-=r|+)@7tH;!pr#a6{uRvJjnX z4BAZaNd<(1_-n?!{=-8b;dc>$z}-oCS2vKn;{afLGp}A67@y}$(Tay)5jWB0Ws^;V zB;zp|Rw+pESLoXB+wO4!B$)A*N_IbF=?5j-u`UFq9$XixOYHBZ=>Ar^+N=IU9+?wD zOzp@9z~XMD(S6m*zO9I-AnK&jOESU^$OD|B^MU0l@8s)?Fg2U%$u!~$uekZ zlDR?`m8vSaXH-9Td~l*H^+ykq)pueA7$0$^uSJ|Vp8s$@(Tklpop){d!%CihFJ$NL z+Nr?mt==pvS~Q3ao?bO_K@VlXg^}uz!WH3R0>RlGHAu_okNquqSlF!|?-r3mvkK=+ z2YTTgsn+|?Dk&9@{SbmrSf_*{e5dRagag#StEBrYf!S`X5#JP5p<24G>T1~}EPrKX zbo&e{%G~R~OGoMO0ucT{6ER$Kehu&Kr8=UY_j6qf4*4e2Cc_drCA^|4#gn&Xk5Kf} ztCuh1r;i->E|q-6!@Y8bzpQ`Zsx-ZYE@;X8U3F7lQfgfJqt@zZkP+8Y5!RjYg=E7a zX{Z?9>VF%%1t_9LH?LTW+kFhfi+Kd2-{=CfT&_$zCY#O8FzNT!C!f#tix*r zmjdp6C>JN@Jbs+MZ`Co>TPmU_{vBvYBoR?dJ#(vt(Q`whVHzedA8!4;Oqj>w8Utz; z$H3AZCfOJHRRc?MHm9AL*Q7O(rC-vRdn1!1x%^Ea;1>Ps{82L5%GZ$NNh^$S&XH)k zMs6w|)$rss7znf4$I{k%gxd#CJ)=WnY<3=-~;>XJ6ThqFBYk~|3oDy^D5M- zQ7k5bv@>SK0+xXQM4$xv&Lb689WxCqPno#Xl_xP>lTGErlV54tsMuU&VDv>iS6u$E zjRK`QZb1O=Y?O0vuO{Lk2_$D22|cpRZ?q}+61auzsV*ELE1`{Coz%r&1uJ3VnE3av zxLj?Ul@C+UD{KLxbx#;EisTXE2I2;ovs1hEpuZ@3vkB*z7SbjzcvGbj zW~%b)=9wy&q>@$sP28(~C?4AuwL}oe9ms^;Ac3I+h*!!~Wu-+Nn08-Xl_awIsmA<# z4s~v5DZ*)6u;8pF2qR0P#+TuA-eBWS)J%g}O%fgmzLzv;NGV--nS*S1;KB432Uq-!;}`hxOd|{su2xK3XD*o zy8!DGxbqNE(>KF%;&Y9aUjLn(GkTnST23I4tyAI8F@#T?sCaD2U-#3>26lf8-}<}(lvI?;|1T=k1*i(OKEXJVF6-3-$B`lg*b ze_mXGXMB)NMYmYW-2c5`Pp8MU$Ty82?>1EWD2wG^2bv`Gp@Ub*k?0 zE3lpAl20|{Dd(qkt!<4s7XA$kBAA%$NTFR>QTpz4?eXO zDCdxMcA9GU2L*5!iv>CIO>Z10=Ujn|)<3dqUK}uU~GV zK%8SN#x4HGlHxeEV2^5v+$6ORv30-43W8j9d4AJ3Mfa8@^8GF+wptwbuqx3R2<`-Q z)vJ1Zre-G9nH(LxcFq*#Y&te;?UwRec*3Z;mLYU!K@f3S1M6(=9wN*QHQLr5xN9ke zNm^Bn<`D($kyPU>`vz(^lOfkw0<329MR+yYDl>NFOE2N2gY$`&1Xj^MKn^Uv9*L%) zy)+T7gn3KB7+r`vW*A$JyY_0Bzrs;I%FB8T^Cn4ji;3Bwlig6y*}#&A;>G(O!5C{3 z1G4W!QH9vs7|FN2q92YIy2R$$OU0u-elwYAZ>=0J%G|$sTvnqSaYShq{59>N#gaN; zgze-nq|IW3G`|x#vrYtPBaZlS6!e%rI+&@j_9N`=BfURAS*?4AHZDkFgh$cDq_B!x zCMyO|Z1Iz1N5hY|T}nD>CCl|nyhAJrR1+*T=$~t!0{7i$GUnBui?`8?lgedI{K6lS zHXPZ#$r+%4>-)z@(OOUb75HhvS*QRiy7mEL&_ulRwm|HeJsHR_-m%2+HJbaw04QTn z$@;i%6%m^Jq_GhUTpZ2Oweb%wfI~jkV!*79lkVS|w_<<93yHuaK@>JoEH6xBRH$5Y zAu@Ji#{XD&yagUA!5SBoj?=B3*0XwK5X1E`Q09BQyirD#dN2;tljAX5BHLBbo#XU8{i_{Z!jWza`D5(Q z@>w4lLm1(vMW6f&Nz{QNLlIP$bIe%^qTB@vq6YBLV#Ty#u>;2QbUU4Ii2sZIk(3iv2Y{>-)ljn0)oyLFRLNmgPYaBH#@a!6ImX@=(eN z+>_@d@>n11sQE>;N(+JDdtTadvyz1G^vC_C)*4 zXO3_~b}C^Oded?vB80Jixug0$E8MVDVGol5lFFr9uHenqE?S`B%__nDiGX6n;{e;k zH{#)wmMwc<2@xI~Lz_3TBQLLe+=-bFa$a+^`DBx}>YqL&!2%#quuDN}M6kmV4#k4( zdYAxR5=Wawr0viUts=fo7O*+O~2A)TsR|Evr_;AAua z?>=*IOytxtK9HZs2$GFp9XWJruVVFZ3YJ2==;KTvDu4JpuqHbP<6u(taU_i$zNo3< zXtzm`B;|@?OOcI8Lfqr?%eWxsDLUCe{6Es}CSwQIaO$Wc9K&1?z+?G`89-pQ-Vpo+ z1s;7`wMHOU&Ly^ydRS677XvU%VVZ*~YF8=Ig)S&>iRLa`bF*rUtn#Gvr6)y;zTtqz`ZeL@FiwwbwLH~2p62H z_Qi|i%m&V&R|hUm5aPc?1cUcw9w2K5IRY#r|aM2~qhL|ZhHyUlyJNO2J=mRjhIeEYE)eUbj4D<6U zD%ZiK>qbZ-MitJ6jd>NDjmBSODokV7Vl$3WAOsw6xR2<+c(1{*-}&mA!b36L3yMCL zgF5hz&hs)0`>;cw;Y)IlqWBjO$x=*pmVd|8d++9n;2?Y*ehLi4EUBr##SUv75F z6KD{kR4#B2Pefq7VradBNBtTJaNW0lwZ!v%P5pYxm$mLMYax6a8J=*}@DJ+48;hQ6 z&0kd3_&yZaJ-Ip~0w2vXkUix-{{B|sN~i`tZM^V|K~vq0VbR?vlxX&gvXLYvGu8Bp z^`!O2y(ip~;5wlp7~ODOz<*NFaMHkk+TL)Q_%%5F>uHYHkucWv=+`fpfMab&$%6(3 zT}HJN{xcGwh$x+cXx+$Nh&R=bfcu8pn?LTqP)ES!8zx;&iUbU?&rDPsBm22TcZ}+vcs|XZ`h2A zeEl0T8vijv$6hecQt^Rr1OZYxL#zK?IZU9W>b^%t3Dc*F&Wz*~_Mx?b1`w#;0xh z0m3JzwMM#OCz)#wwhZo!$CQ|FtL)HW94qMd zqG`#N2LItW4N&M0Q#g$wBlVpJO?~jv=@`sYvR9Rld^f!0mwhTGD3Jj=bqn4ZwzXI` zzW^y%7T{Q@bJ5gcxo(D@3_^&VY-?M;4qkK zzxKxl`*wo0O^Q$K8bk}7_Pe(YrRTP$BbUPeZNv3r0Ovsbf34WS zyK49!G3@jpGE#y~Jk_qjUBgUep2_=0wK*Cf?)bNMmWL{yCS@4yDQ1xPMH%uD$!!p+ zh?0c&GD}--v`XgMpGD(opyn=n>}29`w8o__T3NXGB7mxvMsaQ%HvEX^RLN>?6sh&} zjrc>>2hwr3ET|_}S4ZuKKzS1OoP*LUZ@!vZ2HcqsXM$u_h}? z$1)1}|3G=wu%;O{=cjjYETHK3kBPr89V4((22K)ERswi?wUCZfs)>6JFV&E;DPQQ@ z1k=U9cpHbyOrI{hnQsNKN5TJfxRmHw5Wr5`{uUugKf`6Mz6LC}r4P~N)yDm9^knhk zpNi?};s!_e(d<`#{uOP0B2o9w5rkvn3D9}zaSt5(f&Fobunaa%YUZ#VVi~Ax`4WHD zu52cAPd_LtpwpJ*>*^CrH8v{qe3{@zbIq@jWvUc12V~6o&gQ$n3(1JB4b@LHuhlxf zy3rpomni+w+L(3yHR!silF4YqP0Qf4_e({8t=7lFPviWUWW2Jk8%BFK`7snbb$REK zB(Np8-dx0z6i{>vA6r;esHhfu^#}0j_zc-y{yL)VXDSqaS-$e%E@K>j`MFAZfI*d- z#goJrNyg681r&Vv>K8Fz$SSa~`!5jkqZBEKMm;93!GwQlb|E4z%eulR61H$2x7|NV zwlbandO9UUMKV9bbYRk~%KCvy{`~D%gTC100ftVji1bi4_g{>8 z6RDqc(e>|qAbaY#1=huT(dKd@Abd#%CpDwUc9h$TIuV`MMiw)FYu%HM!St^ z0M6O{*Y8yHmj}3?ztzImF;f1LG#!=$+0TVCdGa#jNkDne^tRze6vP@nJo6gVltEEs z_T5nrsN|BkEXRJkB%bs_clDdBb-(@!+a#r)X>)m}TGL-`APArb05eDg!cYUDAPqEV zrY3DLv;yo6REnLiA{K~{NS~eBvdcm?L8KpHPMR6~<{iGj5YE~;;shKC43nG}o~uvN zNyndw32U`S30S}m`EuJ_e|?rbvTbqXd#Y3M*tk+STRX@8oO1q}e!v7&rK1%%=TvJX ztX9IdxO87xk3rP^iqCXQNw1JY6MtBP(oBY^v2f8wPMMCxG2iu{lBU+prx89qdwga~ zFccu1`$8np3y#e}8HvBoM0agoJcplENZKtE8yy;S6FgVNh*P?eP9ODI#NRgz0s-Xl zm!}!S&`8RWY@@S&kQSjg0k(6mhpa*?pfk%JgO6q*k?pLe0Z1>ez6`j&hpEpC1-<3QboKtB*3=I~e z5MHecx9k+mOuCXE?@Y^;)u#m=(M1uI##`lmgoRDFT# zo8*dqx43zoki?<1ZOD_0u@5W`x@ejY1`EZ93%JAji^B;Xlm=(6L}R9I{5bQbl^+B| zO`@c42mlF=g~9kl)d$0>Q%=mIfaJCo0V(y%*NdTssd!oqKK^QPVm1(}DF6DqakE2& zN^9}6NeS_G4j1JyhumDz7sZOkd=D`hZaexY0~YHzWT%<$ls>(WKTl7D$frHHvJB3& z@*K>;(Hvk{TT)D-KX<7Oh9Q*PZcIM5aGhrsT$x4BQe(7pi5V-%SkoZGRdKhpG^uJg z#8<47?NA^tg~W9vg8)IyalNs9<{_-BdGjxOWNYzL?p3rR8%A4%xvr_!GM8MC%t*GP z*iDRV6*GUi}fLqW_}90NS7SApy|nY(BB0;)05L@Gcr zpu|9Hc>x1xkUBg{pSqZ!dpDdFBl*%4c)`dJHh%x4{DotM4qE5;y*G5AlEebHK*&Jm zUh>^Erg7@92>wk@52NJ&j3!W)$T0B+7dE1Zpbx}rO=7PsJ$>Uy`=6X)ae5j3pK2OZ zWk0=(d~1y;i7i8`9BU(jL#dW7T$hX&3ZyTi?o=3)M5N}8=65PJUcE(CA>Pw-M$@-P z$jmf9eg&|N zRoP2Y>nMvAOT|*T4g*;dugg^s=3?WO5-ogL>F>}a#q^pj!;B6N@tc~X!?V1qK2K%M z84Nr4h=4}qFkupO$8aEQ1%WCG8QYl~Lwv3WQF?khL0Xq(03v1Fw^j;uDLDW#iWC4+ zvwjxs6(%!ycz7RPgQ>WGDx9t58R)UfuqFkag{^9|1PbZ#Bt;#RzAG1Njp?5B~SQB1()Go`|%%_SCmEFJR}!3TcncQe5x+S+(hQn93P za-1eGoTvFdcJwjKy8~1yl0!KP3g^!4#^Ao4-PfenGv2npbTQvyQ2D~vQjtK+IunF%nVU%VM z#vhtq6Jte25AuZyAS5Z4>8gpxp%wF#b~%A&8&>Gpk$xBo}+P;_`RtJ`t)*St3IcJ)2cGx*-Z+~2x z_pZO2|1O1QovFmZ;%(C6SLC4;*rs-;3!_XKEy*z>THME#oL|U9elMIw#9#)sI9k2a zJLML!3x0fwd^)p>xjcSDSCvAY1^k`~W~1dG1vGg2fn~z(C;JPLb-6js&<4;NrE6!R zHDehpZg-0zPujn7Y{~b|)hiQ7#IswdBQMdmsY7$#nX|#S;tJT}` z<>rnW%k6D6L~4+7*(QH&W(1s8sMf;`NV4($sQM5ota{V#e_CIALvKUay@U6>7-TWk z+1C6f&9M&RBiq(%9|*aKyb+}?^aO`Tp0q!ViWao~Q)0!v66#GnF+#hdPSXS)B)?|Q z5M?u%Jl)Gpovy???>p-SxD5LEy)385)QXWv2Lfwzq!>yiz`dqo0FsTL2kSQ&n5w}^ z%eJ3d0=eP6U?UvxqFVCrZ|U*1azlvVt{GR6nC(bV>Y-3%W|{7!-*|Q`arC+x&AHPM z;MAgZqN;#;_-y>u?IGX(stJMCu%Sjn;z{N)EIxse40yzXBwPN$hrYuc;S*HXtYxnJ zsIiThPLof~9Bxr$sbN3>`ef13#drArordCqAFSOzi5u9^GE+*1@4XiV-g}b#l(m`E z!6~5!r@lv9im8uk*LVdaUouuslgQ~qyYnd|T^k7-v$LSRU&mfP`W5@SXW~G3pwBcK zaP_>=@k4)rhB=TqCokDfArL04`P=*I&DrpOvSGih8sdmrrgR!-ml1_*f;bSU*%{~9 z21VTlMTTI1NJX53hb<2u{%zX5+Pj59I8Z%9x_N*5p(G*+RFN*nDqa_FiCEdwjdFa) zlh7-Qp!&awvn7kteFDPyaZ53x-+2dvJh8)+C@T^>*=hcobiLnLVSiZLf9q;(_$57tN&Dkg{UG+Z8 zedD%RbnTIaVnV!olFN;edaluZteBD{q*qgfinUo(x8mN0twKEcihk)wfGyp`;pJ81 za5%Uz_e*yA0QS2G2KV(#Wqm~N;#;}vQmEmV-mrWccr8%TefKk>G)tyC*oAH5YR9ZM;24VV>;-hHH0w}9a20hN+I zr>W&AK~mp@=IyVE1a|8$RB@sXD|l+OYIKm@PmJ^PJzf@#W;*lcIX*!W_OQDaUZK(D zS>$~5iF|C=xPv8XH~>&fK7<62s4b;%v}@3DaKy(FsH5QaFd*~(DbjNWP4`J_CkegB zyI>34XK{SQ0b{xlY18K*bwVWA+sE7MHAt5iXS<_9-?6>(65&ckR-PcAHFG3+|ND6C zDZ9;ae-z#C+2C2hz0-`g2<6jz3wDe8ZNHPA3!*8*mo0JqVR?bPC$`Qd@}Ya|+@e%= zw3-#VQv;rb_fT=9%n7}7r0USe2-zjH@w*haSX~Frq1xW+JlX%18{M^n|5AL*ESrO0 zg&U=9FF2VxrW6mc23lUh7z*6~dWurr8bJR;vNz%&-{u;vQqbQG`dx2YYH`%-1k{f& zoXUvpQPn^3O{^J*QWq&}7XEQX2xj_k071op3w&ckQt(DB|02!oph zndsh+gUW9Rijhe$QQCHVQfMAi(sXq#?+K$Ix=I*%_fm0>vKX`Btmq}k3L%Uf5NAAG=CH;$YUqX_LwXEg}6hZ+Dt91%^(apP1l@T}?rChtp`U*iamEF$O8SpEOsx-RLKxJ#0d_nf)QcSC_X)L_b14zYMJZ!* z%fYAK=f+^1Fk=!@)ra#s^P91Qy}=UMV-sU35okl|0awruG;Hx+d>DhrqSY+@4Qc*& zbn(TNrWS3f>4-*yqnBWOdw4P5CYwDY$yOF1M2D8cu-?aWjXY@ET z>ZQTp_4`Ibj^s$2jv^le=-`~c*oT_{#4?r{7?PU*X}>uaEY!rwfz*!5USVcefx+Lh zqvCYJz#-sVz+|v_jXuNQ5zm|{>iQOSeC|NuH7#}yix!hJFRks3Q#EC9x8%^X=|Il- zVr=O&;9o2t)OFn!UPNE(8O(%;|@ER@!=M-QA1_4-8DY zkJHikRLuf<_v@qnTXYUKBmgtFZTiDV70|p9blFa3*b|xs(5MXUt@U6(b$lj;76}jy zU$VO<(lwb_?|EbD{2i%?I-DrP6r6u6gbL;rx%P(ty{kXpUH6+FJZjW0Svq@0DWk^4 zdpwy8u0IR6KFWqODn4`2>~Rot@kSRL3YL}$j_ZgdEjkiPec?L~-P#`NKy3^M-9v9l zM$|VO>EFqc^rjBA=o`#@&*8`p#8XcNkhc39<9vn^9=Q$PtLLZp9sh_obrZ`#Qg6F` z=|I-rMJfH@Y@h{%e@AZokhQ&!uxOqk#% zs!O%Jj_t5bq1A$%p`W$;e5+JeGIfM9@pEhkTbA}XN<`hV4FlsjQmzz^U`O|?s@H4{uUbv*6?-OgRf%hMpDM8cJ) z^^{8M6=SHVojj2VHPvQg{E}@NPTS3IJAbBqdZAEPQOdPN0w|7w;N&X+09ye6+g5p6 zAequv%|0`Xw`!38Ct8ss2kUQ=I)M$nOTa(-&u9-teUdWAGVqHgq6Q^&vOcnXJ3)5D zsoo%4NCWjzz7n1-0@4!37BwHj%B$F>rUZa%$ zmq?!B5Ur+Nd>cVftxNPD!GV>)&6-tigDbE9PH(aLyl=4Zd=*K~nfY!3HFe6Vzs&!6 zi;C@1DxHgNBZS4-g=M&3;EIc>xJ%FKrfZ9Y62~t%|$9M@>ecHP?DJZG^+^C zJrl3gWkQ`DFj58-f0u*f9y9;(n!%qgQMy3PaX&Vl?k>)Zx=I}BB z2^X@ey)WGfwslU`_AUPNS(vpfPn*zUZDTr*7%e?%^b{g?Q`X&Z4P|@Uz8=? z2pFILE;zQ%>JVh;n>%(Z8_jp+NbncAPCQ)f-J;?7D{96x{{tUR;1FeabREdd|D+fB{+Fz@|H`%b-+G#=$IQ)1|m#9AK*U1cZ>xJ7j4ZU6+dCYSkjZSySL+At+5^Kg^CJm2hPm-}x?x%qYE zR7#Q;Yjhu@lTGRD2j%HE3jTQ7&{gH2dt5z+3U3T^Dl%x9>fja)UtW2!Ek5>JoJ^9b z4DjrFQAuL5MZ2>O0s7@JV77uWpSPyypUHcXB{%MDFi^a=+j%Dw+-^hsXcZi!<+@%U zn@AN{By8NMSEzJm`SDBYa@hfGr{!Xi{_~7Tle}L47=|7Ku(o!$*NJ0O^&hgEU(T%n z*-aJYBfI~Dby`$_Wdf&xc=c&}h>TE5<9+$bZRhCPF3`2=9}DyD9Lcv@Ep}8m1EARO z>+nA`LkD{7cy?0z^Z`4Wp30+xLZy%r!$*z6IEBnu&VzQ3Y^6FqqnHweJC3{UBv_y@GW` ziVUszw(M?4oHkYQ%HW<)i z-8V8qw{*Rt4LUyyooyY(`4kP{K!m8LAhEy@k&=OMYE}*bqp=b^AOs+D^~0r9HNpQ_ znk!9zY%~RsogyD2b`z)$$sa7X265XVfw+o63oDeiL1HAL>kVJ*82|VN_~!O-=Jw zT&<-aB$VM}EEFj1Y7AIqWD09V=S|cjnB5E1nZ0)tyklEKhnlDc(O|45KUAUnEZkD{ zw&H!}ZJqE{$rk_Y-+u@NHEQH(7xE<%&P7#fG zTM1Vs0T$me<1zMC3k%S0i&BH4-jH1b7P}Z%xvD_B6BbOFO&qJ6&8UX+nT(T*^%z4JB(~XNubD39q&J!%tl@bnE5H0g< z02|=IH!;?I2S)!@eQu+3C&oAk$v$n6dnLt3&2puPTz7zo3I?Dv{wXU_kR7zA3@4$m z#SWyTn|V@*K{kp739HVIbyol!1*)kZjZU~1`-$)rR$)h*Byqd$mEB*y1S}+ZO}NC9Z+wnNW@fZD6DM6e@f} zU17~Mm%G9d!rQyE?%sk0QDMQ}S8XL{t4o-H2D{I~A zfjAP8bm>zjjgHK3bs`v8hW$(KrnH$pG7<;m-zc*Coi%#riV&YXjEPZ)Pb839&SxfL zwyLv~w~6>KW;|fBU5YJ-JMqy$c8O*Cc&jK~=sy8YRpAuu?25Yf-Sb%k$_*k0T-V-d~Pa#kMbDIfIq z<YUD?8>oOp)GVyk(^vu%%S;ef?8dq%vF!!yy^ z_i>XDH5y-NT(fc;kdU88YsB@^It;D~}Iw`8Vx zDymTut1K3tj2Hl6z%87BVqHQc4+F=)y;dB1t&5U_MnhYGF;P&;xuxm$WzaA5@@ZzPiBM&Wc^bH7BWo51|AGr^Dsvh0dF4_3Jtju5Jy6<;<+#X&^KF zigQl|?LKP$n-@1hg{Q8SQ*W{uqy&B+=J*|4c1)L51LG>Epgx_EWp#?ylGuwk&V?$n zUw?mR-@+wP_N$uTC{25OyH00I3@_iqrjZn3g}NJ_QM=eayIPajCNL4sna z&BFyB0~OjN(Bk~{ESe#M@n^*@5-2)zkprB5l%uF@%1rFrmeT2{e2_Koc%j}Rm=ONN zFgz5{Pr)<@EYlq2amjI^wJZ?`D>xGE8IrG+)?^5&iW z7j?3rFl8GVmn)iJ8<>vPza#+!^#i%I6HtDOM_P{I=^2oZ2CvfRhknoXmLoImHo66e zKi8ztRt_TvH^JDDGyNPo&6^NpZE$vXD$@uUP*UCC*BH0?85p8fMWzIPX1jJh3smam zm>2SEjeCDqYw8KeerQdzy|Ia}3IjCR8hOig)Xz?-CdlrdwTPML-L?-h4i$cXHa~ha z^^jBa3DP6(W>&klSS zo8P6ohxIpSV|14;D3!v~_l$^SXEQCfx|-SMGK(HQ*|2*-Q$x$*ua)MMQ8dOf+TVq< za27mb+TRLlHuHcbF%gc^5by7a_@CFS+Mw54QXmQ5f z`ZA$@kQULb=9{vgZ*AHp$l&{){9R7-WiS zmV6GE`4A+ZZ7ycn%0jm4Sxl*Ufcce~(=^#LFHJ}49ATa+&HoM~@Ibp%j&6C&bfwZ@$s^ZgruuU6uEP_QGEI8MZ~9WVfb~20Mgh~JhfCtT;$LuG(8A{Uyv1Zvtbl zXy2eUH0nWndzEisX;^;&8PkV8jSaH`;JRS2x+ir+&jBoTs5~|;!F{wa;uYp$^nioR zgE6}052T+vFj#i?NQurJ<*K;3SXwprk~CCpNc{w78a%fe%5fdhRTnZhagrq=bdX(= z%inpsHo~jPs4P=EZsf3eAfFxOQQ+;%FqJASvpbnYh~&~`1slu%6|_>fLi~KtV7eSJ z9*j{GOwqn#-f)PoUVM@6HRAV-Qwsq6bRO%liUDzomKz(V z3;v+h(#?7EA$s4LQzXjK61^U9TJ)EeCzb9mmruf#X5u|I7P;vv52e2v0#<+bjjO&< zWBnQb_N9F7iKsP>)yP7U``ck!G8Pb7w6j{21U z7@u>{ad_H37wO;xnJ{LC+Q<7kG6A2b%*rdlgA+NQ@(s{C8-;{&2t|AsXDyMqRr<_s zmi%iKLXHPc-y-}hD>O>n8zQPa@sr~Io)v=DtAx`h?j-`KY)>-~#4;f1lWEgQ@3H}Z zs&?u@jBtKvKN2UaAtBy<}X&g8LuSiYmK|k&PZi?fX0$Cs0ge@l?(8WEc_$J4E=S zQ50VE^SdsAW1>o}551fi6sr-5(xKF%%F;`0Pn)mxG6H#DnxT4BDGoCI(?erzqv_UX z-Ism_`Oi`^MaO}?xF~=^`4aptF3&!xM&f=_3>+{tNF!XOV<1@Qq_n`$0?Z2x7$c$Z zn2Dneh2*#0*{pgFMa2&RsVBx@X0GUqF_GUlpu@-DPJ>9ebmYNL2}&MXrx{oeIY0>s znrOZ~5D68SqFv_M5bivsWBHT-yn#GP7frxUFJU z9y~x24M-JL+0~~OW!tSnn1rTrH+$t&(K*>BQ#^LiDkcRJ;-Oe*8DolbNjfK%3NjGM zm;xs?0VhWw$g!|>sX!2Yh%X9?j=4@PRSl+S_ZJ>8q2SSsI7EPcF&KCwh8Sbq)niyt zkkn#GK#ghO%?t$ze)1{nDJ1(jZ+56gc9>gs-{IZVkZdpt@+^!ZffpP!extUwgAlU# z2104eBtlNOlMceEQLs!$Mji|%Ff?gl^f?W|-<6O_e~@H5>)!wYcxa@_lQ76dCxEvz zc}~gXvtddru)F;*apcVg@&TUt!|D0z+d#=JAb2)kY%5<9SuiV8uxL)o*-ZMbi*%ef zA0R>s@Fe}OpH#ld$WiI}l>tRO4o$NH>x1V}lfbKK*$4etE{p~B!)?Z^;b(`plR@l7 zs?3)!_5HVz@!1SrHP;9w2!Pz294v$Q5Q7jBA*FLKp^q(LtSw=lC}I6v!fsI_yanW- zWJbio#G6RPVTF*>n{uB*;oMSwS@>N~Qr=pGd=sf$Kk0wAPkc0#4|j#OBt$#timpmD zQbV!)ysXZpHrBuNiU_Gi&Bos@7`T?AES4jBPVZDp!wVlHME67~+!O3l@^Y&XZZ}up zgzR22Ol5}5T^cUkPl{QK6qALE)t2&TBIJ;8OgE{ZW}&>h#X_iJ!UdTx7@!&UpUUSu z^}NhiE+X7H3@WF|1^dZv)V@|TQ zKk7wLHDtRm^3V`&mmwsjBR~?NK1-KJDIW6@J_MrHF|SZl4Xl`jL>051hiQQ({__a~ z7k#Xz&ypY+|Ifb@!Fs^px&>>#%+m@LnRshK_r=Fz!p# z(hk$*C)heA6Kx8;TU+eEGOz4|Joq8P70|@hTc65%)g7faSqG6Q%k3i36nl@ux%6HaLyB#qs`0{0(UC{0`p*?$(HuCLTdnp^^DLP(ddKHOnwP=yb0g!4)+ha zbSWpnG$diFe6)9~yArW^>I20gM=+}N(5G>3pi?c>GZn6FNK(v?N`;oND(Nm#J){_G; zLN6Q%&w!K+0%nAs*0nhiT7QlS4hLh*)W^P}6O*@JBbsyH`uN`q@l5Wd${Puu(kaghaooiF|yjm0 zk+XdVP6KyexMN!;LT*tasO*{QG3+cKA$J&CkOzb6AWELpYMG;%3t(}r((I_mlTmkH z4!U@_^sB|*QD9@pPi0ZGWGN$=@t+-Y#tQMAYEXSo@{GC;yT!y%q>Yd+fJsfm;&$hu zgjxB&Od&_lJoG7mFVx>LJg8k-NY;`{Le03LgeaPm6kT3V?}yg?pzu^9sBJeUqn2oA z$vx|rRL#h3`d1#s5S%t)v}ThJ6IR>h(QI#4Z(9cglHaBAGfUQ~Yj46T9|)vMsDjMK zuW6O5&LlGB9dddRj_mXmH+fq~I`!vaKE~Bl(a?e{-&=yT@qBtNYU;+nf~%h*XNC7=X|I1sfQ_WA+?^g*k3*Cm!9IVP$^W^YGTZ+x z0YUy=lHAG4 zJG`;Uv;PIA@iVP&8fFWNleH#y>#zKU*g@TgLBef|ceq2z)o+r(^~0)#2qWEn=g@KL zStR%>fC8Zhv%61DGEWNK{&R)uM{fRXkAR$Rz2P6PF;lp%zS}jvk2pm#R&C!9nuUGC zzaKE_@OPI^?rAHsyAH$+lS==W)zQI z?XE6k(`_v_#rdCK^4i~TLI=DQ-45EU-+jI}y}1U%0Y5LyW!zb02t9C_`LPxvGOd*8 zMVd6u&VgttG}Kcis&2D44UO(IN0-dE}R^Fd#mT+xrhIC9u6VeP`B+0z+|N0(BTT=o=rABk{ALt!>*O5GykmlxiYH!+oOuVWH~*Te)af! z!S<7){???gXYu{{jc~Wdclmt%{4F0JZXd@^9Y6 zUedz2Pysw#}6w6L$yIz+uHP zoGPgT(O^QQb_z{I_+$O_YP}-+xA8gRE^V%$91>D;rCf;um4w@MGH^tB8B*tVui?qN zI|T%iG0_Rp%B50|-WE4XWGP<6mWhVive{#x8HDndq?{FG=|qZ748-|4ya@}OchA>^ zC9|cI6>;RdkImpS0QU?8jMQD`UQ=4O*VcJeW1XkOo|D&~=$DVukw#^%kRQ)>GZ9`Z zj=?%~qaV23se1u0{jg4{cSk7juo=4zqUUFSZ~4o+2%GcMnZ+P*FSZH_*;R8SCDnf$R5`$$r9@ zncg#};c-hxRp&TUTM5a=hFiMV0BSVVJAoBDB9(Gyxnq(s-r|oW3$&-kUp1ZNffFux zh7%fp+k)y}m-!k+_pI^+LkD6QxT4iRW|~K;qS3!OyRzk}Q@T#+c63k-K5LCN(au&a zlWi?+(J%NFc0^lm@dtgDj;#OTYrdyDDREFPmJ5?;=AdQbh4n42{wmOF_}WG@(1 zx7kY8`V36k*NSHtb*Sj*B?z?fq{hUODj7z61xsMQi4Yf&?ojW{Vm<|T<>d(xoXK5M zC%K_z3CtHALbx_aYh;-mIxe^MbtMcLfaQR3`Hn-OJmJY)t~svHb~=$hf@>>CzhGAF znd-V0?};Z=gOXdX(JO-b)W zKz+kNQ&fNO+O1N9wX!0&($kHPo5=d1)SQ#=U*-i&hSDEmH*_1}J=|7CtU)rX~+&;@?q~w z7eLlu3klCcpjdBH;X3Z2l7cMC)md#n6~K87?@1fIGoZP_(zD@QuIXUz1dbjlK3$Yx zXw0AYBNOieU2MQ^31W7SRl0e!A^|gHLTLy1DN!z{u+f|_q(v<{9i@1g4_2d7d3Vi@ zo#*k?vUPp$nvx5E0BF8^#EvdcziQTtc^j#4@$Z=tL`7PD=6aJybfj|nstY^_zS78!ELyMk&fy|5+$!!C>CX*Ld3Y;k zHTlCbe}z{Hwa^g^UbZW7(kxcls4#OO&X_ga=`wlQ;?^Tab^joJ9(Op_8V&>{x17xv z={lvKQNZLnfL(Hblnxa(_T!CE%jP;nbGD+_^d8)bO#H;TD-e?cp5{~g| zYRo4(2r8zPdDq)q+>DD9@FjbB-qXLfZc1i}gvu<($*Duj8%t`j!>K(pcs=Ud>*M%> znnh!7&0nejqAEfSToB5+kkbE-xEJ^bzb{~18**OZ2sK!K#)3C!*ET}wNvIbUk~gc> zRC@(P>x4C#JOUHO)ooQ$|8H5ojjvsER=F1;P3UESa8Z2%0;X!0Iab@ji4%kGIAWy) zQjky%>N>6zofV{&!q|P~(t?)-^b%ujw+1~jC&f-)@|ZN?ob>YT!{Elw}ySLr)k6_+=l3IIjWMr6$nklgcglO(-c{_*2IW1Tt5CFdv zjJB5`V`F!_L=IJ`L#hrsMhlq>_x#Sx?&a@iAEx!|I;m&4P|@0bADty9xg|p=tBEiG z!DXeu%pcegGFikt@-=D^kun#EC4L-z#Npw}>=36d+p6P=GYoW_i&i=p?tk$>_XHt` z*DGHQZ2dy)SNoK|fJcYvsC)ID9cN1lYh!e|;;#>mVy%{|+|<2PzE*vT1>DlM9n@-} z1#0Ds&B>{7YNSE?st)m^sSD(tDsQR2Os#~jx9qp$GcD$}o@Qr zI|uVf#8ZmTy-yWq%);n}WS3X_lgIFWGv_sWekX30KdxIlvUck(?(L7B!#vw(6xlI*Gb!_ z-Rlr+Hr4ww{{=n4J)CseCa*FPa*Ri>NRhUir(GBPW#45T(K-4}`bnX&uhs_XcylcIFd{0axsaDtwX1IV@RjXp8GSmKFu>X{LQ)e^x@)EoV zw@_Tsl6=h7q(d_&N>7Pw{C`8g-5lk&jd+jE`jli%_Q^|X9Dl9;rSi})yYTm82VL*4Y zVf7b=H0;zg_+F?NVR#=LY5pVcw!lr26eJZ)!^R(dScbS|$RAD$`A3T9cT7~I>))#3 z=57%+?t&`sD#b>tI+U~Ijj1eg43=)Gd=u%cM>dLCLb(xnj=sIgTf_M$_{7sPA3RvU zjTouKWPs;)XO&kRlKN;X?7je|=1ur*m{j@+_bgQG>Q|$jH+Mq_I9_^SQf_42B9Xog z$SFdsY#I{E>1$mTerzaps$bw-p5j(lKwu(!dJGFqi~hj^+1@HWUTOLuJE)NS>h!xh z3x8}F?`Wm^o8@G6+j)q;QR3<~C+e1E|Kk_2FOUrrV=8xOyUNDYQa9fC%8Z}esbq&} zL;zrayP?cEr57`8ym)BQ@pvo8t5} z)C<>+%e(4|F;iN>;9WjE1DK+>HjR69G^tzGgp4n><`&h>7QtQfAR*C(-f%8&={?3*DSp& zKRstORnSVbjjPUQdl_{-uNjt~aOg<0o)4~}lC}T=hDahQuei>0bFLi1K9v?;z@o=` zhKSO@TC6Tn>D|PP&`++qAF2P{^gmgb!;t0hq2FUhZe=B)kQ`sC2u><*Jb0dMDokW* zok=f_XI8;OXI^`zNhHPDx9P2~<{%;I&+&BW3hw$BnsGsSSf27nIhd1Yrc#86X(BFJ zalEpfeC(qYlRd3Ws`~;Pz8)&x9S-#xC|}oa2yY@JKGq-H(LW8FZj~d%bpi{u32^`e zQc=A-72qO|*;E59?B{BtL^^i)@1&NSeIN*_=?R>M& zEWj*C3H?hwdwrI(4(K2~mxm|71qa~&@KurFu^6XBUSn~GI%I_OBChw%W)1^Ll$DkCg(!Y8WAxOPjt>ZjpBF=YrBMW z3Zi4nqRss3qKWUmuTFKhsYFDpIHbQ@I!$$`U>lE}XWUR&oQkoO%#e(L0wxyQqLm%H zUxv^nm(J<99h+n`EvzHS46?!M+W7-ov__VRn4_Gp)I7pGqB}-}xrG>s={mZCCD;9; z2@+#nxe@1$c}{$(!M=FPgT|Ta7^ETMy>_0m=g0&8T<*>G7s>QrH&*HoCtn}Fq7O4I z`k^6xtzr;eu-sOYH;Iof1&+s7OBR*RgnLGms2c%XBR)T|*bmHowkTU_-4Y$6u?EAJ zMQi8(T_qN;2xu)+<>owGFx(c4uRc*KYqx0PTS!Klp;wuX%cJYpWwWoev&~j=_uDiE zOcF$1a;~hh+XLeTqkRR|_qM4TebLEE>r?6#ouL@^-o{wlu0z*V_MrAKeL=%k~wKkMi_^AGFi-4Z9$RSl#RjWa>NoXS@mjtuE^%*8|lVR;RynL&}0q4Dy<&-zj5g6 z0=|>{#kM2!VHGgl-sIu@J@PfH&rs-2M)d56g}#H*1QZfPQ28V~l50Lys>Ob)a6RPCmUEdT1ArH zjaGSmF7x?%@$8Byd#zmg@0DMF*#M+r#ZZErqqV!Z-m>wn)qQzFMf5#|aUSO7IZj51 z35^@<-opRbd(Wt-x^!)KQ53MMNUI7YNhop=L_j2oS|B-DpdbPw2ojYn86;CgNs>Ww z&LAR+k|hg>B8o_kf*=`GPWguJe)sPF_U<$GIOCi%HUs|hgK&-OzMp5#nN0;3gP#xq zPw3a4Od|Nxn^xYAw$;0N@BFh#W8)W{8RH>?m0AQ#g9!J`jG)vG(9p7^!IFocuLO(M5XkY9hU+mG zDlE42jB04ff!phR9aOhSna!Jd$`23U@V4^ZrwSkB6;9NkG)tOONTRJr zJ$uh~MX8dYcLBM+QdJML3M5=|kc?<6TPQuyf8E~o=FABX?dS)lL+a<}Q?L_!rVCG5 zmbiIASGN)oy9a)fpi=71md6OYXgpoYpz!w$>N{?$dzq_*HM?kFC1Z)0m2Az=o6*P2wybMq zybFIX7@?{k)0!pPJK6VY_r$^h^i}Jb?DDw;^E(Q**5|12X5uvYOkwYtta(%4495(N zO~qBch{5rxrHU{L`9{|>tY(=I2Xn9>+}+V`+fp;8kxG_9<=QdC{D^*pZ-#XqM9ceA zaB`69vm|oV?agbZ^>QAL+q~CCyMx0`E-diHI3J&98@FT+)~8*)5b>q3{(0MjO25L_ zxOJBUQdz#QFI%kY;_a`f3G8H1@9o;uw_G?_r%bjdRjsfi6MWP;_?2P|j2kTH)LVOL za9gsFdpmTa-_Ra>-Q!BPQ4Kl;F>Sv4v>qm*(nrJKJYp62(b0L?(Kg|WtR|sNYi;G{ zTKlL|UTk+fiKFVeS$n3{6iW;SW=%$`ia5%IxMQ2KKc`&aah`_wJ27Ig`f zHzL~G8SD(JcB%|b0}BmIuF+W1Mz{Ugc{gfGB~{w7D<}D+tw8&vM!f}{(~g-Sh}h%M zx=d+R6f0`AU7E9EZncM@n5~A~OMC1!uYr4f?sU=mVlSyl9rpYdlN+<$)cc$Qhe+;! zj;y+67E~2A!iKvjbz*mLGw?LwbJ9NM##+@?kBFh&s3)tqMUMp##s(h5@ozqmI!Gvr zRcGEY6>##R^-O$FycguDycR95o#Fp7``mHSR&e89>fXVl%hfIGp4t3|xi6{1E>mVo z9Tr#}UVgn9;BZ)+>?K5Ngk(M}`D*vE%PS@9u;Qi8#?E0i<5A7ccn=$=T9u={Zvv-P zs0A9T>ppLkU88wgq#XS`8_(Y(z$e^z(KBxEDDLHP^OugGxQ)bjKu z2wo|iSR!7a8aSBIS$^OgI*@Vt##|yvOA+ys6t=7>wTs*1?s&Gw6B=@Ya7=4jGum4`PApWnkc|Pa@m;s$=8t^ z&j6m7VvS_8`UdnnFJq_ZK52(sUG|br)5U@8i!;;}XT_J_T%*i08EATJ7nt(7TH^4! z-P%~A%UE;4GxvdyFM=pJE?lX5lUo`d!HZ34+*%xR?@A?|^4Jctud@3*cw_#`9N@ot z+&|g;{9sFnWIw~6x#i9WmCmq*Jh1u4QO^AA=hrV^ZipQo@K5=}89a2I$@#h;i6SK% z6Zx`P>CJ*^^sT40Ne-=gm7e+MQevNHhf#_!CRuZ7p3EYA7H!W+;S5L%ksw5HZYs}4 zOD`~?!)14u%wyzWj*-z9LmNU_l=w<`V)RPH$OAQW#~@DFrhJmHMizsG{o3vD*hCuQ z4L5<@lZyQ*)`cYtX?G4AZrj~%lDv^l8{irLuqah&G3zbL&5YFvgSbI}w{i{L^5OHs zo8bi`l7tOlfPmsF>4?wXf=@?_3o_gyJ{Inott=M{?#Qo{6c?7Rl$JL%L0Oe-$9Y9% zK4?p!ipGV~TYA#$?UXr9UV_QltqgWw0y@~s?t~YLoQo~?)iH<{8t?HXi z;=T4{Ds&Z!n^{pNq|6tVt=mpkdYf3Tcct6cS_G^JJbvjMM)~v`JN&faZqre}?WNtL zJ5f92tPZ!jFt*hN$SYkG<+NWtMf9$e1Xy%Dtl0dj|IyxPv%5%JL97S+Ncn60THzOG zd7+oWTm2`TG+gXXP4=JQm0bO@rSM#^JFJKG#~oK)d5#KK{1DCiZQ`1Al>O+{C%Y1W z8-v8-0)n3Jm3JmrcB9PAgu)$ljgl+e?njkZObOT*!QCfZ$L{Vu_LcD7oBUjQ-1ek( z{DK`5FFW;BiAy2J75g($K|kKVjZzUo&*|!jJpE*&VQ~SXd!+sL635Qw%+2&>-NEE4ztW;&-J`<>qmn)S^v17J6T447 zQ~o?`e_6hCQ}(8-*YwNe{s?{k1l5IZXC#b)hk@+*ILv9JIaM0s^oyLTCs#&ae80|E zmK{QFrdtxW>&Jh|BZ zN;CGHfN;+;(+Gu1M=U1Ffa`jEO*>cZ3PFF4hJwv9fl2q4nX5c3*dAiR;ZBo&Y7eoQY9RfoJx#2ZpF|J-*@*5spxVr`DKzemyKYr*YP-`%h z7RSMtBq`MkkAow~00UH4Q*@ySPNJ9k$ei~Ql81(F%Tza4x+}=_TO5zEI`gR>jw>d2 zFi(Q#@2OiS&bF>SY(3z!3h`pW0lV=_iajBEt_%@478 zww=Vh(7{ubMyG%*I-%K|q~dfYB9p;_-nNwfv`R`l#+ELPOx>@T2Y-M@hB`c}5>Qio!Q%t8}aEnu$ zti@0R_P&R`4^=vsD4;A$`C69W0Tg@4(QEs`?w);B%*q#)#MY>cW8m>)W0^Z@tZ}i# zj2Ny(U+iKLX;k%`p}|fb4g?z+U;9Ka@%)sv%R?oEhI9hga6yl45oNczAJ4OVGhz45 zSqgP&uG;flBG=SUKHa#tSUY<{`$bal>-`J5PXta7uzcvqj~^Jh_RFirys*edg^riU zTGfG!{icaEPg>7t2nO?OP9s0=H8F?`i;MS9(|9uxU)WpZJv^L7*A?F8a~t5jWroyC zfwjIlvEj!;;;%epz!%dk=D-nm>qRwJ-F!_=G(voieb37NHJN|#M^|(1bg+G|Bq*V0 zCPJXBr&)i{IX+i|LQjM%)9?b8bpr!QXRDp4@euv2UxU<7sXtb{`7S24uHQ1CaaV(s?0l{gOEn`X+5sl4^{+6I3|(O zB+T>!tzUL~6=OV=GBTf0&u5(T#q*`xCxiWmCDV#4GGiUt@1nF8b|({0tv_~c8m1>v ze(L*<>fXit6Dv2|wJdWi^_2`Um8#o1(s23hCECSIj6aq^`~FYv-V z*>UJ5`0}*N{c*I1s7JnvucacSD*7U3+i`?zzK2%(xUVYV$I9NsK~=z>kVy^47RNDW zk58lPh%{tRN|9x35BdDO{qc+7msq-F`rPW9z?9SXvHNcFMwd1{P2`bw{H~dzezJl0 zNn1S)Yy7}{rXEI{{@D8f&Q%QM&)mpyd<{K)HBbWO%dHAII@l#p%QtLvfS0me59 zV=ypLN2JE>NOj%YYyiL|3get2;SvS$g;96n9b0ioi6Ig$AnaWVZ@4mo?<`^vAZ5ps zPVPtB<$@SIEY`!j*&r+o?*j})GEPUjii4OISgb0-9s~BI+}lmz!9zNUs&wh1V0v{d z84CH<`hL3sPZ9>CEkUQZASiocSFAZyhdg|g(afoFCvDhwtfT{hn_{SVuI00>m_Qpb ze`Pu{3b9KJCGki;LgMiz1xf~!Vd^K0fv^ZVAvJ;)2&Cd3kbNWmp(d2K`ZS(>VD3cW zUQI7^0>+7f(l_Etq@(s5c~nK?y>rz>MPb|<4^7n|Jb_PC6dG9K7V$_z2Eg!+u`o{1 zahVr`Nx0ZXMzKlZp2{b&pL`MH#qVr)&lF7FNQMoQ+Vexgcv2;Ct8<=77d?@qfVH|V zgDj|FJ{|Y4yp_pX@XSK)k^xZ4(lYm#r-3nrfv>)i-P=e~;)z;&dck;zOal+cVW5Jc zlmj(%wGI1MnQSQF5l|hW`IuCW_LMpYAikwaq>gAvngyULB6)^dV$b1|U zP^gB8JCz#hkIt)eYY0NxvSB}Z@=9tS-5?H6V+$^SC@?uJcs)$UEC}5)EHuW$&5?!F zH3h^S^t+`ej1B2-Kt;BOMa`TPZeEH;zPycVMUen!ztOyIm62P2f+(zl@-q5i;~Juwq*KKmES%X zIAbceys9Fci@AI6_LNFGLuLD=jKwU~wlx&qIEdW)1igASX-!oHQx#P@_XbN1y-|&_ zIb38u!0ZkDN^1>+9V7cu4cEC^usAz2tcIsgM>nNb_+zbz&~>WA+TF8d%5I=isAq+Q z-QBho$tP)b;zxD3ll2Pc>J^RZmA&g#)9ckWSe2n#jiY+}$;Z0q9$z(jtndBUApNo7 zv&Y7EK9%FdrP~w37#lOA221Y->+}ZOXAO5gHrSUz@*54#&*)Sf;V#}!JkpC9jY76?3Ma|wt{4JXH)D^vcg!_kRdZV|#hnu~Qc8Bs#_H*I0-caa ze`C$;t5|N0EX}92^L;62>?Zr4)-Aryz7)x>)l|Ryrua(PuV}hDR&CywrS+^~?PH_M z>g2$)Ctp9e5TmK~^Ty5D?gXKmgU_E5qiLSTBkksgBN~x8p$C z#?r?@nMW_0kM=fZ`yO3>`QpdX{?_WRXnK4MkYj&EQ|qY!kgs?ukVP6yKy zqiG0}?i008R=1_;Fia?RhQO0#Jrj;CES`xFZdjU$6z#&!MoGN0{uND&XJh1cmu4T} z$OYzN6&Y;i;#B#rUp?J}U7kxw-+|31Ue&*Dj?FPRU`sM|Q{&_`2^CmKu}HF6Nc}yU zHY_isJ9Y^yX1KnySZ%1aP8&`$1 zwaLf;hLAvv<4jLMcH6RmKwi|yP@xMaU8zP!O4CYNb=R@rYI*%T+tpvu)b2{%mzCA3 z7IL94)g2=}>NVYbWnXIhiFvSYNLgsDel+x*-Q)L`Wor!+ZmYkdsnB}kT$0`TQ?@qw z&?aC=N1(~!lrF{d?RR!xoA(#WzP|XryZZGdKq0)*0%yFl(F*dHi&l;ZYrZ1^qNb~_ zky6pObr`c@fG(Hew#`?MWJvwbwEExq)`Kl7|JEz~MDz5S{>JZDCHlJWYz-_w4cr=h zsC9<=HICxc_K+gu-R)sj{)+7p&1Z^QZ}2Loc1EvWySww&z$)6Hy$Q)>c;=#AbG*da zCrsh*t?l$wKiqk;w)@es2%$-groJCT-5lzBCV}$fcz0#*%Um#dk7KssN$+ISMkRv7 zEd)^6sM|ghCdDod@RFJaeNH@MAtUeOiizb?=B9bG2!D!h15;`5dQOY9UWnylON9D) z6kiUye<7D44Wf`?O+VsG0B@O^ z5-oJLrA0kSISfWd3A*B+&Wf`1?je1>s!sg$Rx{%*By8M0GKd3{lyt&V-yFf}gctR%XaH zg~wwoqjlquKd^bxIj*EwQymz(K8~!7bpdxPM+u$nPj((hf@or*WNDtc0bo1*63vkG zj!uzL!Aj$Iww1I20@p=XIKa-s+>7fM%a#y)dq3i-rfjcH2I6|CB7a(*yVuN>pE6hl z&fej{^A&j*>Jn8I^^y3r?R@Mh6ypp3RO0XHZ>1j@k*wtZeX^dg`;Dw>5q4dFlJ!^V zcZPUR)34I+pOW=W5kBwVBkQNWCx0WWs1m*j$430e`oB$9>qX!%WVO05_@BwzPIr8x zPI)R_YUWL3bdbqEC##;b7XK;lTDfylykcLRsdu>|3$ZAazXeXWc7}NE$1uPxA7IESe7mqX1J{^ z7qOwhmEwHA6Y9T8KU?{t%9TIKs?Y#y!Pe(4s8>GU{f(?fx6iaiG-wjZN@*qDEADAk zmvNqwNLGEjwf{`kbFl&o#L}}!s}f^Uw)t0%jpKT$ z&%RRIdwi9S=>1FXmvv$E<*YyP*aWKj@a#nPT%%bo&DnkVoG*qDFOT<&16wTcMgli1ThIQTdcfRO`Jr?&anAiI6KWUzVGYDBM&E z2yP)2uD&Hvi~>>H05EZmf!1a?h&|SyjMKoFse=HcuLkHvR*h&2TB&S00?8`2lxWeZ zinib5$O48kn2vTktUGey6&hXQlHrO{-)rCjgt;aBkh(24j{<|^LIOBgRkSt>xrIC} zMlG6Su9iX};|)-dLq+e1M%pjso>B1^sTp57QMK_}JS8U_?T>@22$LW*ixuTpy6Fm* zXlpc4|h`&VLaTg3Uv?~#Y3 zzb&a!>t}z>PyVT--YfL2{8LifBab;QzaY*}mfn;(ydd8{+;drM4EtA-8nF|{_DfPR zcO2yYkW_moe(B=sb>p&(IGW0eJ$^Aq(~79l&U4ORm3#H_#3SXP4PjNwPjl1zT-$ZHhgytC-38`>hV56kgsPUD@ zni4m{PVK(8NWQx}9wip^8%ia8ioox?N|mGdwh6|N8D$F2tH-)Sd=fJiu|hnq*J%X5f_Q6rv3T@mk>;%Kq6|baDn&Jqtin?_+ZGNMA$&VZ~NODhl>EXL?Xl&aBlqI zGys;9Bj94-PtOPF9ZG*#@1{R;yN$ZYfNlmi7Im|c1dhbEf~ul{sL>Y2H$c}Y{z;q7 zcYiV>N)c^gFzgg3!<&?@(c-Sg+gM8A>{0-D>@^jN!{t!mK}FQdg-aYJA}qro92pCS zp!}yIFsAZMu+2zoEMl%H~o14<;nTA1r;D0amXYCmMk@}ld!SJ5#_}|@BzF3p! zrfNhZ{TopKO#Ltw1Q3<~A5;Hg7A&-F(cbT;z+Y4UA(KFivY0f~wCvr^s_tgIjsDDfSg4X{z_5X5H)!~5ul=_!`cT*v&GkQ&bx+yXBpAPQ* z@*h+GKL@ImR{NhoO(v%PHSu;$`BIRcsxT}XRR7~X6!F+nr5cDtxyD~ixPVC z+($w_p;25F!Re2IQ5DH)4bif>V%k7Xyr{xEL)vZSH-jzyFrmwrXC#11&A=R?%Bx=6 zNsgq|J&y?8$Cj?+{uk#Y5wOhKCzAo`Bns5!cV_mdZ-3u)f)k<2n-Y@lI>`TJ~bCpNyO|yeXU!T%7_iuDCUX}xD zY1jt7^-F!01BSu7-?zIc*1m5IUg9`EH0u6HZs<+$>7V;^VaIiQle9N}9?p>ZHt&AD zM0??Ap49Z`>+QO#?>n1xYc)Ru`b?Iw#B_bf!5`GjhH2E1vVS)XkTb}J^Q*QZ4L1Ur z`m@QUx?5?yHi9rzIiQMa8#-+x7@MKVj5P>7Q7CG0>P`;M!k|e!e>#LWZyI)ENrCln zBMe8C3)O_Fc5t!8oxj$o0DBo5%H_csV7w3GdLi1v$23jJ726^aD%v6xg8@#cDlw{C z1&hRD08`gcIufgp(>|!^6OLVsS^E+~^_x-FRQ_<2VKUju{4j(ajDgf2AoZuFbp+;y z%3H`$SN+LIm|AJn{Yfv1paHol7*%#Pa93g&R3A-)jB#oZ*g_VmdTYF?C5UgTg-qD7 zl>w55Gp1n3%;Cn^Dp5EafD6wt*7-**q#{}~HSFoXq}Z>unt$jnY5wRgqyLTWQaeuj z-%8l4wmtulu>T+FF2fuq!DHjPg=YtKF1?TbDaHPa?vnF*`hTH?|GK-(cW>r#Atr2v zcN)JXYywdMdEN9(N(uskeNQ0z}F+&1LbYBpj6TA23r&$i$=x6i#Lfq~@=r^xvyQqBOor{3p_ zlG28RI0@A|GtrDR0Dq8c>^@K8wF`J`VDLuJMCKW7JhmK9+-ZxcdJ#Kc`AX!7NHP1P zwZ=sv#h&l2AAPkgj7M(Nzl^WI6@0Om19B%W^kO?c+dt>eQgryS^SC+c$L@st*B^UR zVMK~4I&G32EEQG#JpA(H>(8T&SErA^Z;v?~|JYxwI{x{6?<=uAr^*H>R9j#y8$^nk zx;~!@nYb|>aLo||cc5rR>Q)ER*0YhHl72?*Jamt}!&IXMUkhoAy7Bd6K$2>Cp~fL( zop%n^&G+)OGupxRL7c?fk1nwZ{!X#u-zY}B5JEE-o!s5QC%YMrkIJPlQtcEp+>9{j z&t-Vh-6`U=8EHzD$Mj0IOFWIpE6qHXvFcXJcZj&s~6F zz~^t|A-J0Ac^VChmXkafGI3R4U&k%_J2%%2DK*6v9EDAa#TN#Iszx9NyfMVHb~QGX zOm4~pNSWg%DGK72zyJ^~7<*f#_gb5-_x*`{n#v)6+pF9F?Gy>4bI(~NhT0?&5J!r^ z1=`f>CO_Zy7bwwzF)p=)=^M@A$iqqLaIK^yxK@M-1ixFUO(BRUL73p9L5o~)KEp_k zEH{WT1E3HB0>DO5OuyCwI+lM*9;w{|4-n1CE}PA#>GFK$K|T$Z$^8YIzt@ugZD{^) zE&0E-K3H(EM@co`C)6fcquCSvGZ$6DQTE5HfoMK?PfDl2(Ww zTcZNlMv2p$1dc@ak}%ixj46P4m%*jM>LC)q`;i~SUkRT@Qn5u1m9CQ2C@6qj{{F3C z#;gg%pP2362%fEWezpOBI-`!QJ@XR3F>;Lv&G%MS{|L=%FTNr7vl0V+g(^}m{|3#A zb|SBTLDSr=omS3+R`KUAXvVz@A^8oO*)YcL7IN8*04VCeQa{Zc6=$ouO| z(%_rtpM0hW5f2B2K{@DiFo$adhAo?joB#?J_0Q3i7JML&^rZt_LiLm~Hbv?}_^;Nl zyFM}RO8|9+g*_hY;14JS!^5*1zraI?;}ABg(Et#DdcEaysB~eQuuNwkg$w|P6acEf z`CKZv`4{-iB|H2qtmyE&uwRnTg+H24)>Y?!a)Y7eh)WxWHm|zfU-4|uQdHeUz z;TTG~FM5q6O!npTpJrBDiynzjg5w0L_xQ7$%t<+xG z4{TMlK_O`#x|W0IjL-eyvi|;O-?xyd^KHXrrHRisv;^^9t@FNy3!>4V3Z%nJ`0$Db zJb=OeW3%_K3H!gz-XHrs|J&^S<37*-yPCavg@g=rlu!|CG$a(52q+-5&S^nH7hQ(} zKw-G;Ss&oKY{(zdVCPuw9*r7`{nbjo`(^LR1spTwF%|K;v<(ntb|qt!+LN zQqvw>0L29lQC`4*n&&=$rSx!OidX|vtLlY=;YunV82dxjpZ6AlC=7u&{MB0R=k&6e zCo9$eBont#oc>O1_8hPSUHnzwzj?2vPdC59i&&uZ#S?R2W%(;cw^NO1~H5&Dw)uC)lsL=xQjX3%OkANYx=O6|%~Q zP$7Oh??(GUy)@xUiRaz4%L^K?k1jy7 zqCU%8LLFihkKf<*cB;}&0d0xJoT|g!6NO14ZN&tH?Z9$}nngVRG;yhrS=gx4 z)`{8jz09qoESeHmxlh9mHerEmG-*7JyI%t9U$3JZHS}M7BD=TjrR6g>GQ)kF>!PO8 zS(ULqL|2S;=C3npEShcf*oTpF*-}p5Oafs<0b8o_`;YD0m>St84Z@3j}pHr8?r>l|l#uas= z1fe3vXr>aC-}VlR8cO5*W#W#RQyH>Z3exOyVQSV%^ds-4?8*wynr0+4+1|+}jSfgQ zxf>@KS1v1nPULfdi6Vp}Sl|(YjBItb)otw0t_RPQ@>;7rW#MD2mz|eflb^7p#D#CI z>)tgw!yrNRbbj|7`q{kXHR4)l41bkUZ8xk}go5%ahvCt;dm063T{(Qs&lvbkHB#;l zyC%T(ds?XNk3)R>=)60=1KKQO?y_xV9qs_9Z<)U|r>AP^qAQ^MCa*wT_`w-QwF=?o z(&3^i>z1tg$-u<(jDGFK`mWbj)HjSr^fY)V8?h&@XpYh9Xzl74T)rBjWz*jMSXF~V z;)Z&l8u(S2aQTyRv!ro zz7Xsi=IMbrqe;Q}AT}*m?T2UH?z<+llSK#=P8_|N6%M80A5h&Ck_1E|lNOA zarV%}IgF9`i#ygw`X#PGscUi%WUp8s&g|Kr`_cd8(#!KVUtSsWP;p5Zt-sXel2|Q2 z!FGnh!|#68qdktD7}Fi?g^VpVmyB!nTRq;yr)lW8kMyGbcmi1PhVJOB9q>L@{gb|` zTimp#1v*{&rgk+iL_?E*;T4eGJ}1TA&a8|Y;Q~n!3WOlMD6dC=sa2(lLbgX)=oTq* zN^nGhP`<(GD07nf*l8)@@Xjl&F!wDrP3asCL7j{9Mm;=VLg}Hdui>3$w{)0VF9~}& zVbZ5S;gKSg3A7PuEf;yL-d42HqY^6PX{Mtc*CmUtiH13G8}Ph8jUQ5`vqXgRG!!%s zj-!+e^JD0wT+Rl6c`bT4o2NKc%t6imEneDiR)y#38SS5Qap~LLF}iR{+kwc4bRA~C z_V^Aa0mUoQZISG!`GsV-z=v_IgM#io^tS~xF7{66mt;sVT*_5Ser_BEqZsM6VChOA zW465FB@KyI48?b&rc|<=IN;$63~dIMWogTr@!vJ?U(2&_9_`^0k5CMrmqS=nNjG{} zQi=aaCunx59J6Y@ukc{wA&7D2YI(~(pp7&}2vf6^1PJO#Y-_4>-4`zph>BK93!c-I zJi}!(=^TM-otM`%!w+lHtDOj;JI7t88^4jj9j0#Wfp54F$+{V()kX$Gpr0lbF6FZH zkmctl3`-DioSZ@CFstd}=@FM4Pkme7&3>vjT!zdfIa`PymLe&h$k~ouly<^RXdPO>P;IzlJID&XchJgGMalt!r ztG=EK-&0&tI`$61D_Fr2Cm1=Tj%uvE$NS*?+f3vwjR)D)lA%smOOD*}(w(8YG(?4E zJfBhcaiTtt_>EJiQFdr$M;)%0kNNL%&kP69?=>`rteO5SG0Lu&lw-c5al58i?_j## z`1t@6{yy3`50lJo3!XB+r+by~T%60-W17`Dop@N^?xsKGVvUg;n}`i}=Dqqc z;5l{7kGqjq_*y6D<+`QD9&9GfduUoijQG(Qg%0fUE92 z4)fh{sNC7Q+n`MLRB7^xtOZ)qlbha6*FE_R!-;24dON1CDat%!Mz~mZXn%0fc5c_x zyTf(d%c!G7d^;M_1DN}#QD+otk5Ei>6rJ#u8s)5sxRqWe0o|@T<$XDeGaMR2!a8ak zN#7wAj@z9Pll|}KggDh*=7?AQ#Tg62ZTJ}Hc!Pfu3N%P5Whi-qQ-7Wmqe>aco1+vv zm8j;m=9)otaxw`C^giB9u=UAg8^dzqp-Q?&Pr_EVN;^nGP zd^A5ux{1+6N8Ndm5r|$iQH!5u$knAK|45^A2KX)v-AOu{%rr|@kUMq5{nAO9qk<B!F1-N)Qn*Q2v*QDB)?#_cw zTp&A}vqned4_!Ayd0(d3$n`HEqDgQfi%QRw1#o?RsJLCZpHRKJ3ZM4Y-aiFh%(g%) z`GFtbitG{kxi$$lwzJIl1rb-$KdB(x=Ef-f@7?d%!A6i3TA?UqrG8$`eZ8Gx0y+dG zHY?Rv?Hix+JF3TNd=v5%7{C7@KI5dtqLS0O2)8TmW?s>)oV4kYk4>|U6n(^nqKG`6 z&|!|e&Z^W-O3x;iRr7PV?)XRdQ)!0VA>Rq6_jlmSFBF;feOHFqff~R|IlxaTpW#-! zbvJ|GU>>Kr`~9#Sf2UA4c${atnfp5__C&x}UC~Ws&VT7FlxW42P615hx}E2T&hz`K zI#VeZaTvI(?%uw+wWmnW#0*cNd+HuQzRAYbdS{TIV~5cOE@p#razSkdb;h}oJ9$Vs zQ(1&KJZU(Qo*fYW*6Tuz5AC%3^%PdlvHK@;oS_uDvmTJWiVBR0yN&p^%LbK9BZ|`l zt;}@U4;P}66KomcFFg{d=A_6Q;0ouwXY&PBbN$VWEtcaIsqf8Jrdh^8~UQmx$_c>3RhR0wfP~LS4YL#XWHGp31uqZMyJ9SeyF@+~W z#+gh(#j5BA52$&cuHRa?33K(*+bG|~0KqtXg-i5_Y5H|6y2cvXBE)n%#TY86q;EuZ za}tuA1%4V>8=+!$J(%-6gg;@~Ny5<=UBkO|5TVwH&PG(xM)b|AzKX%_9NScKDGW!1 zz=yH1cTo>6wo(XQGj*7cwTipB14aW7@)Sau!wH#rE%u^W%>FS!a8@i-nt`3i6aAer z5mpmR8V-r7J*eq^AS+LKi63GJzwV*R%A$se$_Ar0613@*l&tBuJQ9QV1j;k}A=`8WYk(_clXyG)QY;fgM0HYy!X2^OT83{E$cGgSJd?5U zlwQ{KY@X}QCNZ$RH& zrZZQ#D{wtpxxtqnr5FRGl9HqmZ|_UXp#oy5dU26C*U>t~ykd=JNn@C-9#jK!2ARMq zX8~Dl`QXuAHA4D1R)8pRAX5vP%SgT3*tA7h=THtWUB7QA>B$D>LE%B zBh~iZCRc5_6ko99#NEC!?Ie54aAR(Q0zaa+-n%EYbzKZ?9W4Qj~`c^5=VYoLEK_4!wy%Y#jLFA$|yJsz+}%u(99KH;WJI4 z+D#E;>M4DaU&yF5x4r&iPK~PAV+OG>@xF*R@0m9ZQ5m>u{jyNzNJu>uebWx@H$)-Q zz?%;t3#Jf`hAcSm1|PACGvSZba=?UG3Z1^kw5_0Hdiu70-98K3aVni`ALG3UWR)PA zQUeV|#zZp<6v!motwMg#W9nO;rd-uwI^|rOji+Xcyo}Bo^ywY2guj(RkEby#KdoTb zfPBtDCfA`#2=(a$v*)uW0HaUy5!Hv|m+?aP7@=P9CMQWfd*Jo##3nM1v#=EJR|8N# z=6_Ba_mr*%`SSQsX+Ws@hdN{@P+Q>5(BDGUSyOHWK6-_rY>wc0<_%o|ojw`7U<@z^ zC{Yf5$)P8?U)n)=_{Vgcl&>gJ#TcY2XKC)4m(xy9Yp0qm@?Jt8UZTXFsqPs0nzpLR z*TP8J;KPl8Xv@jX#*)}t@&IK02J+OM78t5>!keMv2s-b|$t)jP@IAP`&qnPBL)w$WB`Q+MXO5J~#XIqWROcyxtE6yxN@3OcN}-K$IJIUK z(vm}+KQ8Cs`2xD1^rjOl^y}{e#mYEHUeJ%EzR-JV057?xi8N2=nlEZfCAVXc#~~Q> zBe~&e_Xo%zKj3Fk_CBA|EYdY(}+_<2XN zkR&{BY^Ra$R?6uxLif2Dy^- zs)rS2Z_Fk{(!J^P+DW&4R}OvT)378kU`_Ixfpcj83xs(0nk!(44%YvXyG=>HPg8z~ z>=bmCm79vKh>^{1OBKm_O168#ctMzB`W(9W8FTdN5WoCD8t3o;b})T<#H6E#Lg%IU zar60T(P2iN;V2u3o^>Q+y6#8r$f*%fD*6psK!iKW(QgI`E-j->+-yGjk@GGnWe$)=+-ggAu+C zv3HYA58@{e>5@P3L}sH3DuTRzoc9}`xBAgM{qa4v=>+qdplh~byl5eNPM>cS&jnxU zzG|xemUP<94>V4RMff*va} zGG8YC$@IH%R9_>R)`X$cBB<_)EbF-U!~KZDlb-=R#e(-6=&nA=Qj7DogdU{o-1*bA z(?##?dPWr1bDsP=HDjze?A%~9f2T{YIAD)7N1{gkus7?I@!(S{7 zlTe9kmc7^T*3#w|@Pn*O%=IhL0W<)idYP02be)w&^a` zLO97_&-ckwx#@S2t2}1jC0YGtF_5tcv}G_-C6BBTh5~88K>OK8m0vPVpfJC;r`F|c zv>|^s_LnhG#>TS|7Pjm`)II#i{L0mo(a)~&NXuueuh*fFyI<^C)_SO>=h#PSeo)9= zSl{}2UPJ@a;D>(iGw*h^rpei*+?;z;(aKxBC(sYt%lyh}wqlrDbVTz=r@Re-z5tGh zTLZZPIC64%-89VCd7e$gOV+QfRIQGJfiau(^79Y+>y!pIHjbIex$VuHv#DOWZNYTD z?UVP8eL)xuqW0|{)K`{6pHIuZ)}W*XYQG~gT4y&9WK-0WlbN6Sbk?An<~ZlI8PsB0 z%>sGNsL}e?1&(b#l3oS}R2637-Qvjp2X z5)Xm)L(I1f{Rk?YN0XFq{2(P-ejRphn~UuMxUG=O3c^`b#uri27g4<_RCE`a?nQj# z2v}#P`(mVhK=p9@)XWYzyayv0en-7OjUv~*h*r4|G1zXDJ4U8lF+vaC%9 ze0aCcLYzRD+(U&sl6RM{6r;~86&xmB*Gm{kLr{h8+&*JUN*Y4q|7!Jmy}(_hR=T8N zeS_2MBn6(a^-p0V1~c>f3|!1W{63AgP(t;XX{9lnWkymAt|giq-IBh|84S<`sk2)r zW#=oU;cc4e%OnH`ON8v%4X)l(Z?3w1+E>m9bA0ADDYdFxh{T8biiY{a8%%t$R)hZ+ zZ}%Av_xkn=e~dO5%;??dC3;P?jLzs?^cE2<2vH)Uo6(}TAbKZ=-lF$T5FtwRL=6cd zu}9Wg|FzbA?`J>zd5&Z6w_ZEW>zwQNy}q9s2Ye3Sq~`90i9#(Si7_q(e2+@fS;S8! zvY;RwJ|V@$Q;)-hXBRduLe{RWT`j|h-0tpvQH4_Ri*H}es9fO?Xfi!uph35Z0Z;MM zs=G0Hgs=}6E2Q%Qf&6UeReqe}_@e$?U4}T(N36Pl)z`gH>K_;YEM2LJ^qg+Dp1!Q` zygsD{oG2ygCZ3ifwhI&YLP?q^krS1+?m(OVi8%S`y!ryuN75rnHjqF=DSwZVh{2NA zH8f3iPQv1uWMA3lJ$@YXMby=mvFU0{J=R2DURnV#_LAJVLX&p;w8F}y9*#x&>#%d; zeTQU=?+xj!!gQttA#P)|Ey_9}(d z&KDSN@toAq$J)f?p(2?^*k?*+Tf(r^;V2JzujCkZ3y^i{x|xq1;2W@OE)fe7X#Skv zg}_*__-a#Hk%ikJCtb_DOg7=-q~t*kxe3;59ijD;nM7AZ?vb3APco|2MDXVd4T}t~ zxwdVJMYHE5%);AmYwdDia}fYeOzXst2u4UO`A#6p^sMoxlA zMDaYvd&#}sC*@Orvb{4WBl|3T7-RP3{nJ#CO^kijvgGV5-EVi?=^1upq=k=vUKDa_ zy?)=ChY72|soegOY*FJJ_B1Ys($ZtM_(!2tBpprmEb7!IJKYI4A>6CgOe^e%E`aX& z^9Nr)PJ70T@}hr{q^y(KboR7q!j{YRSkJ%1v8k(=Q{H4wb27^o>^L)nLSd?~<7-(6 z{strh0s=8j$&v}-dP{ZD{A3UA;Qh%HCY%WpbvQ5hBJUZ6?kJSe{wUG*Su-AZ-v z3G3!AzcmY%O(e~f0;flR5Jv@pY8q7^G)v825#5^C>nX=$U;g+blsu-1s=r4c8pF~v zdrP6KGo~%iAAil@>IgGMbhtmrmo6O~BpONQw;jz5g)&~8$jYBUeJFFW1r}$j6;cSG zj?s3cT_mg#*d8HZ8auYnp(+4>0TNbE)lUGxz;=^c%&$a^H+6EXS^*@{6L<0yuqf4b zFa&rzRo56&ZID6m;G_`hUb!bBYT0e>0$B>|9L+-LZ&SIPjYk zfy^flrdWdl<#^KKfFsiGRI_7MdYFTv+PVO?A$I<{F0&VBX#$i+u0Z5u4W2iZ944pm zlH&fupStnYDhlf%2cbh0U-H0I4YMkR+!nZfY~xRUVgSO@Hso@46XXoBQzY(lTLwfT zyy@#pvED^}>rK_!iSaDcFjGP>4oa7Ni_J1EwARy2d;9F0CJ``*{N8c~@71n`v0NAk zX3qkG>E@?A+AR0<$WTG|SgFc>V4``oH)$4A|Jq@fd#HMNK#)fDx-6=Y2fxd)Zrfps zd3wG?l&Q&V(Kj{`0~aTAwFGsyxgbG<1q;ikAC&3Wv+zF?x<311 zx{71e)U%&1(uOb#)1DENgS$?q-Q&vv0&3pGm#Er&SOQ2xTj>NjAN!XsYMg$7K3qOf zxXo?!;gqz#oYUF&LGi?3M|gu)uzZIc00`||C2RNMSJi)SNhU-LU;$y^$N>Vik*HK+ z&%Vb~3wLw_PF0)(dnf^rR^>HOLD8K+5SCOiZ{U+BMIHiy7mcV(Rw_VZTCa%JY7mY8 zx$5UAI)2fIHn5b{o=AW!Ua%laf)}Ibw&5gh6n@~{INf%-YaKp5+0I~o!d}AQRsMMX zX+06Xi-wm}y{ux#_k%-&Nnel%npV=~LZ`_aU#|A1c4hMNW%?>0^?4LJBq6Pu@5WqH zzN(r)?`N^63p^S7y83N&TREL1Of^Q^^M`+$e8l&uedhgmBKHTi%zo3neV&Q}xrz#3 zzEvhr7^YL6M94i_RE%H!;`yx_*WkrA@f)Su3|kE@`6r}B)3Z!WPKi}C98i zC9_+QH?SsO4&XRA*TZ^x;pZD7St_}5VXFR08IO|ex>B#}2>KmTv%jvuts7Rp@hqcV( zPuxf<@Vna^dS)Owp$i|B7pOFe) zU}0B(A?GH8;_N#$2GVOQ8k#B6a{`$H`14m;u2Sp^C!bem01b z4V{uLkCL5~(tTE?BQ>Q5BK>tAs`edHDLkQQ3s}rpA*@=S650hq_0I1Ew||htw+)ft zl+tEJ%9jXB>nPO02a0s$ROpa=tGy1+QSPirkI^Xu&G7nR} z&H+=zg!@5FBG8{iqBlxOfmc#M*`+Oa&9ovm6tpEzAabsf zh;u?-| zLu6-`CHM9VtA>cI65I(KF7YHQ^CTt2ITd zB}^ikJ)2tE)PiZ$+LqMb0Wbwt(SK};w^r`t8I>VLyd?wi8>{nckM@hGmkni$i;VV# zs)q>5^5m(FD3H8>y(Wqr1T-sAGL4*C4K4csrlwV(ZDnAZ>?#T zpqZ^w-Zu2A##F0*gRZb?BF&Tnu-X!_2m2K|Mkb*Jpw;HX&{*4GIm;YUjAXm^&%R)t z1i43IVe6EgYm?juUp>wMXEEVvXEfv6ns>$+6iO<4GBTB|s;w_QNT)EMv3>IfZ*U$aa4^7Iddy zMdRI(E~qxRVO00J`A9uMFjw1=nZspd*_lfLsL`#ha0jV14$=#D=-swcBL}mUcF(X! zmg1deeZmT4H3!`_AIVBWa%rHhY6z{2$@%&S#A4`R_jS{UDXuo6GzSDIVJREB1ECJ}k_O#*!<{%Q{N z)WI>vk;zfl7L~>&RPS=|^rICuW9g$&`^K-0tCI-sbqfT)$5;>@)^A4Y-*Ez_ZGsZb zLAM~rYgU*(WcNBGg4aBXlWmQ&6xn}zCgIz0hdqm-@XAQf63GBAsz{g=NWaTr!&~#8 zD#gCnY;1x$GO(*%EN@3<>?_CoT#R2c(eg0Sa-0`%1hPyn_5~ZSC@egA7|gQzKBfD8 zLZInL(gzQB)2H1ZqB!R(g{Bda(~)5&g4FUbnni^&)7qcBZ=ad1+APu5-a{hI_5fx> zUKB|&piy$7*5#$vT(hflvz2>>QA$W@9ePt*pyIam%=o_ZHgCkpenqCPqsJ z_%j*>*EOPorTgaVXO+qNpb=hF0k4JajJejHy3l=#*ABrO^-(X>)q9dZs+5@yxJ0eI zvl#nfaYS!`flCS4IfB1GM8VGDEo!~n2=Agz*6Vg z(zvwncXP|s9ZO72tAuzX>>}d}m(@s{RocsjFsi8&IV;c{h+up*LS?$o^J6Py2@xqq z#J6_rVo0jV1P&k})3lBh_>gjGOlcoWUWEUH9!Z;RO{4Rkxpb`o9rYiyrf0BWF3N>L zYz`gtSA17HP7Jl=)?;=)GF#l{8rR{`8=1_q$q!i%YqI8>TmL0t69%xMn+xK{u?>*4 ztr5{|fC+pOo#SE*um=o5?q$-`iy-Xvxhy!O7? zSn^u|w;@u_)y`mKT_wftZbL01#V)96qiNMzgJK!umz@p~1dkmo&9GS_5PYh*>9=R` zA!|*Kh=pa)&afX}o@yScGRt1}UYi|h;RDjgOSXNIN>6on7xdp+%l}J?YaL=H{I3+3?O!RboPVac7C4v!#ew84 zQcYWSN|EA5d!9aeBjc~&lHK^tw|=*lGq)9ba^xmDIqK@?_p<}Iu}iI7Ollu!R#|Ce zaNqXKsj9|_Ae2W}!^xVu#*9BacO#N-x%*b;^kFq3xrzSlAW1@(-6}%SfL&&^>DaW& z>BG9B!$J+bL$VPaifXo<9ap6cVbGxAGi!{;5N73_;#U}_Gk-kV-NO7TA`u5~ln@~< zyJ9^74D2ho);ng1wLsL<9c#$Vo|q9-@o;U%3&LEe z;+lB0&8Xd{kFJIjB*a>wO>PcL*PX?HGa;FTaHF_g@dnq?)o=?RQDR%1=)&?7&R6hq z*#dLzOG1%wid#5^@8PQ7c5WVtM=#rRBE~-~^FkTP+OojR#ou z>H0aO))EjE( zz1N)9>`Z`K_kvQzY&j8X?FL{Wq6*Ep+RM@keS=j;L*~{fC#kKn zMU?tkThjG2U(`s;=hO@hkK_tJTdz6-&Xz!3I=TG-6agQP40qh?!vvA?8Yj8g?Zzx9 zh0zA_(uIH#giXTDKDE&Q9ZvHWu9d2BY=8y2iZ8?15ibJAzJ;FPpuu3J=OAi zRy}y97h-i1rwQ|=<7c(H*H?TjNWL_*YOiFGForH&rzkVvy5lx^)>^5#a$(7g7Uxt zWI`&t>ikUuOM!1v=J%@NnD{7>JE||~u%k9HpD8cF-*xl(2fcmCdKuC3wJCh@y&jn| zo%8dHL-?Zr<+xLrqp9Z^SJuzOG;ZFfdf#U#v96UuqUwfNhj7`{K3}sSFQPFdzrG`u zsJA6xVitwq;2gkm5ULtG+f?`r5H_qfE5C)*1ijqeAQG;RuX##j6Vncj)5P6Xeb@+Ai|Ph&g-=Kc<{*)QUx`kx`@?79Hzw~sepc7 z*#t#BEJnF9F_AakrlBap4ISyql5O}dcYxYJ;p{O|&JQxP#bQMPa>z^vIZ19y^df}2 zli6!i_{1*bdGX`(6CG^^vXV++&{GKXdQgQ!M=^Sz2~0=2%PLP_7LUggzAf-RMUEsi zfm;VF7bJubkQ)rlRA)<4X>2!i)L}V7C9-MR$QxMny3$Fd@lvuS8Y2|gnWJM_i^xfA zND5w%tFG}quZI_cJklWkzh8%gR>GLc85fckSBVYkNSkr-Vq<3ie$=ah zNe*R_jC^YlOAxkD$w#T7WI?1J->V7^+i5(qdq?Y3Kd)4DqI1=~nc18Vu6DE(hM_VF zf|@JT8L1-pP&4ppu?!)%c7)&UAmhXwoFQvdsq;j8=9mXU?Vo5UBoc!ZVvArxNh0PM z6ao*YnWF&&hWyMEx}pUNn;U~LW-q;J=j3wYwcXaH8N>dhdNKaia>ePrc%5#q7|VAk zW==t9gr?wYC>ICY#iG$$344=AKR+uA48KC$rr@=0x3XV(*W!XL=HNk&vNq~AjtQ-Z zeA4cuF)a}RI_ZjFRKpn)!P85-4j-1~Dh<8Q|2e*;Xyd8M)GZCjiNcy#xOQIV)Onn& z;u#aUMD9EtVmQ~T-t&D4jN(wZ>YpQ8? z5@EZE*)`=j92MQ%H3zAXC2BQxTZ*HzYFjZptziErBCPg=%5Uu~^G_$Vi{ZuLjO!Nq z9V!jnRfJK{Zv7-5N(t;Otpt{7+=a#d0L@=R@ga0jYtH(&I=>bYc)LHwnE5b6o)E;9 zE~VEmP{^4V4cdRuqizK}ydDD>Qne#JB(#IPM!#>msm$70~)*=u^`?LWcuRsAt73j zK!KbD8v$o?Awy(#&)v!uRt6Jh@a7lJ68U<{GE^#=&`R>T>49E{$T1&Y&AghgH z;t+H{7VCYVAmD^uJMi0!1?h(Tv1^W57t|jZWxX1=LCUVAtR6zt^{RP4caAAO2-Wb> z6q@57REtRP_<%+4AK4VA^+ruv?}~f%w8aksVFSvB*u75$??)xkF4MnfO`4@2qHZi< z-OLPW!w~R@F#Z7JxYk1TJhW5Tu|c<%+czLHo>8^&5a^3-`gm)~x}$#hlgey2a|dRB zGB&PVpGrTIm5}=ImhXo^yU6g{AdkZrr_8m=9hdL;Eg)5I{b4lM5(s$3ez9c12G;I( zco|W=sx~c?{KPT4AJK8n_u!o}ftDcM$o*&6j(dipKhzRx9m-E)rX0kPRdCuHY&G|kguG1yyxTMj*nN^FXN!^^5%l0CoXD-EEB>-B-J<3`D?_-#qEeCLcedet6%R*1KopXI|Nf#I{dhgzY#2wHPmq6z3W~fYr8Au#@$3{U*6 zWdq^IWT)cL93v?m4!3?`k127ISuwv`=+B#LkI^|O-Sxn|d1yc&83&bo5+n$5Oi*4O zQjZaA)fGq)4Z~E1w2~?sbD=9WrGBMhd6SEa=F{xHg*?5hU?^o(7WSPc1J{7;XN6|lgteVz}-=t zklQ;H_}7CG`PL7Kp`jzzLGE1^&%Q#Jji4@R9x2+E(N=h`(nt&<@If^ZN*ES#g9gWx z6#3Y4mMREm_ki+23dJq9IVIE;)GvR^4r?vyAr@5$2-N}$_GAvh?vA<@q*a;5kZBbG zJtinJj+$JHh|Y~l1|TP)QLI4^S?%@4O33Ee!->zKHG#-k2fFPpPa5l(n6=QB5{9J` z=%mpL!5V*VCKPEGUUFT`tpvgwWcAX4#Kz%a zMPRIc333++!Lotj`I1d8u%&Q9?-kJo2!Nyqaiuw*UZg!)4UeZq@ghJJ$0{#iLQjvb zG0gx12{=$gQtCv;6R=8}R+hVyzz_}pIi%7@sxiniWD+-E((vvDL=_Kkr2@$VCDru@ z=%_@!FC^)tqF7Zi3hB1sm3>4eYZ)n!jP0Im_z;<|6W6gyFr1T28y_YDL=Fxr2F`>$ zMM2yirk1BkF_ntjkQl1I50_kfVKWdbydKm~#Szv_`n;6%oQkYP5n2qvkH&*mXhSlG z$Z5FJhOl^4lw&pX66cLA*`XOYQ|T&D0y<7bpNG`3C~XRzmy>JeiOq^Z%_MJqsQ@T3 zr6fN_)eJM8Wa253o1ncMVs@HwOyM{H20VI)(q*?YZn4!B7qugDVaoKtREg z%fl@nFr{p$`ayeC@+}+jo@u|D z#wZ|}!Y6mn!1Uh^Uqq~PX;8ecN;riM5(73k~RYPLXKeaW%`WIs@3F_hfG_wH6x zLyKWW4$zRXA*$QMbncf>U3`Ed6;$!OI1u{KiH5D|Aq&PL*=__R_?iZ~*-Y2&3yFjz zCoRH3-!e&|p)~mD99{S>Q=#@Ag|Y;+k{$hzm){&wwr>sR5^;*)rhmlE_WpKxaa_z> zAxn25{D&rxTzrbh?+H}*ERTGhUm|j$H7O5_Ao2Lj*{ivQFp{}+k}gNcWKJX_X>L`B z3s-7XsA%ldH>9xwevzHHEj1N7LNSOIUa>`rEow6Bci{%jcvL1NU+YWF)=C*=@t z=^Ns?BZ`@=Uw8N56--vNOVE@JzecR#-x0#&CCFH?Y7p9|kRN*xH3FJ2zpS69NXC$IPT6{jsqn#_@N-9)DlHWE2SkH_swgL~ zrTFROQQZ@++JjlU{$W%#8+!mCTN#>(r~!{yy@_EW*f8Y&go5M>SQrvOpOr&Dy{{$1 zNIv?6OJTxdeG5|QR!5c#9)&>j0;`mC-Z-$-Lr#!wISt-D`4DImS4}<8I@e|$ltVz# zrP|1OjL&3(-)d6$8cvW005h3DVxk(k*P3yIz-=Qm`-tqPeo)yF@{kWKRdKBaLCu19 z?9ELIa!+bS)=QHF+O+c8TAZlgeqc+o!mE&kW?v7tWSF!?LmH)#O7F(yE7NgeHY(R{;_CXrS zgwsI9RENJh#Id}YRdztSwu<|-{cb$I%k^=u8eKWWW{_O4__@>&G1CCG1KAa4yVX=D zT?L83C|!Vm7}p$i{*p=pk5rA?(2qJ&U$cJSQAY0{d`6=zgkSofRt$@W;-$eLSve!E zQjnJD5mKf>+~eVHI?607Xcrie9XGrigrDGpXGtfSGD`k#iJsDbbXR4Rwrh;jeGC8} z^fw!+d5l-nnNtMmA7L72>m4@>%0>wGNuXN{Cqbv9(C_NdtjL-dIb(c%HF01-MRhTT z3lzx%*~n!0<~)JbHF;?UjhP(!MQ4%g4^e)SEHgaFnLA{eFai1L)#xvN0H%7c4y~xJ z!sdl?{UpWB67Z^4lct-70LNvoXTW3CV73^D+RJId&&8BOik{V-5INkaRW{p^0Wo^Q z5$!3;HGEC!DLN)RPZ%Ch_MI{Yeu5SLVA4~fT<>Hv5fO8y&Q7p;yJ9xYEKp}wt}=jK z3M%q4_9*i$NABAOEL@-vRG+-H!U>wSuG7wE%1&|oF>$*r_`=?}%+kWRv+eshvB8cK#`DAElCUBeAr z#pS1mPQwVNAcUUNE7w^JV$0Ar^M-apc9x$Wn=T|TR>NORPLSrV{2)X%|AY_(jGkP5 zbnjXscP|>&7`m2x>9bYyUL*hhKI;Q~5TJAYnK|?WZ_e^h2=0esX+nCe&!w(|ljB$G zMDf-8KludtAoz~3O87j8F!e-=MtU3?sDaC`u^>|=dxwzyGJ}C37Q*KWsdj>VZSPHx z-i*p?0>CzH7(_a`=Mb{^Os*^uu!R=dN=nbCSBo3ycH{@YAb8o^E;`i!*c)*vNYv40 z+zw8V6-58q1%IZkw-W#!6Cm_VD9YD>%Fv_@@vPy>;`(UdxVbHOy0w;5f~=2>h9?QU z?(Hf3Vx_BM5Lw!j#Mm}6-o&zgH>tmYxWt{ky$5hx0HAYmFub||lBd5uS!c5}xWcvx zX)<#L3j})-WuW8m)9-w zf$i=ZkmeuDy8%?%)kh41UvSqPJpv#!mLxv|9$-%&sZN2<0yeM?>&aKB-)A1WYSXq9 z`<7mJez`-kf{hN+O$=PdLNctzOCSVyiJ^>#`+^x;@bq<@sa56puSso(S!-VOE7Yua zQ;Ss~Z{a5(_cIIKL+7=%Qs?avtknadQ_z~1k{0AG{f4q&Dz+-YNjA7H@M%fxS^d46u7ibDf*h zu|gL3>g~jHNZJa0^j*kU#xI!jS3lt&lg}YpT7)8RuB-+2s9uO%A9!Ko{h-8vi~<1K zVv{+tmH-k4*1<`1iVOE8wWlcWx?(i7sOtu(bEf}A_U&h1jDM%NwB`VA@J+Qe!TT1O z-$txpLP7Qi8>I;pyp}_OK$SVY!deGxaV24i%6kGOUD2A)E>L?075D00HxcBBG=bZ# zv0=MTO!v};lYU+nl9iVmqT>kirt=5p03Y48_VbX#l?sBM1dOZi+Y_+}mqi}`IbOq3 zLDN27v-c;P37!PR5COpBUbw4Fs?>)!F8ue~sc8Mdk42U5cO<3QFJzQmZ%s9O9m)(& z7mNr#AqG>8|FEK(5>TA-3mX=yOlnx}z!{}z(EUIkgyD97Npe-fYsa4vgw5;;z-I7c zpvpS{od7yd%zh#C{X=$$bq8JwJo&OXxERZA6^M>rU6JTRSWdA5cL2CGkw;99>%znU zp(A6rp8Hi!ysd=9p^iQ0L`+b?O3O5HzTT3gjtrn-Fsho;$&L`P<4lU=U$T>M%K^Id zf)nM{$%I%QU`YvqX~mJ6?M|ukyb0w}eZV~9Q4D^|THccYxFQBLEUuTx zH{xs{??zXBc_<|Kxo(shAY-Lfhye_o&I=M&dCc#}xcFiUfd91Vx*CglXLCZUAj zPN7%{h&wi{GwI!(I|*-1+{ZN=siaZq^f4JxdWJQ2Mcsyy`z497;4HkFbcRZG-I$!{ z`T`@1+%u!tlm$0t8*dvSb*VinLUMilQN zZ?V?vSr8+YNywmJ**SAKzX(w>`L4NVx}`cj!V=JgoyO$TM{hi83GQ&#Kg2)r;xV5W zRn`2+PVmhtmaN~$_Ryw=)LccN}Y`$@3@C+8~W4-(i7#D`w; zB)8)@rs^r^Go(?(9RKj`$vBJ*=X!;KblzPqzz%$nO~`z$>QM1ny?KK&d8olCgf&2A zy>J<;Uo9kMY4&<>^m;VOWP%JYjq%w~8;n?Eeq`~MHu^p=) zn=6$qaI4ex2H_=ileXXHBcM2lv~0sCsgIUAWSy`0b9#Cew6of_gWO25oRuIh% zVY+PC76xNeHtncARyx|}*@^+Q>TfsPCT)#*7#fd7R;HxX zIn)NWVwY0@(3A9_b*o!5RxBLm%{f-PhgK#2_j$#E$y~7$b`2Z%4=WTpF{xvp`t7h$ zTb|#|j){NzoYP)dF%`dQIbN?WI$OAToy_Ul{*6ItNP$Byyiv8vO~^IsWdnZc*znbf zU8{4xh_Adx2ndq(b;C~J#dg+X#36Iq9#U$`hR4swC1DNBX>@f@JCt2SJXPO8p57!? z%Z@(^j7@dT;HML-$m~G-0ZU2xQYS84zkpSDQ>DHy`c8%j%k~vfn?Al|)Sg-j#^d7q zCnY429r`Lqv!$vEs8>ASJZ&RFje|;QUR}J4+9F$(-nVXd5e`&H2}xpl}&ID{Y$} zsmty^sEc$SXL_vRz-cv!0aZ_FW{YQ(CdOC}*6b!{TMYzpci|cOetTZU2K~`M%V;&rAE`7CTf=}9OXLf*m!@3;uCDp9BsU$eDSd(nWT}|{P;-tE$z?rtkt(kL`F-lQr+Q+CThQ-IR@slg;?n+jFZ3v2Fij)U zGE9P{P_OT?f5O!YlOF*t0J*OJ!L2~qc04$JZi~`^X9xZ6bv8es#9BTp(w&>A`*migqC2fouXm}0(lYoXXznnE}G3H#H99%Dm#dp<}})!N0GFE)}!<)5d;B^c!*8A?y>g)dWXj`>YY( z`2_@ORE@G$*wE^OqZPe)kL#V;{ zvqwk}UD{H=vqc-mTv*WQ%j^IQ0P&M*bX6|^B zR`0amqmL(1GVQTL9$G zXqJ_M#*~o*;~=+==4%_TPM+LvxES4EqVRCVI)mOzCW#jFVdJsfqto`_ywrD{h-1zS z*r0ys^F3(w+0+DGggN9#!xUlszVA=P&I!;D(o?^4N~T|ORKTD6md~$NEUzVC(g4ru z(CZ&}FG8e&8{(Kea4ab}wmKX~9}aMUgQVbR5_iiN+YjBu@7!*PaC_T2*zx59Uh?CF zwNHdj@-koZ;o*TYF1a$boS35*by_nre6YLwZ@`b`WypGwxsu47#W-+XJY+UCptWQ9 zsGSZgkVjJP3MLfYB*qqv94aSc*&twU;}k2w6zgo_m`&jF2lj$?a3+BmdK-1TxxB-7r6@ROA4A_K!jgH1V8FezCT_1nNoJRy4O zea9#{qv$@MX`f+IU-@lX)6u@~5(?&{Z=}>A-jWHzNId={U@KL>1#6%367s$}o}qew zmS3TLs1jXp=mUb12L=5b@g0`53g{*1_LY*m2+EC&MCX)9q@>T8EYXH;U^$J~TOUt8 zTAANb*{@C61rZ|BsZ3|09AJmkJP%EuE`2O5z%Fe0^OI;;jkV-#5 zz&>N^@Ponj?$!QD-=R>29*;ZhqYH?`)jne$l@X8{@@T{)uP|)`F*>Tgd^;3wi)>bV zqcy9}n>dmV(lD(B-hya+f@!>qM$B9h$<2;HU-ryZjPazal%>HwWWMxW(x~cGTiHNe zBM8RDf=9#F33@I{dsUnE9fQH$Qh+$j9XsJ+*74Lyh0oFCEQ6W{y`b4I;|W&keeoJc z6}=5@(fd3TYY%03C4r-E<4e9{MkMMTCF9@a#%6g?SE0yTO~A9$H>du*Kd!WZKgs75 ziUFd63RIIu5|hC8XoA*JT+v7@wy2-8NP-wvJcEf}{&Gwi+JQsbGV3E29+RX(!R!zn z$TK;zunF=Q`S0j7kg>GIC89uNiY9j|*%?TgtmC4qqhf=}ajIjnHx*H$O|q-O>_x(I zAH-@9$8J9D>ou zy);~Qn^QlYm;eO_HO&Pb8M^h&VG{u91&ln|jI;@6C^0lQthM8W)V#9hPo$aE*c@iLAhj%w@EL z;Gfifn&kd6(@$3Hi;||+{CqFbBZwXzpKohIX@x4m29&s%#FV2ixEDFlmmdG&!+-!x zfFaQLUmZ#yQf{-s>c2abZpzI~M2c=;MwtAc9ZLA6WcsKZn34WYcjo^RX55|P`d7L6 z;ta)qg&Ci4%r@M>jJF!`f5VK+FDLnKV8%mwmcL;}K^5Ld^#8@Y$uyCA?&)Fe43@+<(j)P6i&n{}-6?w|SGkKu0XWwW1AEOUCVyej(|2rn=A6K1?AH+O_2b^L}I zDV!=?e$b;~#txdV;5Xg1xM)6XZWwXHhq?HqbF+O`ZRc#+zcFuIBZ?<$KMRk`MD26c zWPWhsc{}(w%vhe*@(bs0m~m;f1Ru?Zp^~N0=FO%7iMw*H_J7P9N>#+*qKgpMO}V*H z_sgnLRu+L)XJ^KMUY^I%%OBB11Bd!C%ABe3+9v5I%|a zC-G*DADE}SD~J|~oRjo85&grwahT|@f2Ui#FBUY`X8uXYW8OTOqrlk3yQ<{N_5S)o z*W8EoX)l6C-Uk=kIT1t+tQjgc!#qVL z!7V!#jaB>{F0D7^=C;M2uii*ux$P51j{JD4OguYsh%R!IaxbDQ{eB$G+}-xn=;d-|Fhg&JfW9a zBnV%_jF|%W3oU?h5dJ+WFX){w3@dpCh4&ZH07?b8O^Q`Ox^SKHqoHV`p|X_<|89Qt z!-_ajKL0;@dVz7&qt&?%d9vd4mvOqIas9;C5l9Xr2)f+7SB0u1{$J(hn)cp?=yG$s z{$5@`9Ze`Q7~XQ;Cv?C`W&c;Xd5zW>@&i2dyuYniFvKo1M#N90Mr9VtA9tP)!&M!W zqdAEa1>!JM4h(`HAhTKAIKd%p(=+eux~kUZ=$mZ& zE-abvLz5E$mtS822ut9&h{@pK0rDo15;?A~kw!jp!36uNKWT=k;0mXCgl7t;d^dd9 zALb3J7c&TCU2uns3t==oozfzrVeV6^{s1wPv3Oc^d#qH`dw3@M^0XLESf(9;n9ZX( zD^bxX(@hny2X^6Z6oGe7SF1D#>y?e56^d8p1lqvtgymWTj-t}##ekOM@r_1l`oP~`GVdE z{twvfe|yL;75;n3XZ=49d9OKqMBVR*<-cIFwaKzS^xe=4d3XyV}i^EWs`fIA^qJv zO=Z(3yvzruVtTnOjw-H&%g$WlLzLNXXh#W_J=K7Dmi*|UvOA-KzIv1)_-$+T_ zz-FWTTr^LOoc@H(FfZJP5Z~62d7LAA5yO#nXxI!dT<*q*ToT}rczOR#CdGH2KlR=H z8{AUm`wrq?Mr37uQ~^vblEu_$_*8EwbZqgKz5JHyKa5COQMpD#DHfb>@-fEO9^Y32 zarw?yLn&R)*Y+KmQCt~mLTbITEFHOA2}a|oEKf~uhJ54J&|;kKVsl{Liz zdlez!e({!#G8{X#uZ$4;d(sJ$%uTP1a1Q~K7z3z;Q_XBv9~IrU* zzzcyN!Es1W3QP&_D1yp>=#?)HjOEFsSuh2$a=1_6I?3dEZOo}KUjhN}^5iM4hNY2k zyEBjGgxflv_o9rC1i6=j@gs~Br{PQ}bJj6*?19EaJ3iWhPM<0>tsnhCEZXIClsPW{qX2BzdT9p1Q!WJ zl&0%BC#2cBD-t3E1nm6&nD=s1>Hz0plkucs)?nFSBF^;(z~(JcgH+T}C=iRyEezGo zrRi+{m8%${fU6CO4zBx}rFVw|FSzDf^T|ijS6X;VcoHyBaOnCgKv*O-m;}e2U7x$+ z_9yf=UJSv^?kwW_kp)yD5E_TP4C=7q8RsXXaX0#|M(H0z{z>>-Cl(BDJ8C6D$h5Ldqs4^lh)uFCJM+={T=cmOe1n{oEP zKjdk?RUA#KlzDaj3l?Hu+YYHdbLTKJtFt@7tRk!b%5awZ-X-Z*?Ua zybiD3$XKXyi!|T}XIe?q2S9AB{~No1zo>2W&9SfxH~rV{{~Kl3>iQF9CwP?m7s_sv z$^08-Uo-p@Ho`q1yy4l}REhtAvX_J%Y}}yi9qB%Qq3kBLDbqJ7`@SdI#H==Mq!&~F z3uUME-bV9miYOda45LD};tP5)OY`#+9_`w`EgZ*L~&kJX@$h2b;w?mtJREcZCH^i!IBQ8PuxZ#wvI z*vMz=g5;&QM&6$&J2$)hPVOD*$eXo)awjjYW0qd&rGjIrRxu!Mw@^=PaJQJ@-p~wt zve@snKVh$|%szv=` z_y#s&5j@$kJQ%mZ@3ZdD0(kwp0t$zMF&etA*?3#myK_kc<-OhTitk<7eINj*puwaZ;M*x zBVy4;Ut83pRo}XNU-sFq{Qfbf`MTSC*6Vsdc{uz6Bc3sw;P>u-aQ-PynXh>+!2x|r z(3IyLZ)AGA@^fVR@Llc{glwy=WaA?#8-i|S7*s$hF}Mo>BP8!bp_V{f{j=z^qJ!B z>>cx?e^f9&cK`|Vve1O6qmP9@cK(NeuuvH+?^rOeEsC@BVugXVBA}G*v}ztUT>dlEPfTPA>=DC21JVsbFAE$scs^ zpSyqEjgVI$v4-)-u|THw|8w^@9xF3!{Qqz)U^s^5iR9veM1ys(2n@ReZvDgD{6Dhm zFW&6>I*GQkbT^2xUTA$b+Jo06cvp_z^?7f5mj8L7b4u|ZU5Z_1J|bp2|NBBmt+C2H zN&nyv#5maZ8lM3@)U88BPcl~iJe_iO@jrGlJz8UJ?9z+@~b-Od@q0T3L|E$kE z$1`Ysv*q_t$LtJKYdl=}ZEn&vO|K6oZZo`5^4%~O%2G>hQW`tnCB6WShO|FDY@d7S zeO+Ag><`5F-&$SH02WQ_#M^5f+aDM6`htC+1&aUx9cfn37gVa5??WaKEQD+kz50{e zmoTY7TvgtC|Gv;c5B2|C=%79L8I8X^c+e|uiElffUchm<3e+C832wxzu>WJI|LMUS z+n1sdV?T0bI`eQYcDRBlH+0T$s7DGq*(W*od<7hS}SjuDQ#( ztALYHrNvui|1md*mCL&R)aw2Rdv6^U<^HvOUkn2ifXoctL#IfWsC0uvDcubsrIa8; zGlZmccM6D=S(uCGzNKV|FbrA#jl1=-TL`D!p$Eab!w_Xx5E%)4qnEeA;v$_>Y9}X8sr_>l7P1# zuZ8wpdO!9(5B@|-AD9RKY|wjx>%P*~qy}&O)|8InGlK1~#L-XR)9N0YIM;uAWO4XW zXGrqNn6#<=to0-#F!hs|OWV88&%Ew@r4jVGKdbTFnt~#m6D4_Ihj{U`*N;738^=}Q z^4+pgy12ljr?nvo_2wL4VJ)$61)%JexO>s3VH-Bz_LFGzo z|5>f~nNy$(!xOMO^A>Ql-!+{TMwO%-{$9~1!KP5>1;*9x|t_TLHEer@>y z?E1rgA@iT~`O)=@9qfMq)9M1+Kb*EWC`Kw{hf%TaAxdWT0K%eP-f7UQug(T3xuB&n3KhV*$jz zo!TanoBa5qt@=S85#S6h6+K2>a?T7Mc0^#3t0tUK|6nPgX>wbs~QD`X7^H>+T-I zfFJd%wXmcFa&it+W&~Cdd%3KPCu)(P`@PpM=Tm>e?IaSBUZul*GmE`9fki1JgdZy8 zta;b$u-A7;rt}@TYqcj9QX#j{6)H<>h$z8U(({P6VF-+2VOVAqo-Ms&tkxr(hC`ws zZ8;f!BzDgAA@gG%CH7dww`4$$7b&^CLWE`XgPd2hk@6&B2-UD6XhZVkGOGB1)o>)u z0@7OnPukH7_~whvqaQf2X*yB8zgYzFCLxzwC+?J-E~ey;#kzUv*D3g6i&q@f>t&}> z|8cJ$dX%QRv09INlkkej#{c*y&C?O1g~w5UGISuFc!jIL!)&0q=KDvTzy+S#{jc=; zgZF5X?4X=uEeLJ=IXg$PhA^W4#sT`v=Ko>y6RyVilJHc`srw2I&m29X_9NOpivG~% z&te~*kHJP)rEqZkQ56=9@{=k(Fx=RZ2lQTxLI0$@U3 z!G%XW)_bvM!a)Zp{%l+yOVrf*@6q;MocxT=Z6l>Dhtc-?R$4x(31ydj8bfx9KaL7H zk!_Ai#DL4#GRv1TIpwa@dEQrm9sS_|{ZfE4a4Hojxk`X@A17V9J_FF_Uv0GUi$f^?Spr}J3wCq<9JfD#NzpDgsi|; z-~29uH|4D7?$UKxv9|o{^6AH3LxafVf=+=R{z_G4gPdyx^~k3+e-6l(K+F4~R-CPy zZQnh7^|<>MSe~jw2k1?N zE*Qsm`K;Fd!1(D?=ksTO&oI?P0DxmZ5=|~5H7Wz%Dl zUHBR*!{lmGZ<`XvVSz3FBg1r(I6?F1FO)`Psc>%-sR zrOJ0EaLh?=GkYSTLZVW0+|6t=XJ)KIs%>Q4^P^z)3vy>$s}1DonbUbvBdoFv#Be`@ zw7i0<{Nu}-KU#g~pa5(vihYHOXMex)yUjmZMMkPyr9?M65iPe>j7qFh;nkWXm~EBN zkN>IFSJ(PyRpCFg`Pa_Y;nH~|h;r3Lw9eRfK0NMR z^|q;wRH*_LqTS(RPQr#l<0`MkuVV;>6kIu@vlcu(_TuOs1w5E#BT?x?(DW;UPK!)R zMtBoEk{j-`5hT@zpu=pSfS2kE5yp(J4!bn2e`L>tZ?ODx1}VsV#w8k;O@4hX z@8iYaA}foXYW3pw3_Er6M^XEhEYEy zReIEV?|)}K=P!%?FN^*!i~cW*{$E)1X3cSxa5a(E3!2UCN+@nP5u)tjNK^X)j0GgN z;%s^ie13b4eKChz?wArxgOeiUaEP-zu9uSO@ADkqChUB?YR>=#a5i`G$;fOo*K!4o zPdYAc+!oVy6_VFEqHT;ga?78-HZH=V&~tdq6Y6DSDg9YcP>(iXb)GfvsFU#X;hUrXR_WSy@aJ4sjmEw{g|M7 zqq6V{nT#>PBv{zV;$Np*U61hSOF%36#hD26_YnLYD0;%omB=!Fr8#%Pc z&&m+c38)7=LW4JAqK>xzZJ6z`+eLiUNCGK;PK*BJ;Lk;TKEsoFRQiddnIiV1 zKNs;4*n2&a?g2=a!*|={K>`c-ZtFTKbACbNg4T_iyFYd`e}A`aefwC*)Sx7!YS6~~ zx|i|V6pgfJeJ6T%?zv_ZVX!Tjm`R*8%xl2%CPa|b#(iTz`aIA@v^KLy9bqrvPAcoa z`RHbWvOS5=_jg;OF;nrFrO7di+OpqvG^uT0z0jq4eDp<2$dLrM^p^g+#}bLx$-3T) zclGDIEO+(gTSr# ztX+2-_YS(w!REqfH`=tFp*&eRv=D(|8onHvLx&zBjo_;xk7VbgxwwN1V2$3fi6nJ| zlbLjIrKiUVweGx(qaEQOgg8nV=fnRx%aWm=EZZeD-6045X`Uh3!;{o5@8wZV8X;yU zjY>PY7ZS?_VL9-XqDU=xWr|73qe@rbcGD`Ai#aO8x-s|CXG*g;%Ay$+89K}=r&tUp z%D*|f2`J}>3p~xSU?t`h%HTOLTB}BOel~EbnF8N!=|mSy8>pTlT~fL*OKC89_0zxyX3`?W|$yh8>NVW8gB5)Hz6?ua>UWBcAaj znzp628){U~gxv=GU)$%8?5-BMfBLLlsSz3!pRq`79Zz{1T*hf%_I&ylCHhtW>}WAA$;&v>B;?!Z)Q9Zw*zjK!ViWe+Us^cT)s?ovJX8)P(^~Uf zwSYGbaxxj`=bKFehBK4BxF?B`&;-0Wy0iRHK+Q^&Kx#2zJTO^x>!vR8>E_-jkG>FC z_5i<86TH91rNotvXfwYEfx2e3tqYJ;EWf%9M3q1P+j2;G?P^I7OzUpC=XNuA&cBjPE>= z6QYxDb(BI)cR{B(9T&tgn5akAELXtOOgM2O?u@b+F2bY_VThGRIwI zA_%1}%h3>^PYR?KlV_7&6(BvDZdj`|aW_XRXnhN1%2om&4tm5B5%Q7>?VjQ_3N5cZ zamChfEnEIz<^osv=CBLRg5ZOpGd#@oiAT#N9=^02Ya7z_)H12H2;;8fx?it(Huui# zmE?uQU2^hs#{w)RD_T3)p44ho2qwQLIMM}Ra00SscC)M&MIQ~ddYs2ro{EW6JJZJd-;jh70Bgz1_ER{ zvx7uTUeuzL`0T1eH5xSr%#(GKcOrg6GZ|65U$%Ok!$b-akcm27au--uVSQ=Mjfv{EJ;fxlgP|X9%Cx^j$7jb zjq>ZkWR3&s^7Y8<`{cAP(G`?QF4yg*uIZ(EGsZ|a_NmDcvTWmYQ7J- z8f3P#c`SrZ`8_hdj=RQhMZaX&@d&F&CIHp9T&Li=%r+wb}C}?)Pmbu??&z=bxV{NyrAe}1ond`&r5vngt{e1+>95%T(m=Bc-hq3}Fc)Y{Y zSMbQ!0y1yQnuDD`nq_w_5&?VB8cI~DBB~X}DXVl(8GzigDBih~$#Uca8_TTJ0!H{< z(*e5&(u4ad(@bP9m5C>6Ui$4Sg>v1Yl+H*$7 zfT^HZ|MjzBD(t3e%E%<#1*m=~dB7skhj9jAB$vV-5J!L;h}|~ovRM(WzKB!)5zH+Sz4ig|>ZA!Um?|tX7auyI zjviA-F-wP!W@4HettiAP#sPHCy1VIy!;++D*<6%@iwbE(2&5@Wx7Y`cK9>YUd-6qZ za>0!h!zwZ4z3Y*|L@`?J4DxV*mydSkjvyGl(>q5Vyo2iY@Q52Z=7j)XI!Ujee$zVnIJa^MFvK_q3%$vWRN%6=t5G+yTdMP zvlFgtBFcxUMinA%%3xNv@LO-7MU=m!YRfQ2PV&_bc>IP3&y}5>FH<)Kzu~ zF5(cDEMl*ZpCc{LWaQn6!LXa&YG)WjCHVU#5cj3r*-Y@2NMb)2V8WwFZSh`wiirh| za6g-12X7^a7YZhV!g{gd8cD!Z+ZL}RNqji*Wa>%)idP~DCJLAZvdU`)kp^aXOc8R} zugGhX^73D0_l9SMA&T`=*oo4RO-W9ov2Lpz(*Q7?oVkpL*_u+F7uwLoF z$BfISs8ceirnxi@NIHrrPSN+m%_gVu`Si$Y>?n{)%yFe0A5W_eRqRhQ?@3EoX26*; zIJq1*Unh>27001+=j<}_u0tX7|nX^sX#ahq(Ep9dmKJ$8^qb8Q_7(f3($z1ZEsUB^Q$g#dc6 zsiwtoQ(mJ03w&z1u02 zR3x1p7R}Ho7Pk$Ssw$@R#zzmIwn@D+2S-D~n z!Y&(uFT9}-kD8#y6lIdK$Ecc7S*+10jxx~!Kr}rS8bt!pFIQ!Bw(Bi&CNM2!7l`Tm z)Qcdmg_T>Q%h&M9)BY%*QPjum%9R763h(0b1UbyvH5Tbkx^?<$w)w&|LiK@c$<%hK za7mo|XmxLYrMw-C-q6b@u!3t;o?5e}&7KsPBb}i~oNpnYY>SUX)>_yV_?uyFj@E*y zI0FQQ6r0+?oVwA@x-w+V*j^pAbooS$OmOOrAR4f#oIvP%q{upYeqZ8iKoKs0I{-v$1Mmw9zchbO8$CoWwu2C-+W_ZIN)riziG!NvopysbK z(pP+OEM%iYWCOIpjWFRaECx4kSbe#{l;vx&e#1e#wR$AWF<9I=w^dxcm3RXdzux3Q zCa&@X?;9+R3~p2O4V$VdF(o?@gl!W_Ew;ea!RD(UT!hofo(scyRPeM1k%`ALaYQn8 zXe?g8`bJE<1{=aebvgIAyC63;SysJG3-LlbPy`FYQ5D7?_fkkV<4DUsezQeTqTc)tg-voS%U*=>|5yZWENN)w3&et%Q-h@?+g8#!ns;6ZSiA5t+4rf(M@SpC8n(p9^ z#Ze7vZKKjqd~JgdAH@yDAC>T-25*d7c=52mXZJ=@E?-}{m2 zHir=O6aYP?aGSj8Hl+^l1{qWy3q3{-6?@yK1VFdqbCDY)mH^Be2lHs`n``dxAOk!a zZ@M1uR-HID`Gw^AGZIbm0o*>zhd2FBXt>HBXQ=G`T})l!{X@4vu29NvZUSSJa)q}$A7 z;&RY9>>@Hem`dt0L3*x{A%6ch@OGG6pVSe@;F?UDwLenCJX#_@T4pg?acPuj^;%We zD9KJrT^^60hd`i3+2rf;C!DafJf6h2##3`)nFPA%FCYL@UX|`{zr1$-arXS#kvq)1 zsaN+Pyi?`xt+kdtGq20_kkxkUlrbKe zA{q~~ze}um|8)YZ@SeA`2+9T^H8r?*m9um_Wz4l?b`L z0lG{YdBcw3o>=#T1Hy#-*9UF(O$sNbU1<&Cj$yJtW-6GG& zxhK@ZX!0#-Q6W#{Hl9qD6onprO4d78)%%bVdT*v39|Ls27oP@aJAC5>T@OM5drZTb9>CHq9k^QYa<`?6+d zA5k#BIDwjf=QIBG`AzhL#}Y55+NNGVf6;~?=OViQo`sh2#7pf-Xw(U6wsS9$lhAL8 z&!kdb?sqdmPR@~r&cO@jkhkWny`f;}49T~-@?I#$YF>nRj@F7%eQu7y>Pg14d90Pt z)Lb3A)dElG0$;&`z^w(L7n9$fFI?DI5NV)dKKV+Dl~)l3mA>^#{>7`4-(HbfzEXHk zbEJV-(PmL2bW!f)qDk@M!&;j3@kJ>6qQO0C#kn~qt0l8rOBOGdtZv;9`?l2O4%ZNT z?G*Z&umLKUhKT6r-5CJ>OdtdQk~yIo8A0usSLQPHC#y>>fy(=DtuEPh-P48g5#Oz@ z$x@9p0TvnO@jO|DOfA(lpPGmGp$50-qc`3d2$1v!vE+&iem_yTU{bC%+Ddtceyq9m z{auTWx8R9_|C@AvTB6eyoNXVbEN=o_NmbgJwk@rgM?n_eiFakrml&{C585n`7oA$5 zsFxI5eQ=hn^!qt-c-G5Ci9J*IcaPhBA9t7E^dEnAD}s#eq~Y}kB{gvbZoaJc&E*OI zp={#0fUOA6Ywj=aSuPna0%30s#$I%TCklc@J9(ZAJz(PIV9e`Mc=y*Y#Y?YV-oJf& zU371kcPQwv9Y;#${fQXwP|6^=4QJ8DCdX44E+9RTmB& zrh?d)BK>(;?D(80iC6;rhT5w{nb-7~IPgRPjD3N4#V4MMZjEMC<*DXMGOiXS4c9h! zjADmuC|(uPE}(VreCHUKoPI=q)iIp&h00hK$eaLA6!uyj-5oonEOROGfvFjl*{aJ0 z578HGnZ23zMms^q6!*V4oWL)FJYVn>)T(~*cr>P_LCKN1_@*q9iAxQ2Uu%8;wprXCs&2& zo1}_P%emKId`VQK5mH1He7KDI29>g~rWTvER8jlh?L|YdmDBs7T+e0){2Kp_+deU5 z9Uobt$IL!=ei?I9{`|!w;S(wIdAlL3TVpxWa0pgM`5{o)*svP!FpyEd@!`xfPxFo8 zNz z%xq2J%o9APjoLm-02gT`Zj{FwhZsJxGW?uiZz~w*Wni9JfAMlcUHu%tXC>5s*6;DT z?HeHy!P_rh)*SS;^*%Z2PS4FsVI-j(bx;p7CnCPiJWNm*#w;4>3wJzs)ppoTOt*L+ zsH8A%n*2)f#`Dk1Ri}kzo}w=l1f}BNtaa-7-e&#aky|S5f2?M`AmQDSRL^-Ho@yoe zb@wz+x3|=~3HzH$CkFR(hF#9;%=FbthTu+cKK!EhP?_!1#)5Tjx52&4YoRrjRR&-7 zTFtAs&*Zj$ke>bA_LA(|UjOa76>B+bzo7ZsnPY@AylEP*hkcL`S}t0Wt|t)hsqSnU z6nkj=Tnnih=G4r4y4s>BQd@)S)G=W$gpTQ#b@f|lH&us>024CHDwfCjjp~~b_edxq zii}c=Vf?G`j|VT7`|s!^ALkbuS*4$XsShM$6HGWvQ=9rZn%82_EFOWeIHl1W*j-ei zK1)sVaZu=DWq8Pw0^KQI8vX?+jT6_fc)|wx5z_JmplX1B!lWdwNi@kXh(mEbfJ5P2 zIVPN1ODL2VWeqf&Oys1n@m!UX9)+er5hQ>KdZ;t8LRdj!l;Vp$w=$CJ2-T*IL5a)| zXn>_~S!gjF9bq%H^1!x5E?v-aNq^P*E~9ssQ(5nIjN(>rgh`!-;Hf@@9(`GM=T;&e zrIPj*A4eQzHIF1o3H;pq!IWF;QDP+>R4L{Bl)yTcZA6oEAyG_M*TGq5ZAD3El#4ni zo!2uq>tOhC)}bE7Je2vc0qBI3(FnLYd6)OE!12oTvyG9 zn3@t=+Tx;PiN#qzO)hmCETYgK;Hu1_QbJbJ=yYFI_ZBDQ(>hSQ)leT(&+5mzQRUWe zK=}b2GI_$TBI#+>4e09Ny}`!9V11}j2-SnSHLBXs5DKe|&K!GB8p%9tlvyt$c#bTk z`{q)eLvaf)QBiqMux!~G8(ARm*N$|bx#yEwq75?^=EyfY$;Z5oMadLt(liB#ygbu{Jd@RO zpkn<1HMdztP_9MRzjXl>-y)x`yY2Xq?`fJ1Woevw&Fkc~4g7LqReZ;jJ z)vh&f%<<}}bF3E>n@(mGWAl_Uw5b0YT`IX6vW(97(i8SuT`P`EkztckIaT%f$zH7I z(7LZbzeqp&{)S)YBsGy*N5~`c3#ZCx)-3&Xor;~#sG4CLG`93*QV8mgkLy2hQ4?9T zY}FGazyrJnCEzDhy~Xy+-+O9wv00@UE+b#8SF~%9hSV8teR+^$A}U}f`mtESD<$cm z>*DqJ8AReuF!3cc&_HI=T~6129*EsBh6wvgFyslYybnwrv}Q) z%Y8J}U$6=_I^-v65+M~3VHy=G(;gu|6Y(Y->Af6rFC5QM<*O7B8A<}z2oOkFM@8Y_ zP$Ib2FpbQ%hUgu(0-P@?8sDvW$zq;VI~HY1A3e%}#ArmfG>0}!Al<8kQb{A=^Pw(q zMk^D#`^yofIM@CZMD_%oZCiwjMx?Jytoth$p49W@6K5i`1YtYY3dJ#z>>p(21#RmF56i%>U1XxJe%(#K2K!t?p!uZF|5 zoa4*F;iHa1x$E)bEfF!;xI2)9p{jU-UBUoW!f+PPXgi_-a2y}z>5)Nj10mwX<*M08Na(+~#5iX?LY5PAR@+Y#@@hP#S}36~*iv2boIgdGc!Y&1&a zj!oZyZMF*(qsWh}B&p9t0?CMlc7eD}#<3Xlc^O*&11}gtmUh4;>G*CMiZrTyDO|xc zoot8%wgFR<<&kJl*Yt6}U<1#bOSczu=GDy5Hh^ixWw?~2UlDP)Fy*>j1Uu4~deSEY zrWh~y32rv5f4MOORt~#iBE0Dw;bw|eqrZC0H!QI#Q)>!QIG1_FHbrsX`vQMfZc0*Y zM^-|%u{8kbKFJcmWDBmi2m}a$1FwpyNop)S6 zcT6__u4(>sV1A!w?o$Mbd{6$TJ19v+{_6w9TyWk7XYb|gf+f=eaJ(m*B=G_Yv-t~X zAcYXoLSMd|`D}rrCnWJE&~KWB5V;~~4V~p&F2uVCIZ6WF`jNPy05d3nEjB|JNluc` z?iDe1GWawWlXAdV)`~ggO74|pajiwTgpu-2k@g_+!HJcZu~ba1RKl!ODyUQ@r&O-9 zRK5ns{%#N8fCM{NGjwf|gKslnybp$X+86}0x%FPI3;23Tm zBpdc5(|V6FOf!{ax}0n%%APUU2niT!aq@Ro__$G9&5`&9Ri@Ea26R?lB2?}^stjYS z3YV*jbh|8PS{0j9ML-fx@3mJYx$(yUnOFA8&)ATp1y#ejVKId2yftniLf-7>3O7#J z*{9Vm^JNulG@!`?ZK^qITb6~asduTa4&ul#t8LGzrL>0%FX3$;Kn;oNItjF(+cgwa zXS$t}f2Vdh2xl-;H^EpxHLBMnTR)yru67YF+)**(cFtu@%JOObBe{k}vxe6}4J$bf zYn=^mpEkUgqf*1weUQ7hWp?dz(6ybMYkQs7_Mcw+ws#G9W9r1v2sNjk#JZB?HX?5} zl0R!a@}&{Q)HKcr#h5pVdqdT2niy|3F+Xc!{nCUr7j0u~<}z>Q32x@gZ5FtRqQBX^ z^!e)j>c(HkK?pzs&_M+M!-R*GYJYF0`Tq}EnS zKgli(D_m1>bIr>GCXfKg<{K$5>RaXKFHM-5sPJW{s?pBN>RhY+1w#UkgYJgc->!jl zs_zKGsSz!ZP6Z+e#}3b5Ce#E$1Yz_e_d^8XyVlu@;KlmkZQ>z<@O=kMpJH?;Frm-i zdH$#Y><UAZ@TLXpk%NP56R zytEMl&fP8+x0ohq6{u86K1GD=^|&XyR46!rfODnm;g+i^J#nvCy7ZeBiv5IdDmkI3 zb@*&`ZhcYa1SarI9h8L=JjLYUeS;9)g^rJK(*=TH@vPo!_G$kEG}numW8Zc_I@Pz` zl}b?TZx7P71m3kBfNpI%=3pO)EeWmO*rx2+FW5;xAl2xZm#+_l>wve3!#$*O>v+@v z8Qrb0?66->j!8gSlm6$jrg8r+YfXat(C@N7Txb$sNY9b+U7H=bUU|Pz;TMoi4;PSwaqs@NJUi1-$+pI#-|% z`627f^0*(e4o-ormMMAm@sxun$Ey_}S#-6M1r1-TDhs92I&^v56e}BkgU$)OsZ%;+ zx3!eK*7}_;WbP>5M`+anWliQRHYyhAd_C_387S*B;6l^i$@+LQ|7+0Y0iknDFNMBC z=f0mVycr7rN3sS_jzO|$KdMl2TmXI6V`H!o6EAv}7>wQ-GG-A$!Les074C3fi_#iB ztJyDxo9+T#9*M`GtiOXOsYc5afZ5=uob81$@#B}Fhi9R-1N^x)cFQq+GWhO@+qD8cC)8wD4R#1;GV zoq|sZEWucgD6lq6Crb0?33Blv?hVVVu{Q-;?I~AY71@15-eCX%a?W!kzi(k{iNgX3 zs6C#6?SctP$wsC9k6lcsQG%o5FS|COnx{^DdT4*`*OTMlWF4_PD!3GEptW5OHtc&_ zkIx@`t?NiQ__n`&s5jzI zn~5@N;{!kIGgdjgh5N8R^BLCssL%Gab==o0#&iEp$OUalf1*eJC-vF$j_7an$nQdy z&fq4S7B&f|611}L1rcDe48Id){^R;A{a*6WuKiWWf7Is-6(J3vUEBS)n6UG=NO$(O zeV5x~{#kt{d`e+Ctj}1F^YTB~0iNC#AUoh+)aR9#JYtVr4(-~1E@ZAyf;|5ZAxnye zgG8CbfNq;;ekZHqNHMTJpE>h`9bmG%KnC~-5dR>`c)DsLKz2Y)M)nV)ObrvKnE+Uy zlhp)P%hJ!R(Q;ol`(4OI(E>)LC0E`4uxoz^SxXp*8)jKCdv+YlA|5^Mf5gnyvJqo8lAW>#xi1{Z`##)C$lKZ#%3~nZh-1!fMoH?r_4k02Ix*pyxr8{k5ctc0R z@(>f|ka_O&lLXNSP{_~J?4Pl8G$Uu}$1NxLDqG25|&T7CyG4m>Y@F;X6-uSgu`e$tvcSHOMK}MPkDxDt#M*R`STF z6cN2R*5hl}DX9rqc5Ktmdy;Y1y_^B2>EMs#<@feCC)AI9-I_l8eKYa9kgvE4)(4yq z5vYc%eEVFUJ~hJoFpcuo&Xcs4n!M|e&!6()d-*#%03a^{ZcLF(L4N`Lp+E)i5OM!g z1*^0>jr~c%-yz?c5oUUSDER5W>9GHcQf%%;$7fj-u)*@*blB%_@ZJ0!^p`7E2!j6p ziGqLcu)hpSpYMu4*kA{G$##c7lucJLLO_u}usr z6#k!e*ae@=lT~P4K_*nE20U(LD|Imqr^dgeSf%5-l%WY0;6D`ndxs5CM*smOVqieTRJi?6CLf`IFQH`19TM+*W@p#fYRnSC{%r0$?frvw~BE4~XB?wfqA4 z{-eVdaQXWV`>+&iOyd8p!(REV6bm|w8+QtR_@l!<7CHL%&4=3{{o`kc{f`wat=qrx zga3NSgnB2|ucKH4rzWq6NFGX%;JfRh5iR~0gna)}igQ~>Ce;fKwiGqa`w*D4>87`M z8m7T1D8i$8Yg+w0f3l#tZ#P0xpE7c*C>Fl$^91TT-l~{}>sNxE>Kcy|>@^CJoNiM}d9o_(LKHypHxLDer8SI3Pon(Dxms`44O459%5?!+ucH{Zoz1 zo9xa#oJ}9B%%(`(O{R~V(4aEbn9}*q*NK7a98%XVc;q?+NUE6nci&L9bp9$iV|(Qs ziuVkLy7iHhIX{G~f!e6`36LkI0pruDzbI1Kn5HWY=aLaeD5f>esquxLKM^O$p%+6z?k-Txx^1Zjlam>|X?*Gm%?_CHJnFN}Pii#hh} z?!wO*w(S}fowd!cI{j-X{n5+*HI)9pHo39fBr~D@H zaG^#>SW)<~@77cDo#zSC4HI04))RMwj$NQ~F7Bf=P;ez^*n?Zfeu5|WQs_iA_gl9S zg}UqSPDdx={{kk>d{P#m? zz^#wS-GcPK5W)JYE8q6Pq4boy@b_NU{$f9+Jc5J!{c9%u z%`^UgW+rVrXDQO#ej=rZJq!yWugv4vR<1)eNPtFCBoM|OtWBLDDCvg{?}_4-I10Z6 zml)x?*E2^%(V#OWqvVtd^zdZsmB8WL1Kqod;bDOCOQ`-rG&ZQv6U7~&(>(GH8r2u~ z7Iv<0rmeO0nl6;F8*#VeE?rAz?l?Ql_8wW^9!i>uc7o__g_QlTJ8VELup~_2v1$3 zfrOI!X^y@r7)yvB0;=0LGG%aHDNe9~TmtgQlE(-;=MbdMR33a(dI-pW^a67@>t((0z*fGF(7 z%(-CiYkE0co9j_NAxYdaTZ(;P&tT3 zD<^~w)Mv8d)pW(+d=Ux$I-b>qhF~gIjJ?Z~MJuya-A9?4CxT3~GQBVZ)_JEP6BEpKgn*fN%4*Gt zY<`wiRN?dzQHB2bq`}AjoqU=&VZXJO1@$7!i&c8ZwPbO*nna^1mBVNOR3%*^eYN`c zXV}Wtnv^ivlmmur+9JmC9QP84rps08sYi3}wH1;!d1;+)9!127_)M#FDFP{Va{QQd zp7Rh9gb;xdN)Vu9x$f53$9mH=a-oLXMZJTOhpt6#N$Q>AtP`0++~#|ObLQf-6676A zpO0g1E=ipQU%8Wbuc&RWJYXeUp`*YF2@+i zxc=!Sf7}3Sqn2UFt(SRn06uSLf7_m#yl052ob02b(&yD>1+#=+L7aRpR7Cx~6Z4r& zV74o7OmDem=)NE@%!K5``G{9~dp zF|U^R(DZkx?0524_w&CLdb>Fn<7Udz|^hU*57IE5u(972f zx7j`=*?iJ_owup!n|Ic8b0j!Rp^l34i*aHo7oHJk=8q}(1i#Fu_56&+#gzF#2TZPv zwO%7P>d29@lXrlk{zYlleg-x46bGCbp%fc|6X6cP+c-C=_B`(J331wK>c|wc3Fx0%G9Tf38%>QG`Vcp0Yo|}&t1J}cjm@2d3r$s` ztx)UnF7ojQT78x4BkIwzfwua3DMNCYrbLK0rwP~0G2Db2l@lV4tF^rQ-V?N{Sy>X5 zw6A33+xsGWuS!+ss@N!4Kc>o)srai!kT`7 z0kiH*eEM zhzie{M7wI%*(V5AXynmVr&wJWecZigT&Yh5pAR^j5-S#!$FH~o?z^OKwzP2IMyjf7 z9zQ~G3)C~F2*gilq1&4;a9=s`Fdu2BeWj>TD%zXRiLK;G&8La$If#0vR37uVgY+|8 zVwOjbq1`cRUiuujZ}6VuT$sdDZ@0Eby|ejNZC=zOCC}iY?buXhygT1w(KSwwyNt0Y z#b$E4!H?QsFffYk65|L7m}mCMH3}fOTZfS?H(hkWXyN)O)A{0jxQ(9SyC{;ro{Kl+ z>3y*zKP>(D24}}QWBxmyatt+p;}WC`^^9jQd{0r$H<84)ZD@miuzVsQkx4fu`R&cT z+$am_1ZDLyk=rc#b^D`t7Z{JdZ_&*5jICo_pbyp@T+lC1e5j-j82NLZUm8w9m%O^} z2$wCItI1rGbHGRia#CM3@Xr*drMcNQIxv4$MfN5G!q;g)@9B9xpMGOw4~~OTI(=2K zR7I3Az%VwC7FTNif~Kc}4MNwN)Cy)()<)_14Ti3(?Z`WbvuRHV>1(oG-AAdjISvwD zjLj?xvTOM+f|21V76MRL9=b*D$ZxUb+QekCWL%Ni-~yRgOa=#^-lmfYJkG%JOSoP?=7{0 zsI|D%Zq!|*pj{fx#ZrEVZF_SGdGpi$@|{O;3B6mXo^o+dMEAB;7(M4>2JlO=`n#Gs zNhReRcY0M$*9}Q4(M5}V<@IByA`x%7uu!!?-M!P1dq4EERbvR1$CW{aa8?!{?SY1` z1{ci~L+F#^-@k<;It6A6$c=3n0l)iuDq9UZX^n5*K<9(?c8;P>6**v3L*dUJ3U9}b zacso8y1in!EPyb~eXfqUxcHbX?84=NPkaQD3THH#ob)%NlKWd(R`Bx?>D{U=*LEz9 zhJI;L(EcbG$p7T>mDD^YhhRUk4|Z4jEd1 z|8h)mn0NF=Xi8wNt^Xrw)^s35OXZ^QV%Ul#D&3hr7Y59hTs*;xI>8&F;X|%g$!6lO z@!8ROb6bU)o&lOg)!~mP-C*GW_jY*MKGC^CMP1R}zUYmJ!IX=n-T~4+*TWSX!Zb%8vW@|g(&sn zNT;YUnW=D1ZxwF5AC&!q>%-u?QYh5x7`SLO)g)Q2mj|{y+V5a0T7f+xHda&6A+j+O zlWr4h%ZJMAi4(QM=dn8|Eya>F`4SrAWLlKY$D$M?BQfaEVjQ)_Co({Rj7mdA$;p24 zA~J0lohuSkwuACrxpcLNDvJlN`kL&VcSNrYYqSaK)+a_4dIs-dm^Xb~I?5uSKB7tk z(;9&4-k}o%QM7TU0UFGVn%IOQc*1&`9+j%93-x9|dgM;3C_m;l7BA(BcHm)k z4WPa1ETn=!2Nq?p%oCXLekj*;$2jF=dRju`)p{ilQAb^~hiRmfN+yAse5qtXF4Qc6 zn4(zPY`)a1B^ZHKR6ng@s7>ON&*)k>I%y6Loy?Bz;EsR7kzNEJqfJe1#hhEEvR69C zZoYqhG}>+HQOGF}7d?-O7+h!hh6nV!hj}WWZg9`lw5>)#i72sqHUkhHsR7@ z9;9sJQ`Q;KezCNR)o8umOKBB_A*A@p05tg;6-{R`bqWj2O$g1%zpeqV9L(e+vK6aB z6XoO{-=q4ls(MTdM?zD^#a_s2OCGnX%4WuR#59Y`A=EX!O!X{EgHR@vLzQMq0OnvX zc2#^T6DUQ=xEzHLqja-@<>tkyTxKo>)wu$SNT_Hr43De_fGQld&MKLW8S1q&QYW?| ztP_0%321AnTnEYl7a;~lPLG53=mqx5i)E>n0;pWqj2xnB67Nc&5eY&C{2m34*c4OS z2IGUuEo$I;a;zr^s5~unP7tbxAFbPg4k1)giY7tDav0aRTvw^;JO4k%?!qg|zj5>U zFw`&%aL>>^bVv*cf|NtU&>=^?y_ zVHo-aFANMu=#+L_(y%|SMD=RyE^F9Du^Nn0#736m*A=e#(uDB>BEA`>5zC4zO_3_?BN7x^_rpOy?HL7q%ZngZLjcinv9vtJ_tPAYfB>M8#QKFLYi)#173+C3|5mB5 ziKx-o!=H4K3-=KpGpP<*s+rUPe@Vdn(d_|y_}VhDG zrHZw^_HCm)hbFL;RA>j(fk+y;+Hn(F$m^h8H@_4o?$rLl0FFHM#eP)d-LQeWDm%!V zAtZ4}6nmQ=pa*eVGwP%cvH=mppN*x3)3bFs0)^U*Se${5~JTnVyPim9=`hZEUkU5F^=3*n_feWdYkfh@p{cd*|hd;FRjylI;<9hh`x= zK)e9?BLLCTj@gAgYb$0o8I-{3*s7~XsSkt%SxnAm-jJhL0Lkcc^Q^m~w1>#NbM=n$nFg^$os$|CLq?paAX#YH#i%l^s^h?>9k=d6^8|pMmzR-u`yp#HQn9@OB4##$mSM zd%L5Mpg~W(q;wdlI&f%&0q$bTcn4?r+<(eVx9__Y;gvD%GzjSkEXd~Co)-P zCb%cFHfPQIM+kMggl{^eV^K5IIx{g(0SboM?9F$73TF9dA!4y}_(A_J=ItyC~ zZ6gZv;-S!Yc|_iSW(IKPwD1;XaOM?prlaRdxT_Hg{0m{RA08Tn$e9AA$i+D4ocvYV zoP!d%V|YN`WYo)N@y=yf9B zh$>;qAA1?YF7r}+tQI!joke!OOzM_gsy*kmP1vZx5}o&vA55$eGj}uw>qgDevsCA- z2`0rV`5=jWZjivbGsSxnXyO?#vo=(I{NZ8YCyn54K4-qdHMnE>Y6>B!^krJ}1e|QF zptqx(t8QXPZtN9uU8eLFUE_#RBZ6l7GWEMrjY6X4q z67FxR?5}}jJpRbDl_6e(IC5-E@hi(2`eb+4PVR`~-Ai~8a)((85f=!7&p-mS38_dy zGdBQ1Ojx~gNzG3~lZz(+urs$Ys6hmgg0Qs*xHu4xjh*mE2Yz1^Uwjf3 z`dhMs zz&5v;=1Kn$O39pj{VOe?hHMHzEcx$v{XSM_8C2>huu!n`!;Az606%h08LSSC3rc^J zzR{`b1p!|`u0+-o1wm*60$JTml{YxPcUk`rf?>KXhka`(gD{%m&^Tg?Vk0N61@7(M%B@*1&w zLSFlF$l~;j174#IKwPG2t&Ib)$}GP>5zhqxiey|mOl(gd^aY{6hH1(1<-$xKz{MZH zN7G^C4`@82E|`N(Xvx2rX#RwyT=H*ygUtK@@x!Qyi=srLwzXGTn&Pd&Ua3%cg z*!Z0`vuq+nQhN&QyZnMDoHV>WYq$Uzu)ywB}DiXT$!Rmp=o3roq?RyJ)k>01zH2!&S7UUQbTC*Y+F;!L zo#hFI{nsb;Ss;Iouc818C_*Yl&gq`>fnFIxTCw<7@}q}I6PZM?@NbXrTH@k{;RFsH zJ!;@_hH7NpGj3>dn@W{qUp!fhm^IqkxuRmF1tngQYTikzU9_w*WqgvI8i($VWkYml zU9yCN$U`tp_NjSAikaF^TPZ3-?Mg(4`3KtNb=`O^I~@#RlzXJIRs zww~6}Q(iYW-{@kggypU`b1K&a#F{L2Omw$F1Mnp@y|x#ZPZuMGTj{t)pvhaJ(y)P4Sgu(f#+iKtw}B z1&6M-)N{?-mzS0xoWo>Sp3vlj0j;nKsizZB{yLo8P(tII5?--y9?5Sh-_UOas(w>Y zq%c;v50j3o@W4*DCF4*QJf_0$o=-8_A4!UrP!85>bO-?n`)Fq{y zcYmzH{TTKT^03Ol3{!U}%(IEz7)KmcGmcz7dgR1K_l?D($gLVlt*5G3=Tz9s-mOA! zqZsPG5?0Rb6VXwrlbp%1Xy(t40VRQZjAEe6osYb<&dqQnObqvIGt~+ZgG zP2^go9{be~G(K$&YwEk~S#DO@qJ1Y;NGMVjd43}cK(~Pi00Lo|k);qO^p@(O`@~1zh)c&gIv#f%QSdk`;)3E$zOpRT^mZj>j7yHhEpcNaQbrju<0=N5M5LXqk zYC6pTJV(u6@iyGi?;*$cr1JSEIAv@z&0wECJeI9*{*FRVcWirsKXmiq^%-ue=;&b3 z3x*6>kZ2Tx?_LZq9M1gnLRS6)?n9lAk6NCqRY)aVAO&9<|y&4vN29b(1O08XJTTP7a~%m-7ly z%WZc<^FK6-z5K383J#)tw3f+#eV}0^7Y0E-VS^xb3sax%RC>5)s@xu>SCw61 zp?h;UZ5rG7*8YHZq;_;jh)(sUBD$Cl+T+l$XFtO_IX7(EVLdmxMO^usK|&9g%q{#@lo_{jphqMwFMr?m`V5=s@5y3AZd6TgAn&~{}wYlY1ZnE6o~SULsmel7x~c|-V49dso~D>CaqxH z&LuyKAE^o{KZ`eCe!RnJE;CIzrD(IK@^gl*4u{kNg#N;`v#!w`Odhukwzsq61%LGp z|B|CXl-CUf8Of<1MKcJBmT#iMtr2+ncsZ#XmK22hA&U6j%qkh?aoq2V*{*bB_-FM* zwrJkxTYf?tkMk10TNr}g(6N}gjYTnI(Lj}v5C|F?#2k2~V`}6c3X~gR{k@T1IS$;b z(>OqTek~IX&Yk6zov4O_A&EvIt1p<{o)$0>avu#*0)b&5OY;1(Bp#jgd59wQWGGP> zQX2>cYMo|2vK}XpvOK9bmJ=#U|5|huc@SkO7mZyyHmT&RVi(*d^ZD)(#oF`n;ENSm zHPtZdU)Li#$&3#nJl|>#E+ZuNmlpdtzyF>5ooP;;rDST*WUKmpcK~YQ{?*az-BDY7 znJn4N!uM~D1Mg|>%TGtIQjx#IzaTWx+gqymc(0{xJk0T3{=H>SF?G)SHlhehxLl1` z>|r|~N*6r%=ibOoH&G`?eqwDap;n~JJI@G7m!N*E8`;^^Hb<)Qf9j2*^uVqy%frw=!`S{Gc8uTvc8B zd9rQ|mc}&SSlZ`6!(X>VV4bPCbQ1(jIqFNU&3xZbQ4d?H&6NIqe#^DSHg`l8r7<4Y zdAlSYc%~OnxS2m@kJNW^|H zPj+u4D3~KpL9MD^U|c~glvTh;F4(nwf(29~>PohzgVwi2GXFsv{kG%fI4?I@ z>V%Mc_P9#=SV@nB+fcl!f2_cUWTzhLUKws5v0|_|9%?R0Q6bvHDU&0nKWIN#GkLHH zsT_<_p3qYc+Yr-jAaQ1ZYk5!)-WkTCT2Tto_t1sK?x}uXWnM|ege7Ccx7lti<0YJk zhPle++g%mr>qDNe4$`xB4qT{U?PVI!xbgEmWs=J23d$bY!%;}yOw)AwF(FV{SC&&` zPz=`PGDnzZl#6BLfcyS?xsFacOhGb3;VQQD%SgHPh?7*3hzf3Jd`MX`M!6iL{1nqJ z9#V?N712&l{yf?M!8DkvH93t@=&HSsQEOq3tj->bc&FCR&RsvQ*0~*%kB5ueS4&)K zPI#)^BQ-AYd#qbQy#m$qAw+&KMm^-dEMJA%h!o;A&0Eq$75pA0YM!xVkXnPi>U$W< zAx3?+2s_VC)ZMO5m(V9=tg)z0lAF-Y8X2nK#4C6f{E^~~ni^3_7UNC*s+vK|=%?fY z6;`#n{@0s$6Q3F2OAPR2+c*(dP0*5t0DNLkpQLzIGjeakV zj?zVqtYl7dSz#nLHAudOerK0|&pzq@NYgZGGDoGdeT2E*WU6tSp}2M`T|RnXzcu0z z`PXTJR$}U!PFrA89R$&o(VfaQEi$^$%1_n+KLkqSTY&1QbH16u;RmHN)AWhjcK6ki z_b2gu%ZKjZ{$ik|Qq*CjgaE~&(6yO)@sPXSqYj0+xCQEu9Wjszs=s);CGP!9VOVZY zu{e*ki1D`!kH>yHrd0%w_r=c;f=4(#bz`U1JH<6l8m0;B!8h+$bz4w#2}1eW4y@ds zW2;U)3c7PWMhf=>wI*rwf?XAkoW>|&9Obim$S5A@CF5tjK#rFX^kcQ$WGt^nqs`AIi4|-vPZns^B)CcxccggmM zZ(-Yr$%j3wxhQ+^dQaOu?UDJjX_el^X7PcG(6OgxG&iBuMlsIBmJ>^*@rHS{`fCIUK>6JCGDJ$Q_i?re%na$jA2*!JF<}sUb#h z1~~qBP!QYlMm!{;_RP#O6Uk&`8Rf-fdWoc@Y~}%QO6qL&S{+D;5=uZT>i+`*zq?0w3h19&JKvj8ZUcw z5l@am#SY@ysi07Uy+gxfoz8UhTlH`UOHLSMEGMqF!_>;^BX-|(WyxgvW#;$*+3Yc+ zo%C9Zli6bITEidHkBwvVjM$Zzd70H_pUkl2ikvsz$7Z=kX7X|?*Y(Dm8lP6IRl}B6 z6YY(6IGWaDi^nNi_P-hLH4?`=O$>CH%gnBfI1_JuGw-IixRAEE)UfzAk@CgN;>X$~ z_P0rr;-`6glLe@$D-=xf*`R2iT+T!AD}Ie)zeQ*5IwPeSbJF?`=SYybC7}V0-^-1s zqLxHWrI5~iR}%<=<1ImrW%QCI+3g#FwTXk{F5);=2VyJAPIG%RD~dx)_NP|Vx!6YM z@`N0#&A`of(-sUR@HBn|V|Y}@j#bb!4hXr$gS;`|?UkS1z#Yt}+cHhGcO!ZJ1@nl|~uB-WxXHe#s^oN=EyQ$MqD+T01Sxey-b zn6tQl@>ygs)JeXCPBZqt0fgsCOzgmCUkO{qlX|ud8znCrX_sx)tnKD2D-5Rz-lVPO zlQ?qD1bT za~s69$PKcr39|MIV(jjui^rjL#d);(*y$tAMN-_qAEEz0uC}%S)4Tt%+OqkdtF63$ zUu`XMu>^{NDO;tQcWsrT#0(ETeDua9-=HOXp)GgH{s|?rwikPF+LjaEHyHD#Xa(BY6&7ppu!}B+4J$< zR@OHWNd!dWBp5N-b*m|G;6P=eYjBJEg1Nwj2mkJL;j?VvVjIs`n+l0e{D4B%{fniwOGX^>1>Z#dqDrTj3GpVw@#nZ5|*JmkAr5xvIrAK^STfXZ{40MTKW`u zZBu^mZPyq zPyabiDm>0PKQc*G-iwGh-4z^YnN81wN1k#jTEeO57gtoQ@KSgvf>pH#f>xQ9mfyp` z5=BaFv~Vc5?eH2i^z+EABr=P^ZpJ#1(TAbtWkquO>JfMTK$a6se-^FhRv~WscVb!} zHz#mnSw(prT<;klzU8*!WC7K?6_qRI%8AfwH-U>WH59a8pUBb+e@E8F5QR9Kyo&SS zGw~{?qi}sMC#kKnOPY41E$MP35IxpJv&Jk$#NHr)AVm*RAiW=RUA6Xvygx7K~k5nr0xwAXVG%%Q6{sme@*ZiLMq z^_FU`glvHlb)w?FPq8G{8KWa*26QV`#t7b9aNAKXiR9;0d^a}U~b!D;t zifH}X9KQTfk3yNj>DA9u^s@lv_)DaNiN`mdoIgqFy!?K8Kjx_MZj?fz8%Eei2szY_ zZrHFtV{jzDE|5ty+7Pp_iUI@#C&)aEs>c2v4U`Gsz-zbkT}n&T%kK|D@O*m9S0fq%=gfS?&;lONlj%1bh9R?0MY-f>499q`$56$~s7^KqqsX3C2 zFbbTkF>&lAln84?(Q8W8O@UX9=wgU_29UWUuT9%mkJBBk(X*>nQ#=7~nGwqME!N@1 z9R+L1QI<;%vy#+n+F$JZn#+BzNJgl?MolLZ6mZJ=2;Im;osokYi)Q%U4{BT_wrQiR zC&fyQSp5eh-waK2DI+oqtsrbc_`+qMq()MNuzCV-s<>=si6|bOZPEO>R?&&cSNCS+ za5}l(*HRdT%P0tGuG40wi9q3|u**^z5?<{HU-2NLq&$KVD-)@!q$jMgcETN>>8K=< zf)wIPkiy9#W|>r|r?advAYubS)+t@lB88ppVI-@kUaeC~CE4adTl1X3U~=O@|uFt%~yMaMYNUh=5%5L@=unOo$NarQd{)%JP(lJ{CMmXf4_HQ<=G=0bD3- z-V4_*$ey{1msR}6LMf5|2dSu3DgIe^H?3n0@{FTCQZJBzF7X?}iWYZtHtvMY-cu#g zf2`n9SwYUGB0XC#6!gozzdSm`*4c?}{V%u}F&}G@E$(0*qAlEg*Vx=|f7hZW#=DJq z^HfQJ?(U{)`u!y2K~ipAB>`7eFK^vR8n8;M#%V)!_N~_D9+6hCzn2L66B6YvTZ_WW zDeY2pX*lzixqhcg6K@SkG`v?o*@s#Je^)D!ZI*Cpc`!ip??^%j1Kf(cu}kOgQX+rv zr&v=TR=|rmzI-*KafwRKtYrAfiXLqn=;_S_$cW0U^ZL$1_Cji~4k|;^JAOa4sMx-R z#CvxdA+D?(s#+3VfD^xyG@mZ|Zt3oGLBx{S zAeiNg&q`v95U~O!0w06saHhay_kOsOe^(hslF46KJWu+Dfn%FBfGP2*zzX}yXL1|x@*4eycO5SA^hx#@sg35LlCZZ)fOorQ5vNb`^n6M3GvYp?#0$C248^}z<-4@J-zqRJ z$mlfez%&5=iiB!DVplIh!%R+DcT=MIeI?m zm~vZZv$403*rSqcO)({$f05t&DmCaO_xY>LQV&4r@v$Kb!7y9^&ne1I3+R1LVD-|% z8WvT+z~tp`B^!u7r??b@=NU@raJdfNb)OMK%-{39bNe}S^ZD)WNp~yoa1kC5NWn!T zp9}4=9BP)J>b z5{`NN@WXRzszQ7@3l)r$TR`P7mEtbP2c^hsxNqT%E#7AIQ#_h7NMtKA*n>3$zc>0$ zkXCg%Q?_LU{G7PND0+G`A|^jN1%#c3N3#b#Wq+bKQAY8BGo0)SUKfa+w`bVv@u0Jc zjol1wEn`|OgHId27OL~-Wr-y3Aqp>h%_~bBs{+)ao^;l#y*EOhEHNc-5T+L2-7Ew6 zgDkV`5!Uuks{-Tn%diJn0M8mp^nzk~i6fO8{-}gL00ogACY0v(`w5uQLF$&Z0h zovXY?3j3Yi;F^L^i3D&1a@r*33z$l}R*st^YJ^Vk3aIvxdLLvFGEEpTZSdeHph^U~ zRsjki$qj=6Ix5j0ixJ&4R2wRWA-(4Ovd<`FEuv5<_--i%PqBqM@tqsQqj@Rx31K2& z?C`K+;9Q7bB;fiqtukGTrCiJ!VW9dkTypcZ^-$d1t)M{~uCNyJSLNhaG!(6h@KOL8 zLj3Gsd@c2}e<1`DB7L6=8;h6+887gpM25v>4r?hdA+EhAO)0<{VEs8-c zh^`kjpvZel$-d00nWj1^WHX4A@?@z4dxyPDs`Ctcq3mH=B-kx`kqACs4w(>%ptHhe z+?2y(cyg3*U{Xanggusco9Peov*72j!}7bMBQ&jpsVExWcjdAtFA#;SZ!TJL__^WI z%|ur(#Keu#$gH4u7C92`(|zH|lZ2<@)g?aIB$RoP2Zv|>Re_SvK;OE)lHDX8$%EnW z#Dh@8RBZWtyz&7fO1SrdgbxKVvNq51AOD5g%o6`vup9f)r`sidUt9@TZP+9L-PJaF!_!B7oorPK%71T}YL}M6Y9ah#3lWVm;%j5M>1H{z zEc9h&jlKbq8>W=i>TPcyQPFf&hXh^4=v&Mt^u90=S2-jES=(n|vx)Itn<=M1gjY_N zA$2P_tf~#0%eo67=8=f3ctUPfcMuBWy-aI|K@@EiiV71GRo0r{=+=+k?$rX{ac>e5 zU=WCvL~vc{EusNa1L*yL--m!2F<_h9!~fh&>*HM z$t!3r^_xCx@ZzaInYSGrjjZM13;^XS!?Q7UurbSbu`I;f2E3mmfqay?0Wth#CH(Wp zdJ3GBv(JQ7#%xx1fNIwUihS5O051rvQPO#5&(;WBVB7PWy!#3Pcr#C3BiJh6dJB?A zOx2_Mp8FiiVhn9FE`Ez9E&#z;jDgtb_q>}egh8yOtthBP z=>8KkM*Qiq5jx3mp7@!{%!;XcEni;)q(2me|w&{@k@@u1JFd2ZQ1UQK=cX+%?jJNBXGC1 z;Do-L;+(h(#CkoPk&TMtn17a!hGqwLNfi`SoOMrMYEjKdI5_GmCiNuJw9}5sXHQq% z9VePHrZ2H?;g;<+IqcNx%_vxb@BZ@qzy+m~Eqa9Mm06`=aD?B^tDcW$piF^B85FYq z@`cD=Wv8eDG_eF9{3hBZ?5iNfwc(H?oM=_@(U;6e?fqmpUHCeK^f>h{rbG8w>+RAJ zQ;tJbGoAkGfJ0>qyX=s3eGTtr$Abi@^UZm`8bc*uJxnQ7`buhqjAe+{p5mIj!*ZsZ zp$hSEoFTwJjOPPw;i^g^pH!XN$nOU77p8+R#+ki;3mA^Gk$f5Ss~QyxB}zvEIeBAk zQb23W7&*%@;rXo$LY-p??}34G<3|sIpou<277UW9{J3aE3!93p|Q1(iZ86D=%AF)WB0{(cu_ZK^X(R@^gSJl?w z^TT=mkQ3&hJZsgY8D;_Sr0mTcY@!y%5eukg%?cfrQjaKl)OG`MgwY!uHe*Bg7)i#o zXQ(%!n$j~2EJPkiBCza&G7dD+5;~mhN1E@QVk#nH#?svlQ}0mBrJD!q%*#~=a7w{N zvf|FNySVbZn(zpHkbtYD+Ka^x+7o3W@~!wf^CyXm5pakt2y}ZN5ZSTxK5-!v)dU+| zB;BmrG(`$K16KUiX%kdj8bFQ)+<6?XI7Z3;63AH|C)8OkRs%dX!ap1-mu@V8-R2QE zY>5B@VG8)YtT=>ebku%y-h|Sb!7K9y!1>D@I7{q7%b@sain>o++}_txO;W=$O=oNRyGcbpNgH^N^}Pm$-Z|0aoS`2!FKj9*-Ta_d>8 zbSoLv7`c(m^4YEXs8RU%nBDG{EAQTVX$D{6&s+Ng5U!L;lQ7~PmAed2PhM}4Ce$AP z5fBmppbp4t^df{L?Lvx9dJ-O}K`5xPBvT`MpM>*QCKFQ}Am9SjIs#ui`V*yhq6?Zq z$Q^4Yk#61(7+ENb3tI$osg=H(+QaX9dHc4{XZIH%%H4C;sRbe5iAe#`XFKux1VNU7 z{*5zquD!n-1e-vScqEn-YQSaa(nt6<31ta=GzeT>mpokCDybn>XNIHG#Ga2175=g_ z)G !lcu85-^2SuITKZ)1KD&Wj&{T$ez%J2)g!Ljc0>?`NxAwk8+k9tmA`-f)pn zUy=-b61}#6ZvzC7P*)v+&yT!GK-4Z9%sO90-M_H097zOi19gY~=Jev}knl;mHj<8f z-S}={=?XL(Z-o&-{0I*jL>rY7Oz=2dL zuK+S_5;-!uc7c6qX5wgoc-LvNZ@+>PkgH- z%_$n!2W({mjQ-Dtz^?$xZptw-(8xMdtIW^DTjI|3S9IJx(7`XdoBX`ab&9|@T~n`s z^mWFV2f#$;U!>Dl-@Cu3UjaERAdvRG={#8r5Q2$) zc=~p=MVLkFAiEk**tDj)QU>+SKylQST14u%qwjuLN+3p;RRd6Yhy z{O4COMP;SI?fgLAWbwodMsyv*nSf#0B9$Yg-u^y9@& zq6aYA9~hwPK-4rQUdzAO(f$MISQ-cGHq5&8hzC0^h@h_Ei|jKyK=E*VJp%*eIxag8H? z8wpvcL*ErC3mmlGI!jilw<@V418N$MuBCRgB?0X_kz)l{Z6#duz|Q@!BzbiTVKzHF zDPb7h>jq+lB+kTR?yB^I8CAY_p**T~taI+AurBt>zC_S98K`M_t4#hq!8Z0mOwE_4 z!a_$4SXHZxk!pw70W*Ff^PEGZ6_LsXZ+RIRQXn-|kqWN4oM%`h%? z$(7aG+gey%>X3$nQXiT!(wcJ}a-651R9%^5)fj6}#3c32%hX*jF}A4-Z>y0FGgh69 z4GNb1V1^bHAx)t?F!RW;P-nzgfSU2sS$qZK}tAaF?2Q_yEh zr;0uQ{h}}PG%B3u4Gz}nV66y0&@PvR^+wgc>PWq1n>uBr$uNXHKxM0V4R25_B$y$V zLKoxKmaNq};d>%z(y3-C#2oOPvxK>T+&Mh1=F3(PCUQZgm3*lAG<}|qb|JQNvP^!E z1z~#L3KOzLDgZo1z_`4i*wb951^U9#yADsx@ugdgJ(iAp?|QkzWA>Gok3DR@uhu1- z?0_G=QuX1l4CwIQ4!f3qv3tBKMGitglR4V)aXN_ZOrm^FIS;|=rrg4 zuBD9XVYE4p(ly??*v;_8Ua8rkT%`;=Aj9W9bCuvU z*h}6aEg;UFu6ESf&P8ESeL39iX;q{jMsS_CZHj~OshjuJ zo-1B#CiJ7)EIkNaNqLs#ofef9Qtp~wq)jSvWs z^L5)6^?EPoIp&l#{SYfPW6kGl?VPv?<2Jm0q#epBB9^9aFVAS4re!M_4a29oVe-|9 zQ)IQLU%^w7{-qNiu3yBiyQ9)L5OY6Mgl+Gdv|S%68GWdhiu1hm@kJRTs#9O(Y`$Do zA@U91cMqEgpMJJbx+Ft|yD~YF4tk}f`ll4-nv?F-cbE?J3&r7;TG^p@9JImKYvTdc z@uuuQY4u-55s}`GTR_e6Rocz@xGWHS4$}I_xF!dpj8Miof%glHt-*MV+6vq!QHycA zjhb2gt5_> zJm*x0L}yt2>|NIt4!6Q#_c+eV(+1+!K1KusTz-BN5KTS|;r}u!K?ByO-gS+*&!bNE z%B_WEFwui7*up($s{o>zE1`OA9n-$R!l%jPYvpefqV4IcdrsX093phe-CP4Bux zk5OOhI>9^EpWKjKB0C-Zp`P0)7CQWYby)%ol>Z86rv15U2Ff=ha zTCFGd#XhMF;<9!~ojrbBIWIVCOlKj4FUCp8@R^V@j~y+3->ymH1!Hba=K!X~MuZDG zvLjZg^gdbW#*68>>|Kz(l#;fll4bwnPXQ-l)PdTeL$B&~LD&wZX5gnxYNM&wP*lj* zn+eKF+$Thh4r^F@bYe4z9hmsDa6JP`=gEcc&;-?`#u+o%%|F>ZOJa^JnN_D ziHB^_!rj?gzZ;G}2VbnnjG^9dtMx=TqeCDSBr|Awxa`XVu`esJ>vOVvb3J%);h5%o z95L9D%4D|fXx4V_+i}yq?q;s}L>_;Le@LBUpPW#8@10e-*g(9oyYlXvr6T^28)oAN zqW(n47%XW6>S!gNa+{+)QvOr3eDayRj5@an8W-$>%&$(R0+137Jm0Tqc5YIa5ac9;@hh@Seud8C|S%)npc0fXd$ zN^yFV@quFr1vA@sQtE)WWa3>ck>DA)jb_lCeL#5?`&gaGKz%UBxA;k@5<_sP9dVgm z(cpH5j5)o+?M;yQwUV1iq$>qN=aN*UY`}>k$(mtkEuG(6pGZDNSB z%wVh>V2jnf3eA`;e?FgRojv$U1RJQ(5W3yyz72IDq6&8!_T$5kWRJdS_Gz=9S9(@7 z9BnWE+G9wZcmyX4YsfZIUaOqkKWOZq7ssBaVoT|@)#O2uDZDh4a;+R1Dr7vSI!@D{ zvprH-fX%-gX@y`4JvihPFpLZ%MGBEw{-Zwr{izBHOv#uydj&Nok*ay@OWRTYb|nvM zUaPNh(zUpf-1j_UAT|n0>LT}j7_b)p$bDSZ)&rGS9@vVi{s<|EQ4HmiebTi%Fc(-( zn!1VzUz$xXtmcx`JZ2!c0ah1^?W*E1jvA>MSD%=Vm4}Fgr%MX|?w^RMZ^o>GwyQft$r!Ow2xF}em3)Cb#g`HoD2^Cq`?1SMXIvnaVSGGE${#rDm zQ460rYSl-{8x!cvMmT+DXeUZ2!`Sb+_A)neFu#NlbHLa6X4&rRhO6k}yXkT&a&a>< zUu4fdY18Ff(-k(-@aS|lm);agsH;@y-kpg09HS@klC`=@ zPl_M5s8b>(8c8t_W7j?>!>BLEuP-mHub?qco}jDfJg?&?_Jv{MKF9cRrl#^SR*fgr zlPxu|Re!BaM(qzw^qanLl)f$_L|l4-lzt8`YhLeI$HW8kFpS-h14-n5dj)>@kRK!e zZ754$b=*G0m?PibsNs?H2iqzQ+u|&wM*mN~`6oNYUrj%>O)vEBKX90cwVO!3xsE~7 zK%5w{&qx-Xj^84ugPvlVxem^5OW^WXK<*c>d`K zl1K4$AFGxO62GY`X*^0j##-$VKXU$Z;qBxpH>e&jzg4deaZ?Ej}sb1=j6A64JwIjY6C|5^3*;+k*z zN2a;+9{MktW-V)4;2)Xhl##9ZzhoLA75-<8|6BP!+gRowndVaWU(7|0*AnW(|Bz{% zKZZU#`sC~zOZO=}_8hF4U63i%68(UkJ^uB@-rLO@*}v_qUl0G6OmkaJV%F-sg-Z8B zMon`%JNycUygIZnF-zB{!m%UmH_|O9Qr6X6%&+jE_Xw3YYjB@JDepgDfJfu`kxBW%)_#c^uQ^&66KfBoh$&YjW=|pzTOuzrDoBeAy zO^nlq+YHOuYM&Sb0uBn3l^XUG(+q5_Z=uq|a%JQvg^^M(*(;8GUBiy29ap=R{}2|H>8*bW{#Q49b)yV=OISpvNZpq2 zcOD|#l=HR!t9(zbiWy#Z7Uua!rV;MVsu^czL$x_MF$eVXJ&#%Yblc75dNTezxBB`^ zKAYSa!?T&B`*IEFx5(qmIY&1zdioaNA(}U`GfQ14iV=?bAnAS~`rpg<_EUq63%aGp z_kt$c%{~jeFPf!r6&X2u*OYy^Io?|8`LMDz>q&g_V-w3&a|@MEIqp2S3P5iw z?LxOKnkeT^WObK5_OGMiG5H}9nIT#rej#(w)Llg*Ev-d263 z+)5bAf1C%i_O|;OJ~%aJo4wnt!U*mO8j2h-v|wUN+?eV3@mUod_;WAm!Kd$s@9m(! zj=c#VkszpsDJvD>7kUu&F#1PwLD0erBwor~B$4r1FwJ-# zffytW$7}QL3uMI@f5q#L z#}AU-L}0lLfpe@%zY0xRLO-X(d8BsC?fyn(fLHU0x50P;713Znzpsua92<;oy&4cc z;ih?_UP!k%Jb16QPHO_Yf)2eJZ0{EevCWPZ@l~l)nTHF;Ulk$=Rfpy1F5*SO1gz8} z!%EAph&Y8Yo?uy3jFK>4l4}?e1AqZ6h@i5W-LUyGgwz9##}2{4S3=#_u~r1YFXU0C za2QQqUP?{TDEl-wV5kmGtU*}|M7)5UIDr%UDAZQ66Vz+aiFhDXUn*W=#S?=87_toK zj&>NYqd5mfNFJWp(7{B7t9AA^AJ@a_M;dLENeajMc8DCVDPMz#+}lGxoEIb0Qvtue zz5$Vxp$Re5!E~3>JyKK-jTo$N5x4%S<&cyj%79vT?RU-Rw zgCJH#_j!0QhNH8ots)v`KIQ6mn7Pd5%M$U4a!v2ix!hlurD&20?Fh_#0o}JU6^#nr zw9)wziEriFu@(BIn1u@CZxziLdAi!sg@|M;$hsx z*h`O7kD1i=Dr|y$vL(vLsYn>isYZReUN(PUV1CD3V(Jn$ZybZzkGZTh|99R* zP_8}THr+mMNc?a77;=avT)m}!XDt1{coR=eB4?dcKJ7Nk|Hj^Xa5cHEZMz`}AqmA0 z0@9`T5a zoCpiZ)yNX|>+pPzxnch=^d{CP_Oo&JFP~1FbEWtmTs9QHeEbb-uh`Qsr0uy^?A{sQ zz{8_YkFp^S^m= zF6^fqLCrl*huLO{&*@+2$9U~8KK{*9#8L&@BAk4uQkiOG7O+*!YuLY4V(JdYiKeJO zP$|oHRa((b*I6U-SG&!USC&?Tw@cHUe%Ppg>P@zJPUMaMrZ>qX83puXXFn%~^fjrw z0iL3VoBZR4`mrf74#R4q(Iw&9+ZGAyqLuVY3AZAK`-R%8XZM6VM~IiK!>&^}S=xcmOht3$z(>W3}$zv##6 zS`$jy6yiC}jmP)($0Dbe1f3K&)PB{E0iL2q@~0HqjN~|B?-i3w4!l1+!IO!6T!}pH z@p1K)D@PoEZnn6_pgia8BK|bv=`7A5^ZHL4`E46xODTFw9r)sF^;XY5ZZ+L7R)4j9CXL9^e#4mT4TwlG5|_5GUC8CHs@J`{wn~D3i;_6;cyIPboBz9I z`3su@H8yX)eE_Q4f9S_<2P8Oqs|h6xHhurRIU;(_zvZf=)`!Xa#|n9tKvhrhpvn{qs4u&A=?u!o*^%6X$nJ4q!l!h<5(N?#Dn7pwm5mng z;+P~v+q-@oj5FC06L=Jkj4@W8#j(d(a!mn)4~%9ysfo_a2K4#a!@!eRqWk_N{nIUm z)3dwD{*(3-kFbHOzU#azl2AD_rhc8doH59{0`*3i+)HWwON1A$k7(5&fBE5oxB3qmcPs>jmwy#}G z&xVo9=tS6LJNxFqk4bN;rKsZx(0cz0sS4$T3*qfxD5vM<*$VLs-c*q^?*}W+l5J;% zGa9hfiL(b2TQ<6l5NPpKo`eNfCcILb|YE`d2aKe=7bDpR!LpycwS3%>NSqe~2kB^u@Di z+ycauZHC83(+wJVTfnxYdr^z?#lnBH# zNKc@9hw2UE7`~AIS~C7a>Ol~{)&qzsw;COf>>G!}vQO{x8t0DELHHJ5VX*TY=HJZsU2r)RUwuO7`Dqnz0^%z` z>LI|RxK$+05Pz8biCe`fx93^T=P9{XU8n%1JTKEfJ^H+Y)pTr*C-d}=3qudZ zl&5`>!muJekJ5_4CXc#CoiDHd(B|0O{s_k18GkAGhc*WrR3vn0usalAafWx6%_W#}{=imz(1!Vh8fNy){m$%!i>AkQZ~@1L)doDC`WKlj~#U1p@U`>PQ{yUb#|P zB)>{w0|G4?l0_)Whv97^`#uqXUkioR1_h|FrdBV#_yD~~bj17$fzofWMtw);^msN9 z|9vauoq{&&pfg%mF z&`t8B(8I2&j>s$>-VB_z2J9))+qehd|6W)`t z3s85u+eM-I;C+%Vy-4!hW`TN7WxmQ!(}7EG3qMX)oc%D?|EKt`(^Geo03Xs6&|^=D z;>qBnBE|8Z-6h@-+9zBiQBs!tBmS$Xm$K}vka68Z9Me0D|NCVo+u(mE{+moy8?}x9 zgPVa-M-Wqn&{D0WP845kpnGrrZyoFZB=9dC27X<@Nz8mGCe@E@Ed<1*dgMQR!@^R& zzxjsu>3;PMpSHjMgX#djVap7I*V{kDq~QSv$V1<-ZDiPwW4+B*bIAeuKYYV} z-c-N&hDFXez8kV6_=c^mcm6omA-z!=zxjrl+h^BC(>Gb0&j&oS6fe-oYCk`<`~27^ z_+&&+(3_rxJimjA%8)~<^IvUfgb^rX*WEh@y7pf_-8T>$f-hQyf|zLv!d}K@Sp>MS z!`UJ*OF;gob*f}nMR}+`@cTh^{)=yTPU{abDg1_j%=@jU*KjcYQjK4_#s15&{;SW> z#HkAK83xj-Fk43R^XTX0i#vJR6jp8Iy4_+h%JuluvHn}&$HUY!Q-Adtex}Wr?=R-F zPK3p+l<1?u__7SAPMw(JhQ-e-2+`{LCuH0?1Pb#vP9nIe3W~>hdfE>JY6R-R#DuDf zjbB|&fNywQrMmCXX9&2O++4^-fMdPlEDc*rkG2NjGZb(wuWWGuTult(X|(k#tW~NV zTjBRKIxl?>M|O1#Hwkxl&CUweFP)b9LJ8{hTov5uh`ay|4Ja_}<# z;WPZz)zqXgSTE;zA{q2@?`CLk@axGRV$w6I{eYPC;gA=R>#oB7j4E&4{;a0{!+6`# zlYfXw>)tI`93JZo$=;bWHZ||nA7loky%TqCd-eXI=g2o&LGQbdRF|zODRVecrw;58 zkN*h#v9dLBT;M*Kf)SiS+GKmK>$aNC=+-oHkF@q@2#0IK8P{^o+kK@n1sZ4?!IDMY~-%r$^3 z+`RK7vM7u#HQEup3WG;K1A*uj9r|_T$wM7L5Cn;R{|@e0_%jd~tA^|WlZK_p2lKRy z_0W4GsXf-i%o_^9_U#y(NNx(*XSON_`6Qr=&=N6GOtHHe98V=M!$I|Mg@&`A!quWY zQa@N#LVQkv%}g|*hMk?>a$r!bdgHjs$r46V(kQn2)RQf$Pnk6Ihoy6eldepbuzuJQ zlhlm(Bk=E~#MVxTqJd-mdJS1}A_WD9DKmmip6zOBDNoe1cSzl=}i0XI;&kWs10 z-IdMWm&l@!8Nv@0x~Mi`b{O~_QjYnIO;~;23wie0(d@KswGGloXi#%Q>~5N-oyeTv zX8~1-q)(F-FEyY-TU_$FaWYDko0y0#5b}z2iWc-KX1;bqABO{99Cc<(w+UlXSk=6I z))C#uCQpVb`ljXmfDrsXU!CtUF$`s(lLw+F;eX7aQ`LUh^3jXq3T%$n0~irsAI=p7 z>M`{d$d}?!IHx}QuLA$)T7%N?a;eNo!DKT0^7=fl>^J_aui5@Wb&y*yuxYsdMQ~&l zKy^%}1k&1fNbCMj1b#YMW2tzcQeloqXs9(NO1S;chTFf5fQ~r-ube;rdW;V#V4zp= z5gJ_-K2%lziSuW(j~*K6D^oc*t~>wA`PcgTfuR3?&H3Y|X4uko^zNyg08@JxBv-hh zx!r$pdtPSc70l0b?LzXQfu7FrSHta}f_^7v!_sWyhk<^{DeDvuC!b{`UEo&g&!B(a zm-~FWnUdY1y{}4z6+ryuzIp)S5BZ7s4}*Ro7*|ag{*%4WH6^|C{ZBt8fcPna8=(_G z{7tu;MgRl-@c0kIt-F7w$ojTg4}IWI1ATM_QCy_1V$fTpzVU0*uZG)0RkiiT;~$*= zrytWZ=mr&lfY^+}0Y7Gfaa=!3)a=f0hTCpVe#VP!Vd|f{( zG4B`y1bw$3H-hNpE02tqEwWFbK7~mx2Y)zt^&0~EA{fV$mMxyZUoB(>1by=f1aGP_ z;Kyv17H=zPmdg;`fCiGw1fByR{ubcJlyND?jJ@0T;{bmNu)G^;#d+&(+YgAZ5?8m$ zdI<5UyLX-dNqcXC^I-YSblyee+nI|hJq2|wdVf|`zg26B<*7VGKrbS+fS`ZXSpDnZ z)P-}u6g>Kk^J^nOAjbhDH3jzcxD;TZ-#Q&D<|zThbchOP-|w7%w2vxqCxVTl2(IFv zht-LQtDo9GOE!ZpixT^~9?`_Qh$$b>x> z9f^m|r9Cy|>5D&sCqUS3NCY*@N2((GDe=JrOyowRRhs>@Tw5S^i^k#eMvgQYAbG$p zthUNhEE|r3bCW9wXvnNP)h|YJ!xXCZa`pJj?6kSfpN|_03{ilo6|v3_)XmQ;L&F=O z!XaZA<4A94I9ypYjv>=g{A~^~vHWC(+LWu=+uX&;^3!c&QyzhD^FDh=3HGW@_jj++ z7B*N*yQlT|h{I4XS~jH5=yv)49`qCM?(g8(6t;LZzw;k*egZsG_c(f-7%j73gi5MZ z;#HrCH`_0!pQ=3nm!SVIZ-jrw`EQyO@o71SpH8yD)X&b>JOSp<**`%^Nr4gsp}&1V z|C7jm#OT*zj~G25Fg{}RM~wbI$LLMzXb_RKeT!UT6JRiP?+WIrS-H{aB2DTY6jrC>q*PAgd5 z5zU7WpNf8dp;ku%lc(w|-_{hT#$`}MUa+Bff^kMwD}z8=K&K?}XA<+L16psPjDuGA z%(x(tZmu|E?aC?AaSaxqnEYG&Wd&Q27k+<;bxrcvfw1zO;>G#@{t)|*Nz7j#V*eJS zj~b^;UFg>4OA)c&6t$vI;3M@;@OsvnNzh#oQd6cRF!=Mj_tE0YJtQq7`H zeDfb`bPK1&Zap(qT}RRbVINYZRHf8GtCuQ(3H2$2mkz1dweMYr*aB~+AiUuc75Z66 znJ8i=4DH!;-H5te)RG!ZO`y&h^DUFd*-WaLfOC!64dp9~CYT$J<&6o!xowCf)KbUt z%@NA<zcm+P8t?Am@;$NBk@Sm9ckEkAKenR1Re2zh>0vnYhpDqUyuN|yV ze9o&<;RFPp`0GfH$|1bTP?cTjJEmPt!`krkwYG6SD(4@Lw`TvKwymv?*+zER%j?R`b-C__r zAr_T0*(fwe8+fT)JVH7SjkC3$#c1$3Jd;r(#&~eTVUo>inL55rY{y@4iH|+VFvQ72 zJv>Pz<3^`zBRMEoKz&T%)E_81@97KH_xyypt`HmT13W!e(& z!Ir|!cbVt6kk1iEZr$(?pIiTpkPi&vH2Or9(D%WG-TU&SKmNz$J%Vkz_tYhSB+KC&ZOTwQ3-Ctk zGA{MYn(Afs*6N90hcbVAqrLU=zL2S2aY*Hmjd`=D;r1-8q(@yR^{16(wW#=^wjczdW(Hj!fj3%Xr{_LY4%bt&wv9%E%iZPz_FuDhPc(c!=yoSt!PD)wX z1x9K4a-|N5JXjLJS4|Pg&PRLYqmDmo^hcXWm?NCrq=PFXBUb3v$0u=gV;tmsUA?OJ z;+2>8@)G3UkESPiuBWZZOMQ0c(tP`l?~!Ucvng6c`#gVH3T%O^O#P zTzuM1E0wO~DvfHzOlK^XWOI~8Gs-h`m{rWO=o8DnJGu%e7K95dpIE6BK4h*)d zke%=KoT_JmH`?Q3Yo_%yOGxL`ZuI&KyIMNgQ~@o4pl7AixtZ3Q6-t+0wele@r5DOD za0=YIpWGPTJ{Dd>)k(Y80vWYF>Hb(F~+3dB?Hgs0%{ z9I-6M?#xi6sL~s&#!%l$iH}y1o&#{ox4gp%yG18X5M`cGo6x5FdRg9CE#CD5`@Gzn zKtv&${aEk1y`3sjwqT1AxEdhgC`I;-9mGUV32LxrPZAdG9?dSqip$QM$SZG>x0tFk z)e&oR!>U>bO#NEjI8c;`U)!xJe% zaq*SN1iug2qnP@SsxhL#=*?+&aDb1xmem5!R5ESb`gZGinuj8;gMJ(K1!JF{6E3}b zuTh~I8kmsz3}c;e><%z`)4u7k^b#fZQYgHWX5D$`R)J=4dD5}VFLx(PIs)IS=z&AD z>o5HB?cL(V8^pb(^j7Z=%3jxu$5wo_3!UN(j^RF2R#_j8-ki`cP2Lqb_@?mSZsvD0 zo`^gCw@cs$qbJ*IKfbwom4@mvg}5A41|@Z?dRe)UHywO7MaS1ChZOKHlRut5g+8eU z^5W>p_C+Fn`l8(WL&$Ol`3)KohA($w&hE1#4{B&rO_+D0 zQu0=DJehs*?MbHv<=D|)n~gjI&QU4`anEntm%SW9i5MAJ85*>w3u}voa?6#h-8P}U zTSd)b$g#lGx30)Py_-zwmHx2>J?QEPj}>^iLNykv(lKQl2}P6y#+sCauqI8_^sRH78NSaZ+ z&=?Qp`BB!voa>CBS&vKCZYEo>D5Z)8eO6zux6!Z<^wk ziKJc;QWZ7~*G${zNDxwwXTE8Qu}|a5<6VWyQhKX*J{!ckG^@3qraq7j+n2C@?GVe! z%$~xOT$b^cd0d1xeImD|Nr0SNl}~|$#xdR*>moiZkzGX-ihPF2jIB(6^qyF&vyWNV zbap?>-!60tBh~mZO5T|yDa^#6j9!pmVGnrEP1F>P*oruDesL;i(4&~uqQYd<7;}yu z;SgDA%K8-x2T@$+qp2G_N3Nioe>&=tk}gy}q4P{`h-QYBFy(ozHO-<7oj{JEByI90 znL?hX_%mnXj1aEMV~Z1wGYeIfr3U(mzg3fkk1wdut36^7Z>H)pgL%tqLuRT zWED1E)Pr%A4^gic?xRHVWyN~Nvls)L!4_==Egg>;Jm&Sv%jLWn666o&x`V>rgeOXz ze=Tw4-qLF&1I=_yg39PAIvS}YW^RfYQ<+!Xs+VaMH-=I;4lrdqkvVrM=$xa=k0m=d z+s(?+ISzBs`M%fIP$}NTF?DijPa$}f5>e-U?s3&i?Q8H@2%Ym?Fm2Yk$!|r!uHUi1 zrcLYGo#?NvHt?)+&(&Ksv@tQCxHzA{P@#@qHc?smw2)OlUQAZ(nh%036veS59EZR` zd<=`O<_%8CLl>*%I>ri9B$|TeJDns4XT+%AwY0s8u@{9tw6PWEEv-X=KIBTCT0X4? zGg}gpwws2<`I>;v4EdIjJ=%s5#j?lCE^G!Z4culGfQoy@aXr0va2}y<`>-dxqgv#NFeV?q& zWpdijH4j?re_y(LFmlWbt|S$H0JHVq;SBzKkvawtf3o@Q7~_@LP0uSLlXWgbbwerq z&jI?v@4%Wr?s5_i6+?ty#)Z;;MDI(YZsT==`W;H}zbLN4wYl+}{^(Qd_&G>~SS-57 zMnyV1;+7BMoL>ZUOGKZ&e}W1uk&H~4EHW=Fl&C^Ise)pb3?I)zH#S&NN>EOLsC#!V zneIBQpYkYOiNZN6!6HJyjZs=f-f(L7WKgsRU-VlpxPd&r0!`7k6B$Gjqu$OS2M6); z(QSW%8p77bgZ)mltkERH25At7N)stCJG*NAu26~2R_G|8rcq|nYR(q4^D zGB2WdP=TJqQbKJLok&P0?PC&194M$S+Gbxncbfa@R`OhLz>~0Ouxw7R+h!c`!NR1ZhE_& zVG@<-=bH%WPaS!i=yNie{a}zO39HDs+VV+5|axDS$muWLOlNC#y~aDIl(G z@k~Ay77m$7+ww>8o=hea1DOS|%Bcmy0y5oZ<8#@s$*IA3`LDBk!L#v*BHdJWl5}Ka zvXfY>>vN8I5NJLnYcq_@))Z6M5OB;dOB;BZ@XP@1WnMK!os&W}uB3B-Gf*UP@;;Zl z8l9$AGa{?l#z9#Sj%#IM33Mt@`GIuv-t@#x1|3rdCudpn9Y}(-gbpfi#V#|y846yd z@ckriog>=wk(};cZc9(_X>RUw(_Gbf z`48md{5)W3%;Z5f?adt|Sl4^qGimS$QMH=9);L+x&drkIo}G?TwE zE6uhT6EcCz0kh|DQx`^s!;zhYNd8<-YDX$0&8v?$PRYX_jf!Qe7D?C!ovti8<`u>& zaPI038M|5W?rZ{2Zm~dT@pV3Aac}X`Kmp}U0^gDDG5!Gf9~9VmYTmv7OR zg%uDbQ^l0ppDZM#n3Exnks!2XP@qLxC0A@&Z)ur40b!Se2)og$3y&gV(1a`)dyKLf zjm0*t{5TU({vSy{}qUbO2YT#RR0&nXnw^{&GrZ{o|Wsmr#*Qs(_o-s7mf zoQkajl5(%2vP2oQ@ivQO=kXo-suQa>(&MWRq>E=il!z3^T^g_I8>o=8Bcs>%^bRQJ z8kfVURkzv0Kr66Cdc-d+6fjc&%_|#&3koT(Uyq_XZ>3~5HxEGTevrs;aT1n^_ZqJwyL zS@Gpky@Wo3O$S_`BE4^Ra|l_M`I$^5w+^N%Sb>GJ;K2z?H|Gaypf#t8KwvHuMd?7p z+)}mtQzSQ$#7O{EM7;SvUZRB&?16GALc#VKn=HM^K)p>Ivm_onjf>5RVYbzU2Zn|a zT}TA6L1gnrrWbr+NpwtAH%7fyo}AjFD$KUi`u!)b)D(nGBUuok=}BXYK~SqnSD5q& zsma4u%e>sg=dF^ETXwP%7P6?T4RCt%TUf;mmeE_1oDH5#C&~Eg1O!@%C1jT-I2gvc z`vOsgGVU>+sdF&+kdteWY#WxQoqe_~K0)d9mv)inQlwp*SXf(!;w`E&J+7lp`4p)OD_tVQtFq&f6kakq7ErzP8K?#%}C7#iUo$XbUslVDQ+m9ICdPHck^|wL&_jNtcp%#@lo1 zUiTEHZS+yChlYC3kows5=F%Gp`pH5pV-!+1hO)#g~}- z^Lf(cG)i(k4=Cz(15xq=WYpuK|YM);9=d-n3r+?^>- zFqxvgf^9Vwo7#DL?-cvOv_dDlw&wCYwbV8F)Xh2Q`$)X!s5HATf!_ zA0C&0tLTaGPO3^{Sz3fIHCaCV#$MCiS{AwZXmO&4Ngzb)k#Gptx8;oxrWsB2^Wev} zVlyv$Fj_136~kuU)S{&kPYJBqb+Y=D$3i#{_^gFGJ&I<496Sx(lX2S{< zmBgoSD`j3#GLhBfSM79#t3w==)77i2-&fgK*Er6uaapa=g*@T0I?h+PCZoG5^nJ|> z4V4EyJ!$pybm&tlZK6owQ!B3(85Hy!>oeThr*g@(im$uPW)_vTQL?w6X;MSUv{$vy zt{Ygb8-=bLbgh|Kbwz_(%)hVO)YG5afml$Z{upTbdweSbUz z5Fp%lYy&V7#`i1jCjh>6MQQPt&qOz{gWy&TDrRJ%OhsxwC53#serd73VZHU~OIoVy z@m{T?j@41qAzN=>-zuXzAj2~q?jU?>+UI8x2QAbwdE79yk~pEzO8}4c3LVoB*?n6)kKdo6A;tLm|k8XCkN6aLmci#mtZ$#C@BeK3S65ezOqjV!{io zmTf<|q15TZSXidUTdCw#I5?seIqNW4oWTlZ@RA*-j$vG$S**apj09X2N^Fsg6%OM~ zJfLa?-^ohq{)37%jq-kA>6nIk*0c^a?pl%5j=mRT?*zLv!&ObM!NoaFb?T*Z1Rr9K zXUgc3yV|u4U*v@m%|Q3&ONrDTjZaQP<5hAg_6gBx_~Q63kh96_Zgd*?x>&cr`Y#nI^#uN&rc1i$}FF%-5Ynv7As12fAt_|H}#dtnJ$)x zQ}F6O{zu2<4{n_@W{+|EuyX2xxa4ZA;{E-+bG)zpl@rd1e0nAwry1a(XnmHB`}(za z)fC~QjYGHno%=VhYJD=kcP`cC;S=vd$$&(QXUDB7niwXLb z{V_OABhdr5k13}6;OLC>)dne5>W(odx<*wP+K-o(hZP==G(d4fdxlr8}&9>ZwI^qtY#R2iUJRBF(y8lArXmtFj@AC6|Vh zWuT50P-BGs>Z{8PhWh#*RvE24UTmNjc`EZjm5*NMq&gLxyZLrXcceanT^O2+mZ z_Son2RZkzEB~zJCV@q5tSDTMbbHKb+>?CMdmUm>hrVSQwBS>Y*Ik-W!G`u#K@-$u5 z4iLxGo6QCWH}*fPUH6%O%VWYvFoEwbR$@SHEds95#^_okY1G5&Z8v9i z)cp>Rst}Z;F~7KBif1?ttfR)ny7a>Bowsb}lqnqb4^ny=9RlOAwU&0<6))(@V~BCSG|ftlTVJ6Si?K?+JRGFL{y1m7`QXc3l5>U^S;Bi~531>S zitDCsou1%0r}O?6oRilxhKc4e+Rtj76S3&gkKAUs&%s$>=l!-RUh(|#IEOQ@Rkj(? z-RNhXwjIxef+NQ^t~=Ee6YTu-Zdr`W+3UTUDsX7ku+AZ5oyN4PYGh?Bi&bT6&T-bN z?+2oH(1SEU)q05jo^|nx=j|)vJ8IPvn%JV3{kvruksW&$QG5y&Y|#imMELBZmVUk# zot^M3F3GQhndoFth5IQ9B=oogCOk0HR87V_jq5Z!m@0`^l?1Pck1XqdacP)q`~q~L zsg(oKq=}iaeele0sNZ)mB+J)MD1vO9mWtBAegL6I!7ySaq9Gwa5PNrg_@K*6T1=om z-)TAj3RhNLbD?rAjfROGI};3)sxiBMro@3^+fr_E z_OE<(JFk;6FsO?uZF6$*=Z}}JjCiyg-mOC=zvX($c$KDDQF|{fkGZ+>fc`o6R`A~S zno2JlaZxW(YI5LYz@zWS zx@eIhe>DRwbC2Yn@4>vSavGbcvt{GEt6@O_mZD$4afN9{b0pOXm9*G`&=I@Z_x8SI*u?5LG zOeT``&VFM`czfn^8gk=<67<;60t7U>#VMOXCya7YwSWCfzl{Fk>kP+4<_|P#(L2_z zj0rWP$M8D?_emYTI4g06H}}fzXWR*Hi~FY0pOwSwra7ArIrwsLKJtJQ#2#vSJo5c& z`q__yVD~WM>Wd=lJGwxPr=EZK2k=bi*AKj*dl14mvaqY5l;F@6GT(-)qNyQ}8FCmY z&xiAX4H*-I^4p5Rb9pIIj>_J<7{)(i0-Z5ot&~Y1TxFJ`VWZdY_5p?n$sBsaK{&FT zI^jjD!M9YXdkLsv-iWjv^bD`WlxM`T86Q*z!p>00WK;g7L|+h0SH#6DyA|f zhHQo$xrIsEk}5#O$?nD#!_jw*4t6L^aV+IBe0YwcF_c zD2i~6P?C5MPL@J1!Do`P^K<|!x-3oe!!v1(IjPZzV_@E>i89s+}s?oJ3F}x z3OQx$kvCBh6dZYx0nT^G=%V@9%W@=iRx(vMDGQ(H)d8|L74ptcT-5=dmZ{glyRqYO zzI_4QWjvS@2s&y>zrh?Dqb=^?18J0f2W?wN9!XR7?GcI*1gPU9TG5#|RQkqMhRnOB z=%mXx#BFo77-&3ESH<2Ye+!@?qfdlvqYMHFZ_^3LB|`I978PR=TBhhrRywBh%;qOl zFOIyoA=C3&)}z%T)Hu(>`?PbTHyEMCRx|O(%|!4znAx>c#rQmf*Z zu?((^6{Bq2xu{iXCV@^2EeOP@hVpq(EG|}R?N!>ES{j3(LF1v|zG7hkWGw^T+vlaX z*dy;T(6l{A*4dOh>PBvfd4uCoQ_?7RWcf0CnWI>-Hxf-d!jhAM2)9l03#4ZFTyphu zX0#2wy{%LZ993rH5OFXrYd_B6y@NfYUwGUsb7FuZcBwLJF4fL6Av*qiwm|&#>MGOz zO1sn&uD*)W&g$Bw>Mx2F^^7%5GBud(qL#p#U8BnOZK>FugsA2kbQs)q99yw+Z5UZg z4(z#0AnMuR+$b;`8yHKp8O;?tMfnmX_3#$|X;bjN1)?7WF&^3*fsqv|#NCItkca zW3p&Zgg&`M;{7Jm3GC&a7E30H3t!+Wl0|2j&Rivs$)YQ4`deW|H9ha)@3vg7f^G$s z-?C+r@chDlE4K}sfh^x{qnJaG@UzL9(Om43wZ?OEOwe5WmBwR{f+ZIzS_LlUrXA7aMHhHGm4^>rWF5->rLidqcHb9j@ zP_Qo2P{!{XJc=cr+vDMhiUi*sBsT@J=7fAl(F9h64Knuve6bM%U@TBdGkB(D|8h6z zppEo#kawI$hr$d@S`1?A1u^o1ykw$&nb5PChn_L-TK0m-?cC1MZ7vOjNTJx5^6r2W z??B~xVUxGzb|8pJhz{WOc!YX%>5f}kuL1}v#|Ax~-$!fF2V#Sg&yae8`fCOI#`{UF zd;K}(TDzJ0o)mXkqHZbg^mEA#w60d@V+O>&3R`;i%V`d5%#c16MB(HHEp(vr%rs|S z4uHc3Ksr!M_JLC~q&oR1(e8GQl1_Cu!Q(EH?dEk8K_}a~Xqj<8CG+b^p(qc3^c=QTMN9;lOXm3nP z;#ck=azV@BA|sR1M-p8ZyCZ}M$t<`@Ze8@J&Y$(5t4 zmK&*+8?z7_hOQ3RGSm5YpK))P==eIZU^LSE=u+gD%F=DpT*Y>Qhm|)qrLM~X=F7>I zT||`NRE?(e@JpsjH(o3JWVTY%Ec5i55cTCQd32AwddcMw zOfZqViz4ZounkHM#yl5l|6EHmV}U8`#vCNxB^4uX%HII>-s$o0fV0eWbylJ+X9y7` zbOkeHm*H1xX574qHLj#aD^fw*GjMPXoBOO7I~l5nmcMTH`R=SXM{UdjTeCU!@b1FI zyBhSmg-Ed3oMqpvdDz4%C)sV_3w#r%5~d~*rV*ZUYq2=@@Z~(JpuLIdUeGL*&F(g& z@17~i{6qbY)?0Ihva?uA3678j*PaC`SLozI;d046Bli1_eK5D)1sVDK!{H@rN%xO= zO_zqvsLoHcWtXIa#`hH$)feZFZ!d&e#_NAuO#3EkRp8_hVsS-_I@8iROUokX8%n{Q zPW;S+EyQAi`&?&q5`T0>{%z46nS>3x6NhlTUvI?GNnq|4sYk9F}`O)(7~VSWx#!8(@ocIinaF&#rG8y>ni%}>ebp6%wyW8EKnK3D)a4C8P8S2)m6GeS{KSS5O$5n1EZ3% z3Lq|#?`v4rr{do$sLnpMG;iy1e=2kPsZ}FHmPq?=m1P{QMjut69#xa|IG?ipaV%JHIthCQ>Fp!GxLl|bv{=RI4lL|`GALUOPFSAR1rbil@%YXLDURMppA)Qo_(Bj^2fgX|mu z?VYgf;S48(${YdXc>?5I`*mPTS~DD83>#stFt~1-qNAx-@FH>(lgacqisiearYckJ=*l)q7d`s?Q1oI_Oc z$sz>#sc>B`a^vyjKhNAAEm=?Z{I|NhqaxJ$qaxI!BGmu47ooab*P@@xe#+rwk{qWg zNui%(W?L?%D5j|Jj50ShZKD@NIO*o3pGyzLQFTfK<%XL%Ad}-D;&ZJ!ybbh~h1293 z1D$I_FXZ$h*O{vR4+vu@Cp~rx4-Xf+8=@XX%jJI&&W8-_qDd~iG9-WWBAJyieZ`Y$ zP9admKijVVt<}0e+OGfBk~Oe@QRMw&|3btatY>h^WI^WNx(ohs>_1wwK3cOrTC+Z? zKK-AnPxa!0cIx^{A!U{Qk&{}~rq)x-*04jXx_^MaagAzH8^ zTZv4^=5)9l2YrRjx^PuL0+rIs5Xv~pWwH{j1NX_+&WRLMC6Xvh9Rp3%!@m$rGj+NF z`y48wC0`P(Czqb5ZA*&AfwZX`^r02Z;Nx(oKpl$J=2g@W`8b_hSs z2k~VhJAcD`@PGdEL%9ZNyAO2Xl{&0$_7(pfY_zLpG~I%eMp7? z8lgM_6t2F4^x2@IenB0HTQUXqwqE2L^koX3-u)mMwm1hgYiv@BDyLippCp()sE~gX zN38JGW8>(Ba8oQ1gQ*;#;VXmZ1aoO^Ju-d>ZvOkq_1*u^u3Z1^yWk&3{+SZhA0C|l zzsb&yN>GnVP>)Jb|5SqNeBFj3HOZO;XsGavckiPX%2}jq6Yi}@<>L& zKTrd5F!&4wpKVuQONYbAyO|mfpuDP?>J}ntS*3g2hWIfJTlJ{yo|8@@5ml2;GXJs!_45Sn zbpW|T4y9%Np-)@X-j9#b<4V(W*Y}lPJyyH>o9$q3Anwpwb_4sI_owX4;Jl?&rNVEK z|36rQddKbuoL@W{uG=D@UmyB!0hxbNj&#(3dene=WGOqcl>LV-WdNLa3l9PT4X6Wl zlWMVewgC&L(!`E9G#mJ$kyj?k1@%K-hE2s)IHE2B_+DCPNud?}Lvpi`&u3_% z3#N)CpwG=Y8ye|C=o>gnIqJsw#dCkSX#Qj=`%OjZEkawh^IX>|>~s-b8KzA_KnK5_ zEz5VW0{2RZx9|@*f5|uE#EwmkGzs|sh^1^|X`%CdYeSN-LA@u2lajuuLJOzvA=qVp z5zw^%^N#F)oZpX-{SmT1LiR_<{(nODdvT>4_qgw-jcIz!3Kh&h;~heeYqfLumT++c zR&Fq#NyC*Co9r8UE_9nDHrHFa)%o0VCT=!>ksO{8f;?dwVLMG(`iLkd85CFyI&f|D zQM_OZ2<#LOg3<&31v(5`HIV@9LCM2yK9YM}nak^ABLyUH)3uo9f}O+)xy?WF`-id4 z!)8>|7hfuHV(IY)@KOSV8>v7-jJv?K&r3?VWe?gV{#jyxb|I7d7^ZT$KO*&q$CCeg zC*gm0L;M|C@!v-FujhVfiD7`2c>i+E)o(_)|Fo9)2+bd%`6D!cgy#QKXdciK2aobQ zU^H7A*2nAk=HBb(F%lpNN4JM(&qFIt z$;QCn<=dfHaDCK>;_1ur&HP0m5=6Myo917t-xli6IFar3UeLcsLx>O`Jx9Gw>!X)m zWZXx@xq2dl6buBubpC56&GUM_RAgo+M*OA}E9lCH*}}n=@(&%5zqscaZ!p0f5K2)9 zRN7DXJZ`Q3IN^`L|BvL8|ELYsw8dhT?>shKEw2GHyLYc;<*dQnBPD_ck`9{eHx=EyoSoyawhXgL4&=Gf0t)Xq9s zrQqUHJ(Zy3G*GF#R4tV*?SVqhJ2Xf=g|UGk^`43>FB(mO{!@Gt8jVcvMQ~{}E&ZrK z&022~J;*ba|Ji?TD{IlU;Yi;QfPbQ#BoFph%TssjOPo>d8Sqa^H!#8 z9}AsUvzpebv261o3Cl0766vjwU9tpxqaC?~(t|LUAPI_xx8!~Bv(?2H$j=1gZwaLp zS)gmlR;%W&z>iD2<8|E1quV^mB|@1v0)yIBm6{V@h-*}{`7qI3&Xctjh(Yh+UlW-E}os@q-oc zsvyM43v#X(0`xjSsSECu^_&_ZRnPOu)aX`VCzJMq*oZ+4#UQe5)Zc<2vY=l6-Jbpg zRyj||Ru_YcOsy3D4m$h}oVkxot`BX|w-t1oUI=;)1U*N%O+kH!RFij;{IEKLn*z$G z*$>o6Y$xO)cS$WlWY#)lZm9kh^uSGWkQ=JYIM7DJf|V zZtRhYe;ri$dcq=Q03tSI6x^*WN29|$1Zx}w;f738hfeO2Ds-b1D2C-Z^DTmfML$Un zCEUD!>EwP5jl0gkrQi|&Zj{>&>6KuuN+uI$)TndH$WR*0nFw=lU*(iI!NfwvS6Y~WH zyq#H2*>Aik~(f~I|^MTwIWP9%#1E_lFbWEEoyqj z=1*z+=rP zs}Uo2UojIG#M`qR?+T@rgA0hr9-?*_;iJS|FbR=Dn0xGHQMcR-CbegcB&0MLlNmV3 zcn0&*0;BeF#yDZ-nAq$Vb*ZF!i(SAJAIlu<#a;0O%egj*#?u9JF0=^p+BtkUaWat9 zxIyaVt2xBp41{F$dvSpHOzk;*Hhoh#!;bc3raQ`D=y=gTvARI}pH`9s$} zqt$konfamQ)fv*674fx~8{>?olUvjz(3~~pX$)U+BI1cP$TY^+!zH2PwJ)8N=Q;np z67k3d0~i3mGFmV0!9ZP!;4y!PYfK?8u}<)6A^Oe+i81HQdKKxb4XU+&TtS`wD!%EV zvB_LR2+!T*q+yYSVsN3ZMBM#(1r@DxQ|J>hMh&`T_xrnR?|E|H>!NLqRT4*WZD9nx zSC}M{lvszo+ES<4)yxm|25{uq8T-%O1Mv?oM!xBGdTe`r0Gyv!ZfCi#c zsLSgK`PYV@zq^1c_cs?%MUGVOKNk!6?E>o0byI3#tBGt8i8LkIJ=co2L4!56tD`UW zHQ6zHe5kYdITjnO-&A<$7AuW56AY1!H#C15Gi*KBTOIS-Pi7-QyKl(w^7D*QJAg5+ zOgYW8sTn@!W9Sk)ChSz8NmD*x@^K>X-X>nPAm8qrhd71C6KyZ-g45sC30&&g9nQD7 z&36{Jdrd6A3c;bhtJV0XpdvDgi8a0DB~;13(5==Kt|CW%e{{k{y+wg z6#cei9w!ELj*h(--W*OL%UsSFr(MpA6|Af|52XVhOQv!v=aN`B9zKqYPsZ2SeH_fN zCNrLHT~hub)BdvgvEAb~A)_pUpeKnbr5UmxbKk-|O{rWEU?U_(5B-#xAG43TsXFRt z6Q?c^z$P;!9QDqZy>+&%INLL7x#Y}rZ?}y7RC2GptfXSEqN=$Knp;DCQ&?K*2e;&? zZuwAQrY%gRclkHlKud(H)!7RrHi=aKW7hSFqIIUl3FmCC8Yr7h^mHu{RA9IRD zr@k^@&sjxNrV zabg!`H#@GiabhN+{eu&!4))=Q5vuR@?wXRc)$Zz3_zPcW)D2=BXCJ)$CIIjx5qMFI z)b`h!GuHYRV{Xb3>8xpxR_*yTrmA|K&9M~2YsG!u;p~OKfbZGbyNa9MmpzMj?Z}vD z@E!%dc4U|2XnR1U8gqRjB|dPSIc)t< zWudoH&|h7g;q8^$TWQ+UGKoyn%u|NwAra=60Zz}-VE@Hj)fpCIwoMFT zOU7aTm}6Xgtm2rTM?$A`3nJ@z8p4QAcw(YiAUufb{YhXnR3hgZq17_BN4yN(_E54; ze&2JL(sRKet@a^8@@r3sZj}PZyq!`EI8U+^|2UJe&^&0JQxl^T*}$A3Z@?%|Y%!VA z)^yLg2?=@#8TXJpVMH(?S91qg;e+bsx0!$ejV^s!|c{q1}==yjKM0l zlG)@$abAnEIDvRmXh&_A>^vN6d{{QYFN{6)lKVB`R~Xh&HJ0 zAZfNko$U-YFnqX5B+$WS?Uo`7Ru)d8pDrG;EyWo&4`6IBGUfC-evhprOy3|*&;3w| zvh~R0W5au5wO6U3ug~vlzGNdtvNFS?mzPNC&#P+ZeOMtaQvF>ws&&Dn<0i@VFMIfv z*+W@WHXzGqZ6w^&e0<{@_`YO@Wy2qtE-yFWjU|tnJtvsVOd;Coz@9h6M*$RQfpSy2 z%yGlKPP7STuj=R*kum4qs&Hv@+9OQ& zofssLr?EF&-S)Olhw2%Q6gJzw$Q>LY3tzG?_3M9HHLT)nZDjXfbJ%`@Ld;wa#ZM`HBQj_2w6KS zX;H%RuX^zH>P@CDFS{A{Vv=sBs?zfSQ2(k8NlXw9HN+Go zBYA{~jMDDZ_C9Z^N78`!4&>(pAK~%fK0ht~-f?1FFRa*-J0Ln6v|^oMgCa4sUWInC5~dLq)I9>MyIjvd^^`P_K7L{>39(~w&*2FW4StH=^F+Ts0CY|^LTWP} z{}dqM&I=19Z=t<;2;X=RECBOoP=Go++l?lAag;$v2i^DSKSQ@7St)}XSji-jD+fu zg^$uh;0obQ4FNS%5W6zYM63|dMu0Vl!dRE|^~aDxb(_8faK;p=>B|rqg-|zPq}_XK z=0vCBDV@-C4(#(sPUBo!H%ha`+5qw*(>>4xEnwJ*k8A7lJ1N=rr-@^b4`#4dCQ% z@H>SEzgkU%TZW=Nyg>ulfDUh1=K}LPvUtoM2ps|#@9q1Yj2n&o8L$WJ$MftoJ*a<& z6K5nIunrNNLKAR~KhR|)R)hwtqf=)v12Ys?cKCbh;;eZC1uSB~%*fGWqccgG=F`7a#?8iTlr=N$MrzVuiJA2g|i4g9kCOkFiyDnFPpEWL@n%Oh9Q8 zDaL$O{N*W1n%L^QDdxbGXFPUh7YwNLkNB>McV|+tC8Ie`LHffQPob&U#n>-&xlgP@ zob?cL-l^Pffw7=;#ToE(iS+RNM6nGQ-^lc2jj%ZKjKmAgM*x79NCt~*2FH#yyEi8) z`6}tj^dU2v`~G?0(NR8=WFz%Menw3sv=9ua9DZ=88~vUMRIOaOZsdWO1k_+EOJN>s z(-j?1hkiRRo9#HMv6bX@H~KI+g~&y=Xnod9M$U9=&eCGeaCz1`7*p&9k$XA>y$K-2K+v-|sh?JbxaUi*NQEnDE>UnfWn;y<`zHS{DPO;yEZY4^ly5B#{lA z&V!C(%C6_(=tHseF#v)2`Yw5>gN;=B`G`w0S7-qyJvM_<0c&Q#5HN#%FT^?!i)99D z@FE)(UAcS;g)<99+X}^&3h!PPN>CI@K@?a-i>_R-Wo?UykF%7PiqtNP)G3Nd6N?Oj zupUc5A3GOoQo#8s1o_&EO+@)ndEAs;BwQr@@g7AuI0;jr1ZObJ2I8v^0q7|)+@dJ; zRw6<*i#%B>HT5hFpePHbD7_FW3->8QW|l?S`bylimc?C`#fu{Ela(h|utu1qr9et_ zOfWN+%F&syWB1An+8FuvvR8CUZ5gnQL@TT$i>sE1QPUm_Smnc>q8!_b>f!QQTl%cF z%2$__U(7JLw}VXIU}%|Eb^DMEXI70bRV7$gQm|A_imGc0SI_xWFF=%EW>gQH7Rz~p zgfdFjY#ki;gv>>2Rx)e$+GR;J#`=pRVWOaP-j2!3R?QvV`6CRz@02Uq6@lQ!GRkzm{u-(LYzpK^4Eg ztk352p;2k6+vr0Qau|=d)W08! zXHd>nX>HgZOXs&+8*gp=_&QHIn&wwAMwwRS&sexWU-zpRqxo>T#ch9WqW$IPcipI1 zsMgVP{C+rzVj%f?n1Gc#40n2K*d5kt;U1B{n~CUj)S`XnGvP>Vdlz%hH+FDYn+nfIBc2? zpR;?hU|D(CyyCh4GZu1uYS~J)`_xL^D;eGfn9^WtbK=s(?)YPi`SYvmZ~LFS0N9*I z-58|yM?D~xDxR7dPL(+{K+HznLv&oC-hM-B1Ypo@y7zeKxd?h7e@~$Oml4)dRF?(k zOO;#v<1N<@1jdF@#jm@q!6%a`s{DAbC9t`^PRWoue4SQcss1{n(k`R=MqQrkboRkR zhts#ZR|Y*!TXrr5tDs_`|8lseThqvz@AKZo zGg-g0!@+mNn6JE8=|UwGNF2b^Rh{nt|P{O$8s6YC69C!rI_5e{WQ?3JPl+E51>_ zCsvKw;y5U*bx?iI|AO_Mc(xCyX;0fhGvzzZU(eTmVZA>X{C8L%_2S+>IO>Bw7o1>+ zJ+wa_cn*)|eTWWF-x$=U=7E9`4+SNLm}Z?a+35r0r4CPfm3Lb^w#v(M;J_1-sLFA2Vc#ovuc!azx+Dybo0HwHRk5W_1Wu1ynE|t z7lG?tK#HS4mHl8-An>2Bj8GT@h=A<>UY~{KCi?%W&j{(@CZ?bI%&JxKyFQzfR5AUn zKIguSWB;ko{|U&BL%g0RsIl#DAm2`5!kOjP4Myp+p&Kcy9+Lb&*5})w1b>cg|5l$p zO8jg7(C5DB8`}G?eim1~sdRdUb$)r~_OT`6-|2JIX#(|6eMZUfaK>)?0@5yAOonDLPco*>h2`iv@OM-^B7&p=LFQQ-%op>s9{w+N}u8fv{z5ipA2 z`nF(^=J0JXhBpKS^0JWPGF1*drrY^Ze-wf#6gtT6q5Pt0lmOw5kRmJnil@Qq=nn0r z?19>PODADSOJOIseEIes-vJH*G3myM$mkVHoJnL&owrK$08GHU#aRKOyX6G#Pxgzu zjMbUYBVo84P3?|%qgN~8*ee{$MT6v^jk zO#hZ-nHJ0OzmOdD(`hS)=&SvMsH%aEO;@56Nk^G0#z7;xp@L!$w-;;b>weJs-(@;+PkgrcgvI0beTM`mK z7$d+sW~~s;KmS)r&I-?$D8)E&odyRXfVidEy-gA|fTa513onV&OJ3me(Qmg_*K4cgKJo%XGRCDnG15fR8x2&}0aN03{YOT4-*)6r zM)?1cd>i4k2D&k9{Lp^U=r;QNZ>jrl@-3ZS)_q|~$p^gKfu2t}zqxZ1>OTPV`x!e>z%h~Ku{G47Z%G(IzK>|Fs2v!Z}syhb-(4?&vL4N(a-GZ&a9ug z_07&og1}fjS>%)gp|-+|$}b}v0a8Kb+Z?@c9r>n_xymYFAcx-Vrc*}x(8H-YgEaN# zMM!=pn8}1d0Jx0V8rv*!3kL&t&b>Vb4=_jWDREoUlXOc7#2_Yi{?33>1`J|q;Tzd} zN-2YfL$kFmxQ!JCL=oISf%3pDnm`oGd~K3dvna!?Q)3L}C>Z+(b!VTheo=QsQ<4Zd z&hARAmMA`|5d##g-yLNg9;@kK$$fy*&#LW-KO12hmt#!JKNnv7Z&LmJ3i|&hRn!3L z|8GfE!^!N7+5Nuz+1eBN>ZaRk-e;>*@3NeGBi9KIxsZrz`cLYtbhsAal89l?Q8ui4 zsRsTeVPhzZW2os=dM@pj5||oyQ|Tk-qW+~so)U#_hcdX(V9+3oMI=W}JY3~u_My|i7+jxMUm)kX$-({{)#>+tTpbPFx@X7z8kJPf z%{}`F#JmY)e*84J33Hfz7=Jp1@xKxEUwaJxH=_Q3Jfgll6!I$tU4N6UnN{yyAr0G< z!=`&ss5!+fuvs1db7%z0M@OS+S;y7}N`Y%sq~bRPxF=gAN0d{mWLd+6aZw!>&M3P2M}%v zfdT0*O7Hxze_(-lFd9eRBbUX&Eb$N3Jl~~A(+I!IaN>r3$c^B>+$lm)7XIyT9f? zzb3iYW$LDVp+I7q2X30mvRNE%cl$FmP+wZ9B*SpQO=YNiuLMg<6-lp0ZacBp=xGs3 zqy%f%@Nq@DK_;tB?!&2BlkFE!3%F`k#m%hG??T`6`0bu>^z&lBSa9%lsa4BlJUI#j z*`kuDf|CPrNh)`584_$evD>d`k*IzP^`(`Z(_p~Xh1_yM#4I%zzuaAHb=t`$BA6OK zlhOIxL2%RSPw*CHouO4sk6j;Czpa@~hR5{xRAdF^=f}WJRcio%U`p2XNd5W72w2xwwz^SC(02+2y1VG_mpYS1*A2jXsK^Abt& zK9*KQjzZ=EF=md0zPADt$#x;j7{?P_N^7nlv^!0O_c+|p4U6KGLFS=oIg_r6WBGF} z5lP|*bL+(|WBpy#xim#_AHDQ6XL$dk?EAo}DJrul?}ccBa>c0(HtJpI^Vw~*5)G%g zfqAbsRz-Dg_r0b~`8*@fGPOHOBI;R+Xrl?G!!UMeDQPTOx%{t>v8Bxw34tOBH{=;4 zc@)K&b_GC1>r|qdxplkB5?l_iO z*@j`@C^t6F16uv7FCNOpa7{7w`X6x^j}*}v`t8~P?$?>&w%;$+`)0Sj)cc{wmE?oA zJyg^EpbazgE^{K>?m@z0oG+46s1H#R`Y}Bm>S9?KJo29`sdUl+D;?S6Y9E^hUt8fB z8O~@cGvc(c5^Jf<6KJS@)6l*DAWYS!Zx~gHPAg!f6zqKGmWIl^Zoi3Ec+_U#aARd=-%{ju9PfM|e<|H1{-{f9-do-+X|_pv&@H)6a)p|o#5>?=?ejC*(>Rk;wGY`Rif-8t z9ZyDl(9~tV*jo3Z4lYPb}bB!jIJIn4tLhMR$h)d3{^A zY5K_JU^Da}bg&dMM4gApJIKVUMqVn;)Di|o;q|dV7Y1F%g%W(0Y~@$`v6YbZbvRBF1IKnEIx0(poVhQtpNvgLOBnTh=N1cx2t6nzp=Xla zYlP@AoASNUjiT~w0TSr~*|ZMB7$Ek{s0GDf5+=E&mX9!$r+O-J7`85->q3hW`GKTY z*RnEmS0(XU<>|vh3)k5ZdcG)`&}}iWHLh@rwO!?QH6xOju0YjS z^cg|4A(#`7qCALQL*T2568%%Ys-T!y+04)_6(N3ln>E)cSkJbkimCdv3Xvjl7~vg; zM$N>dB!&njYj5@DyU|p~F{-_oKrp;Dsbr^sVg$3OAZc14krTNI$tPFTR3`#UJ9B;6 z{g&4%U*R~w>n5>h*INcpKHenjkXTb_=~E5&wDhu+MQSH}nTdr4XHLUx6syc*I7$?< zNy`(AJx1p{FSScG2tga(4-=4lVcBewJE*OfjjGg z!{&4l(OGj#*rCafa)Z1kAu)1$<;V49+7}y5h8+`R>QCW@g@`l;TM(DIhvoxh2Oqt^ z_Xd?~)&uV-yGMaIAL~7>;Y#Yg1j($Cu1pV>gF5iyzB@!~G54%(iw)nNf5FgtM3HmI zTnr~-tJ?;4K$n>Zf-fcI++s0JUk)DA4HQp#$5N_B#2>V0XN4Kwj**rx;_0H+sJb2< z+oZVlsYx-z5m`mCN#?6Ku&GfT`&LRG@W6}FX?r*UR`9;@F-RnDy&`Q-)B-N_gn`IY z(<_aigt-0XX#d7T8Ig8!u$yJS+LDu+YoE5ZJ^jrnsfH|Hq9jxsKt8?!l?2VGKX%05Zor_uU(<)DKYADjCUPI++9}h&WjW29*&?&iXjn0xM7_wv7 zFq3XEh5kSmE2HCx;4p?}{GjB;O`j>;!r|pgR`fY^FKj{cLEHkHN$VV%Zhobod(T^? zBzlk}{qk|XHB3NT=waz2+Wtj?{12&2^wl!YweX6~X_}N6mywq=rN~wBO9{;#Xwdts^)D{B)t4%cn(Yk`SS*vgKF8ZJ zZz(nm=!7B*jcfyeD$H|ppCvJJrF_i|AJyM5dmBi7GE(v(Qeu>QQRRt8vOSuGAoz@Y zx$g&QKfV4qGzSt;dzI)Y8o9`Oc_u^QxDnAGv@N1>p{Np9P)tA6BXhrM@U|3bIL~($ zcRE5${w9yjvQ47S^VY1_0pE=4{0Z%!4M+sjP`fJk*L>IX)R*)pi;whhmN9$3^_MsC|^XAt7_||yyeYjPag)tj~IwBw(_T&fM#1M1G)4xF<%!lPpuOx!W|! z0DmPJPwuV29YJWa6!tAR@uQP786q-rjC8_QuOO^r zYWg1gNfz2uQX33j8<>k5>^Ni~!4u2H`?f2tMVQmeX5y^8pvbpAckBHKHoWxGsp#gP zQs%ot(+LH=p^ozMKr)8j(h$HAu1E`v&KoXAc0WKOOg=x(b!KCP-6OXm)obuUSwworTmk z60_bdsxDHvNy-Tcl?)Aq!vgZvi40G101`Na3Nlib=3AbSq+wVVPejo<)NRKvHIFcz zIY{;cj)O}`k1$QRF7(wYg$xiPM*o&pL`3rbDK>vi;DtHh{%_^CmC!=3Hf^L5qan zbB`R>0Eb(H2U=jIN3o9{_{)TP(SF4hODDNP2B*ZwJHVdZ>%nG!XyUXTZmZEjjPO>b=tcVEJkU5vVq!DgVVBTc%7INUR<7Bf2qqH;$UsNS1O#Sf z=Hmk5aYrSh^Y!2wWlX#+rm6FY+!0tac{U~+mn)m7nq=tf3utK>Va`H~Ryl2Zcf=qN zDZdfMlL9Zj5zOfp%t`46&x^o#N1_cRaqf>~)9_}yZ$@B{GdO`G6q_?ZJ+fmL(48mH z-4UWsBg#(}$rs6Cm=rV@+Rp{Iu$?2J-)&%~izM~xNfB=n&Vmw-$YJhClC5%>T902+ z$#Z|KplNRy?j9j=TRu?&HMT7$jDqb*0aQAW#)@XjR|Z4N%(`<)_<2|MmXbOqaSB^I0 zgZxBBee2xvEOto<25&xg5TqIaC{rg=*~Kx@hkmevs31!M+hF(*fU=TIBcK>y%}Gwd zV1LsRzDZZ=S(IqR4$ZPj$wVv1bSVYYLF6TI+~=N*nB)_g`Ud$F8&-hSL}|pMpxH{W zOdn`B8%(to<{wo?z#E6bmr1e5XtPUL-KLL08oRCsbA%98M#1k@5H?x)MktkZBp09- z$E&8uS5t~}da#9*sD>_Kg@M6vjnZxtGRDW1JiV$PmQ<~x=ygVkVk65j>kF5C$imp6 z0zMgrqR?Q)oFpG8i+)rLwD3x#)*%7nSwYeS#lbd!O)O;)*nm1D;|@zxJ_m8c%? zG3Kj-OY`a}_G?&!)n{g~&r`CK+e+wq!Nn#}4M|SXlDQxa$rXnb$cpdDlPa)yk?8jTpANfKt zACwf2tugk3pa-PH_!yEvS5l!KP!GU+!P0@A&`MYj>AJa;o#3eZt^~DX zYbf?58Slm&sIRgDJs(2gbVf0@`$DxqE~~n)NQ2A)I54NNv~Ws>E<0$U`U}EioFN=o z83Lj}S5d&^e9R;0MU9erdt3!-IT{IBa8hF>9j(n%|2d6Kiku zw1YE?^=(S_2)jbE5S;*aD4`w{-rAw`0p!$POm8qKih813-@v0X5E5ka2Hl)@sa3eB zF&jESSwG0M@(Nv}6(CWA?b3>EH83RAOFErVzYkRvgNff%(J~%@yeNGMc}oJu2SL1* zv}mu0W-?xBcj;o3d#FInv*@==+cL21NF*h|Bs$Ry39(tjP#=Z3qY)3WflN9t+zxo5 zy;ZUwJ5(=?A%CE#tUT#X#Yhx%?#@U%E=hoIGzD5xM@t{R9~OEz@ZT;n4=*z2iowl-)k4mzHegiE5GI) zwh3#r*CcdP=Lb;m+-v%vDMH}*GDEMdt&V>-qLN<=l2z0vr+V1{c3 zbUXG9W>CLer&7)o7`Opeo|r|ipH=)0gQ&jNils_3fob9ao*jX{z#zTZZq zvijb+vAn?AMofOl?w%);$Wua-Kjt69c-LQetPXd)JB#3W3Ew5TRQ-+T7WNBOy6C(` zE+CFf%$xl&+-{y3O1kQt)muria^48A_YDI2&VlG99whMyI=wnnemJLHxTYH1&FR2d zxC*l?UrE6Rl=`Q&Ouz`n3VJ)sS?VXYrRHrUv7HgnF;%HVC|Vg+@t2L)nNqn?=K0}ydUQ#_TG{$wm`Sq=%fpQFhRIia_>VZ+tA)(H#K zu8~|VN+xEOVRG}M`>nwOA zW96$f5*z?M%lS-Yx~E@IdWHX7qpBAGJped^%_a(hB(Sg~H8T;Pw;7MYT~wc`aC@BD zgX4~gB)6Z9H_A>N9o>*)F*urc=HU)`o&YtzoMVm7AAofyVdsv|nrf<`?{0~`R>mO! zG+cwTd)^;`G1u|d)^gr4YaBxBI}=px)C&l-F>xl8+%8Eurr308~vF!yn1 zraOzFW*x!}-8O>jI;|NzgMau$Sm!_V=<^X1tVSIQa-1SJ(+@z((_O9Mya|BH60m5{ zFxcGd3z9eqyD!C=i)(lfCVUSznvP3&kIW4@xwF}&ouUwm_py@Lp+VmXW z`34M2^B!FODU`(a2;b$5CE=oT{VHvG$$%HH?lW}9@e-Essp%QjJKYPxTJlac>aHW4 z{HL6s{TOc4pk_C<`vSCBAQy1v)I=H-2cQOR{S5m+TG2Ujnd$QV{_JPK(Xijf*NcFQ zG}vlK7kL%|6o7$GbslYUD+Gg>`$4MnhBOk!q*kOmUC^sRD(JYfQ2wS{6GPgSQEz5T zF_}$KNo#Rtdn|0ka4=XQT4&t2(c}}6^@(Le7Qm0`L=Z{`h!9JWvKMmL)hdIF%NAcI z>uD!VWP)+SPapG}prePO1rF`&HL*EBRI`Nio0?r-r%Ex-e_D;0(cRoWC!r??B%b4| zaU|8vo75OmTV|)mNp#0Dz`L`)(uD#DLzHN&Q}c>sGu3U{h$;oxC(Ah;X>=cWD0S98 zzU41wfVkm*jE<)u7ACONRNcINX+*{xZ#7wO4Pkkb+}gYFy7>M>(Frygh-)?BtkQQn zqM#rwZa&$iudbgH{V&WM_)Hz!d)h`nv%9$XL>G%CEOkA9qi~Idqef>%LxDPP0Q?F` zuj@tUt=6Q}Ck5z)5^8*$f(}Bj)Ei7BO5_ zTTju_WXFFyo9KjT-u3F3JEEznf=N?d%uNk7yfguz?Ik<2g(mL~+z+b|vzdtU(_m(W zVC&zMu-`s)Pp&0?PPrbacq$`Hq%R|aD;`(jj+|;wMk6kGOoG`xo7iT%t-*koUl{kX z*RPB;=!<;}h7M+1a_U1hO?={6*T)L1k8!m@+EqG6O7$FJ9?gvUXu>=-b{BR?)BXr;qRv6gz`>~$)m z#S>t-@l!$_Z&Q7J8(AXNuzgZ`bG)#$(P&p`kCa$uV~}aurlGe5;2W@KE(rq~X!P`! z1CB0l>Gig_ECaiCUdDZ+3dzKaFJ)GF#QGSu8ho2y-XuBdu#e?sd1lrr3qaYdr=n%_iRsA+cYit`_TzbIzC zU%S{|fR3obs{VM8Vq9+@<{O_!`p9*+^s3k-ii#q4Hu9@kZiXFpVz@`U;r*~HO#s!? zr&cFxGw!kDT&P5nx=Cc#-PfUpSgq7zJU^4bB(I@QZOoowrI#$)v8M-xA`}tFH0|@29A!UQ)ESR{cPUQGO1Cu_acm3OVBZ5+EJNQc zhfGg*Y)63~WJCM<2t8GBbTH@%RR(U5U=)?l#~5}PjP~M-r1TehR5oE$>lFXb7coZI2TlOw}iiO;JX6{s27XMu}LAprv1$e;oI5W@r> z3*(W2pSj(tqA;;pq$Bf^)Viq@=pYH1kYrqXGk}#idXZRgkyOs>1k2{l-HC`<3E(#| zoV)K`>Eg7D1C9uL(+rOlsS(z)%9}ixI+%rQIViv-Yo1joW;ezjV4C#&j#?2(-;&DPO_b8XOcj01{wN?y z$&30$ImX-QZ~bW+JF)H+s)lk(w8P?M-{Nu%icPgN)4Tk?so?{Ii1k)8xvqCrb)~{U z2ulVKLbEW{X}i+hHB$lguwGGenU3Q5-jreNi(2bl_K~{LAzli_n~LaSPDqb!(?{!R z`pGxL7M*5qMmKRPpWz*1+NpS1-TcazRKFg;Y*s(X z2(v$2{+iUVff>jF*EfE*%imC&f0(qcUZYwCti=$Kb=pvdYyTh(tK`X&Vm1Zy9)Ag{ z@#dzuBR~3#CKAv290)LD;e0rohfeQ(E2kN-w3t!R1N`ugS(pw)1d`HAO2@(xJwn$R zxA9MSU(BrN*QXjvPnK_T3&7cfsz+W|cGK?-bE?WCARz`ky+IWFh!hy!ca$2{)B$lw z@dVS+F4Bw37A85}dCZOzN*J;u0S@K`iwC7w>&H!jb}t|EoCy;JgV1911~7%5ffAy^ zhP|-O=F0~&5cwe4+}krzoG+6{LhCv5uy1c$Wx&G}W7Xa9{L^VrcSMTBXXvi0Y}7x0 zenZ?7N&>52Rbz7JQ_>x-arQA735IvpyD2 z{OhX^^FyAn+zg|lH_aSx{Fs`zKAatxzw@Hg2%#yicd&DacH%{b=aF_zFj?Vj5ww)3 zHE6#-bo-crvt%jl&_ww;O@(j7mXV#lBgTo8%ZJWZAoL~?%@`g+d#xzNWa>d22Vb?^ z$M*s>(|CW6*T9|_Y}hdNVp4_~-81Bi;5n1PcX3Hr#)Q_WwHiq|&RXBnF|1h-*3)t^ z+J9(VH-CvQ=(sZBc+uTxw)UTkmZZcXJ_%xsqv$J)(CY zQcal2Xh6Qu?a0D@LeuX+xvMB* z&wQe_!E$E>o-uSqS$7CZ<$Shl9pVUPS_dM^if+!(1BKixm&Q_K>~PvQMXQkA?IwMZ>p@Ia zrK0D-qJbfRn1TkkWJPwtp!8nR))1+!NIq{QcbybRHVB~`tUNBGf+4LIBh9)AWMM=O zpL7H*FtDmTlZw9^sW9+W7f9_atLrCA%?hN;8=!**8;r{uuE-iu1q)pd&;x=^iev>j z2bp-}EX3p83wRdIl z2$hBOGsq^r=NEU8Y0@1kagkD@LUIEJZ0&K}7(wm_VxEl1r*~wRcLrf#S+6HzAu-4z z2I(eibOC*KtLc|VS3^A}3X#qVH+O;I6@4T@esHn=XayOrI|}kn9r^DKs-A^{cBF9x zK#Dn;ZSFCO9uMTBia-SwieWYlEa-siKE)KoFcKjDjt#xQFEKG2KiRGF?LJ2Vr_zYX zXl$U;bNhq>3?smUG6oiW7?E6QE=xCI#KJm<=vDny>T$oHzK2;q$PJQy!ikX*=;9?Dip z&&#!JP#zV4KSR{wXAT1vRI>MDKiKvv`4zq_aQMeKggrP9~_ff-z(jiqO4H2l2lFxyvFb;@8LkdrXnZR)}Sd?HE36- zQH|vn4yp1J8e>EeGtnNhPdHyR_0EL=BFkngcLJJX}(;UK;?_qjJWd2=$Fa3d`w7%@yTZQ z9`(cuafgERHi>tVY5UP7RB(-^2KWFi6?0!g2cFl9^_~Xs?KtwOQg01bFkL#SFjqteQW5_AShd7 zm=dFqJgbhb6u_=yzB*>H4Mwa+nkc;LNiSNrh&$%g@)pA(abW+rtFv*WwJ|l*MK%A? zq|GW}&L%4Qp2zDass*%h`Ih}SPDJpnS)gs?m z9Br}JE!lHnBDyzGoeRwXofvcZ0XLQkD;`~U<~StOAOmmxhTXo5j41xIP3 zkvUy2PGK>uE;yJNUnqVk(Z7;`jN8knHR4duaPPkSh(w9)!fc>MX}z9mVR?QmW3iFG zHj{oFncCAA`W?>twd+erkdDQueswh{`|(g5^>P@R!Tl)x`@!=(wm^oXrB|5?ONI-c zHo*+%i>XVCiFpP?P0Owc%RZY1$5jt~HG6amG^28sxJ3F``Iclx4Vq55dU6ck1+P$! z>LJTkb`gfCmeZ72(Buoe&hIOwJ%-n0t7|SgZ*r999E=uZ4PM6@waKgo)vaO|=q;_T zhAC(mx<0&F+XlZCENWz`ufQ|j>a0$E0Gi+em#D6pofzF;8cz&at8p-H2aU`>Fx(d` z>PT5>CR_WIa_5-((TVt@ud441dyG#V9_yX*c_?N4c(()4vSknLgAs0XKti*G$3!XV;R~ z+9e*|ZxbT8Yq}q%LnKFsCkH1s-iTCRPKE1{k;IYQ#ksCFrLfUsXf-XHTBafaQ3C=I za~p1Nf)EmBOhTKPxWV+_@D==XL2`;TH7rMu&BEHvh_@S>IE^@enDJmyu;82XahvmV zYth~{&lWT`A5k>Bt+RQXd6OsCOo)W1f1Ll?-&|bCTxDIzB5`9j&^*lBY;bBpw1!3M zu7&iSU@^5V!B^(8Sb_?1(d=@HUXRO|0dEgS~-3$Y$efm6uYzJC)wF*J}4Yy2C!q6n>gq)Sv2VW71ys5_C8b0Kq*Y2#Ky=RF-ut`vetysS#aZi| z^B1sSv99~RuFrM9U)kK^o&|OFc##kVOf-hvU6=UR56)e0D74+~mOXt?k4kHyKRZa3 z)MdAbR5DiN*T(aLBnU-oP;IB$~$vQ@im>9 z$3$NnbADtB9>D|zB_{X5atatUSY7NI((3-kobSYwcYC_{VUA#_wO71#m1Gz1yEOqo z8_6M>o7=yr?&G_;+W~dIc6Y=dS}3C*>3(|Csf~w#gZh|g3;_tv2immO@GRYVECf>&lOTR9w+q3@1|}L~ z>3y{5HYX-5@q{xUQ7uPYQk=Iv`y!jKfl4N>O@*%H=1U;}7 z*Jj(2#2&>g!tLNHYO?>F(~_MD(CAiDsgx%#Os(AnEJf9kQGd0QqZRxHua6@Lb-w!~ z!IS5%cO?y(YqPwRHgfw$#wTqlmrs1L<87ZZvNhb(YW%IeUJ`M(1?kese+i5x;^UF! zPJDitEL`2-C_lf`oC~8cdU0b*jYFV@DIjx*43fhy@5p-7tqdfm?$nIG>@%H2L@Uy$ zx)GyzFpQEJId&JD>mV^m&-%PHE_wT<+ABG+G>;jTcXwx~o?J{YPcsh^!F;%btsL(F zsA9ZO?ExyMmJVs2YQ_C)?!2>02|6jWkYbsnd9A*U55DFkpPSmXKje}yhOMI0RT%Ky z@LN0^E!13#y+75SYn`14vs<{5DtZ}bt3R4wtW zuZOd|WZI7XWK*xGGJF7?lFq9;f6>HJ;+LB9y{=yQ8l~u%>KE8n9 z?q{r*k!@dFB3735$W-W@o?ILv9tNr;p28jOdY*CR{!Gc_=6$61eU1_btsENra+Gxx zpH1x(nkDKY4om9m8)C^<)wHgMAxc5+Sx@92 zf2j^P_83q@p1e|Ogc;JPX@9coZ!Z6_DwRb2DgSgTUJ<*TuK-#$=7<#Vs$_=OeXq_% za+5m7>a|$8F>}CBbpG%(hYCq{u_cHl7+0|3z4U0BK$IR|ehr89ECJb_qYbK`S4f?> zLUkWzHm8HDT`k2in5?3J<_GHR3}Hf;DfqNp7Q(F^=`S8^oKk={YI#@sJjISV{=Q)6 zM;Z#rlwie#GPq!>uvs<*|ASfPH~^91E#@g*krKtN%@H`WmtMV7S~c0_) z%^6qJ-km(!(x}fkKCC9jyVFr)Rx%QF(i_RBhBqlppqGh`7?b0w470uUDXFb&^AU39 zrUvvxaa}Y*yC`SoJW)>RjEP*b@F!eJxmx_A?si7!JJ3V6#%R4DJeuS)5=(0AvDt(J z7JDybbijDgorr&HSHi1G-=4Rif2WD|ECBo@}EpXy5~iMy?p%rc9=v@#T^`D-*Oln!Re`Knvz z*HSWX-}`t|UuLK`QDWt4)~h87d9$(+ySIAO?SKd9Nq{khS=ZG~1J+_npbmeQluzPr zdP%8$E5v7ennYYhB}}y}wg@Y6D`h@Y}oCFtzU~?uz<@7DwEEG}!L$Y~`OXqL+dCD==Dx1(s@ww`O zo`=daA70S#_fbUE8^tH0=zgrc^7BR^AicW?@s*M+!&?dK4!IZ9ZyDvhns-1dE~KpP zg4C~6i+=7NQ@j;y;G-!uBRr@VmgMmTi`X7nm1hpbPFwDYdG&WBjR4`pDu%cNj|6OE zQ)%DPFSDl3(~nX&SFmp7gmz%@yGI(og>#^_qWd4%DDGNe5)H*U;ioykc`zwPGx!QUx7)*bAA`1F*yQKjqh%`J0iU3UPS z1}%v~)O;zID%r%@`;Mr(k)*0k%cL-M%SV(x1} z`#kV0AK$YA#}v^qcn4U@1tE^fPQ_paM$$SQu0ulZGh!t3qW(8AUozJ{Fe^{G8$tVv zu)rWP4l0FIXfW!SsJcG%6;_BvZxBVWC$>PKG6+yz=CJw-wk77NbcAPs?Rx)p;Ht}??y=3 z7a3zg;5|YhRTu$4xJ7e9#dj$P(Ib(?mJf(wVdIv;ZoTG@zrx-b!JISQ)3xu#SrFuB zk{Co1f*K-~vCI=k42~%&igD%dAyH0lfz=}vO51D;%F$Oa|Kb@Nob}iTI8+s&=r(wW zCvzxnU+m3bt=de69E(WUF;SUu?DTqMTw!b)AZi*G%NqQE)lP4+f^303g7_TP5EM0U zPq)+SNn;rwzaG|B!LV8Zn>KnX&=A1Q6iwPo5K-}zTaG9m32orF>#A34HijQ8F{G~H zr zB9dsMc~KyWW8_n~;G-inwkd!=84qSiN}a-Z0!C(P<+?fYkJ8*aht~Q^HwWJfoyHHG zHoScSRV4siA)!T})R#knI>^}NQj#7jiZ!HBXrDQ++(R6(CwvIn|$0HQQ7tjd+G6tuj@5&)#7to8majULa?L8V+>JStNi> zRDvdjBWWz7ve1>VIIdh}EU-e&M1u$67FC-!94B>Ov zCvxjVqXl3ru0#Mo5d}*j54S?#s4}($G-;tIPA>PZVh9dayk2Ey?s_4K03pyVm7VDs z0AM5F59>!3-)km#teroLRYEdDNbXjID_2Uq?nNpvr%engYSQ*sO4xm`~UD~Ffa!$K!Vsl>T6-ihvJLVs+xHYosizR|f z*fElu{vkKpvUqiQVtlvYJvTwZ2PV)$LJE&%BGvtSM}AJvP=&CD^!rB8BtquAwZc+3 z$wC%MuLE?tAcm2&urAb@D&GLh8i8gcvKs&=nBO)HJRm`2!mDv zDie~guPRN~D;eboA7|I;8xpvo%9$;n_w^H$OxJWu(o~H-N39b|2oi8qfgfu&ZWWtltx-!wPZ7vGn&ep`KBuZXfFI?vLVX`aQnFSoB1l9~U2l%o zZ5+d_ss+8^T*t>lk&wJi1~!xn-h_eGptO&mKlY(&L^R?4xj#>%7(=T$3E z{9$tAJr;af?EY7?95C`3Qrn^H!4&4JG6>A+8n97-?F7z5Y$M)^3Y>#*(M!v z&}NyamTjUsMqp{;vrY>gy0_2xe_FRxHmSw`cm)XROt2Ja05sbF@H%eil^TZyA_&Oa zbKD)Fv2WR^-m*+MLWSCj4rqJGj)}Sf%vU2>Is7qf^A8IVggHUo(nUp8M?KT0S`;&q z4vxA?DZR;59n|9rIny;l69iKxv}N|KoN|44_q(+EvWniqwlBRFI0$LvO75WgWLL@P z9ATKvDvz-&epBe3EHb$Og<^Q0ic?Gxf=H4FhK_X!|9Xq;%4k>$MzAV%=X3U*jsaq< zZd{!qTC7GF(_vBO#`~EhQ;x$mGhG4dP>1SPR=HuB#yalP&f7_Z&gkO-HM(l3)d;yj z`4j0;Vy0ngd$KFePK%iyx*8IL3A(_5aIOXF;#Fiak933D=#Q7Aex^fy6O2AT_>3l4 zAfJaG)r^US5oE%lxdr3x($KcJaZ;uc{Nu4cI?7xNST7immpHZ;OqlFTaF0$ZeS-YW zDm`Vu#2#{jws(@#Z4!VO2{0XR@E~aDDJX*ujWfMw8+dIRoQDz^l$-+rra`9@u{UpWD<@c&rlcAf10$xb_U?I-#b_3>_1JC!7E%_eKSaFxi4|B=ymaLZ38KVPP|- zo*uAzr&1oxJWyv|zBZ6u8YcWK;V7q@qp-UP2j3SCb+u6QTU^kdtPoac!_}ETNM4MD zfouQ(%sn8wbE!G`O*VfMcx>^;dc(RYT+kV6$y=K-Nx`83&DDT8Pr#JM$$1|`b5|zt zbyiB%pq^_H3!hZV*WLi#=1H(vlA$CdrqD-^tMx49Bul$Y zkQvD&4JE@nSd=RWwvHdPhJTA5HVcQ$Kp~#9AJDlBqVHfGW=)+Cc9x&+Th1g;*CL)y zPmvaW_yLJ-{Rt%soH)69@7B9Y?p8LYF^ZOY=DXdntWj+Hh4ns$rR~{xYzBMFTk!5D z6#s3x41^x%Q>Dwu^y{mQ8%gzFe)0+MK?xn;wTML!B;!PyM&>mvNCW?t#*%EEoCJjZ zGMj-R0m|nBt#^ce?Hov!*@`V{0l>Gc7=(Mc7f^D9OfD>u@TE4|T1wAHS1X&CfWG^` zpagk4&N}q~_zN*18v1uZ&JNt^iSn28*gje^auy%}$w{R@qOzUr=F7fBZ_W`a;0L%&; zoZw|3$)jH%E%R8KT;Mwpnw)}>5`q2{S=ejDyIs*W02D%5^9gwT$p-?UbXjB6`7Gl8 znT6?-Wbh`mVL!l}Ry-3F@tUR`(pjjR*ds`|{hBeqpu`fsAL%01{f_A520Qrf8iU*B zLGlFl3;|wu?$UwEMjlH~P^X&)wDrfky+A7M`XdH`&-m*O?txI6dn7*t@8ixMsm_4U z0yl9EUy*;HUd}mm(WY%H_p3zre3l^jfQ#9gn;O1MfM#2~u7DEVCWbK{ei6vtMr3W^ z&8(>;eNF8+%w6}Q|3J-pJEI&4?M9q{+|JB(51rQ6E1h=6an=q5PeJQm%39EF`b`yq z3|v*BlRWUtphp!6XRl^H92T9*%!1RI-T`69B?1utThNQHPmYRbhWkvj{H+z>Z_4 z_otJZPDohxqAQ-2G)^j%)%D_Gipx}-VLQVY7v)BP|w`Z(>(FV zpN)SnY-ue3ToGGpnF6-vIp4-D;DW(+2b-126ukFFgMi2dz0yW|Trp)K$=W;o6}@qq zk1wM44QlSZa@j(WqcZu$+Y`d~9GUK9j-~#*EG4V1HpFZmDBN8QBbH`o1%jfO1~p30UAYtQ5FA$ZNWMvX$&fL=5Ezcnlz1WHY4SzHq)}+et+m0QL}3 zvF%Dtw_D1tLT}HsdL7D+%$AG`coT!EUjMM5n&DTP@edyptW9lt*M&Dh(WLv9J{Zf@ zc2#Ok(rY(>5roU^3BYCWXP_!N0G$AOPE3EH^aDb7iFJpc@q2$>8Cgl-wg|#(SzVFn zLs?F7f_4G;4KYVd4jV$m0Kp?;*M8eNN8WY_ahOB@`3)u*;6vLiak1X2l#VQ*X(YCu z($NM2*mWX}y0vN}=~@7E9ss8(sFMk@+{cj?1k*fyNu-#}8Ba zq|Olub)PqaQPFRZZKT63hne#<)NkUuURNj#F_D-aw}Az0;EGVL66l1&EwKlA3~y+Q z$Ald0%yDH}O+1&r^a**p!{T;TR=#*ze_L%xy}Jdx^F-B`w>q%XXEXdt#&7$}sx&D8 z@lf{D)*}vCw`xFm;N?Iy$5Rug=R0|d1H*^865AHCs{66#Sjty8AH>ijCp+b4`|{PY zu)r+eX2x!Hr^6@Mp9x;RREW(PWedh3TPdYF@5#U^Ca=B;T^CQj1!CtTNt#~Ov=K(X z!sXR=qK=!|NBZ`SD`JInurA+I7Vfz`!*PoO47iv!$~$)gBQrx4S;bFULjgq*`Fp+B zp`~A;8}MJ&ph53UIQMC#=Aj506KdTcb41&{FsKYI zu^&J*tJb*+y2L(fBCMPoyE?IHcPbY4Q_u(nL36)u+VDT!$@M@TGH32bNzYjE_**$A zuY);_uI^}uu?vf3=-VsMn`CI&+=>O`QlT0AbrO`A?P=fQD9c>xBu40$uIlqvQYff4I$4Rd1ELoMdp{tWUhr>3}M^87%k1p+5^4X0ZC$L;y#sK>9* zZrn_ShCrhV@XB}j`~)bJ1%3vXuYd=GLHp(@G)0M7YaD^3INDM3!CAj>_3bG=Bc-6{ z%n=p+Ob)&VxGF8_Ui>%}tO1tbMcqtlcI$@{IcysYHeH*NgG%^0(q4hagEYO>3Ys|` zM|H+S_@n!$B|x*U@xDVQC;R|TC(A3ROfiA?ZV~I#8sp5jO{ThpKRiqK6MNdSgSVCQ>~_cb z>vXxV3N22$=iKwuT)7T8A6aa!Zp_>j2O!AGS0Y%LXgnxVUy625Z^#%8O=fMMDp230 z%8S=?BS;{oM!}XfpT>?nX?-3dnlY#U39r7POk|kf>{CNx<4d9Ceug1=2VRe0eoqK- z-SEkM^B>ta6C(&{yZGM*ubMQ9h&CBoTZLe#^`(M;`LZJ2H&q&NqBJ~srJ@` zl0&D;fsZ1{uukcwqIzqI!#+LC+Yo86zPuZ>PuT}uK?i;x zX8iSaQEmsTAe3#55&cQ)G5XWbCukxBfDVDh(}RTrz*a{9uK12#aIgGG@JPY7cog-u zooFwmE0;=wV_oLGZ`0b3E@bV2+O=6$pxn-FouXsMe1FnMfY zc!B7Mf($DKPaHWvAhd6{A1qjhU?mbWRa6O3MCRlx$0?#}1`;w6YSL6UCloc~N`%CF zHNk`9oCAUYB^^3OHfbe;(_6a2g9aTjFZ`75u--!CC`qLB<482KNQ7n{0dLh4nZF#= zR2+zh52bP{S*}(Z2@cr|Fd-35LGnX(>UmrJBClz~)qljMW4L#@>is%5Lg*rw&zyk65_0fn|2{|!7;8Aik!N+`G*w1^vcsx}h-!n7nc-p0`YoPb<$`Shb@q~pc0 zI`8>}7W#^OPO<#w6~E%{lo8=Hx&sV~oxl-^!D5TOoR0#GFu&IW*wweD9eD3dD~2+ zuQo}m2BQ}V+!n-SkjTP2>xY^>KGtN1h;hd2a@XqK9OPu!DrPaBu{#;RX`3f6O+~yF z`$&?zCybTjr*17k6D{IAcPkqu7Oyw%N^hGymjcz9Kq!m9(v$t7cl)Ov0-`TBnaoD7 zuMjWdnWFjHX0D6%`L^_Ya!aZ_T+E7#3whQn>N`J0HK*pxqVz>S6sf1Z#m{HJuS{=1 z`*^UJ&OlSr0GJF{Y2(+iHON1mdlWXBG!Q51YCsZ$CvR3TP(+)!dyI z4{^j*1`K_2jr?DMJ!*{tCXF7a@!P&Ldh&CTlE5gKd@;E5ww!^{O7fyz+RXgEA>s>8 zgh+niyLjEfIE&9X#&MIz5GCVy2v@=+p~IJ@d>;KI-a9F423^JmDO=Gj^$Tf(g^$wa z#y!hed>1l0qoiM`U6>U9;L%Tp3`WUhq1x}HxChfCU9t2=B3yRu(ZCh{Oi3cFB-4++_GsuO`f~oK z9?jki*T3?5SLP@dpIh|D(5p4o{OQqnbIdngdo(wj34ePu@19NbU3)Z#^elgSGy+K8 zhxGp??>@&w_S&O4)%}fIne${7~>H#olJ090!2z6{jk{+)>Ifw6Vf4rTdjC@b`+N<=!UCbL_!_C1dPWIkWrK zb$MtYqPpZykLEf|&;gp-_1mMNaIA6uL67lhx@f+FU-UKNW8jK~G1N6&;q05m&Gt#P zle6v1b>6*8WchUCC!yD}v0u0va^5=fbdNY5cAjrnU&9sEnQg!D{`P2A*D44xa7A>Q zG$!wU%Yej9rBM4ndH0m6sF4+CL9Xk(UctU+brY;C{OwLojDZ6@9&zv9V^Xp?>?S<& zYOg*QvdE9qJ)B9AkbjAI4#&V1j&4B2G=|+Fm^Z#}mj0$BPB3Oc%Kb#-kGy;Psi9YI zbj!bp22XaFeH3(GG)vTCEdqlW9RrBTSwK(*Dh6!GMKSZO&k_VWDg9$ZEnJMr;(*h_*5Z_aag5UVT zank0Z2~3Vcs3r>V#6TGU*J;rjXfM8FaU3lBD6F=fitu)E+=H4#5x#&z@O@-I4KR^< zqQ20+NKTCYGEsLTaflcl8O31)J&r;SAgL;n2G}K!qqXBOOE<_tZcRHM!-*maf}sIk ze;rL&R0yK&d{FR!lgduLm}YTgNVKg%YclFBBJ6yqV?ZF(CMRClAK8GMhuum%FNWi* zj>yxTB#Hp>m?=j`lqFA+q~yq1$3v=6nz}q`FE`nGlSlQP8@UTJa}bgjs#P}XI6NTb zaR36fO&=mvo~ekhy%wVi#k>(((&efK0!hTJ(*PF1wDRktW`cv{th+(XaU-bvfF5ko z`h0<0k{B^hIF1Vy2rS7TlBA)>U55aIBmn>z;XwL_)EIVofhdwpQoL&9eFO1z!iVe! zX(to+y>iM|rvgbL^MCOWu>(=lAvAw5rIcgS5t1*(>nyevIJmQnI{HXSN;tLl@9PW! zXAc(vC#U z7tx$mAT_FVGsfo2B+n|f+js|=gR-qiV@Ra@Zd(ab^@8bwRM*j(5q zlVgSAwqp?*Dzk_gWxC{f@;;`4@1qK3(+(K=$eNUGskx zd<;OAtN;EQAai-w@&|BoOTHrz1CUJ^5&Z)ogAY?h{1<@COOwD^C!I&T-6H9?OFi4- z+L@>E2aB+1Sd0P47OfYqlsnY6F;A%%E7s%bm>GlpvehzpeOg`j$nvfK1jzm__!t&} z@iY@*{rThgZ3pnLfYQO3ca(<=Jpvzp;I?FYTb&u@2Z16)N8kk409msRrHLz<{QoEw zv1tBX@Qw4h|0?*!YL2Ep{|!L)52>isB@8OKl~yx&jQ0nNpz0j0O=q#fY4_g%WE|G_ z-~S~Q(O2T_76_lIR3sUi`0W;P81(HH-*E;aZY83YRZ24*6xTFUv^S=?s+?wtDoUz= z`z6VCzg_A-fRlZ8#%E)H0Vio#BN%{8_A>5qZzIYH6VU#4himK_Ae$6mHmI65bmXn+ zGL7B3RY9c~+Y~y~$J0_Zd&1i)ZMeaS0Z!UeUjCMfYPqlxf@4?me@aEKQJ=W}1CU`@ zgzr*BfU=&Y`ivLS($~Pr#4RqGM@Ei+0w>rPZlkDg>rq9VR(b(?W@=lxO@!DU8Av6z0t7Am78`7+p7PNisVG( zn+>H|@V+U;8>8L7e+a_oJ6{WU{v(de9O`6&1|-Fp%kj5x^v#Y)3Xg9SMWusxSkBckTJH zRxMl#>TzMH(oh3}P{a`ekMyMEE<{2JR0YHZR3w6NJef30W}p`KwnQ$|OfG0+PQ}Fv zD1euzNO>(hlZ4xid7>b~+Tna4+W1I-do_eG(nx6*!4z%AI*HkQpfS--inFKFr^?A3 z!h92pb3UD*`nXFiI(wYpJ7GP($`6sbJboGkF8QP2Hws=#mM~LB^HK4;3&{9xK4cBT z2|m?H&b*qL6C54Y^gl@{0NdtdyD3?mLnDe#$Jw@q6;`!FsVn13d16DN?yQ`ow@j4s z{cs;NyFAJ0(JzV)iB8wUVWIlSylrgE6h#sighQxrXKZ|o4aF3E9t)+3lF>Bnmi6q1 zpop7zLmIW}%v7wq048IBi4|L*^s@pS5OOWXJ3ZxQ2*#%cH=+p~-sy4QCW>GL%}xdW z^5Fh~6O~{3m^+O>6fppei;z4mG=K%ed1kcZtZZ>Y))eJrOvv35QvWRYv*~T8 zrTp=g>i7RB_~Hg;+`36~#T`Ir8D12Z3#ER+=Xn7iP6th&m1s@QzYG4tF5d06U;__e zSnYMehg2I6qZS&o&uZ*7s!bNh7Mj}5YF+=g3;wr;#P}K;4z=YmlW&cglQni?tHl)QenEzsmu@5d=^Buf+xSw^!opsUSLSXI_~;t*hAd? zS1&O3s(SIcR_~v^0MWz3zidjY9OmCP<+|aYb_f0eP$m9{O<56suz77$c4hg# z_|v8|X-uEJwkf}OVtlN6<7RqM^}oa%TAv+^m{VN__PyaQ|>mHon?`*q<43yTMM3X6gc=Lk~0Qkvm;Fy z=DDtI%Iz`=2c!YjzYx2oMU^Plbo(Nen4JIES4EU}|7SMkKaK|5$j5QwTWQ6U4Va^0 z>u?PZRy!1|;uSRoP{8SNy}KOnR-u{+F0DZW7c-;mA`v zB<;adcfyM~8ZsJzpKe_r4Phy$SCy)xr|w^Z@&d1IN>NWjUc5qGmy+^WSBF^7OLl5LB*gVE6&8g8R zRlD)pzN+CL6TJQ4cu?f~N+cu2jw#D)St51RZ<}&Ffi6CM+`Fy%FT_qt8f7u9|IYZP ziO-j_MT=|u>H?@BnsAa=`)x(5dR#Ql=xdvLoN715uk+oh{r*0_72W4E?}h%7HWqP# zmBbi9^t%^0IRBWa!q>WxY>zo9XsU~jH*;YF`l0jN z-a3}-<1*jdzkIhpEE6v7G-HCrF*Lw+u=w+0)>!+H81{^T+3vUBhzbBZdNC)$*g-ot z3;aYI0059hKrZ4b;06HD2hkQM?pX7#D3n_VTL1t{2Kb>0a=}3sVZ9dxm_9>(yHwBxB_7UI0|Vznd6ijOhiM zM+W))k7F%TA)oq?LxOxYQFiJqxy(@9U{jrVSFkSs269+(<@#t~snKUjl-NJMg=sPv zpV)(hcv)yd)iFoIAH6`LC$SK->L|+OBsvgNif?hHtBMl?;7}~EW&^LL_uxa!%O{XB z6bKFj01Ab+j6>FAXgm=(v&ptbh(!Eu4J(p4i(q{FqtUl=Mbh+~a&kPqAZRL_DsYy~ zko_11NJYU@T|?n0uE{DVX5x~zGK4&A+{N6MGzU~JK3Pfbl^m06?zSVfzO3mtn{so# zqg?j;=;&17<=1=wqymBeS1+*pVWE6F!it@|5(-kJN2Nvs3rO<|cD3MR)5Dlv01l}{ zs-XVp1=wb9_+pU4{V;sK-$#QUk5^$C=4bss>IME!js}qm@|f8$cJXY)g}5Z5A%w9VygS^NzW<8pOYR@>&4%b9*jI} zma2d7hin+)hbFw1hb_Z`e^>YVH$#8R!?zRsa(>IhKJGXEl85V7s`H{W`enD87<0_$9u#Vpo!4VM~9sY&wSA36_5Xr4gW1O z6%6Chv`=-Tb*z6}Eb0pkf|kq!0d%Cf!JnfuO#L1(f#9KJE0{ij+^&KN33Wj(`~05t zV5<9nCOsJ0a8BcIq7;c$n8*8@zMq(Gf@jBlh>^=R3cky-Ro^04097|RjizvbcDvjx-Z>Q0^Do;G7q{SCF8=9hdX zX#ZOt9{x#w(|u|c`R*PBlEnfQL0b{L|NG(}B~=~GT4w)ymLu=I!LF%!u@xZECw4vQ z@pH(lpx&nw?;>Ni3ie3eOtVrDsV5l4V&H_^i# zUiV_wk!B78hGhN^SN^^LZ5cqB`t!WgmHuJh@7rdFm=+-DKk{(%p{)C#Er1~U>JY<5 z__iEsnW75`72qS#D96Y0MnKknMBYyKhp@t6cv6Hda2*d6u?YZBDcJOB%Mk_I0I&(L ztox1-#37piAipYMClEI{Q7(Y};qcs79XGkY_}e5BdyI~o$HbX- z>5sFE%E*+k#c45( z1;hDhM=%+46V-zS)3>?@dqa5B0f232f%(<9*#A@Ae@}Y;bMa40i_xDrElc(rJOs3H&o zvsei$eYsHo9M=f(KO6}C|38lYPX@fyYTv%p{vd1mLfK`;vLA}w%ABPz$R|&c834G;9ab?K*CQvD#{ha+(kC+=Y z6pcUqcr8a9I5U-1%umc197+b!6(^9bxBzizv=I}M$)N!NuTdI}%R8!VDNpG`^$Qzh(vHRHWCmL#5{rsS>@8s1+4mg2!71K3J)njGDCwS_c!vb z3piIeIR&xm3;5bciVDM$&D)4dhg>TxBOZm*R7I6gq|nOwmc|&#Rk6=oRq+m?C9PeY zmVw2H-evC9I@~wDAD=T)KOogt(iPZzIs)|LNdi!fI=#?GEYewOQjldM-a2QMTCO;> zEBl!Ra>%?Cr2A+FcH)KH&9@t^S;0qEe(EDnErF?5#G49IJJib-S2wLT(1T#|_Igv%a+~v9fQ=q(eRrCYS-hHt?6?Z{8Qe%;(49Ki;Z-Oq5 z^5OSPO}ZIj(ZPXt@Y--MlQ^K81!T#IIAAO2oe0H}N%71QMq4>m#(5w`x^)D}joL7y zDl_)*GO1hfSn2{bmOc}SG$N3VhzBO`i9hg{Gk$xmadamGZnG?H(nS<)MU+@rQ_ca9 zX}In9iI@*@JP;^T6=Ui|CeX5^bmnFs*NR-_N7)WpZWg&Yds_cIvT9BEa+o0CUcAG{ zK?mI54{B;;k^E}kBhxg&NA;svC+`SnUSgUcOt?fA-w%x>T5-y!ng`;&fa3n@44rH4sm7oX)B7;4F``Y4RW0g}$4~Fw!?ETI;Z}Cskf&T!uM&x0Y4{~!U?@;UC!r(y zW<2YK?E!0n)Wvyg?Khn~3+HI~an@8e?4-t?jg`V7BD~RRz1A_lnrt&BoTtO9&il;5 zjB;#2pypLFACD!08FHzQ#5NYjKKToWZ@MQHUx z^rus9;GS;wrApZYq5(k` zlyNF)X&el1B44Lu&3W*)?;AQMfu1w)SNpMA7~{W4rOSG4Y@=joGW>QyK0Tw>L*tg& zPIg#JT}GXEyko%555snvg>d7RL(8*AA8thlH~NwE+q?tJN@rO%CbPMQ5Y((qN*cdN zCjWAukuJ)L;;SYFUfF}_W0^VNtNuD?XL7&xyQ=Vv{$`OXCq2_uL3h2X_IUE`>{y8@ zENIYHK-~_>u_=RAbK4xGF;S-ZU44zEl+q|rE^wX&koqd|qm1L-B#ZZ~+F4`TU5#nY z>RaEdDC<0Mg#td^{Sc0~05(3q^<+_f2`#vQ4#&o;3=%b2u|PK>O`prT;Aw*j6%QwwIUS57+M}z8>;g@B6<3ZBzKHX>?v23{E9ke0Gc7HnmhtsW$ zv|3wv2kP;u_a~{frb4VGi!1w+qNax^ql=jJT6@t@s-Z9P?oWTL?izl5vM6(3473CR z%N_+CgC9zNqJIA*qB;M&*)9UmHcjg7T1alTo=YM*kdz=?Vs>{=MJi_w`jop;0@9=H zbD2~7UI1Y9QzF4%40|#7m#gG~2u0+}QR1(cXs+Y+wDM@x4u`qtEC0qrRJ~d&p494Jff9v#Dcj zH07`PXeyDXymh{553d$jwXSNJv>em!25x1P^KA<~^qcf~zlr$fmDo-Ac~+rhi3!L4 zF8;Dp5%$lsyL^|8@m;nnjAnbtsmF7*8n$Kk9@TwFkk5Yc>x*#t+w(o|=AedxZ$9JC zF3!?{Z$IUNcvyk&qoexXzCAen)cy1FJ%=ay{AP}CCu7bPIbT2t=?9;)_IpQrk=>4@ z_lSC{{pYj0pKKl8^EDxoJ${+)o>uPO?Cr<*-48D+=vlcRZ@FUtjt7B_Kgklb$W4-p z%(gxQ`+0LPZ6=OrL9h~j2#t8ib1_e3K`@80Cr4%owYX>F{SYIHP;PtMDGDzn)T=KN zM}i7hwLH`q9)Q>lMXuq=Aj6c!!;py}Br*)Oi>Cv4sB;vCgo0GX!%&%zjm5)VT*D1~ z!|%3)>i~l8Mui`hyWfop1Q6@@G{&d1?#FNzI<)rT0TCWk#BOtT&NemZBaOz~{y`(vKG8;iMheYx`KZIvF zneQSQ^Xf=YB1M=B!oQp>h^9`FkVu(_LIkNH2chgsUjDt z+SF;f5^4GusXWwaVp(Y>t!ZLh5cvlwb8Fz)F2;e@G_?4*~e2vIJ17!HAE>%n6M;DG3?x{K_3>Sv7-&tBen)mpKzJSmZ9fip4*X);|=w~OHTjy~Sah3?et(ykVCO$L0lmE>x|2!-IvNiu| zIsey1K7ghGC|NKG0MB9a`FDdhALQ*2I3?Qf+%L5~+#{@hklE~4NR?ek(^g2=R#=_& z%x>8aw3!u*w1JrT#|R_GybYC`b0%HW%$IWoC5wekibeg4#j}f-2#V)}2nn*&iK(-7 znUXkSNKo=}mWGd%@bg>!GB9tUTB%EQB}?^9N)7!>jk8Nl+Dc7VO3g1zEojQDB+G0} z%I;m3Xss0IFFNAtmncQB;thJfh{QRr1l#$S`(~H>x0MI1lm}jxo6+Psju}XZYP#$f zATJGH8-|Rd3DPGkixX)olO-!tO)4?DRFm)u<~9|5rIN@h1TwiohtiXKse<33vLd_c z{{nzOf4{1_s;tVLm`V+rV1NpWG6In@pIQ*_@iIGuURO4PNDvG$38=z4ti)QZ#(J#B zYM`;&19sRlH0G(ddLz)WGjXM)C^(k#Ig_ort=!tJ-fF2bS)kJptFp>#oeHhdY9mWR zP);YODQE+98m!1Vuk>24_Ij_yI1nZ<=}-gk`?ks5JSl279mwo3PaRs=Xkq zen_6S>aQ(G{^Rdn45Xuu;c)A$YC$y0lE&v`+i91&XHQ znt*>ePR^B0L%SK*(JV&7u5v1LO8cTEJGNw7wq|>_XdAL8s|n1SvOuaiZgeYIdmJFd zwF0$PUz@QS3k-G&wSN1zfE&1Enx^7OwV_93w=yGhYa=lGR?#-F%+R-jJGqowxs;2t zeCU}%nip8RxPw8lA2bC7Rc%(bfxpVPcWJq*ySl79uY{WjZab?cdJyR9xnwakMN_eO z8-u<&mdcQ}z#F{6JG{iJw!i?Zd>Ev(YP+1VwUXquc{{LD+L|3&z1DlZ*qgl=nwx3L zw){GH&i@6R%=;HxQgc&MBzAReG}yc0Dz?S@zFXN0tAGmgTfejbrCSLM^@|Gb+miZA zzgqdft6-q@*$Vr6z^FjL@Vme;NuMuj3U2Fi5lK#+JHBr5L2~7;U<&D!Gx$Aj2X2!-(0Mm>au1 zW=FBY8y2j!fEBRod#z8Yv#7cZ)F22QP{mdZ0wRC{H7pIv5Tif|ozWu zo+aP`9YDrD8KWpn4J=^Af;C~1Cv;ctnsj0Nq#q2#AhU;qoy0c8mcDEy+RKn)TQ0G%uV zB|r_daF?+_4G|Ck0&oC|Kn=g33{Tn&v_K6WK+6LF1Gt=)g(8~{?%A35*oE!ih z;L3eV$(r009_40Tu87_Iv;W@Bq{R3>(|aB=FGoya0+Y4Y}FN60pe%aLfJM(6xL3H89Lm zTG7-10ky0E81U5`@YNK60z-|dGi|KO8o~J&v@H@&D18#>@e>p~C3J?zf66~9Z=O*{R#(X;N89|?cG1XT#=9%Hv81inrahxMdC36%%m6?Ov5W#Y zJpe*|*wioy{yYFB@YDkk0UhnslC97*N!9Xv)eHTV1ijUt-3OYm-P;WcJ^y^$lRA^k zAPu0P*44Eu%he*TjTTyRJGGO(W@Umx=L)3_-Z;v_eht(ZKn<%Plc&tp2cQ69;L6k> z0$m*pBEZQ}t+yG@6lD2K%IC{dc0K0^2V=TfK zio4z=83sx&03fiD5C6c)5WwF~{@hbd z>%o!;nBPT9?1*_e&hCp*ixzL5a_(?2}`3NXi% zJ?jgcse|mW&f40lZWi3)-tncuNC23e?2vBd1e~dYQ0Na(!(2T83s3?K-|!_c-tK$uInJ}B{=IY5*0+Gj z-A)#|v&2r~ z1An|m-t5>t0305YWIpl?jO`?zdFf=t>b>!1QCRRjs$d(h{cH5Xd$P8^-?k7 z5BXdn`R>iDr919)I=z_x``8<$C2!9S5CISX0sd?NK>u9<8yU|CAOIAwk{`*~!p-_s zjr|dz{ty5I(jfZ~p#JL*0j-P*yszN_F8}cl0hbOCw3IC)SkT}>gb5WcWZ2N*Lx{_o zHDkt%nyMa&G;XYw2-huJw{QUW5lqprNWZBZ?OPDcb&ZJq>=1rU}&FnNI1&hxl zNQCCdfh5?(Y15=hi`LZXQ>am;PNiDa>Q$^(lQsiJt;PYc1H_6wAV7cwYNx1mC~&|4 z3TLNUwc13Df&j2Js8zswV1QWxVgrm=I{<88zykt+2!^WJG+NXk7#3^xui_5WXeFJN z)$?c2p+!UG+>F|+j2ktE3@LJCPS~+!&!%15_W#Q_w*kE2{e~ycpIDFtMM4ybE3Olj zFK6D|`E%&erBA0`eL1kMJuZ(mtN?-tYFw!`s3u?msbIh+MlKOJ0EKB00&M3lpoFUq z^SO@|z?jy0Y92srzrQkzMH%Z7RB%BC8)WXZL{i(xmP9}b376M&({Mu$JM>Vd+;r=$ zx8J1jNvPtAa%HKcmKyCv7-I}g8jDmRp@b1`9H9ghkdQ{RWuyUs1QJGI!I@>8VrCjv zN+^;AYGBxLM;&n#VGL@ZnDRyuoE$*}B&cC#DT`Q`(n%a~93jjxWpwjRIG<|KB284A zutHmG!SF*q`}FfqKw(+~fHl-G^iVZ+%Krij;e<=^IA)Z=&Ou5mwN$}rx}s*&X*Ttg zmS2?X)Kg`qVNMxOsnK*(XGV3#(_dJ1CYV=KrKVJ*O!Kr=TW>Y>QecA>7CY>w(L};K zaq(!+K$~^;S!i!F)Qm(G{Rs(2jdO)FZoBpNTX4e_cU*GIH5c4m=TdjBNx21v7IuH# zY9e)k$#h+OTX_taeAj)fTYmeU>)&$=HuzwK6YkbDRU))APd)Wy7Jv++wfJI;qaATX zo=kM~(G?MFCL)qcHu+?fQ&xFpmRolD<&-C?h?+X>OvIKzB*t*0VTQ^0XP|@raAV#& zW|TN2Y&cFDk%e{oX{e)?dTOexj{nYBWLMKOXIy&rS!l4s7CR@Rfx9+}p;VmL#W>q` z`)#=6mV0ixotlWoX=1VY=0IxM*=w&!B4%vD3oksxlgt2MCo!;y;>4krmZn9f>bCrH z%rn<~bFGwG^4PsE+!JuaOE-ONqJ7)RV-$-!E>pHSmwk5HYquRM7nhml6o)yg@D|{^ z2K?*Oi#J~5vRC&hTNRgbzVfVKmVSEbtGE7o>=Odb`Oo(T$wJov2>f{R%l9*Nv#~|7 zd1Wf9h~#LVmw$fx>$m@Y{PWj;fByUT|9=1mP=EuxUo=XljP`vlYZSso&RmnBVwg{Y z6Vy-~EEtE4?SxSuQyXVu2mh7lHKkLl(1au=Aqf+r@PsL3;R;=-LKwC%hA)(14QY78 z8Rk$D0MH>0ZP-H~{!oZMBw`SYI7A~R5s6ED;U-K&un$IMPSntwopScOiBV9CTbxbm zRM$A+pv`sL`d1X4;w@_6K@5WMVjJD~MmWY%j&r1A9qo8WJmyi4dn8&O-VlaLgokow z6rty6mbH$2WM^DtWFv2aKAto#3fVHs{H|gVLAc>Zz2amiJ^4vchEkNHBxNa0c}i5K zQkAPbB`d;k3Z*!&D%`tB?=*6Yz6BzDCFz+Xefdk2c#&}%T%S?yHo0<1;}>p-hN&iH zHE2dtn$x6ab+AJ-H~*NbV2`PpYA6JfMFO)~#t`S9<}imipfiIT;+Y*LnUfaO{P<2NF$`_A;|j@g20Mk?O{1{AVOWrp<`UtL+7+$9{gA|Gk}qj!H6H_ zrqV5na%(gM*=Jkb`qrqhNGVHgMnIuQ8qU=6VasY@*Lc>m2x?WZremmA9U9h;2}PtH zysMnJwFx{>L;tPYlOJ3yds*AbHBfr0oN@rXCr+OPt(w_zxWR7cF# zkr653QjWbSs-D@BORnkxRc@1s%#Q|FxWgrGam#hLkd9PbwLRp+aQ47nu2#B)L+1rK zHq68trL&C%;qz4C1~JJpewF*hFA(^=UkD_R_sDuX^K!XlEU>bL>ft{(L00|)B0cdFe2nO*05NKu~%W$eut$l55ciMeSqo*`R zMs1)XOlk4qKRKW07@ZuO zYgvFZ9&q(nO9lj|b_FXapb813oaD@Cz?wPm>ISU+0l`>637i1}!azRnyXSrHFaJ98 z0zaAWddqnrJc3JMU?>;EC`R*{Pk89prVo9XgS+`~UB+n|MNdX%RrHa-6%n|92%@Cr2WSDwpaE(ife4_65x74H zAiy?pIS1S)&DuMfdkUB{7AvX{(!z!06Q5#mu;}|hoS43nus#cNEe(o-nPR+&V2I_| zH{}quOxYAc*p!K&hCo<=H-IHEp#}q@27-_VvZ97Tm_d=E20`eQSdoS>fH7*Iksld5 zY0#uwF`yO9HY>csY^w<5`X^wxDTP>$zDoqEat&bsEDr=hG`tDxehd$BC^4aY5@CyYRpHG`?YYpit~UU=&7rWQh{Iv`mXbS3sZy zvBO&YM}Q1SS=_N;NDpi3ibfMHoM|Ayk;i&`NR;3M09ZpdY(rICJpZP7rs?p<4HHO@ z{78@t7J{4zjf9SM+(oJqp5+?`VrafxfJm4$i8YKjKZ>a^NWo|j8Er!fU5d`hT{N}{|bNjO4&{I+zg!Gzq1A;X2`TRvh4%a|NX zW1L8LGpv}3L+&e>u22LwSOwpUOSzm&x>T5E*n}`(g|$2`)7XSg^a!dN23*jEo(W50 z&;?xpfW#!rdX&hDL`y+%%Y*12@=A&g@Lj{7ldcP0<`p(kxBWJWbS0 zP1RgY)@)7Jd`-?216B|d-x?Q&!MSo1!-W(~002v|1b|&&%>R0%Nt#SNH&{h6Vmy;6 z75xhZ=X_4+j85sC&gldI>b%bCw9Zg4iS1+w=j6`rgiex>&hf-f^E^-VOi%S(Pxfq2 z_l(Zj^fs)F2x*vvsoEOB^cuqyPO>~pd2@rXLc!#uF@u@8x;#(>P0+e@!fhf+V}Sxk zGrqnWhOZmaf!=T-eXS$xvZ* zH$~tB4mGTr+zB_x$L$-Fgb7Ip*$Ns1QX;LzAI%%w^u$0Qtw5mATtLiS*ack(P8lUd zVysdBbSTLbQ6#9$?z4&yA|NrHpI91GGd)u@O;a^pQ~&uXQ$akdtx&ln)lDXK(p<=e zVz|>P4aQS^H|zQ$9_>=@>qpPogh@D5L`_shT~tPGR7ZVONR3oUom5J#R7<^7OwCkH z-P990)KfSL26<4f#6ab{(>slzm)z4*WT@6kOEGe?NNL7|&_ark23I&dp-aVF-Bn%f zRbKs7Ukz4Z9adp2R$@I?V@+0MT~=jnR%U%xXN^{9ka7zf1w3n^)#0DZQ zQ+-lBRaH_XPI;TRn`|AC;);&sFvyz(G2jglWutk0gL=JJGoaUewby;cSAG3geg#;6 z4OoI5ScC0XgcaC?E!c%UScQF9hJ{#%jaZ7ESpSP{Sd5j}jOEu;kk68&#tmaf(Mp6| zVA61P(NrZ@G^EJK1W_Gb#V(DXrlF2Z83ZNcBb?1yo!wcU?OC7wS$_46NvNlp-N?{V z(w)hL2D(#}RarD71eRsPOFPb2WjxOal3x%6HUe6%?OLz>TCfeTw=%)GRNBQZ!`;%#2^8C+16 zRdLfsYA}U4P06KwQp9v!*?llYUbSmH*^C571U)AncnqAZOjzDb>6N;JXdfA zXCQ>l-PsCD9YJ7)K7e2UMIXYA)%l#>Yw}Bl>_t#u+U@P$2V(>WMu}onghB9wJfL91 zq60HfqaBTcFc1o#G&%(1487&t|K(XT$OA$UfeOF?7H|c`Ti$qmgXImY&3(h20MQwa z%pA_N7=~ev2>}%7gBgz39A=z-rQxvR4H&-P5=LIam8%dw+}vbRuhib{ozmE4;C15z z3I>2Sq=PrWU^AdvB)D6mU0>g=2>)f;TM}+!J~9JBkO1#^fCUf%MtFl@aD`yNAXf+l zXK;l(HUm8lhCVQZP;doAeuMdShCW~f8fbtPa0W5RV`pfnK`?^_zyM%y&OauESz&|| zo|Q_jR$5^MSI7f1&`W2yV>foI(q~kN_G8h7fo= z3YY{ChyV#-fj5YN1%QAO=mTp;0!7GIGiU$|$O8%(0SWK`8>oP8W&|YQ00_VUB&Y-! zr~nBd0SXvr18h4`US?*#BmXugT(i=OnKNJ|#Tg1c%v@gJU1qmD@M4noDh$pf%@oid z_+a@&Fs{fH;bq>5wxdb5Ga=>!fer=x6M;L+g9gB7_G5&AW4i}ngbMHu5DIB z0t#4w2xtL`YcmJ{3nmhX+Ya4UfKkPQkHUD0AQ(*HZltKg9#o402qWkpaT#E&<#c#pPVc@ z1TM{$=(w&UXSOqEpaCs|fEHK)2|xh~PyiO_>Shpu1>kIW&Hw}`0SssWQg8(=Nb3$z zfE@S%1!#d%7;6;30RLO-Uoi+YEU4pD;4^1HZ66SWTeGtgh-L(|?WZmQXr=&ah~dk| zBf5rSs;EG;jnLJ-Q&1R&DK&;DH3nng1zs5JBjbY!_5&_f?8SCj&0IyFWSZLimX`(w z%f42OxzMZU6~zXBxn@5*TiE zmH-NX0YdN%d`5z3F760eaHn2Bc+PEVs5KgJH3^Ua4zOeX&Lg@Wll%6Tm)2|J)1@X2 z1O;Z?#x-yCJ~B#hZzO;3JfK&}Ojj=rzwMr!ebHKZJ>eRcYXGP<3UC8ugMhL|3l!*p z;Kl$1T!0ubHvby%05#Y;Q)%@FaL@LN99ggMbWJ0B;7g`=f#Wb#*$T>v?f$d#M@Y6T{U7aw~4! zPba5PpDs!`axPW`3r0&!2$N^fLgFV}KucfiHTbFZfin=pi-Q2o=NZ4TLLZ zcXxkx@8wXeH)++`QGDmVV;(P37 z>+=mlxMMcV*Ns2nP_WbS(>kLVklrCi^++_o2OL@9UIU zczSS^dcS7(cDH)edSFWUUSb6141U9#gzUWxVWr4gnU?%G+KKm-{hJj!T&?}#yirH-=-#D$s{_toLFI#$Yw|Rjm+PVZdb_%af`Wn`H(6ci^z%;d>z^(^u!Q{w2tRoS z(s=_%3_>?iq96(5N}94}&6ZKDXptE+iPL}yGV?~zn~)+$k}PTRB+8U3SF&vB@+C}> z9zBALY4avdm}UkAlPJ+5(25eNse+Q|XrM&60u9R*NEg&y#-tiE#)}t#tXj8n?dtU_ z*sx;9k}YfYEZVec*Roa12!J0xaR-sh9*s^ESu8p=&T!91g z)HOpVVVH$J!dRX*Ewf>1J~`H$?EE?O=+dX#G&3ofaf!$=YwVo#`_f#X#OIQ#+L-P1 z>esVx@80ZNMSeEKp7`EMd3oVYq3Px-+ zq?%ghhD({I%T%qp`f9E5vek^d^`6z@eR=Ha1{_!{#1L_Sg*h;e7aDvp!U-$9FvATy z{4m53OFS{f2NO(iGt^jeDrlgA2#ScPUDcYsC7ZR3$tiQyqCkGUNdJd#2Z<3}6aX?4 z(L_P%yfe=|`}{M|K?^-J(M215G}1{cz4Xo)VKj{<8iM!XQ{%}J>$~u(yfxQdXT@?J zF5l;yUNZs==w;-py*ArzyZtuYamzh7-F4f2x1gk|a_WYojfcysUJE|>)?n|}n;;1F z_a{Wd;{7<}kxM=~<&|6B_A=9;LZ+EZg%_19?-D*b>9pRdTONYoVTOJ*2Q1CSvCBR? z?X}x}JMOvbzB})|`~Lfl8KWY#$4}`33RI@-!pkwnTwVI;zd(Oti@2eFA0EH8e)eIP zd;dN7;fp^$-O?~KW*F&Gjpi=0wC3vc(Z~N@ivU>fqCu-~-~Z4C_;_leI{Mw8INk-bro35uYwj-PkjmkvtIQFU=P7r0uLy{5t6WkCOqK*F+vn{ zbu1US7|&_2c9mFNu!c57izRYsiTV&kU%u)f6kZ3sAri5OMm!=Clc>bp36F(Lp&C>q zff@j~YGhcsAr`a96&+f_P6X-5o;vs^W#9~jW;`Pr)2K!guCRfiQP=Ux^E~)zv5s|g z1Rn7?pE}V2X8Cag8C-`p@)5F-hCC!9>!vp+erP=8xf(3!Xvay`u|<9K;}1Q!#7%Ot zlb-w}D2Ld@zIjMhx{w4f+(*e(+A9>Y!PkrS6-Z#cuK!}Tyd^GksmopRvX{PO7-u-< zp*X6>eW8;iE0a0D`b^?wbkH9e_!EU7x`K=n3E>*Esm*P2bB!8_nwicNh+XKyeOWuk z6_u&Z>A|ufcmSm90%WX2(zBlSyeIf7w7^6}p>OI!-qo&>HE67}pr!LeGqK03nVn4| z$H6B=BP!8}Vs3#(2}27TxeHiaEuhRxhCxH>HI`kHYzpDtxG1X9m9i9{=95Ne(pNn3 z=mHiT4V^%>Ceodj3~aH?%SrnrPvbbQrA9p}QYkkS?_@L=lBj7%-A6K>Qnjb}JIMYV zA`vonv#VbHDp&)^87Ha=6dk>(t7ur&wZ@H=W3?78Zr`#jI4l^t&WWoETPFzp-Kc8z`$l$GppInhSjn#HAs&q#&C8bjXVc3m!RHx%Ta9i-G6-W4i!Fj@J2OT1y?m-m6 zUYn?YL)%p3NrJRCwJ?a+vRc+=kPfqDB33r zn?|y+F#a)FR@;b1l-RZWN^uZ_0Ssk0jTzf~Zbg7>vgvyDX?ydn$MAqW<$3g@QvNbn zhAd_x(_s-N#=|>P%s&BzQg&eWTAMLu(L}hRcsG_sj<2HFWcc{aZyqsgCEo~X}+;0 zrOu!M1&#@vO4IiF)IT2cnEg^mfF#rWHif+BUGIX!7>`3<#3G7J2Yz+1 zPd!maoG(W4Sxo}~ArF&!v)eH|I2GdXo_8$h-HTa_+`<+Yv&%ZoD^LqnKn^`Qj%rRT zpzA2*8U{_X9lqmw_W}Sk7dnDD0f1}YyxV;|uN#29VhXnx>L!Y4V3+||D1`GvcrXRp z>HTy7Oxx?x#rJ~sP3Ehcfec&N0u;88ifQ2N;0AO1o9N~D3;k8)fxttKY zD#Su8TwqmQn%QX+8+_Z1`a-Lc_MB4Xtpe#UKz0sR0(83z76DI88AsoUb{6QTUg122=T#*40xWQi( z3yW>uiqIFi$d`*nLnLAbd}W4wNg^e>SXX$S+Gt{&IHIbZ&+v2~DWVzap`9j#pdHcz z9_Aq{bwVfXVOIQMxAmKm!9f_rUKqRr?LA@1%}MQeh3y4hj+k6mfQeYl&d`+{6#@e_ z>>nv|VA7!-HHx4su2cGrArkhH5~`Rphzole(j=lA+*D#YPNF4#h1XFEmAc4 zAOMbFypiC8s*K>H!5N{(i1t3k2$8}Ca&Y0w4;ng1NV`g zGzK3m#GFQMWJYr2B*9`g^{%M8CafR*Zo}5f!4mLxE zfQI+UW7?TQ36h{sLQg56g(EDVBlP1S_L><&0U2Ok-$A5-$V$W{B*7`k{GKUD!YYDgEYw<9cFkCxg(GmnCjbB@@Z%BkT2U5d7?8m$I3W$z$`LuG z?LlRjeE&r-#tSp%UlRqwL;~j7B^?NoU@{zLEI+Ld=3o$2xn18UN|ZaA=bM0{x-o+^^g?PL zo;(ufEDT{*;Nf+?=M6okcFyNNdZ!bHC;5!yekN5X#w2>4Pfe~|VB#bI44;E0Q-rc0 zeSX?yR^KA(XCrDTOrGaNSyVh8sM?k2DnV#uuAuwXC+6j6i*aaw!YFdEBYLW54{{|1 zk^d-;QqqZ9-GsKKW(FrPHl^4x5^OTo@5U{n~m!8;wOboS($Vv#=XXdu!-kJ_E|?cF?8 z*ni^b+N`9i1xB)^4;dI}MQVaYdZl3&Y8L&$9~^=pI$s2K=j=VIcov49N~)kxs=AHK z*p;FWYJ#n5LPlN!37#pa+7Kr6;Uxs3slJ~sj+&}6qKdd`o<{1PZice;An@I)CR_pl z=<1oSLYX#buaeFl?xCoPs`D-CEIF$6i6g5rtFpdohDH=TLhJ8IYqbKvnf3y=YV6l0^~w#1Z#L`?@bzM6m*XF%_2#Ec$N8 z>agAYYbszw8541Vh_O$gMH!zlis6AE0G|tY1qDy>8>h_M4w>`CDH(7<9jEHgnrbGEF&XTs)d#wSS`2n!dhkhb^r2ZcCI$Ja1)QRDA2+y z@N-rG11(taH^)ghH*)v(W-ABqz-scrvIRVg7bZXSEgS4|qJk$&Y$&fSSBQc@4|J_` zf(_&EzQVylLo(iBW_FO;W>#|031drRDV8RzJj*mpzw`}7bXjaN4bpQop;;f{b2odl zN5@M@Z!f==q#c(rHIIc|YQ;3~aa<~7F4fUf`>{2b+@Jm_MrYi#@~T#F^ib1ECfu(L z8+G@V#`RYPb;%SpxUTZ!sx{K8 zG|@R_IwPY_Q|Sd2Cs+{UP6L2mcW#+s96!!4SOfN~2>-Un!YL#(s$owp$C0N1?<2J46Heo;V-5EATOs*O#w{gpLP*l*O&9%xhH%8E;USsqm zJRoMHLMM1a6r(~%V>g^=_c(`kE0;_7B9b|dFOgmlC2og+V4Y-2V*!lEaH zLMV&^BIEa)gf#x{HXMX@$O7zu2r&@r{y8mp_*ic=kcUR%Hii0T`^wA1ZH-t<0 zC|vi9r$|}-l;> z0wiQbr~7#y0s_UoDSpi}Pag2m6cU2%&=W=TVz!Z{p-GM&HpwMDU}r-&gKf~bnR zSuZmgEP;0~0~eDx8Sk;zRpAdaH9?l~YlCU8Nja5Yx+g4VuA9goh`PS!uj;;@zvkT? zGlN^BdRKkLk{8?1nEPEjjikf*B{{gA!~ggwRQt7?NFfx0wi7M)l60vDI}ZS`z&ds^ zO14rK{FO>JLlfuj{oI>F`>f0MY{Q}j&%3qPyL)K2qHZ{M3;SC8H)6x`z#F{CFA+<} zbjWE$#yq(TKKu&4xF|$1y<5C(L_${xw*C^fssH;23w%zS{K)@2?HuP>D5c6je5J?x z%j3Gt*T$!Fg?u#X;JZ_I#d<>0k3uRe{iXu|Bs@Kw zvH@>%w{IWzLZ=1L&;3-QMPmb&x6u(fL?EU{n2*^|2ML-Io2;PSO91u{ah?a z-=|rA%iDRyMsX^n0_S@^ZE(6sbGettY1G@@zdPzV*Ph)sKJqhcPA}!ct5?e9V(hcL zgG+o?Ozn21`D=qj0`0AH%>Z`t@muDg2ZM#Rmv`>2{ zaC9k@f-AT}_Q%F20K}(500p@;X-E15*}enO_SCPV@8b|Id=5;5oEK1 zBT1GFc@kwxl`C1cbh)x*Op+pB)DIM-7jInvb7kiB z`xkIv!Gj4GHcWWwVa0;mGLt4P)|N0^$lxIZw9e2*Mm>oV<*C%GS6NAyHhmg(YSpW~ zenqJ8%h<9J(^|B7J0w(^xOw;X{TsJ#0Kj?Uj5WFEPdb^2Ztm3ibE#6RsaLmt9eZ|} zt^b6r!%#MtC1@$yZuU4@diCqsw|D;@e*9=M<=3}=AAf%R`}y~Gztq0~?ZarAW^zKV zvde&4q&dz!6GgiSC7h7L3QOwhkV60gq&wIgf{46VESiSI5=}f2#S~Rsk;Q4GY7xd5 zWt@@58g0DM#uov=k^jdS4LmN4MIlX88fK_LC5#{weDf0LJV|8~(DWRY)KXWIl0)yn!;-c_ zFFH?}Nh6I()>>`7)lmR(<&VH05p;7oA~AVxlu)NL71?B!eF~Bc2lDPaHoEvQA|bep zmBd4Ltq*|p=-U?DaK%+m*Yvs__q}YHIgql-l*#s54t$%IPbh8=$R%cn@(aYu+}B+#Pc%yO60OXT$gWB^3dm*kT1%mjc9 z|Als;vEbmz;8LVn1|x0JW$#>bX}y`}o*6CI=k{uLj7=dIj28e;iWSx5rk$>^*?(7N zS+#)Tho9wdVO3duAHM%rYqv7qelcbkE_UXFqUX5RZqz0I3 zR%0H~(4ZCFS@6OQj}Pd>PXv-DU>9M|V@wEo?$}iBy&Ln)uku%FmcN+xp=!5KYnjI2 zO1m(E_=*Yj)?I&{Y_wr7%=Da~*x09zF_D}zx;6hDc&YBVjX|hm+LdXh=h+)G!*-P{uSc6~K21r4f;f8vvw( zLL+LfD+F0#Y*@7g9&8~M>B&~C3YW9_5Dte~3>wg^=(8!ZYlK1E-4LZy3QrsnjZf=J zAv)w8CeB78P@EvFviL16-jQdCGh7IFVl?oHv4BMMT^b3guYV1XWmgK_-@Ft!9O@B| zk^j7vt_Y$yL$S?=oN9z8j3mfHe$tSgQ`H-l_rxehqh?eToE7aTNmt^Ei+eoJx{AiJ z0WM-Qk^|)~@zs^#O)V@8>B3md630|LW{P!G$}5>!D@xL^M9sLA_iPC-Tym0=ohTwN zxk)Oi*}-!UTtdv6!3@*Q&M;s@=Q`Qh&a)}yo$k!b_NXC3TLy(eo+8LLyV=h>?agyl zqYV_(L%|QGXetL)=t4~tm5DJlng)_)Ed}^Cj}6hE8C?>e{s7QX28t}>+*14?dZL6@ ztfVRJUz$(~QSMO_4tM|xKEoGC6KWKv*hyl_0O8SKexZHEoJcClnZ1|fGiQpHNPyn<6JfCxj)qnuSxSUg-cG-(usQR>chSe^$vnNzF(+ORc@Qh`Z zYiSUHh(g>|lmc7TpbUnv$EebVbA6h*XjrLwPNrQy1fpAs2mnr;5u<{Y>{QNIJmYG4kw`5r<$RKFOnfb#a#D`u13wb1>VVYbG zd_0hzxIFU9FO-?|(3T!FBRE0Dg)zLG%+#SMf#fHY={2(0-nI?jVaUMb+hY|&l*>zc z=!+%FLzuEQAw#J#T=Se>Imm$xx!v)6e+yU{7MeoD)yS9|H)B8i!4HPm@ROk|fVdL7iZrrO-M_116Ewv@j*3R_^g6)&g6BSF)REFyyYd8^F+n|p2 z`UKX#Qp(ZCzz%M@QxnNl+U~eNsY9k0v1E@mo8CFy5CcOkhh6U&E$~hykek_XhmUJ$ zIT819&1~*V{9!Ym4ER(zr)x#;_G+d~@sA%~a+9CDJ`vZ+QvqG>Odn#|aa;4oHTq5I zW_8CvNDG63;ccJ)9Oyw8I<}iPjZ00Ozxw94zXxq=nrGAw9%o;5uXY0{TD#{%*ZS7E z-gU2Igr2o5b3!bR@!dK7>HUnl!E;M>A>aUtzjidr>0bATNB?u0ni_k#Vg83@pM8*N z*QnG2AmpsHm`?0y~hx!3*ff1}zm2TR#z#{}A4zsp2_LH1qnyhxuI zzlE0``q8i9>2FtQKx5hvFt73OTVH%$%ApOiPd*m{#aY{I0>c9{f1mgg10{?eucn&T6E-+9s5G|HKW}+d# za!?0%kOzGP7%rvyFzj8rLj^65`&!Ti!LJQsFaQN`Jb(c15D(QpPz0;c3LOO_qJd^g zkm8oZ5K!>qj4=O@@ROF%48yMp$wEA|Bnk_P6F<>D z3gqbY?p^%g5w*`K;^rtQ5ru}w55=$Z=ji?yS@c%kb`3x@#6DAkS z(HzfFVRn(_7Qz?vaQldF8-J%8y)gj8aR9CE2JZ|V_mLm_(SK5n8gHi;%k39eFJvMs z9*OY|&hQwOaT!g?JUTHNClU@Rs16;19bGQ_mhDM|F(EUj8~;xR?UDJajTu18{ZdjT zS5mar<{}?ZBegF>;PE3f=L|&>`Rs83d4LG=@eL^wC_OO?aquNuX2~KjLmt5+Yw~IM z@2bqO48Y(52d27o&IUbj?t)S)tI#3^vJv5c@4Djtb|;XSQX!X6B)<_5X>bE&W(u=% zE8{Zy)Y1CbamkpDAokEK?dF&EV0hB<8~p_?*K5>vhcV-OQ6sOiz>TV5;Q|oG|dL=ey|!Hlk6_;{s@BEC=+=8@-owK06Ehq z6VWzr6E~el%o?+0z_Q$maCchMH8tlB03a>@u<%3*3Jb7wvMx8D6FQ?4Bo+}0h@dWg zvpaxs!-n&B(y}3wb2D+k2I0aP6Vp6LFAJB!c9tODd=ne>kUN1V2LBH^0}%-BvoPZg zF3*!c9WFhmK^35YJ-;$6#B%%eGCrN=1uaq3>M;ZFQ#?t^k@}NDOAhbGY(Rw&I}Ma9 zCo@5trWhF%0F|!@W-~(Nu0m7P;s1;c6|7|m8nHkxtq~p}6+cvSUU5XbphT53LK9EB zMiWSbR7mFu7~%pl1hhTd5k74cb6zngNmM-UlQzvmI;+%5ue1R(v^SMh0-3aK=J7jq z)I>S(t14_r&lFA1v@POKCy1b3xBw>aa!I{ZK^M{+)lx@y^lG3`2s-d2KQm1ORZv$l zysGm}zkw6J@SN~`7SAq4PpzA6BFI6Fx3y2`(PV`R?Q$@?QaMUs13N%qW z5?k}-kSvo}!JrBAR5Pz8LPt(k^HpEBj#IC*9YwWWn- zP1W-vah2@W^?U+WUj|kx>(O92i#A8jA3qjkL$)g$2Uj-~Lo%d*Jk(-IWnha{88=p> z#Ipl&a6--1W{a&vMzK8;EjvY3GFLWVFcwk|HfFEV$3nclcTg1F-}c$e3~5M1j>3Rs zkR(WkAvz#gq67&_R3zsd2LuEp=bWQtkfb0WK|qp#l9M2kB*X07_b)xqv%BxszFW0Z z^Iun=KBv2@`a0M7p6f$}@t+xXT%P|7=as&La~Tgut*E!Y zf&T;hLnYfgZfbT3CN26eJ&PS`U5}rQ+UR+_I9?ZeUQ3vFf^SE@dz$>}>VYr1?5l(E z=1a|H%dHvRl4koyjb`2m>*1+aOw=y^U?)!x%QY$h-s|+J`1u`S9q(lv_9pSW25t{q z*rNI1PfvKhnTvB$+21$Ne9&Ugl;k`_F`YEy@yY<#Q~MFNr#t?cjcMxhZvfFaTyLw% z_4y2uvw?T2L4Lc7=lyq!ag#>tR9^QW_r3+eQPdX|9vsh8yhj=>J{tI0*!aesc*`cc zW1KyW|rft$4gm5{oBVJEQY4(>RdcDn&^mG|BWVd zA}YLb7WrT~+RfPjhqQo0PwBH3ms0BRbk-nzmLR64@N^PfA0Y=hnkeS|>39m|&QMFP z*2pF+FZ>kW1ih{O>46u9uN_*qFq;Zx-6QWqq6=vxTa)ddHsb}sO~MiK(qd6dh6rR4 zZ2&9;ENYOO7L)qSt4+iz__KM`Y4kT5zO>YM0`bryyOG64yWJoKfSIdS6K6j0lcQkwb&DRARgqYC=6j<@>?>;Mxe4+pzllqn`?~b zg)n=%A&-$7QJV8r5(AM@TI4r1s9u7g(A|zg&1b(-oG*k0X1uU2QqlLB)1tm*)P7Tr zxp+P8o*p=x&i2f;1Bf+9&!8WQeM+0XrVzJ7qcSl3nC+ZIEG)g-TV5yK?5kM3jXqO- zdhQjbHgt2Y@g%!kBnNGtl$oB>z>N@ETZDsnm&jgVuWc@H%_~a$fb*5OW|-D(bzKKkJNk;eO#{~TrH62Dlzsf*Io<3$}9gO zrg``+|7yya<4N^J3YOIh?ug}kh-jf`UuCzC$R~-6a0IT~zAO5bKzbEj-Lz3$@IhMl zsp#8DqrFRs2LYC*3vV%9GUMn42wI@_1BUrbDzQ0jOrh)5#q0wIJXC-nwJ*( z=)7ruXONle+fOSTTx{Twm%MiiKW^x4CsMp3EJ>?Ed)+Thm%?faj?8SuSGeo7jSHdk zF*<85|5)N}Y{gIe?y+yK(L#Hwr>&V}vmhN|e{)Ud6?aaim+w{Lahpz=MBUc~qOCN# zxAg99neNZ&-c!=~Vj;riGTzf?guXHCypsGegWZro>+gRGTlejHWaWi@?oVLY{d`7C zZN9tGC$7|jJPaNZN5}qgvYF&sct5j|D23o4-8l+Q-WZPw45I57Z1T`zZsYQ_Wd7PC z&cGv;Wl!s;S^BN7x24rHYvA{)MY%IrG@|2z_|tQS-U*<4NU~3`4ZI}u;VP5(`+~oa zl+WwMLCv*~k91WF+co_g2f7(X6k2GrR>*k$5Rr?XwG1Nf7Q?^zSfek8z{aM0{U1a> z)eXlgJTMupw8RgjDKcse67h=tbHTCZR~G3)C*0d9$lz1ym(=b1C6lGfK&+iVeHdT5 zjOZp5{kDIAtA^V0xMI-=jz!Ai&wXhUgnl26{e9G%IPmG687tEno9r2f?=!FN%(|M+ zdS=i1exD7zGZ$()M_Z@FZ_<-3=J@;bR3S@H>Uc?S4-{P>9?P7WJYoD*^HNp>3mO(A zJ!Hu&P>E=G6Pen{t(+N7?ipH4r&QZnF?*py)%h&pju2_aVyGB*p>(@CeMGR)$=uEO zRn=U_ol$)1&cQp}pt&FUrt{3Y6>L}SOgZr%3EQN{%{tV>R#P4{=o%0FR{p3bCvi$;bWBkWA`KVZOV#Hh#x*T zV@&tzgl5`LQyHQfM#9gCO@g#dXQ>^^^WEQ!>cgI4Xl%2M;nA%D|6j>0D^Wk{&*tAp z(QT14d6K3t3^Q&RvyTmGkW4Zr?wYPe=8P&hW?U>R)k{zFX8gYQW2-~{7x*bVj_k3! z<8d(E2T7(VeqF?O;f)r$UrZO|&>tsOmTOD5i^G>K#q-05X6Kl-dWi0F7ktdR&6eCBaC2-fDwC$nrWeYPeNd-B=1}yftL}}OIdA)##4v!ar&stoKLv|!30AuzsW<8AW9?)ryv-{dv@tDPx!+z+!=_exmI3%J!Ta0if46{X zAJg;Cv|~3*$@l2-yv?pYf`C;tj3xipooVx7$0vW;*uo54Jkvu;Cc8o@7KRTu)vUY# zX7B|yKbLON76FY8@2d)#aD(!)Ru<8w-`?#om}uwlT7HzoTt>Y8)_ti2GYs|2QvZzc z38oth_tiHNS4&Ti9)idmyKf&_M!)oFG;Zz141-5+Co=Fn)QyyWiFt8Jb-5c3mp*$u zjX6C$p$U(M8gq$02LsH#c!p*6c__p`OYGK4;?u6I-T>YBa68^9rVq=bm^B+(v2WxX$-i%SM58R<-~N)+^OV%XJo5{* z=3hNbj%>a2u_-sxv*Pwqh*z$p9%g`t>1d-R^j9NOmx4oC#hPUC+ucB}7d}B~o;wxO zol7_9WI>$4`xF`pOt<7sx5@JFA1}TCM_+s6=wfe}-CkI(JuSz)3w@Hun#L7PFgDCk zznrvs-j?NexpWrXrpmv6ZIJcI_z!(o@(okAgEDQ=3Pt!j&>Fm-&{YJZot^|15ikPFXUsH`S{KULO51y4Kwcny+8@g~?KVfcvTmV>7 z>#x=YH=Dj(j}^}5VP5ZS7CrBaH;3eS>p^tmOgC0~ky&zN?WTAN(muxE=hRKNZ>2JV z&1=uv@%k7$Fk_n#4lZ9IgUf4mbs}hua&~38fr1!>z*DFPmyY5?QaGV^`~~YhmZm5Q zo+s~7f`y-A863eSB5xr0@x$bj`e$r*pyDCkCrx%k?fwL_ z)kSTt9Mi?GXRBB{yss{A2*m1^d&3CmWfrL^O#4yjtsEj=q|W+KBG2PEUi>$3sT9el zW5c2+rVY+RAMn@6*s0T<=7OrD?t4sqYjjxXij{0}EO}f`wr2@rmX9EEUTzg!>nEV) z%c!-jDeyj~;4AAunSBqBqTp!l5X%qgINx7ydCTvdd`8)ye1b zdHS;q+Mi{X5q6v9mR;u|1ZM353RV2{=>mJMS)gXe5nFbTKfV&_gD{8!}; zWAs}YFMsUrq}z`Tvv)gm*=^+qBu16>U^Bg_EAZO)w(YvPBoNGSUfq4wN&1xG)%zi~ z(!)>uFZ^}~SblTw4!qA$dsp}IwBqRVK-YUTctrHNhnHk*e{hZLYZv4(^&C`uomt{W z&~ZYUDc0>}r?$wn-UnvkK7EM;gnP;_m#C)qwZjRYWBcaft3;yu zIrHvLkvX&OZ1+_DxmeE`_a%SPx}lX{Lc_r%`#dC~G;v-lk^GM5?<8a#UDqX!S_{Yc z4o9$^=051-w#ZZ4p_ohdHn(LU%~^zZ86vKqq2-156stdg~c)gFW|@|>MK zJ8+eqLRuedKzO`*34WSm(^f72en2C=v9k=}*XqSPE0)r|AP(b!JjNBB>s{#8ihxa# zK?FH_@g{8wNi(o&|CMp+gqx7Iv5TJDw^IpH|X(<#*D~ zf_K_6z7e|pipjB8uZ@Ee*N}`1ME7a7yTxg;g2Jh3wtv9AlDe+LPcr9+g>r`zG*3>EoqxaLK-Qg=+EL$JI}!%7`g5rKW#s1=49%p58Cr zFt+73`lf}_n2M5L{(L_hjc3sE$-Ty8<%801dxPw6?zNs9v`xC9!#%5H#Ix;74@r0; zrJrfP=6G3UrII>nb&A9DS=d~kf~6_EYPclRbj`X`sHrAIudxWbQueLAo??YZ#bc3t zd(+*q(hBlyZBy3A>2!mY$|9A@Z1#3d^V2m)=JrC^)f%zJCU6RiN zaRf0S#gex6aY&UGek`s%40*}>*qu$WL8>MZRuJ(CZs0j6 zue|0K>G!Oo#Cd2dd*3^k!B8wo`?Kz+rA^{TjTwL?$)7vr0o{bw?D@>@=5-+{@n&3RsBBPE+U;LI*;?di8|T|zqe`QE!i zrh8c<&MS6_UC&9`v)fX;guiC!m++OI?`XMB6&_ZHGHO)g^0~Ibf)Egv4}`mhyCvO# zKm%ejYx8rCAddnKsK~R5@{Z!mIahX%yf?9X6g&%kLyjIfQFlae6^!SwZ$;{=V_&Y+ z87*`)@A;(K8L2p+C0vufyU`9;+i}m5Ozo8nS;$7)E0jH{SA5&l`THj6K_eDWz83YW zs;51bZhW?rueFyX?jTH*W@XBw`6Cmtvkg<${6}lA5B!c!$WVII?ETgOS>hv&r_a|} z`NfCiOmQ<>tv8-J#ooDfdYd-8J|&he+Vv{#xCC!}X@K{bIwo%?*vfBPv0JSF4e_t& zhkiRA#3964GHZ5^?(8Vl`b>0mI^TC(T)4y3R-#UPTKC$2@7^nMN`rvY#%}sek(HtH zEmM^Iy5GJVt}5N6fOW+|7Po%h=QcC@upR=l15-d^-2Wa!C6A1~mVrclHsi~ZHKRT) zYUinPGbeimvpF=**7bO-)8Py1{8{5K?KAftolceJ5SiO_?hc#9&vwiF;;F^wU(mue z`x)@d2SALTIYml3we9!u%K-ak5jjbZ;JniJhcX4uw;u%_DB#KesbuonHAY{~2ezL^ z_K@%TF72q*1)PU2yPXs=^-1FkYQA3zIE}Ox?x#0DnPkr|as%XG0kg9X zOEy6euOK-Ae*&aegn<{yb`YtTV?~P(`+yIHn1@E9FWulH_aNW)fky{_v`>odyFGEG zvGC8H`|%ihTCUSKSzGh=d#a5?L~KKY2Ow&&P(i~`@wCujgHUAv5ZewF7lWw5DXyt0 z0Na4y)R?d&SkRtL;A3QRY%B z4UktphFwRfx!O_!2iR`5lvOs7zJ?yPvp9oOk@vP(FWGTj#&I-`SYK+PUfZHtZOAn& zaajgY35KXTIUT^cCvE&|&O?>8B5G`-Cv2mwKY7}7 zDhqChaEn9^M@D|%j`b2lU3#EUoN*hRaS5onZA0>yL6pg4h)L3&^w7vj3 z+P>_~B%Z$%bF6%8keY2wb8CPWGXAGcxSU|%zGpn1T|A^b=I20=d26(&SlB}+$aUEp zdOPe_+YwS{2}@#;*C{wHo7jU%I5OHeY*A{cb5^MpY&(HCS1->!PFxKT@Syx{3=KUq zHlB>&tAp04V`S24iKjqo=tHtFh2SugvM}k1cnuo8BkCY*J8vTWK)@@InjUp~fG?2fjvSjpJwcQu~cXWqSr?t0mwAu$*))XnH(!#V&29JZCrR zDMt_a{zVS3oO8^TEA4#eKqXgd1$4-jyVRR|t&)3iL4H0&F)2rJWyG@A#)^gYKXK)~ z|If^l|NrQ<(;=USTWX)a_WBEAl~zZUYxN1QO2;eK_XgmdGWrCILlOX zA*KOj5ZM7=QIs7_CoOZp6>S7G^Gvo z7uONdWr48i5aeaYau}p=(6P;{n>@H=BSS{5L=^FZ;CB^X7POQ+4AZ#KdK&?y9lYm{ zV>etj8cR1=UR6Ukn777;2BlOsXJsHGzI=mldI5wY+j{SxsC+LFmHx?@ZpP@amwI3Q zCr&7~N=AA{FkJhmV(^JkMLw>2OGQL;O2%MP^8=3fgk}wn@#k2^$Dvv2}n$lr;QLVv>ECdP^$WIJl zs9nSz0*X)7z+%PG05ACRP>!D@jPdi?d=xC-VEV=5x{#XP-(Q4DbyV1JxIQ4|qSyPN zKqf^I6ez`WQ{@Mwz7Kx|{UK|bDs`)lp3!=IGd}g$il}GWDDx&>2w=5}pR)u5>OzhR z_qF()7o=grFJFf-q{pbv>K=XiXm@Tf4hHn?X!ja;z}kqJh` zZsY_ylHH%6U?tL+ND7D-F6Cg8(0JRzsJ;uC`0(mW!d6X(AI(|E)$g8`2W9Ogl#kU8 z26yAG5PclKuaEsVuEb8J402~rIx*2Dr;D0>_q^F7mTu0ee?7Xr*e9`+<%fybK_U1DL3k&4jf5#ZI1&de5Z^oq#~dt@HXeg3 zhs!~%!9*ILJjqQI3JMD9h<#u}XZAnv4e$_q^;+D-zRb@MoF9vYt0Kaksflx4dhg5> z}IrmAQ3iqvJDG0<1h-t!!dC)R6>gaQvxlC9pHlQ-*l5w21tyU^AJ2d9eew=*}Q!2Hk^2KNVIEOg_AA=}F ze+f|Cr2TYIitr>G5B>{R{(N|b?DM$&?J6~pXR5%VIuZTkq=07B)=mrnNNrCF$?|(} zRkC^z>nT)9C9u{q2fE zp8kz1{}06K!D+2?H6CLf9EaCwM(O`VubrQ1KW(aut$ty%TOfrND};{>+F2775ZV!f z)UETXogyDCuF{>g4yxDKhjtjFMv#)$s2aD@N8f)wIcuLAt?_6X{=V;X*0D@f>oxG` z#}SSJVefRTVy1)gM*CUkL2Rwx*6zGq>uSZG5}yCr@Y3z&S$74c;-kO}_Lyr>d-eSRwyo z^XcHa{SV;${*2dZ+xzXD#4ws3FqKmfYy^uSOid|Hr59yhR6xS-p~Sr!6fVOMOJN9C z;xGb(vFqTJ@i;+6v%C>tWDs49*b~mpWvGGqB~eNMjA|7zC=`3Ro_X4$3i(^Wb1>^$?QbVL@0Cp?Ihc65;qBFspfJA*~67%pB{k;z1BD zkVgXdIQ7NG3R(bN!s$|9m0@Clm638`f@pI^2*r3Xj+8kuR4WKVl?MRk%K(j`Fu(#2 z!Zn9Opo15DYyyC~YS0GXQ-!pfY%rEE0)%VWGeJEbnQnu?f^CCGZTJ9+q-B6I=(7Jl zqs!V8D1f9I%$4Qae%CDujj8iu(isoJmIAPGVJ(pMt{^;^3vka60e-<4M9T1eR^>nf z8(6|DGzO7+^J8Iyv_Ow6dY6zWRxT5bXc%FS1sf+8gbE&lh3cWrc_jxSUY@{G1g0H5 zUq#)#H#=>XJ{{_p=hl~rWK9BovT60SRcC<$jrRdg*!y0#yi1%B3u1WP6__|ar1lL0 zAD1SeZ`M3w`1RBYP$q^3gq#M`-*o+m(^SHT#Dfy~Wg!l5$^A$k^XmW;cuOwY`Km4e zpS2gqqXh>F!$QHmRhC~(E_**X0EmL{?rC9Z!mtPga5K+<{2(lF4~P>E&X?0K-7N)UKog zINR>tH1Xi7Ey67-LS`pG8*mSO=+33(EGp_KScWMY0NJC$LXcJsr5;TKW+TZqvSP;b z6L!MraqaaPJDFweA^{wMc(8R4L|_^0pGU(vj_Uve-!rGZrv-Wv4?+SjIALJ_ac$}W zD@&&+3jmBX$GN8kwps=Y)H!g*gH*;rGH@*TIBw+>$o~K*Ulb>+8P%s`N$QTGo`H_o znoNXazj6}(zGc)}ALHC(^?jSg|G?Jz07qQk*k9nelt+xV08R`Hw~8Id8)mwMjN9Ib zGZer*Jj3ZL0|@E>+B!TF59hCjCS$E0O!=B3&%;7n8zEs?j}wBP!T!c5|TequzLrbID@S(5%% zJjz^RDBh3#;aQ{w7{e^N2pyqJf*T~Mwk0u(;-SftMg@r4N#9BvCF^@9!!}t6!jmRG zB$-_#pKKUejz^n#!;%E>{Fib4>QY{OOL4qNaiUFi5l?k9O7-ymn_1G9>i6w0W{H1Y z^7C@>Nr-f=YZ}R+#3PG%o(FF75=~>^>b1u^JXfq1LGm4Bd5-*ZM z(=#gCGOE61V3;MybdI`m4F(WvLzG5-A_0Phpv6d|6^_pw!r0N1`F1LExsRElfS?_Y z-_b+xiB?0oAnSK<{1+~b11#wK9knd2Y@A-^--2)ek-dl>QlDaleG|`F)y>+**Vt&w zS%T#3)8-zD=bjk-%`EYzh>QPk%o50xJRF}qymrN+x?KGEJmSkd7+pS@L_WD34p=!~ zrV(2)EMMsa%FwP@evmH*!KP9b*JCS~u7_3z74Xg%@Ld)N&=m^#WZnN4v*chMUqV$* zYQB){1Ro($r1+#r*{2AZQKZ^lq&8oqaap8A_cyboLg)QNj^6(jv&7n`#5SYEzP;qd ze2L>_i4$F^i$tm0lTwfW!Yny|h8MA18X{2^_M|Mrrwo-*7JW&FLf4hWU6%cuS(4&Y zo|aLb(O#Z4U!HSWo<~2Dq@u*9;-Act`HGs$iaNT=cM_G2Pb!;zDqAxu+uJLJ z6QInWE1w)f8KG51+R$P|mEKC_V0%^jT4nY){s>+5q(t@9lj<3t>bZ>S`F}7=E~}U6 zYQisZSD(~u`qXS^)ckC(*_*G~zpOc+t3CP`v*bLZ_OiY9dLE+?YXSN?kYpXixb7d! z5@<&q;X)npRUM4J9s?txFs`Tat*5~tLOSXh7XAyf#P}V%?>o-ScibKCco*LBUA+^a zZxE7f5H@aj;QcqVM53cXYN0{qssTaYC@qN!X@>E18?98(F`i@iiX}o%PDlD-W3n0-Vy(hrP5F`nknY` zb8)Dl^h+)x6nY1PRQ@Dl6~m1|D(@hSH3~6E<)(_ik;>mcH(`*7yLAHX%js zH&}`|0?!*ZzT_!fP06R)SWlEauJqV#+s>G-vl<>7oZF=@%(LG8VLBD{YXL9=W3%BU zvF5b@tk@X67I2F2R)e~zs@2>p3yCCh80+jf`@P(dfv&Hp^f^C9P!>KEHzm|FNp!+a z%=PQ^&v2OH)FUA$NMuD@Foy?0h&lT;tz&S5=z*B6h!9fstq6Fq8a3(>HAvtoyzd3) zlE9IF88hEq9HEi$BdqvUe_rLHPc+xqr%N%oEY5ja`}nLlp=9c1`3TB(MN3B>GTn6) zTM6gVy`Ux@2oBhFa=uhaABq_)GEfphHE?SwH6}QHqK$8!L14TDzCUH zw&c`USU0kxkWp+NY8K_N+WP+0IddK~!SHqYBfE4qAZiLtwp;b?vyW-s`*5Sj#476mf((RZJf5bg zt{R^UQR3F3airO`WkIsK*fk;{WH^nJmc!;Wb&M5UDXC^|7CCj%70fA{CBCqC%Jn?) z9rdf+wJOP+E->alWV$LErNB79+)V{bJogP1A%Y)(#W;Py{UbEQMQ}y$EZ>Pxt3$vD zre62J{v^f>;IQ2t9~ehT9U<5qqy)7;G2DGD$F5-Y zrXfR3F^ZH#FKSIFZ&ls5oK$ob1}67Rj;9S5w(K!T<<5+ba?<+^v(J`VMC5Ezt54_M z`+2nHUku#yG(HTGKKt&qrM~5f{DZDOTOyx^o-c<{JD#tgIJ%|+qlZg|7X1y`!y94^ z9K8d>FD0}DMosoFwwS(oac<S--7pI55hRr@}UNUAE??7!UbaT32l%c>E;h2 z#76RoDUaUWxjcwOuto`@b4dGG_||EZ)e6W`&tdukhfyt$BZL3}5zZ%v(S{=hR4piZ zC;*3;ELvdWt!4_Wj#Gl~7ZQ<|eWK?k0z%U?=xxqFiO(O#dHsb{_7X8aIQ(x&Wwjy} zY7A1@#}Vcf!^*mbRFTs?N=$+DxIHKz(r}q{({z1?bE7q&6fLJtwXP^oWQa6BPq4?7 z$a5>H$W^{{k{HP==9?BBHk~|3?&8hq^a3$A2*|^)2{xb7^r~g`k@0cD!{3>`i{RvZ z99WJ5U*cf8o97Jw?1H15H4$l+7#<5U`Xp4dL#YUP9us0gC)q88n@wmg6oRMBhs}a+ z@^&o7rsN8K*p&_i7~nWyZv>cbHHK5|I1924eudu zy{nI}^)u1oZ(}~E4I{=Jwv{eL6LBQed4u701F6hY6&}=;H$S3WNKxx(B&hiv>euOZ z;p?e=d8)SL@1pKk@rZRyK$qS<=WZKgvu%C~pJGjWY_i1-d(AeGbB%ka!S`SG{GvZL z7(G5>W}_Kr@3+-!ePp*H|NLt<`e^BL{;cbKwDw$V1^)&!8^uIF2H~nLgXqtD@HFZ| zIOd}Dc`>umxVkV7wH4^&Fk`B*x(Lyc72?`r3dSyalmc&yM*Mjnr$&9W!N@9A$9ca% zTz!m<+8W)$`KO7GV4&yq8spXZ0D|~ke5Bes3;o3)QsZ4>YD|ujzy$`%_b#baZG+qR z;%spzGLYWsMwi*o?FU2-RIxf(oPH_!cTWVVp3m2G$u7;em zku904i!VXMjd{50+w%06<0y^B0_xFiWy#Bl__)R*4)q;Xo#RdzSQ9 zbAuYq?@~witR=6$jmI@Nma6}t#I6)(_NU2N9CjE3h}M zeH7wGah#Ev zf{UnY8}l4H&bhkYLXfnNXTJ-}r@z@ob}!ex9XlzJyxCEE@BtPB{E>M4z3YEVSNy+m zx#yu6U7`KfYKj4)E6N{-HRWJ*g$}{ry5gRg8%9@{f5hmD6RK|wwqGkuhaXtYHao3- z&VJNVW16J9|MNS>cJvaI#Im2vDJ=!zfp`cwU_A8ej>A4^`H9jtlo@pa!i6`z{6W7Tz9 z!sXC6sY^jVvB4PkdMZqjjDSTDn@R`4X%Qy-myf+*YWpdGxQ#haF4zgVjB=^Y#|elL z2JVn@;`IR zEESP#xLhCbD#2XnL~VS)V3d0@Bp3AqR(8V46k?V`c^Zv_}dv5 zs^t0$Jk*5Btt%&l7eNRj5s0&7EMhIOlKd~{cNDpsQW-Bisra@=?nt?zrulrcver~; zfA@EH;ikTtAV6*MwA+MsBq<&$>X(oLV%|IGqbMxiD5tcks{i2OxObRSqI!Ke$eQ}% zXo$1)1z9Nf!sd%%UsCAti16#i)l^8^s^h5C+1~glu)cget|;kdou|Z6?Iw@J>pc0Y zdbJ}wrAtm!LZoNI{%-Qsc%&XN(mdPUissGB<{@ef4CZk_&FI{WqvW*BVnn*CsNUnH zPU;V^UGy*AuhEy)2Hp0ULZI%bc+d0IvQzVM_n3z@g@Xq3AnzIHK4$OcM6s@mE#EPa zCrdMHocQ*I{p81)j`Y{I2so%fiC9GGv54pT~6GxG3WWka1S@xvGZ*GSV?>bNYnFm+h_?)J;h0v>AE`k z_(#a#c(zv7xRP4;(as9#r(NL|*}f4F^Oq|IEP^r-Kd~{fK7()B0kJg}k9czKry%=UaBH$k$G_@u)@(7HX~p z#8g3OOd~!n00$AUJBB4R;$!21N^Ydfd7MX{smn9N)KN!CU}tP@1Fm>;LROM}i{dW4 z;5*iXM=7l%#eAMa!#5&@sXc@x0+A{smUPEygK8y0sY4^y636M|F(tyKDx>yKjx**) ztc9N!VO;J83qKlCuCBIp8L^O*_5a~=iw{tZHw3}AKT3b!LPwq#Ip;=@DsX>-0Lnn4 zKY_^?S(4A=5fUf)u-G!izg_M|nSkvrCDG3laXu%7a3ZdUU_m9adkRGc_=h)2~?Ir6q@tlY8HZzJIi&8+sDIMJ$ea>Cn`#bdX?ZI&}frqq~#&Q*Z_k0 z@wPK4kNaf>ALK~4>=Tg~xV*vud*EyRB~g|2^n(7^&oj;Kf2y5gD@}+tQEl`8a=HJh zD_(p?Y8&*@VRVIhmF0hNxp$hQ_0&$^rHxiu|5I1|?Q*}S)Qq1oFOk02Ba{Chm;2Ge zJbM1LxplPK(Q|m=hdqIqE#`{`&f3Of{}(Rz$Lkf64UDds9sY5O(G?q# z5WgE8)Wtt^MZiB@?*E}H{^fH2Q&$+Df1vrNuJAqoxUZ(fjZnkHYpha&xs;;3T~;ao zyRHx$tB>;>`KPXMnH=Z&kFFS5XP5lH(-r2#4QT`a(G{~Jn-4lJGXKBm3enLW4d2VJ zdBja+57mF_WL{2{X*5+BjQ-UBuew6wE@HX2`Y&B!aERSS++4>riVgHw%>BCe;BQ?q z`(_YS9@_A?uJCPB!21BJdHIz6Km`7uRcQe8(+7R}U#?0kg+=3VWi)C}^#8{P;Cw8T zMXnwDIaR>w^M8B*eE3+8h=bSPK7e+7fAk&9_uB5W*sE2fJH`hXt}tw{|A!Co_xHMa zZ)`0oPv9C#ByqqkKS)-pgHC=%I`!L%@C|KA+U|C> z@tED0L;PC@%5N{Lhx_*91N%hy%2&QCj*GCGnQz_+U6lL8TbiTvi<0`Sz=u8?3wd1P z$3cps)wY4eZstaP#nob8y3&yL$VAL^YGIR3i$Hi7y~xyo-n(D3a##41u;f)sr6+?Ilm38h{yK$**o1Wjp*s3-=J}JD-Xg68d?8l3Kz&xE zUbZ{eUZJ8Ml9Vs|!GoXgFgsDG%pE^-ab%MaA*&JK0O4HAyrk9wrC50iu7+S!Rldg+ zr_H0fJsooV=Q#95aG&ND``Mu%V$q7Pe21eKWTBUAv7 zb}M*e@<&16OxRu8kOLFVp2Xy6S$}AH-u*Kg0O|xv?Mb z#^&YUVAB!?K6$O)S!t+k{fs%jH5(a^yafQT&lN@T?NiX!n;Iuh2=$5~|u` zpPG4c!JYI{UW$GZmiuZu29}Fhd*&t=2Ffc#1n;2}TjaCVD_Z2*O|r<}49axI-^-iU zahGeB>iV;O`8mwqWBw1eLkG7gYqPw4B17|>Q7d84@Oi}{|4-1(ZuSU1$<9_!B0fbr zy~2lkq5#q33x_-$Y>=NqaF(GI(2pxWvX6_OGw#xkU|1`rl1vtniwjl0I90x5XWBk* zbSAD~h{vjQ0ifdgFZODG2Rwn~lK@mtY_$V+j0R=376|eEV0(_QGid|Za6}1LJ^je~ zKJ4q4Wl2LQTzJf)SiRmWU_X3CXo4q*NFXqQty%sOe2j}_E|z9eEtvRt@Q-4F^nTmH zUCz5mGQN;-TA62lINDEdfx3^3HJXH$%NA(#`C0cgks{2I69qrmz`4)r3|G0P1SfF9 zM6(8ol*#+PH<_epS z`|+`m5~qMF)`1xylZ#heKMY5`*C^*ZE;$hwio{j{ky)c4Txu?fKx+}JsF8|#d#(T8 z{iRFQPam&#YTYTlS8vk{m3V~(C0boWYw*aFc!^m|y_b}a4Q{~;yVp3RZmgzm|Lw$@rKDt3?gtBLYhAzC zwD*_?-4R~|L#dQECM{&f6@I3+ZnoWzrZ}%3epYyFbDf9~Qnw}p9!QlDCJ4YZR1=@! zQ{pMeT?}dBB)qf$_bA;xs)c(b1=4@d<$i+)7^Miq?kaH|sdB^2s8~avDI#NOw$;{2 zLo@iR3x&&RQ7}|Col*w3$f2}KUh<{eg`2t7QjdAsBb;X0=P#mlR z1Rq^E>(O46Bj@rCh0tWuJlSiNusu~we)uOOtL`_#91YF$X@o#imU8GDskcMAB^g|q zhBt*1jhA{T*d#n;WJ{-$+qf0&*MMQ`@w{g&=w@JL#`Q{GmkaiVsmUn5b?IEcNOBm8 zvCL0A;>mTx4w32M>udkoX?sB^32V_O#TSr9(UW0)Bg6rfweTXzo_0)ysgZjpuCX%@ zr#C59rMzl|7a5F3J#=?*E&sV!<=10V{{&AzbUQU3=Lf%{ewp#-=?a!^ zB~E7od6Ok5r1e1-GfI@ug!TLZE<4rWtME)1Rf8eM-P&dwjttwHp}}{I4RPP1#^&8i z%a>%wBU=<$XCW7dA(4s{AkuIFSX*lp(uE?4;W;;l_j?0kMVf+eJr z!4I-te8KyeKCGlq&7fH*NXV2I{=8g zwBsJ9wXUOkj!bLwLsWP^<5n=2`$%}dU_=`M*)OQCGD2jRNMutwHe`yPc?v!#eA@Xc z_`vrE&QObDkp;|75e!U-d3_+^BMTvTwt}A;@sT9a>D>wa(lHgLszQS-l%tP!G=4Di zyE^55UnXZUnguoVo*>8?xGOMhh%+=ycAUzw=17*)re<=SIFcpBGi9!PKjH_WwSdAF zG8ZMRF*V&0Bb5+yF!IGpNG2jtepThmrD6%`QE1qo9W75nSyvagDuh$BzUV9G~)`2*R6}7wlcH8h+m#)TjM2Bzco6Sk-%IC90x) zXb&s!%sxz`MlRf!Z8OGah4unH!{VBkVTh)a=lH7Yva1)ti}Zi$x?|%`F1#{mQuDWc z{rA4qM1}BnRp?0<&HRzCq2}nt0i62xNtVD6^eud5Y9ux#7bY@9**|6D*UB$^wRd+t zk$bEc9nq;7P%~A3eOG*m9iJ>FsxWsw>bD#-dDX?)EIUDZo7yWI)OE_jQw-(O6>USN z^?~AA(JE8da&QaUE*f-Mgg?rmpK<0+EJpr#fZVQEKM7*eQ|w1sW_;)g`*Y&KGzY^j zqb#_u!l)l|KhpZwbC~9RU7^7cqIx>371NuCp;3_FE?mvTW3IO1(CA>xHI6WT3IaZ4 z;664Whjj5n__fqOz&$kP^>kBim4>0rL1nNl&+sQJWC=Ewyj$T5h4x1r`sNQKeDh_w zN+S3akv4dms@oCIw0);OFwKc_D?E(E6BIsuhWvmFuntC=Cs-=Y-4!Z@HRcg=91=vL z2h5CZA2!wt6N^Q>b@c{Ycwumb*!fYE9uad2D5ejtg3RHh&ZvS(FjxQvs*45$03ra$ zpLHor^@^Umk2ir;%ZZ_w!_rXUDxA^D!PrLTLfdyE#KpLw7z(=_Q4h>T9|@~zg;HvR zA^B1JvoLBKW+@a*7uz?%njDy-IFlE;ysyyN%6sGq00+RGAS@g#to*YWd_g<*gjk3i zR=Xk0*NYjM2J6M7D1L*@aKhlrg!l=s3O!-0a99kIjK~m1w}rBmCo;l|&g4Laip4)q zh@oa7A`yoj%=to`0Zi~QUFBr6Oa#ndcDA+D$&y24mg-%FBB_%L$QE_mp zrW9EWlO=%ap_pap#yS`WK476w_7z4%+(JmvmIT1DDB)NPV71U9UcPT{HLz$wt}eTU zAt{5r2PmjQY7${^JTM*&%X4OxOITcs#KuDEd<;`ZiYFn&spm{}w-ao1;=vY4NUm7i z-a9NoZ>j2Ip}ldyavInD#v?q{@9kRo$tlO7)K)1f#wJwuy zG@|~JocXr=)h!NC-UcHQCtZm0XrRrFwy-SoV(y4a2Fqmtb#dU{6e3bA7^YYY4&wz< z@dSxJQjsdp-etwlBMrW9tepaAWsKIxRB^(3>|h*G0A_1LiW7`X3jjGMR(d7s+L7L` zcxxNQ{xKq9H%yE{m23@<+|wP_?ZtfG9(ET1CIyl~6S#y=U|3qPeth6Af+#Bub_YRJ zAD>+-CSE}mgr?2aG9;C|B%Ayum?>t;OMN2!6TRJM)lXJrL;I~3xw-o#VBWMf z%FNg&b~R=7{y$a8zE+SWPt_bcOK))3N*kH+r#+}D@iOox}EZ| z2&i(J67YR%Aq%jaxw@xvNF}y<4Z#RTBx#IYy+C~}8L6fWHW!00xyS>U_A>N*5N0Cr zjya(mQXlEe4I`Dbc-;R^7kyagVAsH(fQg~;i>YYfmy4cFuBBv7NBF+MR0rT`XG2I) z9k_2>un=qnKo2D$k8h)SFPr|yijZypQyQ|T#?+4C4Td}v_pe~`N;T~no=J77wchO0 z8DI`ya>__2LW5?GU=8~SWYqzw^JTNNP?O6-082EPG7s#=&`FA?;q~ylQJ+VK&#M+c z5@7d2{U4D9z#0m4T8WvRNmJiV*kayo)Cj`qa}Nq0m7V3|R=ki7Idoxet3Q>%?G1^2;Hl?Kl>09R!VbuUo`>4WQaBR0$|ZLQY>6Da`83q_a9J(7RK|(;fL0UpO1Q8?^ zR1{QX&wc;?@%*2?_u6}{^Tn)L6E9|6$E@o-kMHNO-)d=FeVyHdY=~KJr`o~jzJN!q zRX)|{=Fu|}38R2= zb0MD*&4Pam`tkU|ak(L8@k$OIIQc9w@hk}J-?1{;yu&n1sRD~zV)WnU>ru5cR-~L` z4+v$aV0>LjiS>n_0R#ge&^b6Myp>eJo`abRu%SJ^38s!Rr1RRS08?<{b6BbqQ$Eix zoSH2zg@>&YX5wf2`ZN~KMMFZENUv**Na*qz5=Rdp{j?uAHA`VBer{B$T@R3At~!)K zHg7nrx@&8Ef0~Da&xtQDi4&?xSgS^3A*QPzC+sEY`BXYIIr_s_2c33Ch#jDrBvj^Z z{SzdVXQ_HLDB?#Dy4gC0@bO+y=}aOLf8A@`e5_B?HL&jtt40@qiwgYC$4I=MfDH7} zdOjX-dQjZ_TDYC?iw#|rF?>O${k@ls%EMur_UyK9x)!HqF1&8?Q@D-|1v;?nq5ljh zfsZj3zIBpx)jdbW(M{#w?L5}2rI<`h@hJTU4xRKH6^x|%C_JahbdlmDv)?w4uPHv*Z7Zm3J-#9l zam@A2??qP@@ih(O54-@jcYdMqAulOa^MeRd6o>4k1LxjbPs2o0;OKsWnAtI~%6uT! zk}LKx*Jsrj#0nd7TgQk?*e5spshg~W>)ACQmm`#GUfqEfpCq%%^%IHj>^9X7k@1!* zWQQo(a9IguqUqLwbl4->0XHA0(y+s$gj*`u;b)fOI;wC9aqa`EiU4ek1OgVV3-?xr ze_>?1f2zx+_)$ygJ&_6l*$tjE&_7R$g85360Jsr+01!#`9G2QA&}h!H36~OvpU@|P zk(or;5w|kJgzd$TdRz?P9k2vp6qX=Le0M|%u0Zx0GzVmo0X*V|(9{jkT&45h_qc5D zmtVNHMJ%|{0p1Ac2;kM=uH&`)1Z0{-$^q~69y!itlz}WQR|$TBa{4>o>Ga1JrNC;Ch+Tlk)v1}jz9?%sK65`FRNldY6DkKrdU zCb%NpELu$EMjekn98M|@&1F3VQBc$Og|(-?N1M#l6?K`a?m|=uE_^r;+|bW29U*g| z9|~}TlD+rxaA#%$?Y_g@Ind=iDC*%p!z>Z~cH+i4#6KF}1))w$xjC+`%FO-!kF~p# zHzket`;U&UU%90Gla+;cER;E*59{gF#o<-op-g7%kI%$h#ZEX^_YM}qrkfrjVu>-2 zB+y$gQ^Y6v=h{F6qs}Q}8%lz|(!X2Xp=6~YvBZM7zlnXuZSw4;QI@}~Y9!>ifgiF^ zDpLJYl_l^fW}M20_dWYUm`a~=`o45_P*aTa@`HN*%n{$*E_pB9$$V9%;3`pk5qkcC z0Lh2B(T6*n*($@&X4ojLL@A1%2b{+7zJ3q?a(y&zaE~r_?IqpR?i2XlLNV(}kn=6t zHvs$%K*DV~yRCvIq!D#qncY#tP;f~)a?f$9OVN>o@+;?d?_xPLQn;<=xiwMq6;G#V zV9_sa!6AheRtx)j1?)m*6A9u5Dzc6Mnoy1Mod;S9*BR*COkDrk~&A9dH8elJVNE)a8g9 z6$C>NPxBncQh7WJmW*dDQvH&@%XbDtzsGiH_TC2+46xuTUZME~8 zyVVUrU-mcstm|q3ga!WI>!1D~>z!1&n2(=Av|Y1dRn-GF4nDw(ZL94)j^@jRdQ{5q*mCmI^?r~#y#w{+jLkrg z3BIUO`7<{3Oa)rHD}0;r`P-@MCs6aPO07Ly%i(96-whINK(rx4}5A>u2q8O{y1YS8Cb)qQ!wZ%?>S&spv z;k&O>m7;axT(_tCDfMKPgcI2fTXsE5Xz*aVBeK6M64La59Q8g@Jov8O9jfaa13IuC zgk&QBt7!|LMc{(3vF>jvaT6xF0Wr}#qCM2W?J>BAzP`z#Na_#O^?SN@_C_?_^qxdK zRkurXf-J_Rb9IKZnvaM96K9EqyZ0Y=(L@_&vA7MkL#@=ikyEgVr*f(MFnas6i>11w zZfO{uHV{3cyV$$8xEhlTVJ}yUpEtJ)TUV87K~CTUc8l1WY;p5`De$vUa-y>bXiw{B zB^0e74P$tz_NCBmc>d*wh8AMy?0AmRP@HX>_tPoOx4G6i!^E6HKzqz~Rmj}Z>LJZ< zEQ~8NJAhZ4){0nl^g&@j;^UIsLaCS(wQ&q*a%xPlJg1^<6l0C~{Ll>-7b>qP>^K&& z)A>4@(BzkAGpQtYh0upVZBdnj+z!QMXw5>9hk=)iqj~ba@#%!E}E(W@opa zKQ#I46z&(RTu4eDbKl|59zqiWeEASow4oV7Yz-kpjq#7=PMgII`WSK#bO&-c32DKB zgJ>3D&J&Dyv}C0G`;m5cTtC+B)ZBr650ywJl(O5J9*>35a$;N{tCRpEiBJIc1HE1wKb! zq|#s9awYsliZ=9(2Im7M^`_ztPcC$Zd)Tu(sduXBdicknx3zBCG^NWps^-@qeX&I7 zYW)X5&IrO<7T(`97Q&eO&YXZ{4~zyggF0eW!h{S5 zIWXKHT(UTGlDhURIWmlbJAM`fwAfduXPq|)uzrAFzDj}n{K(Or@h~a=Xjfz}F=5Nf zokG6jWMi8Rm(O@6$3x;$Nu-UHSN5XK%h&kewlamhsZh79CKjtX@T-!Tka9+ATCJ8Wn2n|l%I zHdy;6)%j#e7CuH3OER$HVHI$A7m#**`e4NMbSkgHy#yW}R`-^M$(YCh`yrWhyqIao zaPjqDt<1OrW~)=DJRVYTYrTO-+2X1H4ixDi`v@Q0BBhs&|H&E&|_>b#V%q`O|<>omTI6gf&NVEAE zh0acoaeh>o4_EoSB=jxfr3S{&c+s3re**hJtJ4Pi!Na24pHt@!8;YpIIHdb{ks-@o z0gi08=+(amVXy_stqhj6ecSsoG4mL!@Z+_}6ZqLv&>Y7hqk1Nz(z7tJuM-V5t=0HH z-crpaC?A%E2=m+W$NEa%Bj4p8$i^NEvTR_<;_11O>Zd+tQ~55#^z`6neFJ9?HZgtu zNAM_rUq@Yd8Mivl-osDbrQTxYvnnXRVW|(Yb$lNSUmY_}n-PQNJ26KNira*0+!ZW$ z-uy#PV~F`&;q#}|`%$`h(6&HMI@PY5aLlNN_9ukl*t~%6B8_tF`B(0P{ZDSHFTivB zVR-CBuRLBA#aa!csX&$pwyK$6(Wa38%PgOseVX*st~o!^ zDU#g_Kss11yHUyIeB7_rR$}ldQN_{|YgXcIA1{w}-WJx6P4ZP@82yv266G=~X-OJQ zwfu3u3v{m5wr%Kh>*q?EgqVwhdk%HJdeM$%V!heLJMZ;3q?efx7Vahths7_OAMaM5 zE!eTCy|;N!QhORs1v-hm^AQe7vAgLHU-+`hP{188uAWcR%Ou$?FhDS_7fd=o=Utw^ zP5yJHi%K%l$j_ET?%}0{#!UXPjQXQ~`V*mOjxXWI-#?CN%V?=14{|^Kk$cR`+e636 zk~~RAiN%c3qD#Il<#Y>7eU>=L0`PS??Ib;UU{lZUe@9|}9L3B*W?s+0Bchb#%!{X9 zNd*nXN(W2YvV-Y{m2(j2&43H8aM8J~{NEyuyMW*Cbvq|(RH#yRsGj~7@jU+8)uwnF zmTBVF>0!mYS2X3J_>QSulRZ=rG9!x!lMjj5hOLHg_!4LiNim*R*{$=T*e=y~$s@*= ze--N4B5f=m_{D~*{#c<3K6!hy>xTN?^TVN|XNb5gJe3PX0ZtfJ{g`6FUlPo2$@*7s zB0ip!p^sgCc)VDwa@xS{>T5gzVXxDJA6fMAt-a;>_%m3u*?&7$pvH8LbPuypCF~TER~^x|K*-EenDvT-rji4DO#M(#$vI9rE z1FUs~MnDGvg=&mRFM}y2^;aclvTD*L7_|UaGQ_ljqj}&cij2Vu!@-7QgxCooHmQZW z8#B+O*6v0tK8e2Jg%An~W5lAtJ6xDelc61^PK=fsz~6^lX$RY)yatET4{Ej2Ri0gf z??_`voWW~5C=s~2YJoaR9PMF)5%qgfUaqdFh`Yz>>J1RmXC8?e>zCsmDF8Z*LVI$P z)X7WmVhaQur)^Il(F*)VuxK73B{=utTr^UKzEVxXnfKT_??Qb0#>p=vY6EFjEZA86LooAH=Lx zn1sI34TT8`_b9Ez#Pbu?ztR$S8cUZQLLO+9*n?%GHG09(dfck1(inYN&8Am_82-e> z_=GS+7bc77_I;-v2ftg+tzM+7BjbFha(GBTr>yIn!d+%eS%gkiU5~u% zo2qV1O@=&TdqOW_f-HH$05kD7y|ZMvUruqPH@y6%7p&!5-|H7@DkL$+u<`akWRKqE z8kl0@nK2q@SUDKf-O}Fkv3)=t(+WvMvB!uePs$t0HxcW`woeQzKIQpPtu$wIA ziI)&ES7BK-n82nY`LJ~9;xONwvmUyAcxHW~HhHFEN+upOATQq6_#3sGtZg@>hcAgf z`lcU;VNZgPeHF&sxx80+tc$9Skvl>8b`N<=*Zkmy9Z8SHv=CmYW3C~0tEB|d?dUu0 z=)cJZ&oX9Evj$&Qq;U#liH>E&K5~lDCC_VkoaW7rBjL-C!{pOdZKW!;rD$^E*_R zEj`|Mqxk5BTNf$chgjtwVtBSuP2CIovZ9FkUaFBwPI^=IP5B~YLy*+CdZAoIvmpc@ zxEn;qANlS)*EDd~I3`+SDnmb9T|Vm%$Z}X;SIMj*{W-pdS>d{AVbZjy#-ff=$1RK* zTHMTh+RUzyS0qeZrW+k1 z`2{H(CSsB0o+5-&iKBEj=I$3{?j@|@3gnlH&u+0>FzYSM3B^2$O>vl57&kJ%g|&zc z?SDK{X7!NN8Q;_;Lw;D{ox!O^y~;aHYi#@zs|DO083l0*#uZu7m_$`7F0%B1O00;Zc8sS6 z-6DC@awefZP`!vW#En)Yo5q-rt&EUs$4DER0{|EeprV6jg-nUEo+in#kaS$YJ2e)2 z^%DC|)6XXR_FBl>9%4APtd{Pwo{R?BE4qOvDs-Bu^Q4%Rwlc7rOke8 zi+5(t6?U6{k2bv3)*rEKAl>YiBSqJGHm@e^3MI;)N08nqG{&UK#z@U>TFo`T4@~0=jlY!$N`#WAT(s_VQN9fd)lUrwe^)L zbe}5wc<#bRT=e!-_we0I^n-UA>h$(_>2}cjViZeeJ8H(iVoX>=@l+~CkksmJ<#(z? zwo$T{N!040kzHS9V4`m3%=9~pg5Yiq(|g|=fmjSBbcwUFk&D%87O6oEbrdLa6lCqW zsdE&baTM8d6#eWd_SaFIY&RQeLPzT)1>MD0-o1%LEs&Wx-HvjSO>vSda*}`Pq|ocM zG>pg0?xZ;LobB_jEeOFQm+5Sw4nOE!m}-WR^O_z(FjtHo|#9psWphFMp1Dt_b zWKRzsRtt{~0i)4EW-BA9vF>?A?o{H)nEpemes{Di9w3WXqK_$IO(@mG2zTs+Wg_nnYPpAd((atB(g5;sI`l)RxFvb3D4}!>lOxhFJo-YGjr&p*A&3Y_Gf} zG#VocW4(0Bghn%k;-ULJdw4N)p-38fOja-w7pFkcpU?ouL*{t(_fP8X;}r+v9>4}! zF;tk7iI-j#DJR9|c*T`oxRdnB8IR(>Uh_W=XEl$ed%fQL^_);Xp|U^ibMbzL^~Urc zcYpR={p(5Bcv=s|9U=K4nR2|wSraQ23yEWe3QhYCqd@pXnrbWH_dB4QS z;%x;#{kwmhl!L6iM4}8o`^jTMU0miA?>a_xNn(yU`9UgV>*#*G?q1KBgX51FuhIJ_ zm#i=vzb`1*IlO_e&j-`@*Ei4b@B|9b_`>jRV5}>6tnJA7J-j_A0D5z-7Hr9Ji6rbZ zOD+4PS_1~-AfM|8sL-=mSyQVl7|FA*#LF1!yO(9b-EDq=UqEaq-tG86%?584_<8#Nmyspp3@c2}0#C0mP;WVqxiL`x0iM>KAmWDa zcSK=k1{azJLF<1VOxa*sA?Ie#FgCtPws&EQw=ws>1Z6`2R(l_!+n632<=OF%hul3( zW{SL3uIZvM_E{u5L4x&dj8=A#Rq-C%5}x)Gq)x+k<9L_}`A?czJcFO#sSqK-4?;%F zLjvC(85)Fa>4%_Zz3Oj;1Q7fT$Ub=F>K)Y(XnH5emoI_!F2;@zgJy#%MPXv@V)n8& zZju*m5<&(=wSJ&v-*DgQP`i!c#5xle?;~LknR7fbXThm&f_;1gG6+Kaets9P3PRHT zjFJ8M=)uo`H;;o2ei~Z9Mn^qIKc8f~hU6DtJqo!py&dEw=Ec&FtbFndHzFlAK3ybQ z{q>8&0NNV46K9}tFd; zn{?5Dm8_7VH^+Kx=Y?WXMPh%>pnoKvg#>>J?0N8g?Mc+;@^$CWaBk%CY>UnsxEEV{ z|3YXhhTf6O(l{3WSi$?+{mq}F;_%dBm#p}R>ZH8t-g7#phj0jAk?DMmbUK`i{W+mnQp>cx^cZ$=47)6b9ZtvsL&fp` z{`b4WkNGgoY)|sp7r$4>+`=uGn{lAEnlDC_{dy3SVhhcSJP{VS>-rim(T!BOfk8HmBA0k!+-Gq4LC6Tyge9CCFT=RlezjP zdy=GC-scmlL7O)|BD{o74F6TR3YCoBPsxYB$h@R71lcR%NJ#%o|51v_jU&@Y^u76O zUQaJ0%Cp&B>a?)qihTr5Ygu=YWN@KZIbpbDM^os?I;dDsYr*YQn9n!#K`auq09%|a zr-7TT12)8>qTNxKx6;4NG^B!q>#3CankCM3oq++I3M?9i93k&+n{eMH)i!(vP$!JB z=*nL?ndD}0@&Kkfx!QuchV4ArcE-<9*$#>03xuKvA60YRLD4R{Zh4PE{*q(dF$Fe% z&(yO2Yc%SgR%^@u&sOUg>W2jtZu9>~tF>k_mH&TlwU#^6GbmR4H*$UO#$o86$o2QV z0-1n)z+0!a2G?p$%sBaP zy_coV)RdVS$+G*^yD5r!+`7+nNuk^TDp12-YeYPD2dhignvX@$UTr>=Uu||yw_K~h zcZk#Wx64YR0QaZb*T$OW5{z8V+Z#(~#xl%`@I2Qkfq)Q&!|&vbi-rzVr|u6~V)jeW zGtQd%GPy>^>@k=MK7L^3s=82EhZ9>uxdS2+1>>g?ixxeyKB%rJ+mCZ4)IN-3_feO3 z)j%R{1l{XEBaASKrbH_vQ6z-X-MHt64yTG~mPehsXLYI>ZLmxR8tnq()J#p}h14MG zQGut0C8~*@g<+_~7dgmi51$6Y%p{+V)Wk4D_Ira&7!h1E{D-FJ_n(i_`_pFHypnx+ z6vwdIoF}N_)2>o82q#pn$;jwT5=zS0(w^-ot4(U^#7BlDb$;JMO^UF+C$?p8T`|rT zN!XZwGzqHNX!hXm)0LbO<1xO+DcO0PA9lIV4OMY;wrNr$dd&zMqTUSuj!+Jn5a@30 z{C%Fr2KEzs3p$_r+D#3P=9u^aMZH?FTeq^}6n;H@YZ+t*jh^8-6~67xwN>Fc%gu9> z8uMODIuk-^Ou~W>sEGy2&iQcPH+-SyN*v8(tZ4T_WtEpxPR%c*M@}V^#k;CnF($EX z)pYqqYhi-nrhJd$+uxlh{XO1oO9_pn{WA|HS$=))WUFVKgv=@3em^F(_u3dqE-g!o zB##RH1|SR!sV#-?U#m=aA<;j#pNL(3vb$Xjqx{52sqxFF;@RKdF6nm+k4N68$X~nP zLEpJ9eCs>A`Q~^H0kl(fks7*QAK%+@p<-o`00HFA5UyXd@dW0M{B&Mg{6xkHCcrjz z-5I-(W{wK-dl`Ezd`!T!6hjr}7t0EX77R;OMYj0GB^vG!xWa~z?Qoz+7N7LSsa=)* zD>jZ7f)Liv!8!wT9OHgvt80kutvDy_04zzFuf;u5hWt2iHp>&sv zFE`T#02m1?DzxY;D6_RBg(eK+5LrzmdaOx|Sx0L^@8D`@Rn3_GlR^Od&D+O}FN(T|Q^cb_ z8X!Lg_)Q8OGy!=9fwb>@;jKv3n}lp3y?31Nx(=FVYUWnhGs3P<+;R^_jZ&2>q}&HU zsy9#|Dvw$>Eb)$n(iHRz>^|#>JB3(VEX;u1l2j6^`1j zy@5xr*jdvWvYLu?ZU~37x<1l&p;jSH;9mfYwp!0lMhHtuA=8SBMiEd5*?!WoxY4V5 zIZpB1v?*~p-CZA391GKlwQb=%P$5(!EDs>aS&(?CDd2 zC;$L5%p|>-^{^{Funu4rf>Sio<)s%AQuc9=-I+VG~gKIJKeXkV7l zpIBY>CXP7}_UXEGyDAfdVy&;`~p_cKn{@`_x;$pasC#SeFZoSkXntJ*(< zPw9lEMwZAUre~R}D3ysT7#=)?IUP>Zjus4WX{@5j(z2 z_NUL*MQbKVnetTv=0M9p`zEF6d*w5AD#HV6n_N}iwDv!|QXyf|g-TenuN6m$Rbw%cxRbnAQyKUwp6sv-7w;D4`^Av=2fXuu!VN$}e$62tSCV?0@hZO+oxAiE-*@I}-h_upt?F5{ULVW+ zu^+$kV=&9crYx%+56!$wkCnbrXqbiBPpXjXYFkS1Hq*@|j{whCu|A?Ev70OJxv#s^ zwb6Ec+yk>>-uwuqG`HXcz25Dd`@U|4t}uuqV&4NH6-%{L%qg(6{hB&3E> zbw)=Mtx$LO6W37_J$+3@|2ldonmDar00sm0pf;2()GsQZM9!m%s%*HmP)u&rf;rJL z%`x(>9BeQ&oDRm~#(}4b79tSHQ}qqR5(L^qUsa)7%ZO>d;pWHrA`MB@w5-d;iD1Yl zmmyRr@$he&(RXFxXDZh6?Qrj$SQk-fq4yIBNuP#O9@&nl1?moA&bC} zAL?fqj%Y^(J&FmI^)9+8qvBk_{M-`|agzYpF{q2UG)rneCnQdfO0~48GeqT&Q6mjS z^B;-i$XZB9$ANbup3_qorW3>36SZign=m3DT+k)(#D|&T9V;|<7m!_zam`5N07kDn zjX14@cr+*Z;SUVhA!%02r38*~S&ZN^efod`mh3=e>1H;~2VWn1YRN%}rA*PrPu^dl zQEC^N#!ot}rktl|GNRV##f$rfq3Iv69g`q_6|I+I_!#2hplH&hBh*K_%1btA3^Ce?Z-IPh@yU$ z$QB6$4-gh1h>Qe`E4bk=`%^Rq2y3F@IO3^jHgiOpblT7P`9P+}IGsA7x>*M88){|< zA>b!22Oyvh@NCOclAv75WOKoA2B-}J65XMz-vKvYrQttJQ-=}b3BW7+gUrn`%{p=v z@YvWM)AgMZoc1>dHp-aRhNpE=iAJuAW3*Rf2BwSuTW``$<$3|Ke@S78WkOZ^iT#U<1HXWH@%R)EKtnSpTqHS2g z%^BfFF3-Sr)}EDvZf=pJD)Hw-8~A`L&lO{TbkT{sW60xTN#mSfR<>sX@NcWdAo-Gi z0I)>fcaKY~#)>H@N{EP<=KD*?*B%gBQxdlYD#B(F`e)I1N{9Lt1hv+> z_`~AQ2*#hGu^D;!a`#AxCsH>WwHSoxjm@9udOEeu;H>+deyj|dO5j#sn zAaZ1yS6efy16|b#QLhc(5`3YE=lzf}>$xG3m&1!WK`w{Rm+|Zr^DOkwt)}{vm^vayBdelu$(jaxlYqtNE2hY(aBm9J5w{K zLNm8bGZ9`BSO^}EXaxG3cz@r+8DQTsnj~=m*o|lEz*$w)-1FN^dK%luaBu2$P)&v$FBN?j0pg|^Ak>U8cF z+&zSIzTy-6I!{k3&0lR^=QWR*tQ~&63`!C!L=?#+5gGR9H^j9G1ePD%VksG|Z!bPwB}b&ub$5nqAjLV%|k`JEpwI!_YXA69mOI9eC^yDF1-v0eD%>)kWI zyJwkt=JQ)sZuKmM_c+QotR!(=bo3~xc5U6fpbW&m*spN-OQXQICm9~KP^ zP}>gBMhwsw3@|cNhbO&e>Shf8{fg~`4y+63Q5@7gCyYoM9Q!!%jbk8U0Bm~D1|N7O z>_t1QGjyAz1-00w@Mj3YJS+u9Ag2x6yi6!RKp7Jr*&N+{cldL-Z0;4{i!{k@P|gZV7y>s zyvVD}^VfKpFh!i=M8PuD+kV0dIZ?aua_Sz`wj@Ync0$hxhlK%>x|9B$<3AV2z!Q@v zdy}&XY2oA0&WNe8f~kq_si}>rnLksr%+vFV)9-Ajmm;QD3Z~b(r#CjHxBg6T*P&lA z&wR9Y*jp6dT==v{5rEtg89MNy<%*;B z?`}L$6n|&RF-N2amna<4U&hya${=3?l5YgrTrb*AFY*CPzKi&Fh?gLTsi0+iuo1*6 zlJ0)}JkYpcM+(?@FFTyh_}~*zi4+E57YIuGJ69L%>{bM$mqSUH5(}4|%?M;!LWwpZ z#HC;j#A;YQt#IUW$R<3#9{w3O(Zqk%h8PNxSbSBw8vk@HSOi|yW0F!2kA52HRp_V@ zz4*#*SY8|gnOl21wZ1vEE)R#WQ9yblrw8re!-X3IJsT678w1|UZ>~4q)=zi#tiNHQ z2s@n_tlyl~qc*oW+cfv1sGNpB?C*V>zcD+#aYPDCZ^D9Ma`scb-zTN(}! zO(4?y-q`TP#$e>;5AV&R>#e;A2r+USOtF0u2~pYJhDG69u&{)z5RT$$+WiNd#}R+cdz1fAJpX|6IO4C5TYb#kcCPxtmdpF9 zZa&!vh3QQWV<_sBdLus`oNZ1Pi`}9KolZVD4)J>M>+cO9Z+7421-@H}&rg*^ zT-UPXaa`ZNUwizr`^V97!vlRTEo$Fc{F5dQqXquvsjzn>;?tT!r!BYe0$5sCTwa{E zZ3G{mz6OfE?k83M0e$Y^l!*D*0rvQ_oe&$lBZZJO=yVktC*8;ui(E>;j?}S zomXQ3Mm7BGyE@t<^saU1qs$>E=Wk+0C3?~{qU6$1H! z&0`|CK`K+y%U<86Wk1z@o54{$0r%?uC?1OK6L|+}@Uzu_pHIp?kE7S6ON?hQvhcoG zv~;b%z!86^7t8kXB0pA~v%P<;cBH8$t|marD%#zcELx1SEGhpX{_1~jMf|)u{rTRP zw0u%D`YnbS=%iNW+DW|q`EnO~SM=&*29EgCjW!8Xb#?`K-?G z&&8kC=n7a2=0c5Od*8X?OBVY4$!P_<%{4f1-iQ^zE0kx)f%AgYza%bd&SJo(RXU6L z*D86t5D0tp^BA53ph{j=u(h4t3}`I1(E$1>=jE}Dl-zZSa12?p;7rAYjHGOJ?fst< z7Um2Nut-vE7Jxlo_|{IN9={m^_e)Gj_%C-Jy!8iQB znXdn_TyUoAzxLXHOxJ(yHJs`CpMw97>H7bt;3u)_{cE|HytqI8&x8@&0mFYRm-c|; zE!+Xam-qcK#BBQA|E1u6nk~Oqtl!glalG=%^ZkEJS6snoGwAKU`gXAJ;=$YAp5Hio zZL-*)ulM@*<+tPaZ~OXiq9&Ao8%M9ZZ6jg4xPnh|>i}2qHTieY)aGtGvGgw0JGg>> zuoKT7$NvGto#pl+fxoi)L!xjSuHcK0^6w@~FS+eL{m%iz5Bf4`gbtS>R-M+J8>_~e zTakK?_3&f5A*^&L#$V~(qi+H{^LXq-3SdGPVa zd~c|>Ypw_MYE3di^xI@{By^2WY6|9YP>T6+l~kN?>xjQ>il3@Ra^jD>KuPwkF!#zR zA6&uDGS;c7D$DZls44ktbtv|t@)w^-*8B}8uHesy5s6d{ryUE_mJA9B)_$*@cscYV z@0fQGI_cRw044Tn=m8paz2?OOCR(cu(@xvgeCAJE*F3@}ct09Der{j!k@V?Uu08f{ zUqN+=cAYJCiFBNX5sP-9U>`K;eWE2{9qH7%FJE7>)}0NK+}1tgCKIZ|aOTqspL0{u z*`Bd8hbWG-U&jf59pmWQY3TE*z&W!<>N`+M5iSlXYOUEnYGj5V&(vlN-x-2QWe|AM zv&wYW0puD~-Ys+D?`Eav#Sto`Qq3YUB0`y_lsY_BZqqW$cD_A9-3~b8X89<%UTewj zO}@-}sqOC{D-W{uLYDKL>rdAohuoZMk}9n@krOMab6_|#u)lA;f9xyS)R9>y@^~rT zpbj>gYEbZcFO5rCVlTsK>gu4Je2{=G#`f>|8)gGe*qeqvvStQUw?i|7(5n+oYArN! zcv6y}SZ!K%S67XEl(_!SIpSx(;x}cfFMqzzT9C)KxOUQ#0`9NU>R$)#CV6im2Ck<>b9 zIc2EYWfLYZuXY9_ zL|MP3T-Bo4FD1xBTAWz^N)gD8a-;;TsIdjIqX+3j#a}%oeio_bOc$vwJ}Hov%9vmB zk+McYxqTs(C4gI533f9cRe0v?BU}1;&`>*vhck3Azm(5kYoeG(HB+60Q~epAQSkr( z6^T9|uZl;zO+Au9Q;tdJNhptvh}n3p=XX!6w1sR^JUdueXPq;@<9~6QVvnw5y;yp! z{jP-dGt9$qnS;zXT8;UGeY_F|8xIoI#Zr8u9RX>pJn}IN`1-L#fw2OJ``vjRZmuH5c&~`4?&!KPRr#Mo4=ogs|u&Z8It&WAqN8U2#ospfQb&eVwpUn>>b89kP9zD;UwQpBsQ6=|#JMBOm zR$^qXAJQ58kd+wQ$h{=o#L(}@wACJbJ1|l&;`E3%5*ghK}O0v$44^Vf>RM>ouPUH5T@R zfu%BQQGz=pH*J1P^#x{p2Ja#-X8JGaPK{m6UpWq>2}G@`q()1rhEQhsZtmFl!|Xn( zEH}J+j`xhG-j4ZaYePnG5k(Dpm>YH5+rZgoX;iaKaLb@-x$}XZ`!C&JqM|l4iu+Q& z6TKr3A5)=bl z>}+4*+R=G+!$Kr2i4UP_`<%bh6{j5TKY_-u9*1@+`V3S^T>FHu`F1st9J&un^PEwd zcviuJXnN@GSiiFH>F^SW%p{%eY!cZKuq%1-+okO{A*Y|c`5U)XYilp(7^W?Lf;+$b zbhCNG1E+7In0cBf2An23{Ac%1htH-AQlH8^9NK%uG?n+E$TMwrpZDQgExDm$m9^1@ z^w-bMmD9e9D@%-q|U>u|j_Pzgm?FEadhw!pN^4;F%Ndewb1x`aBUT>L|`L;G~ zG#>uNcC<|7w4RR_n2Gh1rkr63p@r0CE`7$?t3tceIgOY0=tgD^%3_J9kr#ng`B&_g zKbx?ms>7%fJ3k9vX!hg#8&X^?2ajD8HPuQxu`>?cV%MMVnS@zh&;myG}1{xYp_-%F%@{QGs$hg+DfF%2Yy)i2)l+B3y3bQgCy#NFbY1?yw& zR*5Zc-L$MHsLUm#*cMAMO>$tG#-Ah)rJQ8HQ5J4iGLQ^0x|BMD^qA9ciO>1&xz(vF z>BZZfYucU0*(q%Ye&bbI#|NgcO228j-w1f|T_qC=XDiznast8KrG8uQ#|HN%# z?PBasEjMJUkBSC;gOhDXlbx54^+L&3;z^gIN#DakekF2aS&s%<%wqXma|7g(m(3Vm z45*x3*&yb*eC9vmRdE!7C`55EU24cgEm>3jImBJ(Q<(gIm?CeSQn1svU?n%>@JbZ{ z9d+a5m>@G#<<2VciFba7tu6wYmaYpy1k_O`ZKztf+#PYy5kR(aM!FxZci0H5mXNOA zM2D>PlZhY*e#U4Xgs323-pSv^7kLAi%gxS*#o3e}~2c>=4Q zEtKDdWiEjo51gEH+|g2Tve0{N>H-ZE_iwj`3-XAWsf)5y8;1>vXa$S;WQl!g7U9E} zJHQ?ER>54NL0_ZEw(LW;?8!c1$x;q5aw`A_B4Ll(tU^tTz{xC23ienW)(*zMa|Z7F z2CY<+FT0FNyZfX`O;Rpf;ifF{g{)M9xuR@~n%o(#k=yBF-$^dmsg2*A$~}&{Cc2wH zd`?`X)=01)AXz{-gO;to>?s)r8{0dj5mnwT*uL*&qMLbPd^ano`IhGgRhZTdmiz=D z+Z-chvxwX%Ax$~(r#Sve#_s)**{0LTr(Xv9>w^hU&yX54?TY21?g4^>H2nhsB7MuAL%j{ zfMdZSfpU{{x?E#&8Rcd!hTto9@;{4YJ=!jng#Q-+jzDq0a_5i_Jm7vAgPi*$GNgDr zX&I2aLr@6iJJMM*uXsE;Fhn!-L)B%A(-WAI#-l!pm%9j_ff=5GxtEuAX}%a(69^|&Ypb4-o}In=0NVt@i6fDUPDnmC963%aHYdI4e}PRvv{X0$oF zWo;J+0-ohY=#UQM0ANMcliUCf^Uwp~v^vOHbN)y|W&m!_$wIwTGYF+z0l1Lp)`j-g zbQBqeo*Js43YbFbmw+j%fmyKEC3_G!4Pt^Cg9QM+*QHYgW!xe!;&P?fqAeUqty!c9 zC(}n43pQjlvE7mf7%C6p&<&=!4cUMI2w(sPpr8rxtWgj-SEPK3|1&NVlX>u1ZMc;O zNI(MYa1P{v4(EUl+9Y6T>XQXJoWMI{K+SI$*Qm+4bdn_i zz{6Zx7=V_#QQGOe7iqRfTAuz|q}ThyJ`BV(fSw?gM0D%0<#aa=mtISpxb;PG%=A)& zFo^Hat>OSjawL875CSA1dw6h2aQFr+Kmzq}4oTa;|7#5ePyjEBcOM`Iet<~7I&)^g z0wLhQ;R+7kgadG3I&XjkCGY{R3l2QljRLBgkEozI|ELY#zz#it2aLD79kXY{HC$rw zoLGwlfl!df%QHQsJbN{s;t6j#{FenAy*`Y}KWx1~thO~k4a(3A>iMccG9}Uw4D%v_ z%a(S4x65Bd2^`3QyKF9mAO<@54jd{DLrV^@dsN;a4&%_iC9ngp(?^@o2Usx1^T3VW z$iMzuxdc!E*07-3@Bweo2Y}R$4D<%M*+1yun?A`6-t+>UR|A2p4(I@bhy0;F8N!*% zrXMGASa3RKDYfy4kC&HuX(@#%Lr}wuP%_+H*r|F>v!mn5ubq0zM~c0z+|fW>o+wZQ zal5dqK_z-rOnfVA=CZg(W^1Fyf#S6_Zh!*x{{RojY!2x_4$K@5NCghRsj}P<4je!N z12k29fCT(N0`OpcgUSup@V^BR&erg<+Ry=FP>Dx&IfH-#8`=(~g~+JsO(np<9Dq$m zI}U;hoI?4CtC@q?P@v&p4`N_vQj45sfIDbqwGBPI!pq4u!!tqSTy*7E2}oTRovP)j z${g*{jIFjP5SSqy4HCGd6ez1GU6V3}jPSEHz`Sf-#e+pyM|o4Ibi4c-vfNbpq#|IKY7gF8szG&D4Ym)xSoJDr9`Lp7jKHq3zH z$$E)Bq(R)+t^C+1fZr_e*pR*7FwkAA@sX_BW)XX(esnKRX1;M_Q{R(68F~-&Ko5NC zz~4&9J}qF`aCZut4d1{HDeyA6DG&Ip+Tifb0zd%aEY2^>4m2Qh6Eg=`P?gBcjr`ls z*^tO2JPvZyt=@`MC|ljkjopguaorFO^3dH)D1R1A10@gwAz%+E&;vX7oWdJ3TYJ1X z6JylVGd`4n5E+J{%ER^T(fZxr`<>rtp5JGl<|qIIu>2a}ME+iN`422c(#a5$=? z21oz{dur3jEDqy}$Tz;G*{}fLAiLZYuKZiwiLA&^t)M#S0W~mx^@ju~AOajvwC)fB z{E!1tU{+P`qBDatl^SEJq)~-!V}OaOYMZw9t>0>n=4;O8*xu%n4d=~ZOXu{S;bZ3o zw|4HLw^vgMa}YTDun+lQ4>f(-9Qt1<`R1p5o#> z&f!o3Z!j@`i~?k=U-2*x@Xk%wN1%AOrfhnM7EZ_k=Guuq&H${S+`tYj5Mp<2g*Lzf zB>)1c?G3B0R4LFxyyH93|H(<7e525#V|Ybf@=cy<%gO@_#Qc5j)t>G7T>{x&0zDuN z-7Z)Z$V9z2pChS7i~G{(H9nhQk|x=>{D2Sn(C+pC;q(yi?ciUpuCj^@z?FLqddChK z_qrN94%z_KD30O){0$>u27I6fWg6&#F5wV=+C(++{VU|!H?D-taRBz4*>Ls%9MzQ@ z@+1H>FhIH_KmsEW0wBNvnxR}G^DX`Ydw)c(S zL_hmQU;9Q+y8j)4B%K<-002z{lPQgvi_<>8cM9SQHiGa2T`xHKfWP&yzS#5*@c<9v zz}EMU_5h3k+3@-7|3D7sPR0#f~Lg)+||RX{AkL+w9b-N|Y*Xssz_=UA&d_$_-WbEu~PQ z-VP>wDo~Gn`$*W!N20MkcIx28qnvKrHO$wnSu3#Fvo&nx+?fLh?%Oxh*fbMh&APKS zZ6IvgyI1>O|2=y1ZqKV{j~+a8;eZ!S^iZM zMEFUJAIEa6N1b@)d1oDS(&;9f%shMTHE-BiM;zwD`Gz@c3^1TI*EYjO9TM=Vr#8m$ zvB%4L>f!AJc$Um&8{>?-COK@laqc-dyTNQT*9`ck0})CnA%qUj5wA4n%tOz(t^L8T;wP*0~M)N@r+OXwjQ zX*`s5R$6Pd^(wX2nx+|G-inJ7xmaBB*I_ByWRt=IV-ZG0px|d8d>YfTgnZy}2OfHW zOvkx7=d7-?*T7L{9P_>r@5v~uqp~vtt_epTe4vFkAA9VnM;?0cod=%C(kYD_ZPFt& zfo-_yrW=C~md={iK#KrR5HMhe9C1o}(%|6=&8#?V4t*DeM&&CBr9@J?G(Y_Io2e#E zZ4wH=P>&i_)C8wGRaI6|&3RP{m$~p(po12A=&L~F)mLGKJq6jk>H-Yd6A2r3Fjgk1 z|1q(BaJz?IdfxH69dX3z=CaK+GXUIg3-E>KqkxmU27%ErOYa(P z*l`t#l3KS9CfN1EDN_P>qKPM;VwPFK2N_KD_nm_mp2B8`HvV|zHDo3&W!{R>E?|{) zy2TbZ0W1_3Uo32{Ef9m&@7{cKrycVBmD8H(rsGWA0^asUvjoOz2OV(cR=2h4tbrgO zz5D5>2fc|2H}jZ>Jmg_5breoGrDNQhI;M>?nP*OQqntLr5sq+>1A`MG4|4z(|GI0? z%^C=3S7x$-B^I!5BrWMd4tfBEO5sN)D$7ZLbT^clF(oRe0-o@E7(5bCD0xF9ViCE* zD_}6~SJnd-6Mn)9mgFzM*tvDAv{6KH z0T8e@b9keT1)vfEs>6*B>|-DN>j6K$6sCHRselHY2jS3B9?T#hJ;y0jda^+z^-+#; z*jNBKFo;1w87w1cO28RKm>JWRV+1VtK}lLdve@;9KeuBgsD5%FQZ-1HtKS4Dyn z;8K^l=g!CG4hKd%+N}2RH3x9Vm-fg%d(}%!1e8aZ_C>fj4yj$( z&_?MHa2(aB#xt%VfB~|>4NH#Wk`ytIpi~n=^lheujXTE(P*6%0x+DiF>(73E(n_Dm zBxW^RP!D_PLtOTLfu8M@ZbfdG z=mE$A{u4Zi9N{8E_}+G9luit@MglZqn!{4$bdbbMe9NXBjgW&KAt(XtP$JTkQI>YK z`xH=eC#v1G)MmEaSxjr1+SKL}!x~mCHJUMvw7gctBi@QJk-4;AE$uHYg4&3lSUqMw zv5Cimnj&gD2{Zfv1@sD}a-HNRH+h3e+c?hsSaXdAEI=F8|AkHH&c7IJ zVVm*(EjRd*$3MTrq-=PmV`gjYXFfx@fq27nr=dteGjq{yiBmZjjDQF7)RP~ygrxdm zSx`*+N&}I~cT=e)t1PVHnQ~asxE#T0IqYbU-ZZBqrZi7O3;@|SPez-53oA?{wHbxl zsB)>TQA;F3 zF4m(*1SDXY&Jo8jZ>Bh8HuFL~gE}=^EUTd8Hkk5V9vC=$$In#l0on#fp%QG|-#G6yM;fCH4F zc{{!~>x0?))&^JrHx_UW>mEmhP_9O26xrPN%%dFdAmBYHbKWq~Tx?_0Of?Kp91>Co zfAsD7HL}qFi&X?YfJI-ok@L0-h@dA=59yv^|G+Yn=FZ(TD-{ogcjyb-+wA+^cfOrp zcIWSVwadFS+pRxD5CNbW$dWttn8?JYN>Ro!CN-&N6vd@skKw0aL?3z(g1?oCd|pe( z7|SSF)-+i)mM0zEPHy?+PynU-0C`{sai~A9xre~fjY2vkG_i(i5U1=CA&_$ob+HnH zFpY|EEQPAQZZi~(sDKba0p6nlQ+kQm@sHZ6I#|lO&^kUuyRd#^KIVhI=NkbW%t0O0 zK@^Y%oVq?B93o%>fPs4+8oLX+5S9`l45m4jBc!G&vI}bJ3n<7Ee3*xIm_M(1hjuua zHsXxdkdkoXKM`V$%HzM}cmQ%Zhwmtj|8zhH!+QsMpp1Q4qq2fP>nf--vl}`>ALVEO zZ@5DbG`gcBsJA(X4IpxmF*qwyu_FYvx;Vlpnx0d@gB`;MYgvbN*g~&4m~QAmIx2wEJDd7S z0Al0}FdU&ZS%8PAxp8<7IE=%4*g~Px!w6K4PMVx}!XG=5K$Q!#{GmM$G#$uVI^8=Z zlEM$$;fdjcFdbTzOXMY)vNy4-L9@fbP6S096ajtA!94M>QVd9<5h`G*3+V~2W>T>i znWoy(Exn+N7ciqIghAa+tp{5)#rIAt~X%{1YKFb0g9ThmB)N53;## z;K;`@4{YF!pEQ(_Qy0psBlNilqAP$XdyVvgvo`q#bpU};D!~JjI;wL@cT0-h**cs- zKC9%!eDue#{7M`IOW^cP|KJpX;2cZe{7MwizDY|<<}?ep1F_~Ak-!KH7gHMTQ8?;p zxa=_&C|tj{AcQyI2M~ydchI152on2K7_&iuF)T?tq8rBa3@Nz=b%@M#Fb8$$Am;GJ z4x|(A3IHkNhRy^z%9E%3Q;v%;0LpufaU_!BSQ~c8lcd};bK{Sn=qwkUCELWrd&?zG zyulqL&f^5m;w(|&{K~NePEq_YwrEZlg{`6qkrvU57Z z&pbVCP=Jve({R|W87;jJq0y8(7SS59&x=>k=LK`hdySL`s%^fUG;S7OBCDC+E)OAhQ zMMY5*6-DZcl}fGGuDG;Q#GYunPQs8eezm(6OSl%PJB6DA8mI^55{D$)wLnn~5u(8R z5hoI&Kx-I){{&z|Svv=@VUIV797y8Q0PxZPu#U|1%>Pu=pFF*Af1R_PeF>KIJ*j~H*v+vKsC`s9a^F-)O9UdL{(8~SO$A-+OII0OjVW`QL3i$ zwC8c2>)Z=bTPmm_1SEir!+C%OXgspI2y9qcal*-NOxu#=3~eX}&S}YUfDYyWhv$$E z3ED0)wXTvyE0l#YaVp7T4Ox^U#E$s}5MYq61Oa&n0qq!p33*B!YLE~(AP?v#eOt6Y z)yEP=*F{BI&<$PCEdlLwTGPc9wSe06BdQq{HC1H7f0d%a$O~oaixD9NHDG}c7?*EY zfCdmy{{a-uPI^)>1UZw$RXJ+_4I&S47za0@2skN^g{jFg9a%Rb(35M~Tcx1tT~h`{ zpPws-5BPu!n3zi<2XZKf)%2)$E0q!O06#fTbs#`~{8`QwPJ8&=Llxab)sF4(U;Y)* z5Fi0+nAg*_*GuJm;n1W+d7F92 z6P>LY5&(g70F>t#2XtTu4-hwf%wMpyhjZm$0M-sJcHIBxE2=Pc1L_(7}k*6D;9Lj6iH8sPNCET<^*6^4$jysr& zkUd8#m~a>{-zk+Pj)3t9#NkL3a^Qf*4NI^bRCwjzFLvB6KISk^X8#4?q6Gm0{xCB> zV;8;Dw$$T-Or|$3k+?ubDl)hq1z3p7&hc|XujL01AO}M}(-3yNfN@U?+{P+#(rh4y zCCN5~A-0Mjy+~TtknK6_T~;+Uc_V5FlwUHfjGgX=NVTX9lsKk!I#hB>cEWOn8Prq}9|X%_{Nns&ld z;8YibqUfo+zEGx3rC{h`DxvlZx~wg^8w4b%ICMVb07MU>>m>I443jmd|8{vOi>SuS zfTSn61_@dyP_AAty|uAs)&f0Q$`b&`YlrZ%&vl^Eae%YTARYT6hs1pf5(ohf(4dwK z8|G*(Sk`C(n_{G8X_HoI%Z}`mZfyCE@5qL1#YSewCE(@BKF)4Vpo&qSIgRUu7w0YkKr;NSZaM54F08+J00&Mwo!V0)d9bh$xB!XBxcl53@xanREUQDo zi1tYAFJ|e;u58Dq@A+oj`^IuBukXsHa?2iR0=|j>|L>VjT})kL|E*0Cx-`f(cB1QP z@KA_@DEI-O(1#w_fhaJ8JRk(VL>BN<5o93*EZ_$Y7>5ydqvY6a3QT|^O_(=PED`U( zIiVZCdJTaNSyVP*fQIpbt~>+~2g0$k@EWgs=o-TV5+-phtpcoUXb%w}0la1hxM_!V zaEEuuNOZ`Yi3mhP&WO`E2M#FIFPCikuIw$x@+(JnEl2jo_Hz2R@|1pQ&!$&056E1} zH0dN3q@qg&7r!>ngB*i_@J=sy;1_<92YVm^AfN-=b;1>qyE*uQ5GaQX*KPJ3-Vwrx zZ^$^Xc@Dll6aG57Ee*=L>5P+8?ouvLGIj2~H2?uz0JZ*M|H|+iw(1(nh=({54$vr) z?~LIEr0fzukU21dB&D(0%qFH zuJ%&IS74BaU&v0pY`8v7Cc*%BC_n)}YPO5FmieiktTV!0!O?Pg;?Q|%^hQbO&xh0QqhzEGMjlZFc zA6W;RtNLwV%+VoAc7R)g5HNTkpuo)aiR#^qSov92_WEvY&QEqL?|dwO`Ojzeo7a4t zC*Yy?Z=eszF*hcle*M{17CD{-H;Ae)S-^`YJi-Bz|8ek_H-V0IkOvo#1V2y&ML4m! z7=#+QfCfka4VTyj7zcHT!+5}lcwl8Jz1vuZ`@qUS?gi@|PVQ5#-UJwVz~2^_%s3!H z2RSVF_zO&PfCnlKn~N|T`??_gvLJOxuYUrFcHzb~;8t+K0&UzDaC^Y+1Bi(BC1(0L+*&XVR=`^Cr%mI(PEy>GNk!m&~R$ zJ9VnkDON_68a-MG=_W;idhoM{?;gFZ@wRe(=MJ5@apJ^zyLJs5wrkeTj$=pS2A)%j z=5E@=CkKdd47DXNVBp)ickkM@dv~v1y?5!_|D_Yx*ju->*RpL>tJWH3YXqukuIAZu zfB@A(i~el-^k{1f$jOVRE?&BD=4Q8Bw{D(0c=WbnjpxqYx^$1daRaCAnt=q|xOvNl zz(BTg?&!^{H*a3NcjLx=`{u3SL4@DRc_5i_q{oaO&}&2=(xUm3Cr40I#tbO_{QCFv z?~h-aG%z(YpfX8?QVAuLG_?s*p@gE~CSmlG&sFzew_A3)Q5VlT+oc0pHfxEP8D`v& z!+{Bbpc2YlZ`2^q0^g9+fCAV&P*`E<2!@qzg0(Z2IN*3w8Cuy~h8Y1#7EsM*qd6Jc zXr!6eq-mlR0M0qKnKO?%;)I0`m|M{!|Iaz{)B_JZ@xU_=Sn13bq?g}pgTPtYB#;?5 z=TtWjJ@sI<4m#=BMhYwZB@Zfiih>S4_|#PS>{HKGSda=2m$Qu%jyKrQi9oW}AP^1*eIkZWJ@mL! z+i&NDBc!3zVE-433cI8aX19r@0%YyYHe#rDp^@fQ~vHrgP4k>+t(AW9h_$ z4mZ|@Wm`M&sIx7YVKsNnbEqe`rJwlNiqAajxU&_YBYR^gcPB&O_C+(t{L!Pn(=1O1 z#Lq0e@5w{@`+FOWK-4rw6Foik)%#@hr>Yu-6s+AFwTXg^Fp`Kphh2@&KCUCSey{KV zI!-rZlU+92BwAy@HQH!{|Cavj*nKx+1mi8e^(h}`64&jTW27S)E^+UApp+UHufIGD zEa6hqI?T}ydJIN5V@bz$3}&0$#G@UqvCTNZ5t+*%AOYCWMmXTHKJ{T|O&l6X3cCZ2 zaF~od@^~3WXePwt4euhw`+yLW7_;IP&t^{i2+vOSDeF(Ql~31H^2mF@qXZ zBog%lOf+5-kEzt5GL>c>_yEtYDb=Y4O&JA(RT%5iv@Qx_oc-7bKg!7u3b~F12K>OT zGDwz4O7c#1|Fq*gNtC(-+%u5-*oQwmCXalK;E-vWn{Uto4t0F3m)t-Lx~%b;!$s7A zm|Pc=T8IE`GypQ*@di7-(wGaHBORu^ok+p)uyv>-HyLD%IJ$9}`c15b*zm?WIJ7T! zl!F}L_=Y37p}&jnE}2U$DlwHwJew&Ms6|C4aFH7`576fq_n|6uql=#b0iY<|bI>ME zAuCE$LK0p@V_2tVt9>kTR~&K-WYQv8{N*t@aE~HfE(~!A6bz$lg7+G`{#VOL7v>maMoAm%Ccz z2k7C(?8G)6xY^Z*hGe8WEeSM?g=m5$NMCfsgE-@WVa*6s>-~xBnMkO9mmm30V zB$>&+{xuf2s?~2AMHOTiC&$1$Ry20ZH;i%QxfH+`F^kzREuD;H&T@@#WQ9U~q7FRJ z|GG7Kn8`X>p^j<3;Z-j$T<1hzsgw*AmQdWQ4 z2+>)#p^XrkP16=bj%(?<(1d$KIExXM$r2R-pZ2)p&HS-4k;p_J=UUddUgpR}4qOpT zBb&f}Ig^{AjO;$BE_HcTvGMSOswDfISS#3&7M=k8Nt|L6+aty9_s(~ELmuhN$9`Xf zCOKyX1obdebtZD1e}!~^E>sKRo&@iB!^;}qSd4JUaXHa_BO5XDk|%|Q-mwS?lE)#D zSt!<|z*X{}1ds z$<&28=&woLqA=)bl~6>hjPf8wxSB+0}}(zKGk9?$#raqyDFOajKU_{TGz@|veL$z5LMNgl~n9smMY=Y^gELf7`l z%26DMgLILVp}^~H4Ah-ZF3E`>N!!q^SJBaxSuE7^se|k#hB|ainhX*=@I&%h2iF*s zvzY^;DIC~66!ke)ycs|@bk@Z!hc*z$SD1r3$O$-f7MKiNH>6)eUBhW?|3;rFi2oogZ`L<8D&KZUdL9<4Pvkv$6O6=s6$HC z1~@33ofz1=QIhqAMge$(SfoQHD#R>>gE)|uqIn4^by*UsP$Vr*`z47Ukxgd!g=L6H z{cuA$TtLd5ApstsFpeP-`IIq+Au{@u6f@3BZnZ z4Bzk(iePz%{xF0fqQxLC+@y&VJmkZeXbhUvgP5FKpV&h`B%8h zoB2&}IMVIJ)Hm2zLKxlJ>`yjG+irmabClP{q)BzC;2?dQJun+SxX(Ix)F*KRwj}@o zBu=~lB-vR*MlAq=N!C-w$T#eZH-JN9s1V?c*ftCpHuPg@6wV#p5j_niN6HfdG{8A1 zqyLTMFfL*W@h#$e?DgCeM;g)I$Qudh|FyviplibH!Q$Col9;STwNBQoKXloc+?}_OPZLF4Dro%paYAogOEsJ zvNWN277ir^WM~k8p{URTe3`@zA|NURSm4xbD%@Tk|6D9p5|f-w`F9yi?PkkXYx)W~3QUqT(6hzg!!frdj-Nt9w_u~cbCmg)nX0|#(G zN}}OYh3T38r+=C$vL>stV&)ifz^>T`g0`u&@`S1Yz%qOfgB*pnB83-eD<2?1Rd|3p zumd~%={b~xIuz=?7;2x&SOcJiw7t+dp2Obs|4Eu`1!Lr^#pP>Zgp0V0138#Z_x0th zdg?rds*d{ELqUnf;FGGprO_#e{z#hwYy&xLL}lvdInsz}~{@9qX2p+}EU#BY0UrrRriY#GvWc}Qaa`cdUk|||IrhdvSW-2b@B5Tcd zO3p^^O$>-MFoWtj$Wt81QZxwnumT}K{{abL>b;Eaz4$3RoDc_yPPyvE+H~M62XVF46!c<_2w2 z+%HoYg(3_>4wL{1q{I)wL+YL`JD^Fq!b75gTzRr*aY-4S_nbD*l4p(GHD5cP0DTLbav0#?$uC5DY@$FhX2QIc2{*?2yrRqJ+lI@Dr z1n=c?n2a|lKqOye-_nn*mMnZ(Mj?&|I}q^@r?MymF+i8ES&MZ-oAp?yb@_&X7Co2C zK{U>06D$K|_uw2Hep6ILg&+um8Mr}UOF<6wz({*B3!nf==Yu?CXleP4M==aWxs#E! z)Bu2k2f*X(WCw5n{|d&!lKv!19!;q&+7AvhPLx{A-zFWWwGdq?fSueXLRK(jOt4u{ zHuPT9KzyiTnT0K zEHeny9LUo^h<4S*B)majBf$^U01e~-7Yp+UkiZCpfIW~v2`Kg#g8;b}EwBU$^qob4 z4L~S_CkT*$7qRe8{5i5$bGn0f@MgXrvx%8sF}*c}gipwt1T?9Xf&*kW6Gk)g|;L)c)kp8Oz8w zST&L`_#PJ~NA7dyc(RMHwR6+>jK4T@Q#y1<_lv)^s`Q$UM{bYDayLcb_psh-R>dM= z-F1A6`v_J${W;Qtzz8UI7ngv2C+#}ePV0hTa)^ToWye+w##$+_ouF7Y>k)(h^gTD> z*#<9A-*Dg269UA@?Ep!aWnw#Ll%>p%8=h&EvrH$2qg;O!yBzhR$hlA9%X7Z^E#hw2aEs< zq<0J0dIFP!INWi$NCy6y%qf{rYBdM{^u=ICxSPLu2usk%M(NpRbvGLT40RbBpOPcp z7Rlh$q3$jbnPeKR3O{dyI>$cuU7@$j5{&!-t@%u0{uxKntih4U7QOCT-Ym@H-(SJ}rQv z8OjSGmYG>wu7+xZ)9REkiK1h#9=kj_XiH-V3J>zjp?FepuvjK0+HrU}OdV?An!C@p z|CeQST8g`8rqg@qv-{|eKGQQj=^H&-Q#UZ=d(|80=xsOWDo7=KEK@4339O~Zpg?m+u$o_G>%(GAfJ_r{8 zHjqPXDF)iL$@iTMcRUPt_$i>w=AajtIe$YzTtnZLxO!^-{ZQdr#Q!+}#DxnS2o^MG z&_lt592OpA=n!JVhZ7fG2+<5!GmQW^cJ%lWWJr-CNtQHu5@kx2D^qHe#%$B3Q>Q{z zqI8o|B|-h{;k!q#-p_jT;>CLx4<5XD?Z%1QmhBn>0|*c#uzEECHU-)mJdl7P|3nB1 z=D>L~>uuZ9wAs3OYa7m+wgun9apQ)~ssU>S_7*5WfIzjtgR2!bTrB|N!-D}g2JF`_ zf^X~It!szQT)WHY(wQ5#4qZ5O-@Jt*H*VZHbnD1^vwIEpHEY+far4#yTQzIDt?AXv zn=kL)!*|Puo$XsWb^{FxyufZbbBF{bPM^LI`*aZ0fJvJ+jb(W8a3XMyoe0#4 zpqy~5G;KNS7MhlQ8nh2cJgsyRRO4;Gu_7 zc-Bb=tpuVv&?>DI9MQn2Vxvug4YzTv8*a4821?qx>2MpnJRPybz8+(2Ma5cl(M1|% zWXu`?3@~S&caGZ8$Nl0V%BXgbx^x_G${GipYEh#Wtx;pEjTqj|Gi0|SactJ_UQ@I zi0O$39dYi`^r{4@oOQ&gM%470aAAWot&&-d@|pw;zy_*owAu7GTU9)4SHyO$`9;C} zBFwRC$SKOGc-C2mD0=R}2dQ~}EGnH2o0PUGZ>TMcK&bGF(m@gZ;_bJXck}w|acldg z+IOu?6;;=^X`meGtaF>r>JCaM-|hmw8}GcuV`g7vV#29C_fDB43V!U_XHk6YiMUZo z*BR$RtP)A)C|SmE)yw~Sj4#M$ruk-^TTk)y8D~_$9ZKV& z=bdwEx9?GS=Amclcf1j&X|u!;jcl{J<>slDX^WSe1h(dS|J>og-VL$@T(e7gyS!H( zLnyt803znhZ|>gdESg3cW|UDz@A&1PzawA(0OoH>IN>Cen&1nAAM;sMJ|cFsNWBA7 z+qm51*ElLtN+^}?Y5O*5D2(15^kt#@H#Rax1VsuYlbELE>dU*ZjwB*?9}cyAjGgja~5 zgrRV-g98JRjux|6zVsdB0^8vm{m58GfPH5g&5+4WDq#~!%p?>$5g@~Yavz_-10CIX zO1!kO6>cm5kbHbnRyx)(szfDRRodZzh*vyS#flqY|4I?hWXCH@p3r87nOR^0uz=sq z1C;Z)Pf{EO6jS)$8W19lEeM|}1Ci9sk&dQyC>`VY=YZm(KuM-3S1iKh2~C(78qG)m z-~eSjhBB%Bu;T;xFq(aa5=zjxLmi+Q2Rg=4j&+1XHLe6lHV!nwlnt^k!2wR{sJE9J zEnrpIP*t~p2tA*k3`#+@$^zI?0cf7@eCqom{|B~XH*G!@s^L-NCMxkwaKeWYPzazo z1(+X7sY4ukMAB*86b?7hvjI325mv4O#04O!9scy^A_dwR9x@MQ)>x54NqDmoGSq|! z9ZUck02GeuhaIND&+j6IkNvn~9p?zCJNTmxca$_p>WC$^wjsf7d1C?Ch-sI$CR5?? zg&PeR= z3TH%s0ANENN7Lwi>;Vk#wr{fkcT|VG1hTd2}1R}*j2nyj%Cp!hXEa^II1NrS4JyVp@WTm z9SYxG%~!s;LWck&l@EKU!yM}Hz<&1g6FO6tvI|}sXhQ1W>$-BgR}(P;_R^O^XE>&i zIS>LoCY7nYHJO79Dh5j@L@03s0@2ALi#N^bq;?T*<*TNkZd__@3Thby+XO4TS+GqY zs&MnM2Xy5@F>q{S0q5|*2RKlU|8bb(ac-LD0=O|bZv4z>&8kdiJab+ydwD>q(&Nh# z&{r7I?5}99nPEC9k9-Ij9_B0(In<%Fqx>!&wW|kYW9bg1LDoO3iR?KZOO6Dy;~OS+ zZ}m!R=;`%TWqJu20}gwA$hp zC#CGYyqcsYHVZOKunXJ-O-C-lLo-V*l=P1YgZ~Jh>d)mdjwfHo;vk}PuA8#~* zxW0+i2&N_wMG_W}HqTJW^6XE!QGL0|(oM(7EPV^oU6mPE!u=HlgC;~SKu90u?H3@`r_ zuQ|ks8T=0b-_T!j%(xDQJ$kH-W`Ys&pdT3J9@rsTDugt|t3Tc$_GB*Tj07CiAwTr1 z$XqDD(v1*lkFqSI9fEG8)*-CAL67um1!H7qR>z@cX1)->9U7$`_@VPO%N#^+6){7l z%A$upqvwJ!2y8_?nSt|5e;FvDzwO5%mKcH{S82X=xZOpL>po<}Y& zM()al9O@6Z{}6A}U<}5jDU7J08PEt0!x2z`p*xh}joK)=XrijzsEx<~1(dHW$YyHH zAt~4)=7LVF&8s~5Fx9bGI1NoO!#z$FR~;V4Fg58&4d(S z9@yb`V#rd$LFTRn7SE!GNQxII+Zyr( zWhypigVCT-8wjoZbc!w@Kpo5w)SLqi)exy#vJSc1z3PA;ej2jkD?sq9vEd(8U-Fc;2!v67L%gthKAiZBONp`tT-`eCW$y) zFeh(?+gfJ<dFlM8txPdN|11X*3LM6`dx`-V@AR5N#Hbb;LrYdmUqeS~>4r#&< z|6G6sCgw-T3m#}75psYASbzmkU<4RuA9i#fBIRLXC@Ak;d)S)uQOEcsuthOOE z7oz~eh0vT33mvjXWG6Jk0hHJQ9FR%1o508_=1lBtU+ExD=@5-=@!6V!f7L>E;h_C+JCYR6_` zf7auhP=ONQzyxaTF4y4$Mt}xvff$GZ6ht)(x{6T<$UclFAb|$<;=vxz!5k_hN&V?n zfDtEkrYab1H|on1KM^7)vM{W{tl}jBdc%CZg;!Kz)O#$jF-hP8)xwT8K9HUOlqpzaNuo`(S)yrhWfgW$&De87KWgpglr&kw zd^7_quX%53MQZdfh-5=#-OI*bPa;9rxVZK=r7x@gLd~o*_ap!5plpNfpG&BdxF~r| zo$N075br46Z>ww4+m1*xc~wid4=1&PLvr!t3`j>#RSOcSv%24`ABdTY0*22Gqi2HLUiqn4*$3f zNxTe~)gWb+5ixXxeB@fRW{#7aG-Dqujp)Hd$;3w*Rhh1326N*%7=SnbLXuNDR4L+a z*Qr&*!IjW&*Nf(ytp9wIZdh}eBTipneJkvDH3~MOp|ElD8>}QiEh5QMVA-qw!nxj~ z(fJ@u8#8EfCB&PL^ALX7M)pKpg)?VeUMi@!!k}?lq7K9ATkftAKaKLd-7MNNEf*U~ z%$l&Ew38AwHf$y>SK~m6L)5c%IFkgkpetbq4Tg_W)T@`o&Prr`XlT?M>x(u!Fni}p z9!Czb-|NWv1yH#v@3U157`kipx|eZ`@gg^1o~mA6G(RGU58j7u8qlSKOdYxmbi-NG zW$Y+@WVt?&htp|-F6A$ELag}R65}3~AiVPG(#84WR_>!!1P_SB{u5lbfWXeoOSxuKH=}>h11=PiCyX1w5ci zaYvD=n+UjDbv{aFk$^e%(#=TbcJ(dmW=;!;x|9o!Fvd-X5;bh#{k?CKZqZ{pTxodG z^+F0MvnVAh*^x{8k1ee63Spi{_dKu*IX}~HMPz*1;?VrcQTc&pB@BJ)OcOru{HyfF z#YWXPB5A%uRk3=PA_+ac3JDfAw~%u-gGkqoo$SWa)S!2wvuYtW?ru7idNAo{N`=5U zQN(7Q5Ui#zZAWR#*ngfTn?QhmdU#UtI#|v zztWvg5A#!5;eA+TEwD7yvSrmR>rD4vy_=5cN#Jr7D@l-F(T$7g=ZRW}r${a?$bG($ zzgoAlFB(}te8@lBzh9~8YM3T#5p7n7b8Rm2szb)>37_5F76Ug@p9S;B^)z!RRcd)yGHQoUfg@im9j!@sT4z0zq5 zQ`SCB7-17i^Q^BnNP=85=jm5nB60=4?JCG(c~xjQS{gRk1NOWM!f)jB49;!r5C%AO zQogt(NC&3mQ`fqh?3&hS+u=KOnd9D-E*N}q(P-HqTCS=wiLo!9j9pI}(QJ2c9$et* zaB8*f{W~_=`@8NvEvK9_gT?@D79sF+oaom3RVGMnt%7DkL zxnaYcO;<`0EZSPa>{=Ou5@^n6oMP1@Dj}H{<~PqC5kV(&fa72EPRV$YWnT@FjA+Gw zdhl(^L-ziM#Re|??$WM7ijRVg>57DOPi@%D&mPvYfu0+y3qng+$Mp0Soyx=G-Xvz# z_r%V$7gP@jZ(vE0zXy!pH@?v{87vUe`@(M5MD*TumF&ep>>QExF8N|NcdrJG*!FVH z5G0ZI=F+`H;3YTlCuBfqh3dXDr%{4^{*y`LXs-%)qH)Cq?^}9M!>TYXgJ9B^banEZ z^9^t`2i)lxyViZ!v!e2AMLjnx+Ti-Y8y-u!eSh<`rm*OX${LaO^IP1X$S^c`G>Izb zjesJ88L{*|pUCI~N)@y*{{)+k5|pTSHvomeEn^-%|nCi{+oQA^KH%yYp-bPn$?UBjvqH zQEtvab9wu$bK2>OLAJ{~4GnxBzVYZmU2hMpuyNrT`ad|!)@|Ru|8pF}p+h{8 zq@65U)h51g`cl8Shr2V#+MXcI+#Got{4NA zC>u^@xMkCalW=V_`pgFFzt&^l=Fxba8?`$aDIe>2VimkgNd7+BXxnvv zy$?h2;|u!>zr_}NUX5ndVvLyIo~(Py7&9 zn5|F{dR5FNbov*}o~iT!j&K5z!+|^^GLZ->?4nM`8(AxRskc{3C-LXz$}S`HG|GEv zyl>^#r_4D#R)%FPK{O8EZJfk_6eo`? zC~a!&7ke)&CsUb58J@OxZh^D73K77DQ&F4#CRXO+LXSh<>%HqxUw2RF*l3S&;WD6wi z(L?yhPIy%MH5-oacz=@jW|Mt+qGhLWq~o-E-}hFH{eLJGv)H=!hk7-a^Y%Fc+5HywU6ho0xe6!J?j4z z^}riQoVL;xnLCHLlV-!9nX|`dJ#XJG2gX^*4tmEO#hEHnbo#%qQ5SXjIlpy7_3v@q zpbI{LBG>A33~z=)NuMhrRp zw#S=WvR%kYZxanXB$Uk8>qDabze*sWA;Iaf6iYDmdyWH2Fbfc2b>ILUV(Emjte-$9 zjfh%vXnpvOxfv!$zpH0|;0u2=K2+({Sxc8j@Zeeplz0NEyF{FX-wOd2tuy)H<6Sd# zTNt(znGwDcck9J6m&%k!X3P{X*DfsSs)?YTu{ALwy>jfd4z^_f(f{uDQs9_UXXu@0 z4e2-HS0LA?Z|bNi-jofe<$0LA*1L!;-49kkZ;ejZ#g4=S3qF`^7ZG)!6RDRAIsNmu zKairTwfEg6H}2!hTXxawN|FtgqJm*raanGt(sk#U&7l;>lrgt2y7K~lIX9#Vkd($= z0(3rbscRcaGHs24JR7>Go`I0^etZ=4Y`g6hx0CSVH0fm*sLpmEj!UW)qB>hZ6WK7t z!3?^gI8B>D`$SU4{+g<~-7yBh(d>IxTl0$B1z*nHY*m6NS>LhX`5Ng(pkPDAVWh}Y zm1N=0(wu%M3O(!&^WNwZRGhjZdc1L*C|7Hp^=kYD({@&tlR#PqdGOCC)2sKZl}C)E zyjZw$av>@w64v6WQCvuC9~d@Joh9qoz^fG?F%`wosl%`|F=*m9wUpqdWVR}+nH08M zy`M}iKsO*pw7!m*o7Lq79*l?|w?mXUSQ$d`ycr{)x18Tj?;4Wk0xS5mF3GL@PCi+V zU8^zWx8^f4O|CvJ2b4J#j74KI1a>cb!0F3Q(NK0C(uB1ds^3HXEl;zu*1Hj7)`p%` zIWh3sv=d=|PwBqjUfuV_U$cs6#ly>>U|2LDI_!s;*V_g#AAA6sk25b`rSvq+H3DyX z=~|ix6`K!}hbMneziO8Sytl;-Ttm(nqgu)>Im?pnZabHcH<)rE6~>ZK8b#O1V24Ob zFTX(~wZ9905Gf0Qp$kcehQ>*Kv}#eh{u%b@Exo+~v7uMbMt-cdWTfNVw!h**G39kj zi2Qg|es*TG?K<81uZZL4xG{B6zPJMEVF`I9Qa$x&*qCw#G${Gk>&GErJ}fkDs_0NS zQpkG3#=neSfq)T+cA8`q8Y8}7tQ?87d~L1)1OlBvctKFKvLd^4ACG9%cHE6)D}&-7 zzRa2@uFdyP>Pej|AHU!K=}VUjm)#A^^R2llM`My*tCz}HbzQ>SM?sV(OEYM?%Abe5 zE(S?(@Vxc7;w73KeEn3jS6(bKXm~5e#71lD_TPW{@}GpX3%w5Y_T&Yf;Q)8F!XnqbX#X`>KW5Xcv8!A3!B>C5!PT8a`7vi8=zcN!hS<_TXDJNo5|3F) z-bcP}TfP7IQS_$tv#zmwUjsth*!O5YIa1+)5PTAHAH@94_zw;q0=yLvE?DzV2zjk~d{+=A|`{6rrm=OPoXUkER zf_w1r?AfP3p*QaUEAJj86CAIjNe?x5Gdcgfe)u<}?vB>kGur=Y{Qv;t^O|rKCfyz_gVGY> z*AVzhAHV+rfuG}=I~QsC4+MU4;q{B=Pp@<{sTwG{#P!s8Ke@?FMbtgFPv?DB5+`4= zIcUyzZT%?R+<9d#8UFu5;FY-#)&2v4Z?AW}hQQn2+)f|z31$>9cyaH|FqU3w{CD`w z=IA40fvQ_wr~exS{sZ~sO|2|;BzZsN3bamYcJX~*|5Z>M7n=2$Y=fBP*~^!IM?I#x zXw@B^9|BnLW+DQ#=DCgupwfBzfLQo43%%8C497B9u+Y<)IAKOKUF79a3?N~zM1)S) zhI1S#KV4ah?1to1QOt_u4M2Q0!a_88y@ofH>0;c3j?EbZ2H;v4Fplo89E?sxa>9)v z8S&GN$KYv~3D1@@89Lu@VRP%Tpz!I8up8a*B%&W*J1Oj|*e+1)4a)Mk-Cv^SSpwAX zcs>oC6-E`;&DRP(WjW7DLrk23-XK!32m9W08j9&AGaAH*%2}NuR2A6W##_2b_2%cA zihQ5XwKe~D>j#1K)=|qalkd^pAK?{Mvgwdh5T|k8k~#yY=5*{<%2(_6mUf*ZL82T_hDuvoJmN zDLcNleu$hn_HoOdjObgZos8b4aupj3d;&VXwto1XP6$;uoKA|oI68eT{{ORne1C_4 zh=zF1uXB&$)QLHLX7Ajcqntx&rw27gn#S(yqy28E^BgMxD{`QSPwfdaZfxwi%q&Tq(iDS%vnl6Gm z1LQWxSe7X~soeMgb(HwcNW41NdZ2u&2DzzzDzuuqRSjdN2N&fSr!a0EWM$(^l=_)l zNk^y7!Q_^J;DgFsxyfg7``k=`l_t_96=d<7@FwczYDx3#$YB~t!&KVlC4m-7kK8Zt z*Gn$y50ImA5ww8YN*N2}lmeS;40rc)WvAj9!)6(?0)%ozZt0lHgA;hL**Lqfqz)qI zBrBkV_r{O$F|F1a?shAkCs4Z7xEc_nKZqdg5;URwoQ$z}cpGuXr)PF7&Uas*698|C zNam5BFpg72?=VBSNFhT=IKEyk+hplijFI;XJ`rBH@Koru*%c#I&X|&xG|c z@oH`j@(Bf16SI{8-wR7&rZiUYtTQUIr;yTk3*VbrTdZvbpNe-BgM>EHmlUK|DlhoqYe zWlH`+B~VwCF~8Z@5Kb|E%>*u2U*^_vBkIVr7_9nK!GHT5n_(OwxTelf)FSz(q285& zRG~<$u%V{M>(vLQg;pxQlHK00Sg0G_%Z5rsZm+?|DyFJjt3B_%6eGJTshX}nemP(3 zM&Hqeo-t9VErsN}Q);(o#50-6VD3bI+JhGD6d?w3wwVzvSz2kYBZQ)UR+2?~8HDKg z;qBLsLS~;cbvJ3J2U)b70N(r}23Gx8#rbDqmW3T5P15%i^qbz2p>&G9{uNVK~a>hRX&iA`g^1*FW>@xfi%#oS#)U9Xul zkM+pX-!+*XUD&3DW%K5SXIx^u6`j8~Jb8Q|Yp`Vc@B2nbmZsE0VTze4I`9>Ni-hpd zR@@07IO$AA6lsDscnBCC=DHPDMhv&416L?L_gYZ4d8${@1IC&5?K-KXhACb2T#xOW z0P&9Hk6sVnRR=S&&iWluW73A^51|+ef|Q$!$zcQ{@encp)9k29>ak8@wQZ&xnW{lq z@5kIz^WhMqmtxkmSb#=_;`AxempLVCC~agG+(ZatJq1yzai{-mmBz);#`xt3Epc=2 z&jtGJ9})z`u)j6Y#SG67R6}gL@%rfW%forwRcbS7PV$7VvESWW?gK0N-mmszjnxn_ zDXKZOXG~q=M=dr%pY3eO4Mm0wBk4hN=)fnxwMPP86`$-0i#qlxtfdKXe&wADB7%1rt8-*?P^^kh#% zZ1=_3qD!hb?2(mh%6OcED2+~q1J`U!VTJ#n`|N0G!aZcCgEh-EnuL{8=RL_FC}GyA z+*VMB2a8!(AvS_vTof*o-A}3b^`yElH?W?z$$)1s=f%B+&sRgQB5nE|FUN)(G)(-H zmu-3S;K|zKwL}28jS1XgPj%7c>F<_1*7v?^*C-~*9vNi#FWfTJ{BB)!6xHC}b!VQh z8>GCFAq+w=!&57W4Bu0dP=ze(d=k(87opGQyCIE;+m*HkLa@`8AXzQg zH7Ph?p89kWI;yA=ZYCamOCS8&<3Z<bls48GkJJie*D~iXlh6a4T@^C8Cr0oQN8*NgaVBifxhDpv| z4`({z_JAW_e=gL!>D(*&y0m|m+*_H_(;m3qZUdd6{w@dmBn~?ZHtiU79p2F&mO-0$ z+6C?;T6VgHg+?U=Te{FOWV$*mZzEI$F~l29+w~NL$InT!|tx_hh^7mRB36 zdRd!=28bg(Mc1Q8!ZUz;sC|X!Tg$K(f|L{=Ra85fZl!L&JM8NZg9nxoV(t%CAwH$M z;jed05G1yj+6H`&T{^X3*VjzzaF~OX%Z}xPjy%{vLLf3!e}6d9jo)E4FUnnrVHtd5 zs_X8jYzJ;E3Gsa^HD(lXB@fC8)w!4ydPPhE=ZC@0t?wrW*lK%*J z&`5P2DsxwL+8NJlU+(uysozXguUkxLN5c?|ww)E`l9jp!^7;)~_g`wlGA)9pLc9m( z?G7sLALRy_3&Ga1sEA9c00Pa|HkE$!2cry$E}<}8510tES$~9GX0RnLJGgZeO`Ure zXkq!1z^5S>;#igRq0@0eJ~T@Pg@fku%44^1x%^(aA+j(P1#_)hRQ7(1-lo*;GtFv( zNiKmbOBM#w7El+kbjvZIuhzX25tz+n%R1rh_cQsPjBoyhYYQU{yEZIB;riPFl<;ny z?kT)sZqD+STODD@T^qcmyT_H#-Pc-o&9q=)LN}Zdd;+uXi(W9m!Ny42qBtxPZMS7D13n8oWp^#FX&d=zK zh$0(EW##0vfh1$3H9m4%?t0Hfx_T{=J@ESJ%1Cb8Zx;Sq+L>^Wsj7MZV9y{Stk_A< zoR$oj>87q`dP0JPK4U4R#u&MG7=f0`Q#*(?Hf#DB(iV2)Go+HRB!zMAuz=&cpyy8y zYk45&Ruq-d%#3y0Om+MFkN#NK>3E0!ngEFdF=iy8A_ckn3Un7*^$J^>kD;0K%+4^q z_}i7fQ@OGHiKZg_;ZyMqb_<4q%nb~(WyC>@5F~Z=DAh-V!;)S~$(Y-xqK9W@azfYh zJ+Q{8GPwCl7D{Gy_YQf*uNiJqTGB&r7>SI>s32-(*Pomz^fa>hz6X|qB-FgenEgDu zyc@^5(=39nOFa+$lSqqiVS}bhJr;twZ{3@>6nHf1J(2LdJ>J0S^OIJYtk)ynt?eoO zp)W{(7C>pA#F7IJQl$rD*@etz>lMCVi5`NosX@=HK^Wo?ghAR*v)2(08AXgK8-9^2 zJjO66(E=Z6+xi8>2{T?ny3s6HQ&eM%T|@DZpXK~&^UkBNJ z>ZHNG9=;^OfNZA|Al%Rem#5h&Nq4kiw{mAaTdi`Eg z&ECfe9hJYE$C-_%P%_b^fZJn=3oR4d6ef-m$|M&SILDyUwlsRI-g!C9zVIRRYAjyK z%l2M%y|ZpmfTn<6mA;S*4M}M~IEac2#uIaVuM=!>yRyco()8mNBntw(6^6A6QA3_n zZNX{&!XRTEetY0r0G0$YR(q2ut$Ij`q(GUeae){-(n6!xJYrm@q6Or04=$Vax^Y)* zG1e53vm7mYKCs0;)I1QzC~@!IVcKxtbNuEu+aH<}Z5%=cJ~ zecfnxF=KdGERtS_mb4%Cf{X-GqUzD9mDlN`Z}#O679>*b@}Wvuh*ZKC_HU~CNl6)i zVimTG1g;IMftD^U#(V%E25aXjzqSFif+VtuC9>12saBL=N9)7)y_b1?>29`6dStmQ#CQcXPofPxKzx^9 zfZq((u}@Vb0<$%I@+!=(wmRw;h1KE7jN0QSGZ%jnegyQY((z39+0lh#5R<2FVTT!i zWrogNqE#h{@my^h%m5r0CIYKq?4)-kOGQ93eQ94(02fR7T1@1wpY0ZP8;<8Qdpr7G z^P`jdU~nK53$r{L*5^u~CYh-wMZm6g4yt~M@S8O;>!inqu*}znTzJ^@=XYfI32au| zK5L;>+ADM;TvjaPXS`Ly6UWX)+}0|V=W^Cky8~j7!MgLzY~;gn`kT-B3sC0)+EV!U zD!h8zJ&;(qRfkF5V&B;OJ%LW!wgcxFSUUwb^?iS%dzw%kHJWWy>N0_9C6FrC1|UHI zpqUpseiqePZk^y8C)$reL9M*&Ah%=KwbnuPDs4}sFnFqf@$`)AClWTm@rc_H<;>K) zE(!zXdOYcPHrw}XxVk9k?@|_I?!=x}6b&L`Z!s#QttTSyMeL}nua`I#*M0F0_-y-r z@%g;jf=AR`k|%6Ke1;@D;@3JnPXZK4z7KMy0-W|pPKcr?jCR^1NH&I`n2lL7py+6U zT6q;SKB7NprL1c#uxcHUA6nJZW@F%#dz6W&mNfs`61rorA&0W3{J zvhS&B3#sWptO$Sp0!C7EqvT>`l9sN(z%0wG^ZKhp-RRIq(a(NkVLOFvBN;a@r?Twd zaOH&_HNu3l7W7}8iL`vnVs5MBY9rry!-(8z{VMg6@Q}iCNPb70D%C_a>`BLnzPcLC zH9#Hoe3E2i5+eo^uInFlhqd7zhl;^CZaaXHP!j~~08ahU>3Rh&dlw5^L(O~u8X=`r zZ9>5bk03T4FnOb0K*sU07%Ju5ct^|0)U(Koa%@_p@=N;MrBO`X+i#|hl?Gq8b;Ueq zfE{`a1ZmIm0DJ3(;|qyCm=#GsOpzBr(~y=14ZoZq5;y`8HHJO?0V;g~adur@-;I0t zCrxRwa0RAvT)o54O-@VlHN(lDRTNsN?Fh^>o7=)xh@MJ22EU z82|JmRtc^gC-MhDg7F|20H)@%n!d*SkTHt6ElqEz<7oM1{YWVZ!g0B>#^jo0K&2^M z#)4eoc)r#U5V2lvKPg9~MM9V9!}pWPArF+q!yOL}ol0v|E8dDS4GaGA-XNix8rtAqe~i5C!^_tp7u- zUiLZYA)N;N7^Fsmc*5p~;Q7oR|AE$r6W6i+HeT;BX!_OhA4C8F=?UkXXA`;&&K44C zc@>36!iR0}7zw}$Zy-5l<)zf@3(rsgTapQVn4bJ!&ovx-H>?Sv~IAI&KUh)De2`_B{P+Q1& zznt+~*nRyP4r(xH03Ony3 z7zpa?WMYjR8Y)z8tIdwoQAOR0fWnqJJ3t~T7S#-N7%UJvbFLmTDi+r+f* z)7Din8H_^gjXhwe3G5k&(98kFy0i?u?MOpSQnyi(QpwnjYfP}2Lucu3sp!mp(0Np$ zsf!{Gbxvd#fEHMAt4Haz1mxJb$>a&i&a1OANLVHq!SNqI0g1n=g1a$+iu$X{V& z&d=fZUeZE|e8C9Z6I!MmZPoau=b3n)dX<`nR&h869-ILJUIh%B*WJOqt-Wel?LC_$ zdZ+Qn=DzM7-{asxeTaGcrwK83TL9oxU_Sr!Zg9$IQ-=L7QyR2byrln2qSb z!Fvphau}Ogs6a01>9}h;qIC(LBmG`}`~bj{p(}~9B)RNISZ{d%QT#$Beml#$M7|!f z(2|4W50KKR;T0duuZqgd;x;FNn(IBCn8@-47`ti(RJiX$F!*s(c-SWrdhQs;RLW1} zavI|rFDzdlsq1L+;R@gD5j!!@84w}p=Us$LIvu>g>yLjZ8S>}-VqO!{8P8@nI?3p- zb7MXnN2ond?uBkE0%$sAQkM)*)?uJgA9vbn!nIE2ra~U5jr=8Xr4$JTbKUukP@bQm z&L;xGw?KsHh#XNYjKH>l7?&pyDHSuu`+x>gD1y=8Mul&;?dffM-?|HTkkqNA%yNAs zt?I;rMNCYjbuN2e2*+c19!mTw{?mD>DW2%T`ofxYOL>+qx1@sKcjeQTa(-9Jml_x1 zLV99P2s+wITM0x9DQq|EDWr0OGx_22er0EBoWw z_!bl*9}}KM_7w&H#WMb$`V6nUN@gr=H5)Hf(cHlemdcJIC-7-%V(o&QTg44oVt?v8 zMNI}U`WlTqXd{!!_j`Dw*fmgbRvxI?G$$DtQ#mH$kH5N{1Q5bFQ>Wc}qGZoIqyu9h zhzU)iv_FqzgBB6mQ>?mBll|<2i8W;lfBaIdZx*r)(V)$${+En&sdrujRQ1|U5*+a4 zfCEVEW-~3(iXHINSqB8=Q#{PdNwP-~vOL{}LfZG)yfd;NgwSnw`BW6}j&sU9<_zBk z+x#iSQ#_Ugg0hG?&S;rskA<_&8Qj8B-JFoqKij0q;%$IHP9$9*l^=@A+aT74nhd0F zaC4^`;*6v^+U^_RbFq&*F||D=DPXd~ssAf;w|xdeYag_lXvnbIvD<*?))$&cCJqrr z*=62-4BD^Ye$FJ(R*quo&tFfc?=Vz9UyISWlIQ-Qvtv+6y9b}(2N@@@llx=^Tj@sr z63%Q!FV43Tq!Rar%aLAKqqq=M(#}L8#kqP1o+eXjzQ8c(uf5zdEC9`i)JdHrIDDxUSNLivc^ zuEFJn7dn;KgTqut8&j}xz1RYZ4T@RM`J6lm?C3&j!n5^4VeUu<+z~kE$ukue9%vV7 z!b3?Z-|;M2M%cI$-4a|p`&@D{dit5-c4-_}J2%zNN|<^(s{8!BPoZ@`&A;T1_a8OB zmysgc8AEsC#FNsCMR7a~5>PqH3eWFXEaVCfaMzl)t4(GLx_F15%URaJ!h6EA zttP8;x76ex?cI5de}`q#QIEC&(ZV#?I1NL(-%zcM3RLs5Z48$iZqnve8P7fX?lmFa zN@l6+-b3Q8{pt#eHoZL(`o1gl>fm@Ho#ZH>1S#ohe&|aPtZ&ON~6wAa4}tfGBVDzu>9!sV~8Mc6$SM?NKtZ-?(j$ z7<936?z%j)oc?KYx_Z>v)B7B121EeY`U|xJ$t)7P1ZC1LMKy~E{-lph?vQL}{@Q$G zHG>`8@rV5wZEK|%4cH)=Md9r2#$AabvGM3xTL1gGhkOKgtS0S0ylH~&<;$b#RAy;je(rqD_Y6%g=| zCGp3sNwk~lMKgAe#mvQ%U-_lm9LkJ~yDOvt$M(4m%&V%Wf zHyTjrdqR*nuOw&>#6FwOg1D|&0?M}BZ~9x&Kj_In`Fv_CW;Ssij7~fgLJV?H_rRDz zC`Fa7yRs8od^9MvFRARGs_=#V4olj=cb<$3y`FfdjMu?=G0}PHKP57NWKITXC<^Cy z*@_9|On!~0#P%n*um&5ebPzxNOMKHYNXeK^8KYDA+*(Q>NlC?BlYr;Rvqh>zOCZ}6 zipv#-q;SK&xT|4aF4i=4g_T@SNyfDEOeI9t;c z5GtiM2kzg(79YVBlvNv#Li^!*H==cdS>K!CD=XAa5qdOP z?^E6l3mQm(zW5IW9fq+519oI`cD0&_!$R2-b!U#z=`JeukA1`3ijyw2#6=>QuI8BX zW)+UsUwY8cD5Vl-Xw0m@J0vvibxP2T#`iZXeW`5qoKu^Ii zDJ0RELup^6n(r%Qy{#J8rHPIoy3?kKJ=3)1z}Xx0(cz0%fLG$=C2YJ}$=kV4iP2s{ zyjOdd>7}XdY2CNu$8}=rONNPB{|4pdqT(>PYy2t1m{|LhDU??lC+6DKtOH#bO0Ta_1G1^ zKb8;~dU@J5s1AM3hih7yyvuGV&-ZkW1Ls z!#SFEDi9+vF7*fX^;W}!Y_#KX`;&3uu&@XLnI*3cI-)af037O z%zjaZGTP$|6J}cQiqx0+7^vVL+dwjU?%^OsshVg2VR9qDH-r z_hnORU{7XiiK9wJcOOSCbU0O>I)i&@(z--qsRNruxcZAQ=JR`d(;RxItZT5M)X+ZD z);9Cr>IB}#7;1;Bk$~Q3gvuI1gT}d8Hc3rJM(mWK(M}8TEL`z8nUi?)=!gZGE!{+F=P zXHLMFi`ISE7i9}EfP&DrXnM4GKbh)hvMb}(+FE63y*!lW_`@${Xbj^!nQRD5$8M8# zc~4VkkkbZ0*+u)W#9iK+{IbTbUP~l{3!~TVWf;z+orwNTmDT#fD~4CIh8MUbU1-&~ zdD1w``e9WG$CA#fIpvs$nlW0K&sor&J52ck!-cfeA|(r77Wb61BxS^!5fU6=1PA+& zwP8-^UV{2a?Ak!T!_0Rl3JZGtxdXjX`&L%D9yB5ESXo*!Ic<0)Zr}7WTFawHnS6ni zx>58dYY`DU;0rg{`H<*4o%npIinA5(Gj5+GWSz*u29bo?m+H2n)-muTPb37Yhx^M9 z9avby7OV}0I$#1FB)Q(vymY#nQFoYOw|k=EEU(&-tt*gfT@q@$bjCgnX9e>ew;TT= z;)4;Z%6~E$P_9}X@IwJQ2^MWKxTk=_>O@t>kWrSQN~a1`kQD9Tv8s%vD?F9s+RXGj z2j&@Yfh<|!S&hMPbH+{5cwQ(?#P!>|<~BJ?in&IbKRWbZli^w(@!uL+-ML%8cG8ZM zSM?CLS$4Kx8f$fy@v(9{ox{NSf2P z*W(cAYt@8=FXS6sWt9%HB?9$MHh&eu3g~d_#j*hr?!`z4=?81%7cP?jcC5wrvH6Qu zuC6eL{im89$!~?Ol$56v{<1(sp~16zu>tcTEK=C%cr3zEIH48G0xPsJ_!`sOs?%Eu z8+#%G&0W}@Lqa23$ox!j&-4H(jK;$KBr<*+X~a&y;g;ludxF+JM#rbDaeYh%Y8SMh z{PgoEy_F?K0KjE@(t$=5Bqe>%C{P^sQ%JpM2i^sYI2-_y78>zXT;!m|Cw3YM5_Bi$ zx+$?!PXI2k2$GQqVF00@0K%J}PBWFGg4dtCB!(;yNUNWHN)r3_ROI)TKK!>kBk z6guDTD~d_=HYW~J;%(S(k7dcqqus*K*Lduc|7b#Eb#G78K#0xRiM*(4Y#ltrN zCn0MzR*&fAf~qZo0Qn%;ZZqSzojCD(XH7xL#ywT38;J#w;+B34z9Z>Uc$tD&%IPWQ zl5|7>r#|H!82P1pO&8DMBvyHVx4?z%!$YkI1l8dnaY%W@&9Jali+aelA}1UkaFvKZ zaS98f58s!Cps7(O3=pE%Oi-3re)goV7>qsGzkOe<8P9^R3Tcw@bdw~s^lO__x13OR0b zo!lHy)s!dMQMh>jbpUvEGbQzqPwB3gYq*`0V3n_EhLtm|Y2`Qj;MN2tOp>n#L zk?8k6P~o4zkSc{gi>S~IXoMV;#FG%j1Ua;e!gk3QGeJd3lZGD`PH-fJq9eC{j*2~J ztyhRXh(ItcWxEFfFB2!noFTt|4R{X* z&rR@I;hj2w1ujy!4#8QZUr3=(qQalc`h6H3N&P`hP`poaqve*taR3B^J;z>t zP1h>n@?^)y%HGHxuEm(NqPV zaW_JMJy$^5l)E&6ElQ{?QEzCrd2dJjF;H%1ey2br zFCL|TK0Z{gd?v>yZ?N-ep;X! zb!sh?>qkef10r+?hau!kDB35^n_{oy;jf8CELCSR^HZ|xDr zVW}R;(&7h-pTAL)6NI@y1acrR;%o-Veybuqj3T=qrd&g&aaaF4Fnxje(k_Ckm7=M* zrFmN^MA_mXf|vRn=I?ER9LcsUX-vZa9eW!$4e>i9k!XcqajRtiT)IHux^vjBKiN~EddjK^i|1;hTSK-EcDb_ZbN?n#sjUZPa_z)P0}<<);MR;F zH!_i_UR#+QH5jd=1`&vMOrAv+7IO9_-g08{lRRT%PJ(;j(j(TOE;AS?YflVcoO-#x zsXmg{Y`1qx@ry8~I`uOFezdiMc&Y>Yz0;gx(Z>_7D4k{6GRWIH$2Pz=_@v*x`X)Uw z8q`|)6jRX<3n|qmWt^4V6#u4ryk!8>K#HCnGB}O=jl;B5N90?Rjon=M=E85#Y-{I0COGCB z3SklUt;K79JdpXAtj16ruN^N7WuhR>8Z@5x$PSNqi7dax&3CKD`{V0PD;WL2mhLNO zBVvj@hUADO8L#ca=Y&>jMQXPKozf2oupY;bcddYS_vp>r09r8PFm^X()1^pQ%-`WV zw}}X+ylI^Yx&0O>CnBXvy2Y+ZxU(-xpxl;Tj z%?g@G7Rj!-)ql(3RxlYL;vzZcHCiUWnf)$A2hDLxnFNUAZa~49&oy@y{vVF+!mX+I z{R8+}(6EisY{cl6ZgFEHB?gEfq8~a1Mu>o*8(q>UskDf635b41ND0yih&n<_kPs1! z4?n)Y>pFkIxt?=9&$*xbe!Yhd#DK6)sMP-`HakD4$Y3sMom6|>p;Eu(xb;YlqW~XL zZdj84e#@HYKIYWLyp0}f92aN=5RIU}uEI)>C=Pa42y&-B+firoNLdT+f?M{iIz&>LQ8I($#M_3B8wv8}< zU|+1$yn?xo^!>=4>a^q32Q=uu#kL<7Ewes)1ixr(VPBZ-#_UFdEF>`(hdpf%)$HR` zPh^l`Rf9DK^zBBf7%wxdXibOf-`^ej_nnDi&_fQf!Z3W#qA7x3r@|hA`-1K8$xuRW z^gx~KaAFR&v2wKd1z{h%^6zMr@S=~ z%bxXcgBM$7tMD!0>)F+o_@i23Cak;dl;1?6xYKzyZz74M(cM^9_AZY>qM>N7Ph8`| zm_=$E&c1Fvx}W7e{XfzY3W16E^Gr)*=?WhD@}r&*G!)F2B*Ix5kFCbA8v_6fvq#=z z2{+K^43lTgMVlWv`%x@2hI#VL{)ua1#skVMVACJyb<*nats}z`PQ+EYn3w!6v!AeK z?sah$onz(~yu~~}Zb!i@KASEnGSCxJJW7seH}}@XkC{v7GNVGj92J}a#t7oh4BIsT zKq4ojGs*!#OffS%EH3Rf)>3%sVD%Of3F|91s?lU(sbFI8dDAm=d5?YyhnP_D3&9&+ zTZq46ss=k7V49gxzLePQ8Tz0Qv!`|S=g)kr&ld2CaLm(Q15`w>xOb>O&ePlpZT36v870qUWto;yMv!MwplKyzp z_KTDyF^6?&l%MlM1lMc#ecOZPXT`Rf;XJ`@OzOe2*WsQC_BYPX(!S5M@$Y^V1aS-N z52c@7(RcPUw)n^iq(X6PWUQtTK*fH{#9Ged8F`^hfZ^->IV@pNsE0#AE#b2g!D8tg z%C>v-Wx|5zEKG&4~->b!G zrL*Gga|!C&$8B5tI5mi(aSX_#boXPva)*UWIFefNQ9qW^10n z!IjvT%VrKyPSbUZ)3{Q}vYVWG?3@EP#e>SU!GPL{p!Dn2B9j=^Kemf(pZ8ABjG17I z_!$v>8m*u6O*%!8pi5U>=Y&|9cyVg3pu9M8GJ>tPGK=iffVvRG-frS9s|^%v?|#;X zp=WQn>{JROJ*JJ+`$~pHb*sb@)nY=Kp1hYT(Wmz54#j)zK77Opy$Av>F%vYQX?SUB zU*n|4439ox6l+kBJBi3*T0_yp$~zr(wfe0M`$;E7(>MXiE-vHvihL6RyBab3=scYW zb6^q%5+~>~raLf08^Ze3fh&YCs#ac%=8@PTCqexRolNB6?_aDqHXrXt5 z;JHXYD$Q!<8`N*X2E8~R4|+4=ZJ%0(v4=z%=&zM2e#yetT3;7l+`z;Ja3xvuf3(gq zYeDzx42|$J6TI7afUr4iB19uBMm=QYMi4$bOz_klj&qa<2UFeC1RbGESh?an%(9!* zGF>e8eWgT{g`FfNr*N>ORxvF(7V@ZoGqY1f=CJ&u%S_GM>OY~PfAhKMI)#+w9 zlB#l9S*xlLZsHWvAEJeGL)9buAk}!f51;P_+XnX4i$&T8$K{vB{f8!uSqV!CohAvDbdPH zC54zgx;2un#}c-Zp+7NJ9?f6<27-Qr_ZMLWx(9O_GjWkI>Q_X^{XFqx>B-DN_3k&h z6=t@q*|)1Uv5o+PSI)m}&kacD?T?W7+OwKh?<{v`SI=@7aT!B(hS%(h8J>WFK!3@q z-Ky~Tk%5s-0<-l4-X1H^eX@(hyE7z6SB?@Hh(5L= z_8PYezsDh5GLujjmPg9fy$D}0gG4jjM3w(hH7-O>_J?~bXTp`SQTLS!15(Y)drro^ zzSPcL{eH>!%e5arS-uW27H91BD(W|hrPrGuxX74pR=q=1z}iwdWcYGA0~bkNeZvBT zIE(UfWjcK+zJB`0*nN78UmY&?3~z@`zrMYKm-#AaAra_g31PKq^^0SEz+_*Ie_2qo z#7ubIS&Q%Y9Q!1{XZ_$}P@*{q%XB3}#sZ<+J$QAW=5XG;K={%J_Ns@)9aULJsku1a ztj`sIwkWf-ND*Fx=r5Axfqwkkjy^Y&Czs^=DdL39EsLBt=#ZiWpFf!&Udt`rTYr1? z#%^y(W=4*^Vx;6TmUyr$Ci(!Ox zw~Uj=1G>T)d)?=rKHKN9O32p-SG@C=uMwW1zy96c>$;lv*E;sYfE2Ky7`IuA1z26U zVvAOuO0ly%m|658pmeEclQxwQK{{Bjb_I_1px*)-#0fh%rq@|>y*MQ1Gm~)A4gs5g z)im=rrxBEW7RvME8j(=LYfNpeh8a1@<74%;J-1l`usA6vi}S*$U<+| zKkf9Z4%}tUW7{;>FTa=jpiBfBRV_QZg6dwsAjkv#CIL15rKD(MJol&GWF8qOU7(TE zM^_zldnyy7N7fVP*KU3PHUodv(c~Gz51H0kwM;b6||Rnuu>yy%w0kx^4&!+8)GJJl7{A5p@sO^^>%VASw`Q z%RwoQH@oEo9U-x%uE}}vt4DsM9j*~_cToX5ULf#7erfL8h1fDFiB=m|L*FcM>bz_5 zqM@DrFt3SkO84mPp==#mpI#!pINU*}wokh;rc7zpw%Z_6Y1}4kfzf;SOIC)q*gk&K z`c!6rW6C%?Ean-8&o}N9|3Cj2e1TS$!QY;_P-OK`_8ie_)kyG{qm4eY;3}2 z3f7ESKkRJdHeopUH6$12o?SFTr^fA14AMps-kwy^*%5}Rcz@wotqjQJ2I6M{%li&K zx$Q?UxXN{835p{92G;AOyusX%;p~;F@~sqt&n;@gf+|9h;}1~oDEk!?;9lr%L`WWK{V@&B&@1RtV_*6f2vY`Y)mg_ z_MAl}O4yY?bMBlc5te?<{E zG<-l>3e3I+VOToeUlPg5!{_Xem`;c>WUGB4zLzrCqO<_WfdEl&*Y$+iAt` zpJ_rM*tfa~%aj7J7vu^3FzNS+uL?96>qj@kzyQttEAqp0@moSdr@@~c?4O+-B6v{= z%ro=x6b5C>o|7|kRf9oo4S$^(i@*IX3V*RS0jQ)XrPg+kP@B(U6pSSh)jY>o8G5E?%-XGwdOtP0;r^fORQP}e9J zpB*Gxs^>wDuo=Hz+}G_U#F#xRe$!SZ@z=rFNgb%5V#QG1PriJYhT5m!zQ29ks!;9X z>4yi}nU$a7ZZ0&x*7&)dW}q>uMi|so4aoXA7o%MlDcZ@_Cm(m`_h3S&ZoS1Pf1YGE zp$aFq9VzhVDl(fGj(e0MGBbmXeJ~L}KqqkCo;8r3s8L^tPg#2r*lYYtpi*f{74PW! zYWRg=}mhwQV#3my>;t#2&zS(h2@I$z!SCkU^zas9vO@Y#P}^ZY^LMo`rN|FdRlLSc}Y?u-rLn{UKjmj3I%s>e%P z6=l+wFTN?=evl;5-#)I8=KSB6>36T!-U>fDeSOsUl@z zdL%AJU^P0p`>B6l!|7f@qg{ldJ*lceNL`G)wz1~RV>CX22m%Od0ECI#&5lH)gYh8L zLrxGbgd`5>tF2+|u7(d#TK_}f*NnWUn6L78Bq7BC8TRH1HzZZs^r=jKPG^=o;od3Z zC)rJoQf*+L-$9fJU?|aZ^fw`-G5#yn2qs>DTtaIvqtDZ?ks94>3uh%-8m-U zi!h>fpsmz8(Fp`SNk)L$quJ+`BC8vh>HeiNidT!|4Rm>v!lx0Mn!(pXdFpZ%#>I@S z%z<2ZT7mvP;~AZ!X!oBS6FqPb`u)e(Ya}5;2;>bYBZWw#)xSbn7pZ zaD7dTFMmkn0X=H9bO}7I%vADp>YYbd(cO)hgqVSI1pdBY)UWT;1?G<(gTD{IEBzfLHY$vqdy1SVoe$+I}V`&m7vVggKi;nxMi%GQO-N@=Cm4-a(T0qhk- z0uWtQn$UV#DavxK61)B`eV4gN7rJ-EAXKK>3$s7-A@gidFzQd!;D73TNK78U8&vg2 z;)B?~e=V$)bFhrbb)~b6BFW_|U4b99<=1 zBg%IP$vUL_W9b}$$M~d|*yuYeOXO;X2MrZwjn-C8g2k=bwZTQLrIGtRL4urN;1YG9 zUv8);VRMEJ6WDKJL`YJ1+kZpZ(M^TK>eeO}(&|OLx)Qp&(#SW*ayH*nY8K=#q!)l@ z#@%vnJY4AiCfZsut|uC#n&YNT=MYU?OgC1J5UzfFH?r)hpv-E%`TGQYgU#NwTkee? zo!DL`2+ClXC3`5h{A^IlZG9338l8mgcDw81iDzUdOSJJ@)P;^f5t#7Rs)c0v*SxMN zQnG&Hf0!RUyEM(sm46*j#nA!Q=)H)H!&eKE$UFrLf`eUc|t$mTLSZPeAgQJ1S6n$opHDiPTbG#SxCMi z(KYZ$CM#{g9U7k6^xoicLWbo$*#Sx^HdR+jl02Zm1FCTZ1R3y!re1l`JW?%n2n$B^dwn35&ZW}p z_n+sr9|5Akd{0LZX%#j5HR~y37Mr}*1L20O$s15GceOMA!u&}IVa{lP??3hoiWs9J zx#zK$iSK<$3@6aQ^`t0dR3!UsqL5cEi(J44O+%T?Vkb5WqcbN&(DQ@3m))Zb?CJ$4D4j4(V}3eTs=xu%@&t%R0g z0&0aBm(rkQOme)$bWoxQP@)4Ifs!DlZupf0*qx$2ROeKREfOKQT=k zHD=0Q(;^Aen{S_Ch2Ljba(MoQr0zriz!2Pf#vL=#6AJU)*xF1DLvec>$&aP7@h`x zod^bU?3WSHpVbd&g!Aeau#VMbCU-WEM}^yd3wql(HM+A$mKh%YC%+&0t7I+IY{lbS zs%8dyri22MHnRB`jnGBLl0Jqr-hwb+i!NcoAX545waUnM)>1@o5QZ+UGXefYb)SyZ zL=f%l_`)o&!nCN-9$!=4%IFa*s-H`l^*4OKU;AW8ODYFXR6x#jdF`11BI? z&;)fK355#p1Gsb4D@a$kF30!1+Q9p&S~Xr^m70~X5(s~kr2hPv#uO=3_Q+SR?(UU= z#QgdWiJd4{{aIHj!x1L3KUoz2-8fMMKq}J4Em;JvGa6b-{ECUWO=TwzFY3 z`>`JGW4y&+{wyvw5z40oK-uY}bO}%qVPnR%Nt@`z^TBx#7Sd`ZP&K-vpZKACg)HGf z;tLmYVP(b<)|{3Y?S)HP`6NEOkR91i2pi&z$MoQIS?daQKhxXxH==ShFbg(anU!#q&f-@!dznN8TrNav|#6i>#ZZG6TI}?a8q(FlR53`2SAsM<1 zt!jXr^Hq$D`sdz)hif)mX1PPysdg}v1FGdO`pf*bV_Q|v)X|pKUrh@TgH2v^02Iq$ zH@P*U#+-o~P@65usWSJoD4y{D;4%9s`1V*+jI5c$sGI|zZStrKH>2X2MaOMF<6pc% z88Ji%@6fLkTFh9WJEHxztgZBYw;(FYUmtPF3B!~9Z)zs! zXij)Cg;60qWU>Z&o8ZeD5Z3VMbf?bhZoX%Xz|Z-FZ*OJ`B8SQaPaAVC?&InsCLlsT z5*c*Yo#Ai$R?%Ov3|g<*yNh}_P;=GpY8{{d0+B+0mU+&!CNiVI|Gpu}`jovYs5RGc6(VdwV+FdSS_U6iH51L>2cNZM=ywr}FCgl?F*Q}q=QFbhm z0qzvhp(TB5b(TL7i8`!*G+Wd7QvI=WfAsdQ%vzm;NWw1Dz$Y5h@KHqI^)e43;FWyTOln_nBr(9ieVQ^%FIl zbf^0imh6U;egnt-9>K(PNC{kE6oaNxKJZ7F)b6jiqJXP@nF z$I5oS1*jliwx$%sI3f_Lh;GKAH~~;cKy+6rN@17%Glrdm2;w4vJkyU|(!r%f4y{aC zV=++VSI{?N$Gt7i<>c=K#VF$pKEr6SeHkum44p4K;FQVdFGbc}JwWd{aAH`A!caZ( z_dh%1Lzp~WOM!PJbUy{v902~_TS`mfFO8Nxa~kKU%m7*`f)0Jmuh>sUz6kD@3W=Fx z``v$rG^lM;1ZWh2xgK;?Kyxz%Wli|qJ&t^p4*T~4Xq^I?!=PHjiq(H81PB-1zlo? zt|H%Qnt&ra_je!e8$Dc%!xDZ6t4j%by5$o7jMwSE+}Fh;e8^Bes?eKN^!7M}MuDv0 zzAsYvcnC84yWl4HNigAZi&!g&$*H>v`Ht|xz9X#z;_vk1CztAh=#yw1f+%HF=3N@fS*Hw@~{{Jpr7{w$r- z9-YC)!^8l|{bj*Oi=XvGY~;5h^>p-<;)UPhC}m~mUs=Dq5=6E#Q1ck{lnKIHLdUo{ zX>s_i^)#v%B+y@)`-uaEuK5al|GjS|s*^3+sjO;{t*Udd&D{zb!HH|_so7*gC|l0+ zr4Ycf)1j0rM1}6mXr-cAS{wnF%8aGIPljTtg6qqWv~p#*vgOGudW%MCZn|?}FK7*iq1=NlCHL#3o$E9$xV@ko6Z_-t=k1S6^3~mO9wpp#y z1AiNxLFz<-*snyL8F2$uiAv0VhP1NStdr-aeZ}&AEggj>VfZrvo&ngub+`N6e3;cz zL{mbyFROlFax78^%~f-40n5-JKm(uf52vr8@#?pYk38uuosm9`3}+`j zcGq1cN58vdw_x^=GP>O9+;;+B;84-6{4WmqKeq8hozOIE)~}`D*BG!Sa5Ug7kaO$O zFzEUQNTHL1WqM42kp5?Yf=VsZ=gZJ*Yv*rWMjyeQb{>d1e>agS;@zy%KuP^;8`F1fn<4j^pepa;cIG0NC7p9I zHc&Vu|w=#}qD{4F1NHOxkHwVzK*#SccfYpB*?(0S>Q zcg`T@CUkEp8f|U+3Wtt1LLs)%>!psp;#ynIx3BSt;-*D=W-f3@pw3{eDDa^olQRCm z3&fp?>ZZhh$+0K}hkho7_XYeunn5zrl8&mC)Hr?7BN1a)BYT&>y?(FO`2zj;gm^7z z)TP;a<9PHPPs-rBy~z1mO5~y!uktzHN~rh3ZRI$eC69Z4?%Qcq!qK1LsP)#XN7aFy zrCPC^p-+e?*;ale)`CYBJhF@$D8)>JaCC~s96OYaae*B8eSF@Zc4v;uVO}rW^~Z1m z-Jr+D%^W6#$gT%UT*47OrB~_auig$z=n1Ix(r}-&Wk0)|-hn|23UA0s>u{_7W1_)$ zr*k}OA0&42NdF4g^i#q)XQVHgl6~sJxD^s#fpj8OBE@aaPH6!zTI7mzgeHO|<>0oJ z4Jf(BI-fig`bFR=JZPDT!@b_}>8zd09_k$i?Y4{U?};{lgBmGQ`ml}abw1j+6Rdy> z3@l%>6JusSiRD9pK4An_TD)0X{;K!C;10oYO)KqbdCqiRu;~=pole*-14=Z^^xNkm z>`*`*2uY?os;RO3V8D>k_ec*17f@!CeuJq{mR@x~c8%Qd5adHXG6%&YgMENk{?#OE z>xB;F=^^X|YKIHd+Jnl7kADk}Yo(lh)0F+0XKB^0U*{~pat-}%BE!7$x$%OMfGG%B z0-{pTssVz)FI0zu%vwQKr#eRi=164nl94k0tgqrn2?TVWrqMf_cA*Q@bhX@eLGP=L z&xj0X3a68Z4(2cK1SjpsOb&rJ7^ME2>cZ>P0RqoBmm}5kOm+MzJaP~6{rQrvf4PC~6;h|=SP8X*Ai1#6vmZDUn@ zj$VB675CQ++dr2u)bh!{;*h_Fl{=daJ2~w6yz;A@sm&|)PEF}znvuF^5yCG2ZgJl- zcnE|W1>yM8YQKb_7EN-j3P3NMGX^mw3GmV+0|LLQHa=lYc&?&jIz8a3Av1&#?C(*XY)E>Y`|5(PQe}DZ z<6O|&ZW&_B<(BeME)?v-{;#w8J+QCe*d=bC?_40LQ*HOvQLZAvpVwM{qa{#3te*Dt zG6;OUADe$)+vewSygrT{iRtgmrlWtMR&(v-p5?DaiXz>0l(3VG?@8dU=8fgyzqGw! z_)^I*=dU?jsd?NoQM^;UYY2pLGkK`pc39qIRjkCH$c9DJaA;DVFuO|6!uThJ>ocXf zu!J|j=C|1WC4TEpnOf&SaVzag@Hp2}>A)bh5#+$JFJIGC%y~dzoAskB2lLP7F^D|9 zSno7_Bjo%xyg{~r-I87Jhsmp8x!)8yhDR*8FaiE46ypxS?Qvv%cb{yxpy zL$&2#o}&sB(kM{1lyOGs*6Q+8;yHTt&*N|P>7`r0j4w|6)}0?kwSSL<0@wriZ{l(L zp%mixs%zVvzP4w=i#V#dn6e*bTajKm-zVsfuqIacjNp!YCY3?&7GfP zdjiBjD-Pguh234n9)aos&JlPw`#Lfx<^v~EEKJ@NOhGqbu!_V&b$=FuOovAuKmpIy zy2}kI;fCnh&PEKJ>_Cz_OxhP5X(SM|%>zj0znHB0Y^u_%P&(n0aoX~?T5O(56)(nya$IZ4mB2YS1qi*4{2Q_V^23F1uOmwdE+fWQ#uf#U1$ z-#K;#u5^cgUH9w3N<(7-Q&~Y#`PR&YsfOT#KJ=NOHQ`sr5lJ|;e$x5-c=2?F9VoGXJGzC?z=G(M2< z|3pN^hh&-wjcpXDvv~!N-7Y9S$}($-su=@=ANFNia7mqTFksb&%wh#!GyB#yzccl% zvsS}%CW<}eauIj4Z8j0U*gxxXNoHW^QOfVpbKw4f>{%C4LOr*>LL`--uRv5oexl`#Scj?Ed z%DDC-IktK5b6&ZH_Wa|jI?WHaARmTeN-MM5#$oZcAxBe|mQg>5 zN`0fMW7Tb~4KM$T;Fh9@x$s;H`H^9KV_@9|uDj~=oqZ3U5nQ9qGvO-V?pDZQ;U>cV zE%U`2bCci8V}TA?4+3gglzqR(55a^l(9I9nH1{yxam>1q>qUy8>xkC~X=#tt+Hhig zIUT0>=<#A6lg@{O__Dc{PG7`m&<|YBc5fgov?B)cjhLV_TB_~;XHT?uy0|;uQ`%n) zD-y9dH1x|=S?n;)Lt{Aq@ZJgt9zunP@G{rm^LVA09GL2MG%=Yq@QO(6wL3RC&l-xZ zGC7$?!+jc4no@IL{}@|EFQ3HE+uwm9QUN-7Cko~oF&dvH4CPG3YTbNK_A@5lG1Vu# zvnJ+4_?!9pE&)#@r_}Lru3EYRzF8E%k`G4oz zP3DS#pcI9zsDz$~z?YR&Z=%-~=|wuIP;p@#gBUT5Va71*VgJy*wX>L>hZzj$2-gql z`qFs|fI?wMIFn(#Nw2E2f{m+>2|+><$d#hGejG~c8*$4}WFIHW-Wpd^HRk=D5hHNL zok4dg&^Bx{1G2wQ*8X3euGT`?aWT_L%uCQ)T^RMdslwbDhp?L=z@bNzR=&Tdt28UE?`g%WZ4DbsVdlke2~Lt8LG!!} zPYRRP=pS18r4d>W9{A~2p^~&Y14mn=K9KyaOl`NJo{-M7)qO9}KzR+r;znBdCQ-m~ zl?;0}GN{LVpk~FI!J?A@{R;MtHe@{a>}LPf(;?#%{wZT8vSjyljlzcT$5)sM45zKyQxXEw5#>GyN?)S5jB$jg@G$s1(YVI4!)@Cbf0_t5Hfew1->d4{nS0r`r-jW*ZM% zQ#Wq1-^U7m+uV8lMG5}%Tc&_>uEz!=kBon8d3rx)$o*!p02Y)d_BjlI(nr>WeB+uH zW&|-B(+nJ=BG$w%gxge%7U% z;)Aps#X6zS^mQGt^Qe8C>yQ|EuU=yMO7N+_#xt@!dt#Je zi$aK^2O1kPs%f^tMu<|p_$APJfU~W-P1-b`hRFjV5H+Mt(r1U=JIf17@O#g?57-b7j1p5GeYqbFvhbf>`rYDs% ziN$|i?^H5rGU6BmX)!2!tV{SrjNoB4151V?qdup=y#KA&4w3HL<`cYwzC}vnoJ!}7 z^ZPp!v$q-c0IP&v*cI3=cXoyem-5x5yRuQ>Ys{@t<; zSD`67FNQ#Yqk}6^8Uj~6q}Te-%)XOsfep~xE4JsX?iF{7ZyDO&RcvPl$YQ+T1-$Nd z6M2dd4SpCHJ_~wuHR5SP#7qh62rWWMDkgT8_4y0drzOnX9?qX2!G3Nce^eQb$zU;h zmt!i}N|7;{s%mtH+suZ^w^deSJ@#D!w`pQj@IJjGnNEr7s#irgtwgpGw1N*M#!5|h zB;7$VTAvU)i6XGoebJv9uwYHtUkEI+|I+ynsHwEo-cD*DH{F>b?EnDSR;x$ z6&bkZYsve-n%BsuG*-jVA$5YG=OM%GMmlAh)t5#f10OIoc}FEA{_!&VTM(Mr*kZMj z_iArwVrjyUpP`y-W?T9RiAbF`_5l9xxSguY8}@ATlWYlwuy178UvAPj66|6teiR9x zbCEwlz_+QSM4ogw<&en%q z)G)s5i4oDQ=H?n;e)-Tj7eOar0G7*u_#ooOJh?A!89t~q)W61US#6jc7c#Z!lC8*; z+{l!SWxTh4<{+{UTyoEVIVb+gMfE}zIoM@rSQxgIH-!RAvbz5j0ap)tWKWm*fX5?% z=g#3dJ|63ixsn*BEa;0zI0b2|ui%l!zAf>Yyxy5SNt7>GwBWSIKP^JbSF@`6lxkMf z|Fn>x)}ZhHsz_B;<<~cOye7dIgL#JLG|1* zsnJA;8X0P>$P}x{R6!r-K?#}C$Q{$T+k2YAH;@~s>e{K!n61e8S(?#fAA%Of)2m#b z!~oOI51~%?K9sVHf{Ztv#7W+;pLS2aA>gAqj~pRz+CE!K2h3;Vest1}B`l0jH1A8` z8M({`=mc3j%we0UyuU(x@@$&-MJwCgRfP;C*!?D0aEJE2eu+QJ$bJq9XBV^2PX7OS z`y)u8hwagr#)7&9D>li}GMfBTf~5YsYA?B@g#{@|d@)bxQ&a>Auu&ujK?1YR zIv=P?ES@AuD@mG?KD{e|swR~&dLxJ+L2`jSQ?{xpSUkh{9hG&2e9?W4sb%|&N!RE3 z5tP+)lErJXP}cKq(hvLBS6%H)iu?E~Xpo}5F4>7r!@26>WGX|r6TJ%=s!B4JqSlB{ zBy%;geRi(euvc_8R+?=zStmakRfZqYV1dl=zDKtE`=W>>+Z_b_2!Q`&fM?H}ZSI@> z)VP0_YS7LgD~WBk}{uOQ?n5p)H_7P{bMV z&I2G3HTln^Ky+jDg=aMj1;#aZp5LGZUGROb_lpHPlF}dUW3`pzZk6jfX*glTtygWS zYtKEk5sQ6m_%E_pb=@VF3>62!TnG??W>h#RrSgL>x#d}WHF7%h3_y@=x3nX-9N_(r z!mdMH4uOU}8DBjp{3}&0!dsLzi8r7_>UsJ7;Q0iF{yUkbH$+_hVyuAaOms$e+P2AM zdX6i>L2T<3edvu z!uAluYrbx??EG*mPjbb@y?6-8NNhqVT^7(0*hvCjE1dzC4ZV)7V5}nLwetnfe|46i zf}BFStk|FPIA%98+~pofnX+@fB>l2irug~Wt}!2<=DWR5xhpEF7@f&b4=iJ_6TRv= zb5X*^p%SlEiFW5z_|WEz|h9n!4#tw6--R-(H>#9kuz$4!h9X ze}aWa_JB2=cuXT-VbtNLWcb(>I8CGdZiMX~IddyQUhiX=|I3Ee3D{;Me{dpO93wlo z?^~g3p`Vv$wv*U0X9WJZ4|bQb$A4&os%Vhf0ZObB5BW`X(l|%=ctSt;Z@1F~TsVw!ku{{~% ziBx*^dK511r1yiWC(3;`x=~|$9S?VrZ9jBc_=$yQCS|rfwB07D?~+OvoIZd@2`_t4 zr?VdrrEK$M{LP&aL zI+%&umV6Jn-z}tw1bs4>j!XQJZQm7CNM&##LZkrj@`MDPs{m5v^RM4^+a2h2T$anK{|zi7{4;M9h5bDi^-_U#H7#!sKUSbv-!xLP zcmOPuM0r2e#RsqOW&Q)kWHtA!_$-d)#jq_4!{KIgYWG$>ql^^+h_)bGLqX4_v~4A+ zAmOQty#h5c8-Spzk-NSK@aL%LHn~+D-KbK@k%+Y@OI}x=C&C_m zgm2+_B9=w2snE`iPseG!Q-=n*DUl$hS8^ixJSNNg5wI;X3=iHseO~qFM@I(7%Dd;V z6TJA{KE)i6-KSOx5D&B*^@+!9u~1C8Sh!SAu!J`0Dj1)z0okMGDahFuED=mXXuWkKKT`heFmM88j0(1a?nXlUqIP-q)pZj{){^{I%UXQiG z?e~A6MKLsbMPc}7V6e|Zk%`8uh29Z&^9VxQ!&5f5pv}#9eSxX}mIuO)Nc{)GKURnmAK8(4D?$%k+5q~& z%ox7qIm$8#bevJ=_Rle0`f-^Bqzpz^3Hv#DDd=bGMcMbh6DIH5U~!*RD$18?G11%s zScoVQ)c^YnSXd}wg$=2qmHh9C=!#~^W#gq zvkRZ~Vi`YA-qlEdtP}FNBk-k1okJTSaFKq#BNL$b&@q$#Y-W-vzRQbmUcT7-_%d{> z>-LBEU4Hdji=1xL+%ujNbUba&HYM^PkS|>=rsE;aYn<+LZ8M8i8I-wR%0AK^BwB0F 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 J+>Z z1sY%8SD}~(n6k-Pi0pAW!aTuY@$>jA`TQt607UfULH1y;9{QJ>o|_vWI>-cz?z`y! zPtm#mGxhj!{A`BJb#u$T&8@klk^8NUg)RloZrslao*?sem$SBIln#QYpXK_sV@s6gl{v83z~#(qo!1TIFlY? z(BrIvX6Q_=9G^rJ`0cqU0hKiM1~`7b^iJ2=BxJSF0C$pLK2#?FXv>Q7Km_+)qRCo~ zsRjC8arkQ*@%fvUR-m;CyW^E_*L&G1pSAgCJt>E4Y_24}d^&V<2=P?S^3`jeU0N_# z0y}_#m8$V>VyhO~%4UzS$5(>5Z1knuo7@Y81YuBxlPTQxR~0Vm60|BMRUYo@Yx3s6 zD_$*h!`J&m!!eLpu;pBKnd*qS0#}cNqqF(csyTBfYvhuMk~mm1foS1Jg?RF;(#j|A zKsU~V0a7z$s@};M{uv2pHgZooBWB~^C+3`96|l!?!(V?-8>@ojWg@zYi~Y=5h|!wO zJ?;U3cnxMv_l1dl1-IJP`>MYHtk)LKsYb3$1_^Ta~yClO7EfMNAkG7=jWmIuEV^a7$KvZ~bGNX#TgbNW; z%GTj*4`(>@DCQpPs=L-hcU?vJ{a+3etH{tuRb_b^PmVvs8Gb;3zpsZ|=)jo+m7JUa z2a%hVj>upK3gTLFPw;>}d03~)vo(9zZ~=?hs;Z0i90}kUi2kCs1Ek%-@hXaFY@&JJ zJYlHPfKBt-*VNBjb-UVd{e{p!3cacbXfE-#BLDfp@3nOTW!CoakW8PNDx0+lw>#5+X6GPU?QG3AlUZmZ>1q#HkL1BbI+4#A?$`@&MY1;4hC@!dv`x;j=3W>?p9vRk($iFKc)U43W={ z8#sQL1qBvNc<^J+_V2~Ob=bAfzQ+fXjE;GD(nADJk7<|@nJlbv>SZwO2wlc-t_4>O zd;Doj6nwxo1>!S?fAwgCOeHj3A7mRC5wM|g+a&y1(UR7M|2^d$3n@AM!$x~EyX9A? zemVTl0^BhE>69}Qz!u_dbp#UF1Dr{9bcX0lD%aHAGw1uBUPOGwv?RE|FKmyLSvrBg zaL;*kL#sx(5l$CN8$=R76AIvnoxY2`wT+wrrVlRqjU`&F;7fGn!xM$Aji~_~%PF+*qb8LBEYEZE1lT}3k4nVlZ;6~G-kn?bdII?p7Fo3KB0P_bq&rB;#~9TYdli=N zh~ytqDh0gXNbncpgWxY+qQUTaJ-CEE{9i8bPY?J>JzoEeOztUeE`9hb8a&DpzN`m# z%YZ9wS2T^ANOqT~q#=IBH}&@OZw~fLzP#N2hL0^|k_}YPCWI0{ zR|56yA4`Xm`)B{?GW2XnpeB{y)bpnr;2H7o=uG%BA?^Co8J%Rz~ZEDl;j*OABZ?l%82`F<|xxaL||*ih0A@3YR9{^SxUfIBw-#FyGwL*;D?MrRpi0 z4R~U_SuGU4ovGFQvb4y)FHH;ngT(vr5^YH_)}ahOyEi;NF~hDh^QQ%#;=ub2!z&bD z>#h%9jDZhT%*6if;~IF)@rC7hK0<3&eibU=f@%KR?);_43^rH?C(LbItw-I2|A}Ak z&R|qU#%EG|V8I!xcHs0rTG*N!J1^$*a4A9Dnb@qW~@ak5p51jxY7o&D`?@p3xidOsic;_($++{cPK-FfJbWi=Xl5 zdfozAL5J4=%rEPS-)^#kw`3wVTRt^qeUrr=*#77C+S`X`uJF-Kc+MxdP{>Cv+<^2y z0Q1IU4Y~5%9qxM>&U!=un%}-&xHogz0Dh$@W}=j3`D!SgbNPy7&bf)-8!cbLt>Mj; zpPVj#>&*K0YyDdg1P(ZXfHoLbHW4Ps$EYDJ4JOa=@sWDl03Zl*M$YYcWYz}W1HnyB zd~{>rwPm#oGn0nTgPi4?IrbO=LL60-06h|wE(y8bMndFyzyttu0NbwS2jeq0!&{kp zL4ZbM!L<6At5p=fnbH7R*V!HCu7Fpqz6-OvuD#*!0`=7|DyHA~1~h-I7kW35425GM zCWRaGnd&H)ha<&b)QgR;AC~KtuyR+IWXd@d&wMoGBUCvi$EY`r!KDoWIBGD+@KyVz zhmLc>rAL<*`>uX|zqIhCWt%1 zEBRIRC?s4^8`vQ54bt6e*fWG6>2e$8@Q+vSSbk0Ck%&Y6L)6D{lMwza9_rWqTr+7i zqW$9oxg&v!a5GZpo*~!S8KO~=LSs6gIggGkBiR^&)ym6AA494mMNMj>yhOuf6I-dY zW+t?{Ugnb=7&%IfcH1n4nD0|lXwX3}GhmHV;VzlBvt!U>DF&BneXK)JM~HJ(S%4q; zoJIx6L^V}$zqF4$HgeQ%BLPDh6>CA*7DkzDu3%Xe(%SI6b^xOT!xWkC8;cw8@*mkg zkW`m41%-gs6~v^I_hFKT zs9a75N-}4LyON)JkWk0ma_2ajo!>H(ro^3PHc=xm%iYBZ>s59zDE zc}MkQ7r~AnHE{c>#_!;+cV036DV(8CLnuOYeB;6kjiZ<9OEbszA4)|J zqR`?|0NSL2M`4YNwFRbXQo)-6UZ{7j7C~L_iAP2}IUYz$16FO9V)OWD``2o^zxE_*jZvwP5ZVI3j)o z>?kRNZp}R3$Av)7puU@0HSKi^#R`AM{#d`r6B*Ng@>iJp1iuF&Q&@izH+j*}b;VWT zOG+`O$y9I9TG;W z-sYlks%*kwVoso#$O&PP$9NiJT@q%-e%U-RbHBhyQr5}b)^H^uEykN12&!(P#F)fl zKK}J;_>g22MOATK;CPjI>-wZo z2GAxH4bT%NlGk-UF+Ui#ogYkQP8Bnv93*oBCTiF^rOX{R|D}i$-hInOzQp9=TjE~z ziDw{L1}X>at1z5`V`-cSvoN~9`eDhP3J5hoZw*EYr_@~Iz>)BCQ!~kQZ|x7*RK>s4 zb9Yt0{s+6Xj2U}r@5$$gI^th5tl$6|y&R4@@ zB&jReCCM+cjCn(5kVxN_>zBIiJCVs`N)V{c_2C8zm6yS}YbaKc?n4IdSXP|~I+531 z%IIFc@xc;RmmQ9IDzSHX`x;C&i;XzsDcFELPwtzjRk+nRB&%#E_{XBIFj-{o4l((e z7oAKO01hbZsq0@nf&L+hUrJ9oWkA!p#h;XRDK=(6X-=%M>M!&Viyj8QDeh;H z)b24e^ePfUJ8po^T8+AL|K_=lGHg7|hOo|g5r8Y|lJ#>+5X-28V2`g$!N#P3u0tRx^6 zgyD^e$1Bb{g_s|`7m8pwqqwuxs$tV>lsWG1)fJNAUR9Bp(T3O)D`p~<^P@-fNJc%v)0{Ev^ENp?DkC4y}rxe!KSp zBf$fhg*{vwujtk5dem>id-h{SWlRrZC%oBWrW&T=bB?*jbJGa#vXJ9wnC4>~Ntdou z4$JC2r*uG`nMEI@FayRSbh?qB9txI(oS!J(Ndro~BD-t4y{kjsi5MJCQjb0eI3VRmt@y1cJ}Tk%h=2;;EmTInP~|- z$xqDQaQ3+vYbkl;#rIe}?j!Y-!89uDYOtLMka6m`NP0E?!d*k;*k1U zk@wR!Q;s@+m(A*?M}a$tb1CY!&})=?edeVhS{chZDp~S?%0$X@3#fU(={zVqtXPNQ z-*0iYjjQ~@h8^_%b$YG0o|!j8agnh(&@9-mcds+`7~#C?#2){U_EnnFu8__fb5~Uy z=ikQ`-k^Kv3jHCHA*7pHshHlEhI$oSoQ7?%w^s+Un|yz>LH)I5%jqB1JC8gKg0HJV zeY)6v2H$m#T=CZ9gn-=E@L>nyI(z8rQ1!{P0--dC5aAI~n1G_{zN?0v!kS5=t;m!O?-BD_{5w~J)!(Ee z_zFE>vMX%I)Opnaz_4`0ut!YFCSrgGB8mms`64*2+coU5%=TDXDx5Tyi-ZM}s19A~ zH-z4i`(eQ#SCMH@vZXl4(((!0&gDLByqF0CnmL+ z{4kYE{aSWvOdyZnSeBj3sfVtd;W4Ry=4ET z^hxu`<3)==@_4d;N@KP8NEE*yF3(Sz)6|)ytCptP-$(YipXuRr6)7;;tF= zM-5MjB1~W>YoBVR!1*Gr-}uK2qefujAf9RJ{etd;cZw|-Zoa_3lqoYmA!$!x1=>2Y zb>rLEd9J5k`}HE3&VIAaCKB^3Kk<3V`fwVBD-}UCn$k|*Y;WmmDJWI>^rx`Ko-$vK zH%q#()NH%t_5v=OzBGgXt~AB}cRC?qg1j!PkF3_RL;~{5Jk)puEnR%=)6!wQ zXpQ@T^ELf9-9RVKl;Xqbkm}(ajebtDr?SaXBv?+gAoYUa=9%c%uGe7}jZG)NJmCnO zHAK}wv`)?%$qMLccfsjongd$>SnU9FQ|OsLb}m>^8fDXe;+@-WaPke5_Jd!MwNmXl znMYFT!9e5Lq*7f0;H$HMtpdv5(Eb?jRv4i3?TbF?$eLh(=~~QY;WvDUpG@k%oOwB zMx8N20l3NA$&>?8fxF+{Gzj+Ps8g5EyB*ETP+F6f&Q!RqTXCMps$>V|V6lnfP>zn_ zzB1v|{we!@X_$G!ak?}0>~H<&GL-26p|0K~m7T0SCFs{E$2(k?1g5ja0IAG8%n2t!5Kb?zemPuqd?NP%v`&PZmXFtEy=4QlFniy^P;Vu z|Ewrmzb>D&&PgkVBomUJI`C2a(`@W1C4+^f(7SaiKnZvIRpItG`)vI9)7``~?2jy1 znJEKpcmG;W_vOLDoaLKVJ!H9ij=GtvVm+&)uWZGm*KVjLYwV95|ImN z6s4RX84cX~ZL}0-OFfpRYM#&kDGa^Gm)~(@3)qUhg3; ziqu&NyHjklsNi5WkNS0819yx?I#-rRVhqLUd6tyOYFcsXHJRH|t1FRZIlQfuB^(tz zXJ+91);dD?>~N-{ALs9P^O`r;#pQgDG4Q~{xqn@BSv{@6GxI*0)eLZY zlZ<7&`_sL9Crgt|`3iuJe%%ocrKc!&@gUAJPX0s$!Ht51fdfM;jF_!bOb^ZvF?+6vYRI4SW<{iWd9L zEw&Kg76eN&Q{XE)R7tJM_v0QqC+L?>;0z}rfMQ&X^rfum^!QJ5-7Sk>&{lZyk<=SG1=;O=ph{Zd_ z!lo#J{D+>GvRf$+M&3TZx$56a@uRV3F@kh&-o5ml61B$$=i>@fcgx#5)jDAhV9Ko= zDZ@OsqIo*gkSa%!ZTJ4To}?e9gubCr%z_@4>la&?1}L8^zKTi zDau{7`@Np? zRJ{8ZxN|cd_T7Ra$<^i69P-EUtuNwXC)@}^7$s_5y5uE9^hgDFeH^_^v#E$>W29?pGP+>{7rvY9u`2K^b2p;Q)=63>*8Ey zo>qo=Lcxw*@xT@C|Z0oxPjK-K(3m| zUk*BIfxlj+J-Sw%df-ZtkA4%5PDR+Ut2$8k9tC%{r5J*8!)&>zKrZAkFGAQ|>T>k` z`p;w&=Lw6oG$ec3_4KG-=TDi3S!|JZUXSiW{Qx!9m_21RO`~VBoPJ{8t>rk2h#^kA zzZy?m^?f2=av}ZZV-g&@U;3HpSI@aByd`38WD5z~^B7m%g+{s;mXQX!n44rLmxrlQ z9V=5rB0J9t1`zE2?3Fs3c2-4t?_wZTcai5)e$FvowD|Q}ftacN>6m0p%(!Nn&1IbU|IsVm#IEgBbn^`o%%=ZoR=#fv~#;q3o^gC1OM~i*&naR$9xG>RHraGKM z7mD@>^C(kf#gxZU?#{SRU029(&!?W@h^)DoZqqUlec@QRn2Hdy`?~9Tf$f-^YK1{a zG^AlL3@!?3BUXV!I0w(8V-l*cJR)a^D@;U^-5%y_=S#g5>Q`?3?os#m6) z1HQ^xUxyCdV4+)iXl39)Y98ndE01Ch@~hq0flCh)S8UnpQ@+aRk-s`M4#hkVz+L@d z9O&&tFMX3Z%ORi@km=#t1$2b)J4IK45@T6K;l;am>8)VtHkLOzRiim4jgG4G<#rV| zn7OCyT7!S8`lXZ^S8w-CiyOxSpE8Ad1y&zk-JR*PyZ@=8uU@f5DpYEij1&$ZcR**f*O(52&!oHv{-^irVC!|~ zD3j`GK%RTzdFGIZPR#$gH&&*3Z#h7Vb&n6Eh{_fWlh0%k1=f%dA*X~2G7s=JJ}FB_ zpwed{93$wI9r2nbd+{<4D*qXFCfSI`bLs!)8I){KjY z%+dM%7vZwfRRjW&D?D|UBo+doQ~C6qr(Rxm zgl6*RYpyW*Y$FXb?K6wJ5tArm=$i=lS(Y=`S1Ho5=gf8F?-aPC zP00i0sy?`q6fj-r=Y$cDR+vGujUH>s*>eACSiVA-D`>@97s0V1dz0(x*(H+n)j7u! zFoIy^8k-fBuX_;OSD>}7MImUu@i@EW25#}?t8C=hu(}Z<2v;)aG`)DYf?fK<| z1N6h92fvm3zql8EG|PZ{CYqySNK*!;!>KmcF$*nmCXoqo~${R%NhHP{i;uMcH56e;|?mjIDjMp$cr2I8#e-< z00-rQ0j^p0O~JW>ib^Az()Rh5EH3ZVjAFr@{Y0v6f1Q3wG8HTH#+ugzPW1Q zqUwA=)1w}#umcZ16@X;Og$8^;Sc9Jjf5LFEx?hbRmmBiC?>Vghta7->;$hVzTty*7 zu^#-oV`270?Tgtfcotz4eKjllfzoL<_syWPn>t&0V;Kb5(&PBKxcqTbX#uzab$>ez zpc|fJ{dhXt|3@zewyeCb@UpqtIu+yU$48VipY)b_G+V}rf4`xLaG50t4jg>2;LC{m zZUU{S(E0VTiT9=7=@Jl@Lj#=!Yy6*z*?s4z=1wk#BmiyYE+o?ua1J_A$(e$9mWKy=H+A2geamlz9+n?`s`Ao5 z1)EzdLXIDoDGRtHYmG3DbEv-hhd5@iS@k6GPaV#N)4mN(Y|Vipq-S&}A+?&?Y$K3( z#Xh!fNwh=)C5Sk?UuE_$HP=|ZG08q&M#iCT*sAMZx?%%aDvBL2Zv#tT5imV>n9|4& zlevOZ!b&&~_S!4MQ5+02^?&(11vAtmmuFKe;4?i!T^qx3?P}0&%m;?3dZ5fpii~Pr zfc$Aya*1NVNwfo>(F$_Ita%X;5`pF?b|ZlEx?>W8+I)u8di~*egh~Gpmmk!pVm$to zUcyX25$n2%u^st|6@e3iRafGdBk*H!=Vq-!UxLBX;KnV zZYO)^YrUBph~x-isWU#yY)3)3N|?Uow!~HJJP-YJeiUzvd1FnT3e=R96;4tLCsUA6t%fG_H`QkrHWH) ze7~Z3r|ZM-$c>GJ%cSvp&fY=3ske)mZLImFgfHie{dl`ImXEqal-a2dx_oc{;E*5^&n5LTfn5MEiHUsRu;5qaZv5V51**%~U|O^GpVD9&OYcY`Y} zXFTs&YU-Iq4twW{ee3F$3(vTgsc<$<G(10a#+Ab&h z0whe2;HR8fm^4Ehzy$W|!A{lWzd@o-y z(*N~73w;dZNVS9(laj$rM^la@etHKItE1q)H75By8;;mU!l(Qi;Z}rRMcCUH@zKXqvvxC>0 z*Zu{7WKUl>np!UQVyE)J7GAoQeA*7^1O)WVE|n%_2A+V7W*nEQWl6zVf0wE@m?mTY zErlHrI%M;LFArRqPPiy5<6~*WG@6+Xm|0AfR0(^=Bv|N zkO}GFQSNTf?7+9a6MRTTpZuO~MZrq~z~}aw-SlW(!%sSy-m{Os@68K5D(-tBb7{V0 zh>b1=$@y5&HuMz#lbTl`ZCMnx^l?wJHfv|~i#^rYLq68T>BcmF2=c9{!<1j>%{RFc zfl0HhHh^Y|)Z^Fde`e*X^q++Mm-pYbYX1FE%R4WVO!A0H0dJkK74)Rrpcjr4Mqj!n zwBXB?OO|>*4&0fLj{%cavK*T6N!lw#ShB=1IHuj@{-|O$T7-Xh=_Kqs^cjO8M;QG4 z2Ct39YH3MYsL?eKE54||MYrSg@?GCk-2Dh5=6W926P!`QPHj%f4pB_Qw#7zy{9OLg zZLHHMb43iQGoE<~|JcR|=2!KQXm0N2V!2~+UJ20jm_=Eg0HUlbsBS$CcxDMi+iBEF zV%`jz5+#8EGLpaCa4kaOb@e!%lZP(E2pf^iO1fC9{5`sF2pQJS{W_02?ixwoEEBS> zzJq<4?ZdI0@Z8)KV2`1n=UN59Ag?puId69X8_FH8t;07Qhc_eM%XDnCB zSQaEh)W##ipGN|c7N$~$2T6MMh`kve!)!K#MV50LN!D#ili5i{G0VjEMTWB@(;o>? z(v5q__c2^jX5!TX=FWJM&YatJw~S0jQfmU;d!t5G&=5t=R_(C15T@y>d0;E6@cJej z`5R_)LmR#|*n_9~KIHk_?IX0pxgL$->sgo89*Y^Gf%q!Z3&?zI zt*2spwc|8I*YKoKThb4@H2;)$%`3shN6r}2Qs;kAwOxKg+7b8!2j0G$(OAVe>OXI$ zLGsUcK@YRPow;Mb5C)lgciTMXgE`shbf}LNlf|H`%8CLzFMG?k z0yRn?d^k97+A1BNdn#6y0iImw?!}*{+*)uTL6}4O1}0!-DxKi?{gLUVW892%^;sHM8{fX=?IAQTf#KySpM_Yx(V@cg|$6G!11sW-8 z6YklTe6sQ8Xab)>Nw!!QW7RB;n3lxv1@WFrYR2=f5$r4R^=8mI7dS_l@yG^Vk|4#X zq9YFvim(Egwd-py_T0|jnuEGGRM$bCP^a`ogxVhA3qVX<`kdY@~Q7YnBwn_C%zH_Sz1KiYz@uvK3J<~!4J8~r~f z7Qmc_4-3w1=#{uAZJga07||B@u)x5m`b~$*Nv%Z-mm#dPK>&V@z z`B>smYi$k5ho2{0(fH4cE;yd0pXQ|sQKnX5CtM{56^!dScZathW+~Ys3bEQ~p#NDM z>b+U^tB*G6Av1^r;oI7PLUdG86Yf2{kx$1sbKsDdamlC~)npDKDKD`F8jf};J!6qU z3;}!_e;}UCx3jD~aZ18tey44d2Ti}xpIEw&C$?C|3{}-`rRebv`BSvY{c!#He!E_S z`9AgPsju<8zVy=B9X|;^4&&+R;@FkyshU|HW#g!+EiOYTn*F(YrR9U?BFVOvlNav@ znT$H|fMa2Wf@!&xD3{5QRkfo4^Z4TKS%z=8Q+>4wi}**g7?D6y(jqs<=Mst}gT;?Jh;$E^w;v{u? z^n?TQUPm?zY@9FpsS@udh}X3&(>nC1nEZ&67|)AP1kA?MpV)FX2~&GB-I`;6OGP8T!IjhMdv)_R!|7(#3{ZwtYR(=7`2 zE#cV*#%wZTN5be4hQJA9R;_vUDktjKyN4AEJoSBkl$K`!BRfd0_woV1D4m`%f*}PKVodKDw=nZM7hA|iG)uO zwHz2k8UP!LOBf0?nMY-45ua*#-qX(WdhvjK;8UdOWNOCy-C4dO)rDaS&2+iVw7?&A znUh%{@R9T7dF&(-wm91LgSS~P-ugkB-rP{F6n?2Z8%ra!j%Nr9Q-~^zp&MOz)@*ld z*p(qE^`sIr$b54}6paI-CtT|k|J5=+%lPctakc@gByKF=9VjOKs0RWgP#=c_QfC~< zAb=W$Fc`eNXuy{fN=M-AoqV19*#ZC&?gA@ugC&iCh4i zW|Sw#x?t8eDb5%W27t$HHdT1`L%qYh5!@hoTP?aKx2|a(&6Gv_Yrk`aNF~EjNyMuc z^3o$|>!uNbyssVGht)bL3R(?fg;_`0Zj*7i`W$^4No?_XxkbvSi3AZMjxR=mp-_*I zW|v?+W8^sf-z@r_Y=Y=6l=U**29!zZ)QZJ<3pMY8uh@dEkuYb-!q!IT{7 zecwZJ$ZN;n6F=peSN7OGe$#qjS!+aWcF`e?yv2w(As=9cH6yLq^zuh_eU|H%{_y`s z{P2S4d1;xWqlP1Up|5)C(1~I*Sndt_9yQ+zB9hP#(U?gF?a2*nmrTyl=qw~n=;4s! zgr(D{XPLx}I}A<8XhBd=X%-`GGjf_eB3Fw)gc^&hz5d{G@~1X9>Uiz5Su}33cRzux zC4+1?aaO+;W-#K6!T)fmidJvFkO%IxAm-u6DPUDh|9>| zXyMx5{i%_Uu`;y(DvPn_%w;Z0fXMxD0H6SvL{X)If~1=MCt^Rxgk(rcZmITgBzUjZW0lo!yA;d*BEXC%AMFia69 z>g0bK2Tp_{9XVHs0RA>Nzv{PNf=9tl?cQUB>P^|x4U~X4mFBm0l9SV*^4JrYj}59+e7h_hD+PRWa3i;~BM__CNpiP9S#fa<@|O5rE2 z2EJ-?zAtBSQEL2w!pU*TGPRls>d!+^{lyoW08KN&#OHq2{?Joi+}V6l)w1p&dl8+h zX4z`rQ@CeN+Y>`N4H1_v6>0t_c_Z!{{DMQ^WomTn;`Q(=6IXa4=Y}BZ7s}nD(20w& z?4+x+xVsON6(ToFA2otj8ecUhVY-qVr!tXHRh^JWJoyvdfYm&r;`RD zLd#g6g9eWKD3A$8i3BGotB|m*pJ-w=RgR&t6why+>JrSXJw(ql9#Lm(ee!UG&XEW5 zvV&JfD8F1yO=&VxM9B*BMpI`BS8rWSG_WbxW_IclW%`4XISmvoDRrR=v#uxj%KpNo zVI^G&L!4xqbU^9r9keFqIc0D9Azoc;#buOsH(%O zz?Mfs3wG8vh*~>$dvv)1DlMnMhgu6|y&1*zeKKA04u$lq@GtXa&;%=x5L=O4h=P82CoMM&1Rv52#wLW%OWsXe)r{PJw2;%MIlXU z!gMNCpp8jP?RfK6Du4x;sC%7w-{D1w8VL*(uj;cK5JP|Mr!61)8dzJ6oAHIGO=7^< zMC2$cqs;7ln@~(h8g^ zEOj3%!`0QXf)_uf!a@v@;v+--OV_g%RWhPErbaXxBhF=(OJiBgN4ZXQ=&@2VGaxxI zjsC*nfAVDCzy%anAU0dakrzgw;SfLPMzyG`(r276nFgL0{P6P@((0H?Xv@(}&L>E{ zKZ=8&Ca}>o3EpT&m=BSX+wpiM8RBiAJ-pVE5eYH7zs#WpZ%NHx3B2H>S zHq;&=p8_FRn{7tre}^j%62)#fSD!DBm_Xe0kr;9{)uMy4h;K0E%ji*`@mQ-gHy-gb z5Ue4@pDrqnspKd*1^B^8GVl5CYWq@NtW_gC-L3r7%9(F-3zY7P@7oq^KRp_h0*{r@ z$^K7E{fv8mW}lE6%T=mk+8i&JFh4PVu=oJ(#-b$Ai9EJ%!D6_>2D|(FUj&+MDGJyK z(d%7`lIOme^Jl(?qfregKDYNyT_c$dDIY{{45a)V@tj6o%|k6>Ww``Z%}!{XN7?x@ zSp>YmsZRIUyWOCC{v@*ahRXGY>)V0Ot~q4{nHmAlIy?vQ;TB=I8uBGY3vZg8>Lny? zeTR7rSHm0hOzMHdIYT^?J1;OF#R;G3&@s#2GGwHxQvxglbY6)6Ux1)>`t%7VeYW!T zb-8&VjCysk2ZBv&ubAtCnvPR45*cZ7oHPX&EDA+E!L}d4bB+zjJdQhnqIt{OO-f!m}DyFjjq_i7G zfW@P+Rzx=Ro}o{%^~wLFD|n%=S+a`WW)N$lW%|eQuv`aVF8j=do2D#{Iys}+>*QPr zR)^4Q#bt8rs9+^|3rc~m7n8CowpXtS&iSE>zdzf!Bb)Y7NoS5GE32?Cf_EB&-#kEG zY?1-#J$JOGI0*q8hfckND4)dIPARwVyGA=#C;Kn8OThvU5}6qW;{ig_+emqwWaOq0ftzxV`C~UO zpk6A&fe8dpWOmK^do9n;v_GUUXlyc1Z0fK=XI5h1$o&QQCuvy39o7ROsf}Wpnuqcp z!9GJwILKriHpm^Ob!=w#WuHPt3=-orBoNpG-ZvSutZ9eO-mzk(I!*;A5~QOz*b!22 zRCFgK4&{HDc~h-=g=i@zp7!|t%ainX?3zcAz)$|EI)zQ}HQNnQZd3M`($ib9VcKm& znQq`h4UN=(#CU@9v4*!M>-xFcI?kV=vnCT)WsvbGMUCrmQV8mlu8-)Sa{eJr@MeJ@ zUSh)(-k_MidM2Hi>)>Fi$KgO2JYzd8;{3_yp9S2?QAVsH4GM;1Wq&{3kb)JOayM2w zvSt+{O@Z*&oHu{LCD!1}VU(dD{>PLy?5@w&S%7Gne7efI2+ms8I?J)yPkBUuyioSKXzM3A)V^wa$6Dl?+7I!7qUP0^f!;<4C8CWr(y*KFu-cF=t3b0U!BCr__xZ*831C!X#^El7tq?Vr@_A z69M&WwR>mPX8E4@3}`A#N^jz>D2)r4Yel(p0*fe~r7B%4bV@1Rw)8y5)lpRdkXUO$ z?Gt#~@d`@ZQb@YwL6zultFDe)3}Ze(wO!^y#|qq>Ax3D;{>35kl<>meNUv&8p}-%BtQJ65?GRKI1mj!E(;l#Y$^lquw8+Q1Dq0d zq3VNGU{eovEMI>QL8dPUNw-0@0joSbatj@L--AzkfhOHCcmE|5{FTH8^YRu2<0er)c&gM6pVZc>_)0s9$uBjX z3DY&~u;f72Oz4KTvA4Fd9G#ZYZR6z3I*s7r#Aa&@BQ%fll+ZXi39!V8!J@$Mzwt(= zbXHLDrU(HLAOI4W0SrM%&V5hJ#uwB)tMqgpa=%J)XO$I}2yx%DahVcqh>$uGP?6@q ziWN42`V+!u8ZLXaM|?N=4)tQc>7n#NiRC^I@cpaUIShwmE)p2y9;9=H9v_~g)Z+rM ze@2FX zh{fHFew>2JS_=*)eI2)*>$gGSiRb>l}>|B<-TO_^$uf%^N`;`TJs=@ zMh>t!mg^T5;bVlDk+KjO6q3CvrQC|DRGk87Kx36?rS!=?(8>J)OsF9mWhhyiEBuyM z;RN`pTS%YM)+0HvIEC|<(4eg{%ZGomdUB&?ll4zD{>RpxzeDxM|KmS1#u&qxGh^Rp z?7OjKi!vy??Aa=NWXYaT%?z0#G?whjmMw(rAzQL$NkZyn4g)aae!kb|htKyP zIOkl~xvq1a$MU@0>G=2zwb*IY@$?ic$FdW1FKa`%)$_MZeo^DG(fWDhDvD^ zYRnQD#VDXm2HsU-XZ72ejV1NHA|dy0_}&Em*KUICZp|l57T8DFBVLMmj1Gp0owpSk zDr%`Xv?W$ladq>XC8jDyPy5`%!%cjCGn{xV-Ks=q_fTUt1`W6)AdWE zIN}@~FT{XAkUL|_gl|vd{oT*<%n%N~DF{+bup9vPG0yH#l61~Y-*xMugrM#wC||7e zYU#a@-Zb6gJIr4|OL~Z_Q0Ia*zP%3r-3AXZ8z*x(TiJWPYj(EpH<*Hohk0W-^v?c* z*mLRunmFNyg=iik>R47!gIQ#vzi~WJmb?XBhe_T@O2Q6|l|{)$Ds`#uX4dZEHZ^~lb)Vq9Fsge|;~L?zp= z#hX0Ka}K~n;M#Z$O$)8B1ePmj2McYc;p6%Rqv(N>oN(C#TWN|2q?`Yv6o0o{THSVi ztH}&uTj03RY-M(8@yrFwacfq02WReH6pSeFA1<7!$8#l%%I(gQ91v}~v)2*~usbG;K8%{u$UjTtj)!-1xR5!zpjY^z%VFot7^{YRMNjf<@mTy-PCYYDM(rYH2hL^da=4&bWTH5` zNG;Fw!CpuMk^jukh1N8jOW}X**YSgyc;q*^_wVr6BJuDZR(Ov%bDQ&vH9Ca!hm?&w zc2Ep2)8tr8bH1Go=oCkE#3xq|>-0@+VendM0@ z;gsac^D0uVqEOa=M5Bu#;=vkLHd%O=tXny}MT+hQ9d|7vq;rp(Ic%T`(#t;zTSWee z^Ik=mboBt7?t+OTPkM=hi@`+PEI!5bXNai;Vf9~F;kO z9BY&_X%=Er~5q`ue`$FT;wP%d*L)6pQK2>`=Z!I?4hN)2#|w=&UO6I>O66i12lW>pm{}_ z%kM!{oTnu}2Mdq%4!|4VdLb9HquBD$BR79pse6fk5pPr$Dg1smNQ@=r*|um*@1p$; z7|O18eCmQz-u*|R_`}@$5F9#kzWjo~=$ME=|I!bH`@4X7u_Ondy3R?{|jq#)Adnqwr+7x z7Ks#Ntle|E5CLO!YC?741OH{`buI>9_S$c8o)zgj9{S?kYbY?+lh*F>{n!0z5mN9+ zRoRJG`qD!szM}$z8zE{rxTzlgiCnzxzMmlF!|{nq_`F{8^|tUbkF4mt#R`%Wx?~_g zH+GpD5@CaV+_kgc@a0#7+3LO_hz!~bV*3@B(EqRoMR2pQuVGIFm!S*95}CdRo>ysv zFv13Tq#|xG=iYa&Wv$b_L4(}gwlXfRIkV1ZzxN$mNKv|OZk{vgz3P3IF*XyYpZgLF(ht9MxmL`HlpY^4A60(ZBvMt5!@D>B~YiyuubG zOjL91wa)q4=$Vwm&pb9skhaO)n+-k!?&}l7S?a_mzK`h}QMMPC(lr$?>jNxvyxYwI zxYM_pJKaoYT?jkePukkxz#75Mw!%&9{bI(SJvhvtm z-e+s3#nsNwZ=+5(Z_hQlUDZLl%J#fJQ_thCaX6+;nUiGl5Z_>Jlgym%vm z$@}zZ^4jTp!c3VAK+0k`Wu^*Ksy<%aG0fQZ`13!Mi^fT`NyzgrHRLP65(f~JB>-qT zs16#-KJZRAtw6;U5|s+N!G!v%OK9><)r4!kk$>8At_pgaNK!hC7=xdP~7udaRWuq)B| zUnznZQ<~{r-SFDPuZDXob0Sy0r;>hYD@pgAVZx!Sr3k~4Ci~g$ULSf2_Ew(@Q60-s zc!M$goz*RX{BBogmI_kxL0!>j&!KXA6?=HE)0bc>KE10JoMlT`5b51MiH+Id*dQX=Q8*w^pn zZpASPl*2n!%*dZjaTD;#=&pBZ zbISCn!>73b-WF?ltdSQI%XV7ntnC}1Nbll-W~lTvIuk>WlA+!usBSCeG3v`28McPe zSb2RGt>bU0zP7&Fwxk7qw-`VCp*uu=Nj{Cqw;|8OW!8iY0QAEBVbANYtk0`Vri&6e zC2hww7V6|}V4v%rRYr6(0^ewcBZGK__CkOee)x?hl*o_x%K(9W`KH!Nw~qjge48yfNGVJ z9@}%~V>@s6KCP1j)m^5v`d?Nod8&k4YRsc|wo{wFA`Z?H-K{kSi7CSz^^%_n7Fxgr z5E#?P&$$G5hl{`gxg~6si8=4iIRv3VNwP*{f$u`XFssG5UYaSwV_l=ZvTZ`7GS=hF zk@85~G>zX*)F?^=MJIH0!Kk-$0x!fU5lUI+Vxccu8Y?5(%Ua47TB9uo0 z2z(dwd5RgGGGh8zb3Ea7USqvmP5wd;(*3>{N*OQ0gJ4Bio91dfvCve_(J3^h2kGLh z5J&eg`r`B;xdZ;2OGBJYBKi~;YseWfgy7MHc+}>Tq&WQvwU)WU~OZgj&aLLS(VCx5S$0^&62k^TN@x~ZlX z2LKYDtZY}-Xk=%1imqt>RwUiB^5P3VS&T$!fy$7H8M_`7t6_RUTwez1)Qx4|I~O0` zYk0|AttBtpBX!I@L3_FT{Cr)F}i5OI5T=p zO-)9?$j$=NOO9)n2>1oclC05D)hhz=(!v0pz(~pNVL1WCu|Cl9mrkAvo+};~dP`7^ zdons-S`9~5ulC28$!pw{rwIpQ*>9N_t3It_{<>|KKHOmN9L7B0{etE4@jJ8Y(kf)9 zDfeMCc7iTE>NuwpbsFR{fv|iPG~xp2j4F!=ywNFJ;xALsVYk#v+H~-*khDU`QR=n$ z_TtkI2SuXApltA*YwA6biVq3*8(fi3sb~H1^${RYE^Z>oTpX{Np&Z%cF|@nRW0kj| zT5jdx4x4%tdmGk(OMYu=WMU6;uT#>wmQcj=e)Jti#?XsRgH+vFPtf${u%_>M6`3$E ziX{W^rZ8R=ap0BfKZkpr4Yz35_67_@@vi~hyn;zqr;grXZy}mW1rc?1ffwi^#VhuX zuF2-{PB1cm&3wa3l5;lTi+eq!x@DGUXmfKa;~}$3Hs;9{pJP5?!~t$`nRsvJPq9Ra z{eYqm@tz$k>@BlZyfTt<&7QmFO_q>Rxoq-hm(Nk}I(D0$-a&1T>NPu}*F zjh-6~Y<~6#wc}$NJwILB-1Ibg$KNmd{lac@%L~+Q(7ot|RfU$e@#NjGC($3a16!W| zhx!sZ68-V8wx#n+@|T#;(f|F~ZFzBw+6%oVvbd!N=|O+ovddaU^-d@oF3LwDanh#0FG9(ZTyY zdcH)y6p`C{X`gz%V?`MKbI|nmfhsd}O;Ism#N+nY@fq^ECOXQC39?m^rH<44@}g^MU|0A_03Ys?m zkJSFA>e5Ja&HI+1mH(02Yd<`X{JQnOr1lpi1{R_JEw$f4!H~d;F2E=IKT^BLP(O}k zRBo=})41`=R*dSeHILYK5e6I_d9iT1&|~D|CY;1e)D(aIQTs${zweG~R}u{h+sUVg zGxmyWSk)N3DEWOt|7cuTPk8h$?e$;pZ!5q8?s&4Y?fK8IqAw-W(h$ZOx^DCLm_Ww% zAp`OrgAs@Koqxv@YZtR3HaMl;NLNWsjYtE8Sr(uQ=tjmu3*46|l9eIDvB+SJcP9rl zd=_aM=hEp3P~)#ws#0ki%l8NaQY%TST}`&h8tWA+DcavRR_^P~$qSjP1z# zf28*E>;%`APmiv)F>e%J9W$=DJnZ7TQ53=4y-{)n1xULDlZ=U3;~$j&x71Ex4g!EU z(lr^YzXe+0UBI@u>H^f@7#w{9Sn55wP&qZcRikW2sM39QAR}jiwp7`!AC!2q-7u>5 zdHWgE4!P4PXl%OEH0%9jr+Fdb^G?fRyzFl4YNp?A+eX=w-S+L~&%4j}24uf<9M1S% zzZA3ZRm-y=AN>Lds5~bq@W$YON$r2^>zJA<{$Htm zP%P_2YL_an{{N)*S8`uRRp0r49n<(!{q-b$cl+zOE|dJ>gaLQJ;iQpd&Eb@pdO=Bz zg`xbnX}-4etZ(%cxQ!GX7;c*4`V2Bz{4af`eVFlL$T7X?`=f$I%A=)Vh0to$E3g3Y zh~7x<>o^|!rto~*+~b1>lY&Ny3xn?WC6%stb)bQu;Nj64CUGGRt2|>L+&c zW%>p+g#9r|9A#ugz4`>`qM^RR(=+|0TsNEZ%&v4?$+JDn@s(kcjzFnODbf!P zKHJ_{8?kt5C!V1zpY>hM2=OD6`^64~bc?|k!s2rhvp@jh`yQ?^JfcUaQTHaxHUz7k znOYMdA3lFgF!s+6?r+lWAYq6o5<-gm-1#N)13&HH8rZ{ohJKS(0KpK;V);JpdA9pX-aXQ+Nk$NAunJX;kD43bQ* z(__YXBpIYvNy)*GMh;8J0tD&vP34rmyrn-d>a9p!IgI`qaO^J@lGw~2spl92^wIgAnuE9u?$CkH$Da(v?ddR z#Jv9Uxc9`K%*jX1KAm34B1T5_$36$n$@iU0i2fJndRo)N%m*;#9a-&@KA|A$}){acjjU=Vc7^uJScc7`gWxJLZt5mBI+OuYVL(HHK8-^5^@ejw@$NY z8IU_tc!h8J4pe=zjGrh%`#ZWx{I_9iA)`o<;51?pY1JjdQy-icbhjxmwq_9ur|QfW zsoc6rFiz}`hVH|GhTt^gjST4kn8f#Xrn}^8sWjg072t-CiEIcO9&&db?FcwBDOZz+ zN>?JW@%4s!Vr4v&@w&Bm@Qi_?7(&d1-}q+d-|BBo45e3u9fz&Mj-mVc6LE6Zjs*K= z>lyNE&oy0*=km|h&NTwK03sHE@c*$B+u=z>Lbj~;X#H>#+48XGO69^X1Q_}QtI&gaSA2v%N46?iO^F<~BC&KdaE}qn zb~;oaH{1P`msyEs{O5+)@gH9sKe=wtvTqqJ$$rX8{8vA4*)_O_f6cS%*8aXedgm-S zsA=)%Q><7auGc0`_QtujXAhE_%uCTMQYV_tAjA)t&T51Q8EYUY0da^ZoSpPgWZjQ7yxK{94*30^ z7&^mhGT*GF>gBbT{5oEP-}8ybOGAqu>V6UctRLeBp>=KnRD(Rx_dJ6=SJH-J9qOBg zM^{dxlFmIi^PJ3gV?$H0rjNRs$0()y>xI8Nn%w^j2b1bHb-xVy=kxKuTl{XEYc$N= zn;LJgYJ~pk^YJnCMuYpQ(8+s=lv2rpYfVExv_1bOpSdc!C|Idj^Gqo;E;4pYGj*S3 zEz(e`8=!t=U#+`<^bFI}zzZ|!c5xPm-5%nW{4}d#WO6CIH0B`ro%xMP{*FbPf#^9b zF=}mzE_LRTaBMwy6H#<~`_=99 zEy`k+=p$XI#X8#|i2mXl_YaPF(|i?$^ZD=zvs?!#e~Lx81z@B=p_8YV43&X) z*`y^w#)(8;t5{omKPfU$qJcqiM|68dY)pG}`j8My_q6nrGH1hB+)&R3!HQPQdXW2! zWwKb2J5R9dxLO4F0G2KViUEK){XTQ*eRdlqzEFV^E%t|N!f#jfApq=JCQ6+LwJN}G z>e!KN7eT~*1QD2_Q^BQ_8Csw=%u&qC7g0~cre0_7L&J$sc81vB758PB0mZpAeEJ0` zX5gDH`1u$Iw%kZOGaK?6!7i=lgd(Rx>roff*%%~am{dVd!-$1G9fMDn*9|ce#^4Xy z{omOSeh%JOXj5Q80C{^4;61ol9O7(uGPH+neS(VIPlxuT;)-tW9D%%>*docZU&=s7 z1Xlj$114ge%+P~IJ-8=bR=~>UmN{kmCCwDN-g-7p{iSL|Exix^0A0ftbgM>_l{q#tr6u zdfrfuefxW#^r`M8IU zRtn|w%|ThAzXJL2rKB@CV83rH)GdHZ6O%M>4^|&MCQa(z$Y+2QC{#XxDM6=m?n{0G z=Qb6;b74#XK*$wbodahgQZPLap_6QjKpc;>&ptEoftvau3xy(p{2Ji9#hiVF(E^Vf zeCZL0$yW9S+4Tzv#?V=5j2I5OibYm?oUVprnG@LegTVJUP)yWffS#IZOa+46E2T0p zcuLz{3pO>(Mdm{LoPvKEP$m-$^RPmrrnZz@0Kzpu&;+BO8xPPEU{9l9$&dmjcVP>CuaRI2V4f=*ebEXVsrmVB675>5}9vk>*S z3=+U^ZD75f?x31y=f>TX+1vjRC2tZ6{x}q+z+;6Da!eNL2}}zIs9nDuSzlL0I?S$+L#oa1?uD zCJS(Gz*Q~Kh%ZQg3&XpaSsjT&;%XrPV8w(*G$&`x6i@zqCxRWlVmePU9> zP*gIYjDk74F!tb*Z1Fn3O1^Vo*5=(2sqTTAk04hATc(EGqNjI7Qqm1-rRmZGS^ ztyBg`+`oZ~Ge859{1BE(oi7BQ&EIr;850(l`E)23s#P)TSO8+6-xsS-jDD7-r)sj` z%&>^!Ghl9!{LZooiI+Bhf8`ceDt8-b=?8M;V!YmZME}I7CEjPlH)}bAt4Gyvw`Lt@ zmDP!>X1R?jEjfbDz)W|&KQkaO`;gfRwX=m`e`asf7JNJI(NFKRN|_4!(PLI(vvqpN ze}v+&)&Rh=ENrugp%4WYl4IiIE<6D(SUS+bxqojsqV@|DP8VR6nth)?Rn1dd$30&t z1g<-S8S~e~=1bX@+E_8bf*%%lOs^K?PU69LssFB)lx&8jcHCuws@Yhv7G*-~0UH*; ztUZKzq6O>578!j)`9FH}8ywTX{&*EH5KLY-1#uvB_c_So!X8;Dvyg~=K4(ze8c?cXi%Do?IVFr=@Y8huJ~nJr^o_UaywBk8uT9NFwg2{*zbl<_M%67QD%M2 zMK6K9UT=P-k5w2Cf|20{#yAK3P=_)zO~rL|GC&&it1&7U(-Gtr<|T-044KR5rRPGg zi+10xELy4KDeZW)m$l<5ffaC-8(@G8eCe&j1|zFiE;Ev${!ZcZgiym>5E(%114 ztuxd!IZzqEt=yzCHjE9I9QMbbC&r04vRE*|GGNd1VPPy4kL1_azIw-u*m=)xq?cy*bq}xEC_Okjt zH!*}Iw`|otWSktBW9j#;>_(x6*glNVZVh_>g(SpGYM8&gpRAC4Sa9C0{HrGB&mS;B z3mh;4^Q73OwaHD_#)cj4{8tB*>wu~JqX_}CsydVLHQhASNh;((r@;G-jzOPJg4z_zFxT_1r$1VtmP(1(`j0u&&AJO+|_H%M*hG@XQDUpQSqE$FtuoEpCyl#cF(KrzYk zI;m$C=yS_JumLNsY|-!)V1jMGaE+zsL(TfNn55Mm^QA`AssY<7Eno?* zG--PBbK3PX*g~1)(-=ir z6|DJ^DckDRu2uHHrGq_)_HFQB0+RD_<>ABi>q4=@p`VzG^8N$S1Fh(96JeP=b4W~! zz`Z%)9S|n}4juq*)TIbH48KGGXg}}{JPv&gJo|cO%l!R%%__qrWVK*PcyBZJ_ROGA zA2D#9LzJ+$xW=!<)@_W=I{IjSBOa`;5KqD2oPm*~^nE&;eOV(M{|@uSGaTxXr;5D z!-rU79T0zOmj%;ND$W*S&L&>~eztrIPetV$Iw^72aywHUUtLr@@3pcQy+$XivhG0c z>@@v_1m0OvJYM%da}*cwojdSj-BZQqSAR(GWuSN;vGeSHBb4^A4Sk#FCYlVfIzGW+ zidZ4D0n9$7YZWlJ50iINDDc^}zX#HaAk&@obH}g$Hk|!^y7n)u{$QYM<-PC^(V&_i z*0l>|sCjzybYvN?4tOdOt*Zzg?m@oc&?6}DE5Is-!1bsFz^hTLmNht1JVeN%G&^Pa zC6!AF3v{P?kjXFscY(-cF;Xb(^dN&0U7W-w48QH;2=-Ii0MD(3n;~W6R82CQxYP88 zRjKg<1+V$(%{L``@N1$K0RIH+qGIh6CY%s(6WGuZc+c70@$_V&cfp(P*+esX)1*?K z%>OohbLO%?%QR_*iZH`AdSg9%&Pzx>N=+%KybZIlFh&{(XDt}}R$S+j z8rDvpDjw9y5b7P!E#W(076^2L0eQFwPRf;+gsVuB*`WUD6yizW)KIZhCoPtov3cm| zQ^HvjPnniJsUnf>TmWOERd@NR{hy11fP0@2jxqyr6*w!o^-au?imjGDryhMnIa^NW zb-7W=Gm9&}3|x_myGOcuO*f|@B*AbIjdFnyKY|DERw!h;K(ssEW%chw&c&bSmqD3t37=I6;{r|1^X8as&`PA zQ=J3WFIk=RC}g*C+obivDxmIPHi~KcHLo2?{&{C=d82;(z44!BLSH=A!S1zSrN&WJ zFl7*OSX9d-l2u5AOkf0(fX4y=vvc$Vmjpb*7#kO7CGwHBT&wqSm z{*_huaqWd*UF^@|ERKc{^{9(0t@6s;_! zg$+`ck5kiKUQ%>f7XArYpJinXyz#B?5i^q>%Jt|%yo$h>1js}pRzNrcjSR5ZNhl@G z6}L$=+>!Y%SmTe5X3P;Hc94Y`1I8s;G?Rt_%jA8l((sMwL^azVywY0YccirOU$jYj z%FvW}KTi)IH6_q;)e;qWk{)w=ka99%AWIH$!KIbC!ZlKSjVmt_wGXt?9>fu3Lx$Co zU;xZ>QhJW?_Rk>|vz1@3B_oiC9QPTZs+J^|h&{I1K#)#P6_QR8DedKYA9Wq|3)-lf z;F&2esnr{oYn?DqZ+nhFZR@b25)ms04L*z8Y3bZW=U7dDn)yJ*nKUB@5k}a|{9}xm zy6K^Dl$zn0*zE&(G0asFRPSKPcA4rTge3n+F9ngbpJresQIb+=W?q%b^&0e2{rmGm zMa4bb!qKv2m3j;A?$ZKEmW0pqSNQ_(?u*sObgQ4Q$mlimq z_$Z-BWz$mekrK>8P)a^AIggzvIU_NZSIy?z<$*=_W zL_2G(2AMp!@!kuUKGM_1Q=OErO4<2d{F}g5ixGoJ{1joSs2t884H3Z#8fwi<8g!o| zb}}ycGCh+TA|E!2F!CCvoTOL>#pq`Pxr6Erb~buY;P#Q}(%%$!LAu)2<0{4_;MuQD zyHOKa4l|;mmUk3N0a};_GwwAANuSYBT z3-sNt*l3*w5fW>B1B}Q3=Bf5_uDgN{HN8WatQ`R}(XOxJ^<}lPrN>-ijU2>kb{6aA zX$4N;^Wm2WEqgFbbzf|DWxzdl&a%_m6aV+GyBae#g^BR3Z{fHoCDruLs#*P(Si3wz zR5}I|VCluL^g2IKsCS}t)bR8o&s=*|wfl_1;&4a_p{8{*Q}~sQ>eslC0-~5#1JHd< z?)(+#-jw^xle!>KbWm&Dg?>u(aUf&2hyvy&=9Nw<|9!bf2Fv1oC~#4v(~7w>5$rJA}xInJ^L?=&H#KY&iwW2?a_kw1DDU{ znwpj$Cp8VzPEM~G4y!X$oQu}p3EBz`YEB2Igy)xB`X#tSN@*ZXTEqC~95B}#h;1Tg zKS)hQ)Lz)?4DT0R!^mf`rWi1&$2amtHIMa@IWB>+*8 z8ae$t4qLvV1*k=1Vqkgpy_6>Znilz}>`vU~3|n97F%%uS_s7uCkzkPQd^pMOxn;@S zVJNA$Gi@j=^Zi_s+3;i&LFYPT-bik<%a6zu zZpjp0kMVi(xWoEvbKw((Xaps_#*dKD(;eU8L1nS*L64N3eD}H`ZEGyLe4{C0D&6FY z0*ELrvXIKSkRR?tMU1eul62F8p8IJsB}y~#*XvS(p7%RRC1vNa%8L1K#J@u}vW}d< zG>O3_h`)J=Ke0W0a{3b+jTw`=w`6!xR2<=fj;iXK{U5EN0oMK^i510QLmHhs42jF_7(2XE!B;jo0ER!0U>9$zgdq?pwotB<@DzVDi<@yo{J>n28&y{ zBt;F^*_+0l4HE=tC#^dM5$0nq*K|tjD@r$0N>XMjo%(LJ;xR!lG@b7?Az|4`{gFeD zQPN;ldV9k5x$}Fm{I~=IDFnmR;4K`&c7bg%lDA|~@$XS@NuA~mE2Bm$gi5{982k3mmEE*_r7Ns!cYZ!0ZYcPDmmE5TR%=p6f2^->jt`4Sfcg7Yju)r1SR{J3kMyKcbPIhiP?W z7}-HdVuKVSKuYH~JGH^|5|8*1)b!Z$5mlo_Ml0cpXn({O;(3W_?owJSjwGkW#5`M9 zOllwLXnALYJXZlbKBsXvq8iq36(=-Ey#GD=*FdJUyvj1slGng}=+|ChIYRIQ3#xIn z@Sl5WnG9`>-x69wJm{y5K{>S<62pOs!sthZq6xhSWq7n{oIrMxiIs3C%@?dsRY9^l zP9}hHLOzMXQZHjmg$fa(B{5~cOZn$uiC2DF{lqGjxoQ^{#^npp;BTK%2C26rt?CFb zWc}LNF*3*G&A#$F%QCvDTyj?oKW$5Ss=N;N14jBG#;)P^AY+j!3)^+c`K%w%Qa%fh zYlSCZG502FDz{OO7t@Ji$PWVbnlGEp7p+SoMHP&uCd2xo3#kNg<_4JpbV3a#N9bVL z;IWUOfH?Dzv09)n3E@gbcH4c>PLLh5O)G)H?2zV#j8?5Q5f?%3bRC4IQj33pttR=I*Gd!3t)(oQL3GSs3jM^`XEaSrP&^1 ziq2eIW78C8MmS_&XCz&gPsGX3=H$nHRW(b}nCA7%V>~^0V^BwsAEBJ?NUkjSG)J6} zQcoj1n0I4h#?4h}-;K3HUeipNSyFs|I`K^0s6r9-7Ar@EAT6Q};m)qn87^dN`4-1D>*>@ymB4B<`CVtllG*8CIuUlWdx zC*u9v?4Evj`-&Hl#rnQ3oXOcLwNU@j-R`_BdD_!O;P+Hbcf&)cpP)utezcSwKu9$I8%fls*{_a2O~waI zNc&B^D|T%Pm502{p)w_FBm^XG!g`$d6a4A<n5}yxPIC!wU^#Mtc8$g>6*KIr zb&4Z?M>zq5OgzChm^P-hr1aZ^eQak~s9)t2pTp1JV_7+uxDbJi@2P;75AU z&C1AGDnb5!g6>6s$K|OH98vkW$^Fz}n+y|kWI*IY*}t4KmoDv&85*he%|)3z1tKrl z&feTJt(>Y9DNRoPmL$X}pXflkJ~0t@c%JZ;bz9#3I?9z|N$gEtGmaw>l!z_NkKC=1 zF|*!j6Gn^aG8AM9{`^{;-XKwAC{&c{*DWhMpQH$NbNTDIBk0yQ_SQYsn@<}>MfFSD zA?-g?Yd0)f08EXMYJb(#tFigw*0G7_0*Z8y-A4(uzE=;B)>|$KgO?M51 zrrWB7B#SkKVV7y%JB5^9p^iO~vh9?R$ZHgY8$j@cNFemj=yZ9P6men(GfM-)2Go{3 z&)lL#uHC;4Pi)qn_P#~CE<33C^GRT9qT{3&&V%LO4M79 zrRX0p-{qx-RxtH8VZLq{o58RBWA&wNT}e}_om)BGW?edR8L8`tF-|092J%&Oy;Tb2 zd&&qf`y^a(2n1Y~o?afzA$5rrt&;`4pDkMlx*Ri1wdLRqia1$Kw%+L{lnu zklEe-HjlmRFZfFC_J(n?KQ*H+9#WxnEZuv(7-R|oET4B@D(fXC%|2$h5L9h+FhS!^ zXT{pw1PHq|CMG~2jw#~g{iwU;RQsZi4{C>v?L$$?jSe#=RfQ}2V?KSeA1C{|=gwZ- z${KRwx{4`7`uz~}Nt_tF`90)(mcrcvgxqRlpbra8Fxjy$^to}?rVeuvyXR$<6p1Vgfft^iUTKh3h zpoce9{1QvxbbQuh7a0BsUu151oahSqUU&pEaE0-xCh1~oDx{s6LPArmk_z(!>f&c- zd{}HY1CLL4zuqd6r7`;G7ghw>sV`7}lndefpY&)4_&A^CV>eVzd-!y+u_%>lISuEQ zHT|SZ;Q=_`i2pgE0|CyML8CR^oZ8Q}X^Y^%TgwObiP*3-bLYjEL`detiU+B-zZN6qb@Z^m#;+uC7 zq#vvyQcSPAZr$N~Pd2jtj(98l;FDm4rcmwBuXciH8KF-S>yUp4C3l&ZFJG%KuDsIdQPBF)b}L=IcShO#Ob`M zu(4R?^s&7^L$Xb(+hrkj=X8^)!8A3b1`LQ4<_zz-!mxh?BJhZxl7-~ubH?UjauAq6 z49#lQz(}_>AuP|f!WId*a$&Qvorcq83`H6fe&`&P*9J>Tc2}<-j6P!zAo>oNL$Cc? zWoJ^4Af!H`Y_-ci4ZjQ3jWdcjf@P@$4NO+R$T%jG`SN`){i{h1$rRutGf zRs_H)yQQawOq+1C^mlAMJ+v{7&cQ|>NzV$@MbK}nPNU$0S6S zm7hKLOOJ21T1yKIUKXDO$-^-4$K%)N++wvh|UdOQI*R|Ry>A}@QTkqTd>R7mXL>52Lj?S@jZ z+Zlxrhd2_*D+QxFi%IEM%FamLH3FnWE}ijikvKOlKCv}j4g|ei%-BAbl#?ov3axy? ztaL%DGv8>QEmj1J*^@kQ059bu0F$lrH^SOT+G1J6OV&1iL@_hwr1aK|Qc%j719|pW z5BfZ##5GRiSAOwQBT(!b-@e&5PV5HpF=3rqGJtt?_la5BQ0bQTJv z3W{pepPATX-1BZMQ)^042~ajugoKDWA?-|dA*AsBk_Tsfee|d%lG8C^gs5nvZ2*`a zNIfXC-znlt)*~Uf))ULg2pnGT^jKk^U@`l!=nH86I~NamRjqCnT~ly8*C=#|AGJ+X|Mde57)=nhWgUVx)|33JSMzkq z5WtB5@=O#msa8M$GZx0qh6qar8Fou1{q&~FtLgs_Y4`cm#2f#OeiH&ofFzqxq#Jq> z>Ae{s^j=g%L_kE4A|N15Odvq0hN999MT!D~(wp?6NJl{sY0^XmMFhVnhu?kfIddMI zbDo?(U}tuAuASMvKJV-Gc2%|!h0Ky}&5w$Hqs@gRnmnQ-gZU-yB5ty-r!#p^t%Id?}$34HjDZVX(@R^ zjQmG}Pu1FNlpnf3yen(Za%{BrBL_|rIPGoel}fv97f&I zfV*<0m$hM?F5Ix zx9B)kz>Adh-t!7c5OhOMPk2ndqcdgyQ$^8gJb(=?u**ShEcT^9BE#pK&kKxI`?0*= zEzF;Ou>MYh?cB&E-s!zr@69<|I!oK*R27qx^F-!tUuT)`)VlhWfDrgff2l3YC)a5vv$TQa$RethLs@w|^@&On{KrO6h zJhW`CI_wyU71LRtRmlHPKR3>cHpFyZC~8#t{TR_Gs_0X1-WJeQX86RZ%xp+A)C1jn z^}FD2&v?yc?=5n#2w&-|?Ii(_jFl4@7#OL0Y4c`V`HK_28~1MQ^@WcLbE!Ffx>|f34FVa*e*DHIc=)Nf}3COuJInJ&ku5fhoGk+t(&CT7DuRSlEeVUg8py-;!D|9@t?(uJ$n9#aoT_v>j2@=hJqcj9SxEO}z5< z0bsj=(%KBukXZ8APE+xjH*ULVnl_%1mep-v0wXR34hZo|}# z8PyjHTeU6_&AFwZjH=1YR-VJkXqfB^n96*gZw0ckHomV5v?=dltUG?~rG0tCgeZb0 zis}{f60Wh^FDr4b>W3~7>X2G;lj_M>y#qH`rC|P{0FgdHU|rIn_wX9R(1yL0vlQtZ zVx0rFim3vb!j2ucCNz1d}8nF2ULmff`~dd&lUk@In}SPHu|9UzMwWy z!`O*-iTbHCenV*o7e$ z*0{S8oR9 zvYo1rI&=7MFbUltHi-tko4N{NQmB1&uRKQ6nDM3|5wXe)?3rlil_kqFBSgmMttFgE zMP{L$T9NKg?=q-GGJJiT;7}=SQ(|VL*2_?%k8A=&c*;62T@`&Q$~jUl`fvSmxBjgv z{cM*@>Po}#shD>AMS>cc%^gxa-xDy+z*l=rX{^k=CKzi_Z5nDy`6vPx&CXP#=|4Ep zF?T-T;$V06h>_C(j%$AP!DoWfHFiANqvZ*&^c#I*SJBJ{lio>7%VN>p0m7<2Oec;< zt6zU!lt_pT9_eXzBFry7AqF|w4)zStnBhBcb$^z4(_U}uDIT6aXfi|LSDS3m@ylIF zU^QW+iu0v`;B8z9v2Byk>1Ax(b9=%o?{iFW!NkTCj>yL~%i8_;X_x+XPc-d@w_dMW z3L7xh19vH#+Q_*!@mW7J=3WKE*1<(H60!94BbynX>ew`y=;1uAcfX!^CHt-E^Eu@> zF6X_LQZ^BH*;vr0^&lu+BDA*z(s8tq7TnBX9sWV=6RJ1bXTy^7WE@_Og%U{HL~MPk zuUnF)J+}de^aFv|zE#F@V;Fwz3d~K6=n#Ci*?o)YlxDzeqq zs#48l&6ipuyYo%n3PW?`| zH!74(f|GY&_1xdK&g(KaIQ3ROTgsNDd?l1T<834F-z?A$x{7!wEyQJJab$LG4>&ht zMs(Rgpm~QHvTK(6wb}~1oAj&DeZT9!66=OO?#ADvPibVa``Z@6=y>s&1SfaULldv9csq*je7X7^gh%oK@ek`4<)dLfW zA7Uo{m64RXTx zPc&$8E1dEb0W}%;IQsu0bI=^p^aDSk{>*R=2vU#BlLWV>wtqBdS zhv4*DLv%2KPZwtrZev)xBR@ClX)*;RDE?<|6o8x zCgW&+R4vrgdid&PcV_&=*!thzliRU)xmD!SRpg(;9$!gd>E`8A)W!5V<#yr6&RAa! z$R6}at|4ZbK5}p9bxn;0A`pwD0nnSlcs_r}--`%Lc&|=f>9s^25%Pp;*goy$p^snU z%xau2FY`*jkZ0(0`d+{9Xnv!&U&S3GoAcV}TF=yO?du`r#-_b=#=w=zKFRkKsQY!( zRS#p>SwQK7T#>S(9*nK=GcW%3pq@NH*%yil31T2Q7dKS$TsQVqJ(p&B*@BZunjIe+ zxfUq;jnL~2_?u3}OX|TE^7KpJ4G~*}yOfBVi6Zf?FJL5AD$MJ`QpYB}D z^1-Oy+*2Wj_O#9K>Y;np9l}gEnynpV37WqN2U2ACK}ts0oP4P5;@huRocx42PsVoq z0+xTJJB_a-AML1zW$0@~e$uQ%`gQK+pYBsx^svU`hzO{qV&j$jhsn7OS-cwgQbDHD z;Q`(bW;JgkJ!60;cn#`pC@Ro9T4p*0ZCIh0D*>yc*^EJ#TO<9dFDI~i?(6O@V#bn^JkFgTb zVlOBd-Y4SyUzt~GHiHe%&>t)JAR_(#6;@H8u7@AOsL?sjyRbVCFna62;FOY7m`@G1 z!D3!H>px8=oR+PBtNG5yP4<9g!oClef#$&_GxKzRD@w0buXo)%VNtekp;dLzN@>Z~ zl%hh8IVwwtyj8*h%j~mBZn7uXFsCP8+pl&>u1EedvY|AL#h+xgfg(6lYE93-T9i<< zzG~)>cx$hzA?z#my6Sa|mi2h0ZhpT}SSU%r&GyjBWvR$sp8fa^F}XSTh9}Ao4Bx%9kItzFsXg{9+5$Lj%Y^q_{@}yiXvow`{(o~is(#7Ixq z-`ISO=?oSnc%AK-j2{=wX1G;JjH%@@sQ^qBU_f#zh0=1P$=iFn5ZV(ucXW(|z=_n} zNl)NnSsUuXx*Mknn>?QElLgS)(QLZq(hfnm-!{Jn-XeCk2x zKZdb+%3W?C!~37c0KkGeu!$CvbMe>J735RyraY6Vp6lpIIHelJ7xu%Vo26SgRPe`O zl!~kh+Wh&hR`i|BK$=orFsSh7$IY&q9Jv{M#ucOn>N?@?g(Ul#iRAR;{}3e1SjyUOn+D~8ms?} z^-5K>y&!U=U4Eg+eqqb9JCr$H{PA!L(A|m?>(}<*Ivz}J zdrvG5KE9{hC0&;Hu*^8e7bl)T@)(h9jf6r;W`TK|Ljc-uiL;Pvu7dKwKeF{bL zrwnnwFS@|DOuxx8dw?U z87zoSlf6Gm{&pJC{wQ;S=_24_TZQPRJ3s*F)1Jq-2N~`^l`B`AAxCF2r?+OXMjNb6 zE;6YPV!uk<$KR%zF`a#J?j&X^I*Utehzp@z7Zt;vrCd+Zk*P#D_ zR9&Y8MQ2yg%eWXnP7pFGkw@KNb?2F+GXVwV>7GsjJODk89Rl`=cXV^xQI-kiwS6Ae zD^xeW74~Tdno0;3cC0sJUCcu_GWWT16J!^hLhb?D3b4{t@l7+W3 zTkh;s=^4>Ri(64_c}wi;YD1SSZx^uTZ`!YG%+6Zg>0rxsI)G|zUK+g#b;WDdtm_=k zTHSkBhR{GrNC%S`_*r}D$MzfIY*ND-on+zQ8rze#4W z4A36uK;(?U*97{>*o4Z>?%|dljAZ9;uJbC+c#8;}OrP->t#-YkW6`Q&qk7B9QGl!5 z#b>TF=DpI2$B(U#qN;2nVzjW^Z=1sZt9n2!-T4C68V3yJs9A0TMW>E?89(0oMVy%w zCqi9X2_m*NE`pN%DOqG=hYZ-Lx;1_e5P=GWG3}NukfTMr1OR4*1KX%WgEZr)1mcn* zyA431kkJ-d)F}SA!fcYrzxTPM!2I~dL?%f+Yl*WJfr-gafiV_9xd7TJsmeUXBGEpK zut{MW3SCK2SZ=x|t1Xq8>@)^|qDf~g6;Z&{o`{9s8&-A;gUeB2G^LBSUTfK&r4?&A zJ`I~|x&Hl<>v=)%yw?8*GFdN(+}~V(Ok$GSpi?++ZWO8-#BLNNt88r)(+s6HOENFq z+2UOO^m68G-=lF`#O(jTA8@9Hd)i@rPxK-ATT)$G))PMd;b=&^d zm*<@yrp;@5Ilceykja=cyII}Tg=gnQ88$ut2QrB`)v)mY0hxSnKUn+UkjX0mF1^zU zJLR*}g%GdY>1I{k-su62q<4GSFCLv&azpRE(J%M$k$*nP`k#E=aPqbcvzX=_;i05r zlERy_{|hpSuNT!*ec#3*n37QW{{WeM-JA6P!}@E={@!_y>Fkzh%I{!_Dqd~( zh+Y04xRT1o-n@M4Lf~t$)W3u<{aovGu<4vH^v~M$W(VY&ZnLq5LD{vEmW$7yk34#> z)+ck3MSBcjKmncB`WU9K=%Z+*2=t^_r=mLs3(V-WI2x~#1aNKox-q}hr(Ce#S|(|2 zgpVxW@3Mrw2;^BC{dCeVE#Z%y5-a}%W5gw_0OU z<1PanFs`8-FvemcLkIC{LfX7D-@}hsT)rV}89sVF$2Z03Vg}))W5|7`V?{w$rjxvU zr0n&Fiisy&8Uq)<$XyTJ=wFqOs3%nqF|Z-sZ^!f#Dg~2qkbn-}(5Lvw-D#fVXEl`o=d-cN$1&ucK?K0w5`Z^!hp=z$>&6d5;yts4 z|9ue4dJkQMc=yzYb(2PS*qa|!o z&M3Ju-M*ox*x-*}F)NN^y`is_`Xoww3Pgpg8L<}Bq+(9&mT z#mTHPEpt<}x%QUvsyS`8rDG{3Q~5j*fTdW$PBBcGvJZAL5WSzC2fFM?gyZCn-6fcNuidxkGBP{8zmr5eU-Wz8z@~XCy<9}}wAz5fg#N?5 z=5yzTdmC=1nb;Du;k8EUz#_^IAp)r~An6v3!Jmy;o^u7q3EiCJ?i(!3;>*I3u# zH<^@3ZAs=O>iZ3}NS;!O4+|u3c1?q|fnB%8(C`K(t}X=c5bA5lp4OL76l~uVeMU%4 zC{TCAJb3k4g3m<5>HFu#JCKF%k`ce>_0wPChb>ByKCW2S zD537LJ*fM+t?XaNJ=rB|_~ujQnFX}IzwBbx=k_l?S*&e^dR0MMY^{_oX0}6XC2>Ka zR`o^aX+K3jBl->}mlSF#Bd7uZexy2{9}T*BE@_SJqHeK@)G$f_>%+DSN zUNv@3>6k=`$}XAv-u6XB2e0e|1{2v-(Pu(dc;}nqAog#YpVi!m8Xc*)bkB3dSl@;s zC|%~v*genvGVMj;vv>k~C=CjdgvnorVqL2o{{7mmoDp$ji&!_rv4~WgG47P!`}--H z?lKdT5l`rbpwF~S@tijKt=eChvFCg+%ahZ9SC=;aMBYt6=I=TElro5&3cbC7fa_K> zr#}nqNkkma8k)UBegEw)H-6Iq@eSvA{Z#sR-(u9@t8-|1UMNr>65s_jP6hqSk3^nF z1sBcD4}9AF8Gn22I--@^B(YHhW_ z-f|Jbo9~d{rT=VeJ5I)mefwAvX42!ol|IxhBfRpL36t1#ZJwcaSy={?4i{hD5G8j- z2YJ$XrtrNT@j(djzdwi?)A(nvG;~vC0>3%`nin=9tRZnBz)XiA^J@Ql-sK+Vz_k0& z!{Xz`=KS{&ESh2Qjh7`>o56@N2I0{AcU77eejP;Q;}LfTRR>WdpGAJiQt(}%6Cy$v z9RGQDnsTApj^Ub;p}bMl**)##^^nL63`#|xXB_=S7<>oZ6$n5fqYDn_Uw#;O&1!lM}U%YIrhN+gu=%Fct0N3JEE?D4gy$=1*b{=o9`mwg{Z{`l*2}F zN)bG@8j;ozQW#`>jbg@-7RgG6igxM%JMqlAP*DPv#hyw&$qQx4O)=usj~y^4M~TfK z(VvvT_k^H>buu$K;y^Wyd7Q#jYY$=f*}TCP9ZF4*0OeL>%U?Qjci93vkZ%OEN&~>L zl%TE~0lG#ki`t*22>hzI8Tl0`-)|TZhb!|08MBh13|3-fbw9c~tP8zCNKz@i!&Zor zsrM19jd`dB=463cYT!FL&%Ha~JiFJcm`hLMFIsX~vu9xw58@NE5*g&7a91j{mbwCk zQ$ORF_raN1@JJj>7Ikjf0DplPWZ@m;VH_NihLiWiFhTeL=z#>N0)*yH*}a-HYd5^! zmlckezt7Tp|3q!!=BA$EmI-y?s2Rjg%YjG}Y?Wx*`mqGK#su&WW-#prt6l<~%6#Sx z&;lS-`JJxmrQ1iPA$f+DksI-+Y@i3mP>RC?Xh5Pc0eX4%0izo9GE&)6j>MY=sB>dk z!@xN(WSVgAGXhXH0dSJw%wWc~&{T{+$Ul4G<4ZHk!)O>b2HBO&W<$Cl30?`i@6>$% z%;AHi*>tPoSZ;C>S|0OrC+dw+D$`<$(k0-XT*kQy3ZEMI0RR?h!GH^civNYZUk-7r zR@(^w=mb!-Gal5v&q_|0PzQxT`Aq*<&47ofG&Ju4IvojknSeP||J&+0OkLS9auR2z zojCF8KXwyEraS$ixTL0B{}?iUJZbTG7QC=}<>|XT1U40BlQGf^YBGkS4nk0ZRNG4+ zHS0d@5NuD#0%{(&C8&HSV38*#`z#HYh8nh~uX~`{9YEzUlvyZP)aPs5kZ(r?e;tBJ zLk|`bz!UWY*eMjoLxp&tqEba7rbVn7jy&=-v@4ox_aPIO{@Vy!QCc8EwqZdK0OIY0 zN0IjiZ^x>Y{3%aPXudDN42azaI)&&HREUzlZ}2o7+%3d^aDk5J z+A$XieJEjYE&7)r_u)q_C%GJ5hN*jP-`x>+j9S5mvVg{JUP&dm#i7#NDF-{PG%Ds7xm#amtgi5D9Vd;7X z)BqVdAR`Ho(gYX$>tKaw%hwRUDZos)Sm2bxUpo?`}Q&8(iq+ z#vskc6D7nZq75eB(#uj5=G_Jr_aE?X962Lb$DAjOWfYaetc}&ebfbyKl66c=FBx~Q z4-bJild$t2(C~4`6$EfZ$nx`UE01Wy%QY57fWMR4f|A<8&VYs9szR5+8SBm_cpCiu zvyR;6boTuw6Yv=bNgfbzdd)B0eP(#zqI3(M0G+I{emjGW*Ti1= z1^%}a&fSNh$h-ACh zbO)8mSMezlRFABN`48dJ@`h%^8;`Dk@_#njn9ZPzf!@geDu>Ri1k3We#?1y!%e7R@ zkJftj*(Y}$4mUFVRa~K;T=j-|9DRNJgwYxd|J?!5T0J7F*XFVPTHvA_I{nVgRbB`z z00ZWuTRwn?ZEd%G2L9$v{C5NijU4#rGiH8lJ^;5+n!cFDQriq+9zT&)xYEXP0q7I( zNv#$KEARuJdBC zohZj#F!iX1v2bjIxBpe*&}V7vx5Y6imVWhAX(}u5y@t$K_~LCm=7i)rw8r?M8jz65 ze{D1P&KbwQ!#{wryL_GZ@6N(gX3ZuY3ub5aSbC6H%udZX9DDEG;@gv{%NL&h&pzW! zwP>N=QFrsvE5KCPU-U`^_>(Y!s;y-Tn0$nqy{|o6Xf_{l1TobHPrJ&QN zEU7q+>1i# z$rZJ!qW@(q+(`m_?F9NKgPFe5{5vymo=ropOfyE!nja0rs^_1ZEB>y`z%JP`R>n~* zzy$3SQ)Bv#KVWbQ?oR&Lf?7YY6XmRt^x6AbsU$d*zX*Jtf1W%KZJZzCua~V}gt{&Q ztnV0N+SQhxh@@b4tTFqvw2~@tvayFn@I6~$KT}#A>v#r3m-)rMjwuxI-zhAAO|Of^ zqVvcK%gARH8O&wpx2MNGyI9P-6sY`n7xZEUzSN*sXmc>E!xyP3rovSW8BI9Z!u<-a zhb91W=wx$n?g=P&95tZz?{hfIB$M-+y1=4G)LK{jiWYj)V5RT1N+(Je%oxy&ajNc^yT`hVYPbugy#Zm?Be$sj4_JVW&XaYx2U4UpfgYF9(jM!Q4 z)Oi>FMVF{GVg?}fhK}uuty&7h=B+y)I4Yu3R^B9tU+ly&bqWQ;tH>QFEjS z-Q)tN_yk6CaMltOs9CeQyykAXCL0B^cC3NBGXnJQsuqe9;vi7|B4)q7K|Db;YW2G~ z=7%)~H@9~0N~^##wp|(Y%uI6uUp|s&*jpn)aP zVR0~jx?P~JGwK(NJbx9n?R*tH7D$qMwIZ#f~uhXqf1yY32$THZuf7H&>yXH zX6dLZ17J&IIO6^~M_2g+DYUT_SZ6lO+x3Oz+lK5p&=fT}I{Zta;1`qp_tz;uemH|t zpMJu6m@kK8ohp}Em+k>B=*Qm6OJ~9LyI{-fXTkZ+=9ai+|E6SVw7>7jmlUw#!nWzh z#SZ6v7RbA6k3rX~+eiN(uCj-m8A~WKfN%`jmG>F;|u0Z;WL@615ysmJO0wS|M_c)=q{rma15GwI43U891wtY zi(I-bXpyZF{B3o1a@~rq8~Zv_D3Z5p%!(5@WIg6*cCQU4 z?RJ7{_djX`0yDLmWr`5vr%ZE5_xOOTpKFNEK63mCtD9>|wMZ{He6({soX2!~GKgF4 z$F0S#Sd6%HfwQy3HXZTxskjm5Q;?$1j^6D5vg=eL$tlM6CiPaF&>jLKTF*A4Scu#4p0d3T?Py zEN0m-*ZZFqg2+g8+8AwfnDiP>mx$h<%z3By%hBiMgWI5D$zT_rbBk}u7+l-Er2*hY zXtu^2gZ2E*4DF>CfY3gP@mqm+-_hA36dnrsF`=yJ_a47}ruXAd$~$?q*3Rg;cwxV$ zbMe96T!y}i??P9K*dYWEm^Y>{@c@WN)6qfmmePQLr?-qx&8~uMK#7tkzc$3-R8d1_ z)uszsOB8TOHdk0Inqf_3m#|WA_nef`eU0o-)PH&$>PD7HRr_MCzW>L&<`fT%9e`#- zNof&s<*J>wu!eb_8#( zWPg^$YyAnHl`7w-8a^YM?=PSL+927TbIMR~p7Etw86*aA$`kMx6KB5>G`1}aU&%NPyYAZrE$^@yG) ztVf@rKnDMot;E9UF65R&8Rn^K!^EuR3%M-s z1GE(5E-U;l)yPmMLokEz>`tOQiCnXEtk|d}Cj>oduYD)V-i(A1F>3MKoC@+>FW}*C zUS^rs#MSTj(&Cs`1*3$>50FPSPb=%NHF0KEG6FLKEmU@# z^LcB$2hB)%O6MK7Vw2vwhXJubq4YE_ zFnVaosyErEK6=`G4&_OgmV6*JKjhG`RFXrpj#1K>5*8NG3Q$wfa-J~kB&FNMBnt-$ zMI(*{u2~e9a`^Z%5OlO|Vk%ukg%Q+DXdH2<{O!cm{pL9h+Yn4-Z%i)BE|Wb9r`~-Q zrE$`z@Q+oGzRX>6TrpX8`$YzHC2}B^^T4j zu**-9w=@3Da#b-k6A&|(J&ZNQ z*fTfeO;q^aDcxA*t&BmWXxqA9jy$jZ$T<*N^rRB+4JRgV4Kw-jN(A7v} z{B5PlF(Rjs^}6`H&isxcOm7chi@!6vD{QOU_O!!qQdFpfX{bThR_EKpT=n4qxcav> zg82KCCYcbPD$>PSCz;-oAt>psOy?{7M~9gN6_u|4if_ll+jYv524f_DpaPx61wx#y zZwdv0KY6a}-?>CcxMQ1oTW(>6gp{1L~n?X{_~5(yxjy#&xm?<2HpD)DfVD#a@ z-Q!l({>a!)F7uz-t=#o<%QKjsHz*ye(4WI~BDTM~v*j*QL_^a5I}TmN zA0VD|`2^3Y)& z7<;0f3tk1u;!izCroqKE;Xy0nC5cq#z?K*Z_OaV6vg0z$6>)<-hzdOQUQ3fVSLQj_ z%$ALY0Iv8Mmsd&tiBoT=$&(3;opJZbr{i-u@>fVvEKME?odsqD;q3%bgl4h?gcU}* zOFudC4QJ=oY2`+x{YkqRpb^z>IoCUvtY^6%EaMvZp0=gELYKkPV=e*mGTVHnE zXz38n>nSd$7?dYyyLCq*dK$VD$HrdXQBw_g$$R3Z1k3ftaJ?uX6}2p$BM)*k^&o6x z6v-|rY-G-tO}c|>*z=~qm5j;o5lKxfN|{=V|D_~krHrbzCf7*N4!TrD4D9Tie!obt zO{Dq@@*3=L0r=dgL27ttcSMsxBB3$Gvqs5BsAID)pHA!p&Y^|+3tZq{^9d)Rs@XY; zFN|5#nqka_;_N^b=+q+Dz$x2uW47@GSo>aXO2r&<@+Yrs9ql-6j2JU!Y*#UuBoU9` zlA1l#O)t@j>h18^L+Yu;nwC?1w&kri;8BC|kz50#e=1{};_s5+31+Aemp1gcOo%;K zXi#3!RFic&uJsMim*Nf~Et7H>$y5ztT1+)nd#PAVWpSeBL6iY5o}$7tp6*5^X;^RL zes*`(dPU~6z2>rH(I2u3*aV?uQbi|Up>L@9wMyc&Q*y}8 z>vBPsiAwX?pbXobH?%>9OFm~lIw4Am;?bGXJ6y=)sa_7*1o4Ln1u7J__qi4d-b^kh z?iD*?B$+x~(mfz()?Cif@C|G%3EO=?&Ey*d9hmd`Kvf9QzZRa(jZ%1R_HClKykcfm>L4xiA$R z3>3`3N6y6-lHtioCQp zj^(+|$b%ep5zG?Hr<09qpJZbHW3Q)5*nR54{atP{2u_aFI)A3BjwS)o6imPgn!CXs zdMQ#blHpn9^dJCU`xN_ycn%%x@+@WR&z5|MkD7sFtL6){sYicMXINnicf3C@`E%pBx9BFzm z7}$I5DE*I1JVGJ`oq9cK*>Qpftfsh|bQmHp+eW$SO10y#eTm{Fh|tivO$t1Sq`>x3 zmZ(<<^;_1Jx6GAWeM~!h(oT%#bOR(-md>KE3!lsLJPaKBVViOn+RY+Cn?L60Acs@2 z*=>b{Jo}<&$vZE2SSPtJz42ip{r$Cm(%0_GxenXw{bM#iH7xtGbI7kDTOd1cgr2DQ z1%4c9e05;c&hA?RuC_RT%x!sRBWC8R!J31YyH&JdCp4IUDu@_EeOKu){@Y1VgF^N1{CXlfRCaz=wXwzto(KTmvbwNBb2MV~UT&(7stoVyS7HR3Mm2 zx=jUO+{KRtJPX$bTtyNm{zy{DY9a2x{$yol7s8 z-W~E6;^Tv}&L@vCU1{6s-Pn}NzsYS)nCEuU&6`!s>AUCgjbcRKgN9{SgU1U_!%=k>!@Jz?%+&SUif$6SrlFaJ&i1vD~eU~nS`8J(?<5Tt570T)3pev6n=Z260&Ir{1%T_uXv1<|E<}W8eJ*R3@$m`Iven%RT!y>$DQW z|HH+%2bU%{5f?qgWMjojxFvlav&gDRC3@yYhAQ`dW%a)aDPk#$rIG^i@n1eNwelo3 zluMsBdYJbv{c<>dpDDrW6=HO4pFYTr2;CZwWF-n*@jm)hXDUM`=vZ4bxZ!!pK)&h> z^c0Bj*SL*~PIXcTG+(bTbv?`GegIXW-70t5D8B#I^$yFm`16%_j;(=M);Bc*Ke-GE zFrslxzwS^Xcj1BUypgLut87k)^9*u}J&KTTgAW+CG*o(ZdgKWoj11sMIc7!Ae_ z9}<}#1(}eoVQPzMAvk0@21&LR+-(Zn7Y6bZfxRrLesGuyll{Ycsql z60*8et~st4RSqSO9TssiBftXBkV@yrs43saI9G|2lkHcxLIHwPwm*l#9XL7*UOhl{ zAIBNERyOavjOw9+o}pdGJf>=~vyblCnQ3Zb7$uOg*3h zA5qac5o^dDv&=#l$vcCniK`bMyYTyt>GlQd1BB$*ncp+7#N4Z3x`>!qE(j+zy7qL0 zKVxE&Y!n#}JGTLq#zT{5__vE_eW5o58L0sP5Za@%gYTv=v&CtKj(qGs*_eFGyYY=* z6X1Z$jFqBDCud=Kq=^T$C3Fow3tS2hHeu|CcY25j-z4?DaCdXp0XB_dcNE@SH+K^ojjL~5`_bpX@9|rkfGWR7CTKr&^d_2m!HC_ zWT45H;VD{BD-`*u&u%c4kJP}-=3uw|Kafdxy!e9sv_%RJFuWs~d|0svJnqR6;Ce2z zOI`+xgjM}Cz3Y_*n0CTnczP zS^l+}aPkAKQ|zL4CpdBnviW72<{kdJak55m@njg_bgk1FNmc_PP`o=pGG8uz7>Z=X zciymCu5-Tcny7wUnx!tpZYtsaCsW8~N5nWo^9BC~T0>OX-T`vQ+^IwFkUN9OqLREXB3PNKAPpV3o!!|RnF`Vt&sG4?+ zs7h^Axb&b}tLtZ~(?>~(Qss8hR?vZve=?T-;{2aX+wSX#8BJCx`;wa+XjLg-y{K2H z?kBUBT-R4+?-`vy)D+I*r&^P>fu2|$Kd?D6=+VXP-6r20Kl?1r(r+SbyJ0p@#G!R6 zuub9rAnLyVseb=IfWOYbIp%R3dma0by?5svht9EgNM)4~LKMy84362cXEs?CC6T-{ zjv^yOMiGU`2&HKG`uz0$4_?<#uj_U_pO5=v&|*gHP?{|CyJgeifIEQCjE@iM@Z{%e z@tgRnq&H&EXU-ihUjZmbo)>B+e_JAork|298eBg&94x=;zA@kXW6CsrJonATxJPQH zB?9LCNdqsc&7=1|ae=bO1GzZk#ty z5O0D>rabnYmXI9BDMen|y=d0OH2yYxo1_CYO|h29ijfu-__K1^I`~e`mucVG)v#}N zxX6cVj%q-YDWLWp3*pw4pMoGq-aTlpx?Naw=m-ws%eFhayHr3!EqIW624Z5triIvk z+}G_hXNUZ=*mUJ16@mc+Ax-n*T8*1K*Us{I9c_x_B{{Wf)Kny=v+^3=;fa z*qZuD{PCw(C$H|NN9r!wNem2tfs+E606Ia{1NM8KqbGi%j01CB7R7f-y;CxPZhm0R zD@XpyHf}kJug@tEkK2CI<=1Q--Bt#+%b)zD;FLyl6dDVByD6 z$Ga;(o*S{CC?h_!V9?^525yUx;p>9)6z3Nk{^yV`*CMB=6PGo)$)hwg{q8g3A$EZU zR|xa0wB(JtOOuCFPk9r$BX`Y#uARfoOB3?ymy8rHs17FU;wpX8g>!F9LM0X#UKqH? zA*9t=*WU-_o&cCFlc?rPJg^gNyiqKwu9FcE@e#WuG7@nVqi+~jFxHCWxwjddU0wyx zdYi!fZ<}@PAi|cXu1@lZL5>skgo*M!?4IO~M&CAtap#_dh)iH_Ze&$Pt>ja^Ru(c9 zY?Z98`8Ax`SUR#Hgw)sIs2exPm*AotDq$}XJMv{Q9OX*5v>LulICbQ-g{}su%PcYGaG6HP8@yE#x}8|K2bu&Bw9|EoGy@Lp z2`aYyrk~ld0FGX{g=cz7>UWAvq)zbq$`H<`doP~u-|4?t)xy}=Pci>^x0sbankguG z%pcS=-9P?A@t_yZ^&{gp4Q^FHE%&(23D3T=8m_cn_R{=IGSh=sz6B{tv_6-KQ2K!_ zhO{pm@<$EKMqSbTv+xqzb)ojAp_a7SLZaD9w`O0qyG#-FwP8PqcZYi-Pod7nG`T1% zHdXD?O;u7*jNkNT!lMd7$v_c_cXMoV0T0dBH~BP6mFk>BPW zswM=!E-8}c``HimAI47=@3-pl$fO1G_oPSKVlXF`>K%A&oG{#vo732^C?<0uO1rH; zxw+|LJyqb6W}eb#rzks4&$}=8s>G*=uUq%&PvR6=b$~2j^0OUrg}*g7!HoEq$z(_n zWE#VztIa*|)! zIBDjN{BbZ#@u}F)>%Z7K?7viHxYf192qM`q**cuzn00K48IOaDIA$MabAb#ee+FYE zT-9D`sy#mN*2k|Fd{ut^OF54EYUz{q4T2Z-^IRYSfEoT`Bexc)qDVX z+^dQDem%RKrpp9JTQxs=cdh2!CwmSOEa=OE>L;ar8|e>)*^%iT{4z- z#!Z45+j4SNl4jm7gq*ltT`oG@?LBu7-O`MU%iV+C zPUMwh-5c;aj?Eulut@kTQM3P6isywzQ}0z#uH$M@tsRV)kXjpm+;N+1xsef0u=P_t(RpF34#to?#>|Bta7`T*O4>MWROR zHZ0Tt?3Agk3YaXk&2gUa0*j!WmuDHO&;)jh&VWA}p4IfJ9_(C&@=rV-` z(0B-Kk}ShANLv|Ivx0-@DggkCzBK#KcLo(gEE4<3!ioeG5ybgliOBT|VqrIq;b-vn zts=S7;n{#efPfU913;zAt|sDJeEGgZV&?=t?A$TipsHyTE*-FMyAdwHxCj#crvhL` z*C7lUmPs+SNyNnih1&3z1yr%uhR;6F5g-Z)VhZC)lx6Y!C6c+*mf(x-F{oS_%j6>z zzCiu7Of`E;&`OYy3xAJ&Z(-Iyi{1BpkYAz|G7VxQX^Ig^snmlY@nA_Bx?-j#XL6ks zFHV!k@IpPJ{5_xg@T;)gw^?yF1n8581g}iF16rvs-X0%XW2b39RZhdYCU?TPl4l5C zJB#q9WbI)9KjFu&#a88poOrXvnz&V zce;KLkO81@Jd@Ba34NkWpeWOb)<>zfo{J&p;&aa(1@=!N>9xxi_VWZ&Rxd=*$;;!t z_glj43Yzp=sE!@++~(oXwaHm*es2+X<=0V~L8v?b3=6WrZqfxsO`QGP=Zzq zG$B4;^FD--FKr+FJ}Y?!7n-t;VeNmX>l2x*{S5QQQjxbxnDhRm%z(nEPvYFG1@&ux z1tmB-iNkF*`m7OA&Am_@FR=2+g9uN%{nrBOga4XFu#DBgx&{9) zpRwQXlK*{1H&xWTuiO2(W0wjE4kh&L7b_j4Yj$UbyPk0IA{gxpHE$DS##2wL5Z0?s zr+5qD<#X5L4=Wy#R%-t#c?#Wqj2Ef}^*!f&!Gok#)ri?moFbuIjU8o<%CZdesn=y_ ztHmeub;$<0EQ26%AVb3E7YX@;)ha>|R))f>$~-(jDP@+={Z(wSybx8PjOXqd%ng42 zHWc4v$uoQYj8O>1R@3 zjd4z3+klJ#Nr3U?(KsvOzj*YsZuEoAas#vw;^Pb}#!jc|Mt-P72!oy1X z_u5oX?UTe{axjnnb=BF_jgZ*4Wy>ns_t^NA*A*uTh5s6)q{I$5O0FF{PRR zYG-a9hR-CZ1NmX^{G65bUtKHg8;~cveS~_wcO=m~*`s=kvf0#C?mU>Ixp*=@srzvQ zqFlyBH`!N*`M=_`?@1~K7L^)wW@Z(3yxI8Pz z=w8Q*FK0wFzoqfebY*|>o*x6O)G~TKqa`u4}^Zv|AJUVEby+OI?9}SVyq`Y_t zHSB_zeBZJQ#T*wyegJnF8kPsU(}$ebBTDYgXn#c<|2h&AbUK3b^aNDS?u%WvWO!&J zi(M+Pv)7{vV+uWO_b2s?n0)4GEi=ItyNCz&suv~CXxix}B5v)#>)ae|EtQ?|{2YD8 z?`NATc5r17g)EDhi4|O9@8iy?@5sK<8QRa(K4WID+`|Y**h87A(8dK?Q@+^cu3pP* zFDRL+58cM18y)1&;9IQx?ax@huV)FN7;5Kz`$JWvAK7`oI)3gA1cd@jyXLt~V#(-w=*`G--YT8Pd; zbFwd02V&{+g;&tJ8qa{lMpI*pPb7mazwr|Ac{&z@>>R7Jr}?uWK~TUH>L+pc;k5z1}~;F(I?hS3x0Mh6;UU7CG zohS9X7S6R0jz4cMuq+L`sI|VI@rF`AHzcoqb6Cavmr&_oeH`!hP zMsN7?e%2FjI6WBf_?#kZn}-IGh0=B;FUC(@NfKMZ9Ma4@ris6=P`?*z%@18)vkCZ0 zu$-_k4VpgHktjL4h$rl)UP!%>O&b|Ybga^G%8uPG&!f-uX6qc_{`_E?+FD{x%3Vz+ z{E^o@TAlH^ZBiEaAe9Ohc$xO1@h?6x?^gSothU`oZ%U8|kT4l~A&4T2L5Ql_|H&do zQ1w==rsoEK0P{FzZ@VmQ><+}V(c(@q2Ps>K6poYQX3+!^XN8O_E({s1#mb&!+uqx) zIQ#kWB4nH27avgjRpd6_6>1vV79nXMcvx?^6mqKN05{PqSlYW&4xwSl1H}aG8xX?x zu1M#7yse=$cTd?H8hwf;DRuSUhdVF{Cz{xyAu12r)NpJZGb>n0xOMC4Z4H;8A0uw{ zeIm+GNrp-JF{{SAYI%3Bb*v+gNxTT3gBEeM9gR`9;V}*vpRduqcUEn~bVupCseBob zh=MaxWJ-6XiDT9EULC&s9Etw62OQaC9tjrDdu$b`3qBDLdTiApk8ldh`p$Fm>ni)D zG@i)vCz%*paFArq0OY}BmrC?XYJNxi6VrK+iCH7WH5bZdZs0EqeGu0bqO)Q|Ych@A zwa>p@KK5c*3aWpdVD&C%YZPbb-}!j9-$;PoICpfN>pr@k2@&H_`A4xDCA65_)r#|Z zl2CKh$uOFp^>>UJ`WgYvGRqD$-Z)ejn`F1LfQG;CGn=-M?E=l0n%k|)0`jshwcRHF zE#YG|9xV6n@SAiB%Ve$@)WwmvK0su1T_Gy8VZ$?KL(2+{%Xaw(7onjFO<*_7@h|I0 zJx2&&YvJQ~h&}4n_Bs4f33awRzGp(+H|hPp&N7mL?nAov8*cg3KL3$&T#s=JpMC4c zyOJL#Z{drd|8hCh1z5UAp_U`a1mTOPbBXFl?<4#ONdn65N05oXRhqcbs;#GA72%`;C}B^`{lh~f(@Y_&He+qde?-)ydGbeX^r_U6z<)A>Hi^HKh!(Ul*6M5BYoDoJ_zH^gE>CL27TT>dNe zV@=rL{L9uG;#b1wI^ur*_$wY8vG_m8WaJ4~@u$H8gDd|`F~ZBhG(h&}xO#c>UHg@v z|0JJDZGRfbGkE>$b==oAg00fgDHkEb9 zU;2f(F7UamH<4osaD+7bb*q3IQpfV?J~`<^r5+L_B-tvR50~rxKfi!VJ;MuS^FgY* zX`g(ho%=`$=f}D(C83BAb}9#l$sG9w!2^D3rTo`cT4cyZ;O6w~*oah@c(3cu)H}CPV}mmcMK$}r5c63=Ox4?@Jt~#h}6k<(CCU8 z4f&Mo+k2H|NH5D86osb?+F5R5hih;RXTnad%uyc_cb}Q2YS_F3CyJh{&yN}G<>lAu zWAiw_*>xRrQePyo{9HLoy5j!oo^Rj>4jV{*)ZD$o)w3To4<%79_hoTxxV~tNaOaoP zz~#PHn6&nfVyl!7SFHCH{8J7O2=9q6H%~HSZQM>~QV_vUq^(3mS$Blqxbe#bE!^09 zkINKvl>*j9SDESnlaY5r_9@CI`8QlR|7HyT?pv`i!E718doK2@Q1H1Y&R4)YFn+3C z8gA(Zj8aQvu=B`H$=f33^DaKSj-;;7KCPyCe^Lg}cGo>&;Su~8 zmBxOI$c<0OMP}2GMcRiN1Z#J`UTn*s+8%^Xpp_XSYxxKW^@$U-Ob>L zb2hFkOe!0?g0R~eGa1(c%JM57_Wnpgg%`b2*}^{*f`{WcIBG0|uchlF9z$XhH&b~# zowjkp9aUTBen>$dJmcek#PvX9IiyVUUrn(en-MIzY4JT9JrH=#sO}GN; zEsVso*9f|$dWl0ZF}4>-k{99-s$N^*S6a^u=Q0OW0_PqKmT8@u8ik$YXvGN#xL&q5 z6JVVVwhzh+e4-dvcvtASIO@BzXqJ3XmTLUrM=4TSkRLzBjn_4n)FE+y&!!dKv0tuw zahB=kJu@^ea)DtsI&EY0Y?D};vvhDd!2jo+W%cE-(Rto`7uIoESfg?INS-RvOF`v- zOjYz*cuJ+5=bXsUlgK%b=Gz){nPt{m-U6^bfYD%-bn`fNSC8k3jyJWya5A43SY~p~{l|Ls&&+huhM%8)5^fFVW>vo&+7vhYL@1+4zfQ zRH;o@AslL8g}tGrix7UO-S}rA4^>Ve#kdmZ2Pr*xtf>ij!?{74sW0?+fhVIpfW(vh zQ;oz#@P@GTO-z=Jmck{=OAH|oO9M;m3Rl&?PIJJdIWKWaH;L}=iMs=4YmvNvnnKXxJ8T>Y%q0eB&Iel7u3=a)uZ}`OVXy7cQ~8 z+&b|Sl4L1plClguWI~f&2jzx7@%Xa{A|Ro4i6@2jQDn>gV;&aTM-a06^bDPu;u|}m zETcy(*y=}m8CM;DQID!7{u(LnYEk-`UE_GtnXPs24Vr|3JCd#-R8{-9xdn+*^Jo~f zufB=SQ)z>_iXhqU$+++k&VoUluL^Y<6lM7_3@x|9)sGFp>SBb1tZJ(eA(tJ;?dRB` z{H8Sk8}^q|$>0Pl_Yn2SOSrqvVs@poct2a=rX?3{sEG(XTQngH5~CjNCoEeD0;zNu zfd~I!wa4<|M4$RO;d7I5AoeruO+o6voMpYqJq?64l@yQPh$-H|&iW+(P*P^Q52vD= zp=BC%%b5A>zv_bLr;{$7826r(4CsQ8oc-ANWpQoYJRBLJ(S07wi%&l{ugT2Ejs+n` zz%>n-k1t>%K|6iNU(m~=)d_TIU36K z#61Z$#H7nVCve;9LumBP3z*}c&CmjE8e8T$<+BB7=Mn7|Y5a`tA+2{>5M~EZKcns% zKAX4lM~BOJUQ8?+zM%+<6w%p=l&%_00)|TeiZHw|=jM~zEdLWtOF1p8_yL$Qs1y%N z@sjUZsAnTr!XHKuO|IMANu5}=-qSg(5#wsK->Y>9<4^zj8yI!>&tmIviMKAlZLK<1Rpe+`$beXVE` zT~kst@Tcv4u7A1v8rc8V$#Y-2XXzdK&{tROCr?DPf!N}?<1M}tX_sloywU8!{oT@A z(kYE7lh4T1;`7&-ZN`0>f;)z19PU6em&lV1_J3;OevxT5Ao%Uurc0jcOib6Ttgj|f zB(q~fQ{IzXOIjUxzi@%EATb!vjQiqpocTt_UF+`aU*jD3&()7i`keDrJHBoPbuBc_ z?_JjQn!G=Fbv^SnwApf-0~}#0MA(cxPVD_K5|J@J*JrG*v=AOGba5aLwY3ri^0P?$5yPGs|M1C) z9wkq)+W2&<2;rchh?mzRl_ZX^F$2t%3saZR_MDN~zoEY9d~ETGyiBl-FNqd-(>4fU z48uYVHY{vXGpd&IF&pz z`mf9AQ&TWW?k}k9GS}}M5pypi)R8ydoU~y(>Hlw68rvpzrWbki4rEn@j1%R)i!%qo zsano?7kjL;jI$GsFkDQlk4Q6cPrKxlW>}scttAX-rtiXTgZb09V7I>+B0pge159pc z^X&o6^iec(0N~*T@xb5!yAgt2naN$igwJs8iZDTN;3dHG&jTSWDhQ6(*vBKJM3fz3Iv_GeY2v z5Qd&eEF2gI_)Xih)%khs*Rmn}JYbol6+2)In=`J-GtSHrEYG?9EsIws+d^5yQ5i9T z=ILJGhVtLQ6lBK4JFAw%h76Ifb}|*5%El-ALaFlzPnV%wBSP_R9#|jw7UauSwyEl!DH9|ZP z;m=t^7DJ^9ABnx$F*7H#HKKFtW{N76d4@E39${}pT*-{rRrLCF!&8fcT2=gCFk(0& z`?b-XWKph&D-k?_7D1D!$1TY%&6&|GRKwwco_4!3x;*4`K4heddt#=fF1VN!pS)Ed z@L)|SBC5d5*N(V@<%dLp<3p;HbG{js>91U7B^K(`=&1@Rt63s=ys)<`N(~+f2@n!& zh>6g`@+pqe-2&P9H4VO3DSTgw^FUJhG*Yc|$+2$q@WfgAi8wK_N{dz-x?*MHSfRSF zsEMM$G@;0T$4sO?Ti>|qD=Jy@ZMo{hGF3`SUU{BJIo5GmK!wZH5yKeey?f^XhsFjXU*LCRlXoj2Hji@ ztgCpcYz(VAo*y2u79nqqF#P3wn^KDJMXsDkypO(9d9^`yJYqVjK@bNY=|`%~ARAO< zPmGhAej&Yyh~ch=p)cZ>RFZ3+D#ZN~yvDAo>f{%FfVt#Ya!1VNCn|1O?7ngZXOuCb zpIXHiSQDmDYv?RDcU5#GAS7P-{`Y#Z^bqQ$Cx&m5B^6qcwknO1+9l@Q_qmo3BhU*p z;akaG54ewsJmsvZvQ!KhM``Oee_OtbkZH;o6S%63aO}Hs3Ve&l|G1-Yq!GIKHaAf0o9c0L0EBQB~1P*+Rc8~a-yQ_!HcFaTUJc#EnI#!PD zc74>fcglkSy(QjO*n#ADCFUV6LOO=1c#JG+f}3}zkiQ6=S+`HT^61Gd*C2vtuu1y) zXwzJhcBwe=ZAf%E7yo#hkeXmc%x^snTswPrR5>Rw__THd4m`)^c)&X$-Jm0>0>7BU zKK86gTp63urXf=RJzVT$8+$NJgx}n5V0U_AYl7(I0)~wmZ#{gyDx4pUSzY0tmjy zwW>=8cXtC#oW%G}W3J|Rzf&)Q+Wk%mTY0$dysFjHL?Fl@LEi?pvH#Sx+W5Lb`nFYu zJB{3TQSai~M87l$js3U&TWD8Y`KNfLXjvBTBKJK2sCDEYye1@Tix`J@S^R2>Iw`SQ zSUw8FLvc@H##r!H39)FB(O9gQG0z!OgrptfO4Z=(GG`M-@06#sR^ zb6uo|Z$H15xqSB%eUqxeV=QoFpIpXspsBy0SirrGd$5E1O6nkh1_6$shuqu~2v!|i zDf4nyv@+85uugG9TBx%MM5%tS==DwgMMu-ow`ow7!VMgM#(7o^;V=1`FAQN=%#qnR!micZJj!_w%XM;N1lrtJOFkNaapukC zYg-@XZbU@>MyRg8Rp6h3F2Zb@S;~G|kls3NlAO>Z*J*sL=${&?#z_t*fXxZa1_FD9 zy#beZ<~*5H)tZLX4_);}R1Kv>$Gsvo_VQZOG-D>%L65@YBX{y3?2?r*G=l#S0XmtM z3zUs(faZ_+4l3+$B^JC_sT@m-XR(jh&qCX!ie&GZAkJ-;v`F*-5rBj@Xk=mJ;KJk@ zH(+{dhON0zE&|bEI|E((z$||Gx2XIP`Yzw#MR56B=Rw#%Z~nT5S$WV$dVsf8T6Icd z`HQ&{9aUi07BPb6+5Tf{$OHU`MnKUBVIO`41UKg}vdgUMW6K9vy>{ojq7k0POdK(x z%1*73v}OGPfqNb&SikhXB+XV>-VrS82$nzM@fNxV7qYo{fr%z=-;sjxh%|Z46-_Kc zK@)uO{_^>SZ26{@$dYBj@@1fDL4Ki{nY@_sab8Vk_3I5p>M7ywLmS$w$fc8@QPQx) zS>N&jPN3&rc{e%B<*1Z*u$OJIFEnmVP$qi>_`(;tW>K&v`ei2CI{fjr^q&nOmmPtt zJ71LjW^3+gO{B_CnIaEvG(7WM-UvhVt6h1w7XI6Lsrg*$6Z5>pGuZYs{f@kAQZ{o6 z@i%p(HsZzadE-~9_UrLZ6IN7R*)5HCX`7)9UpR2?D>sEcok7+;6H}8&gz|G2|F`9A zvN&`%{t)t1ZS459cuBPB6f@I*uSI6!Kcr^J*MUy2_`$SnpeGMB=S7YD(71hpYiH2#Q8yj9yDfPkb@1kkvc;B}rTPmyu|gNOthAqN%j!B z%-r|lhUC=E?GEr+gx=u|)33-_WpUBHjMwXD(z^m8>6#^hAI6Nvws^HB z%oZ<9Z~mYi3dJ0v`kJ~BJI4~ABPEU>4|mGjR+~DffqI?(%J{j^e=BDzS2)i+M^2sD z4|DseKIEzj60`5E%7OX+FGofV?fq_AOzD1GwKb z|Jvap%{NBB2LChir+G7ce0pPe9(nh=B5nIW<=s`@NHQM?F;)RWdyFpNXmCOGfa%di z3kJV}eU-=9kzXLHCuU0<6uM7k>FcYyhc1yAFGR%Nol8}CMa=;mE1(M~oMp1l)>1G( z94CMLnsv5f*s4<%DM$U>Q%&Z*8fzOXwYI@8>WPiI;j*y;U5b`^b?I~Y5NuP0Yk{bn zd-SYs!o?4=#aw7L&u4La>6;m~KC-7A3XlSEEgtv1ukq|!dtJ(szJC?YKWk(pCq&5y zEiwFl1~|n-<3SetDQ~-7TC$Y`eqYe*o*b-D{>^qu>~L)snlkV9{@u^73H7|}yD2uB zA*N zv_eu8bzup#O!->85?AJU8HYS(^_g(TcVrC#yr{uE_0)D_kOI>s4yUkW`-f{|O&JfN z1!B!;9awS&uEd=RmLUmgt~wyaI6-72Mdpxb9U>o$PUI+CVwR7NwtRP{hp+WN|C0dn zsV|St*pkWd$<(Uo-)#_(!7sTov^6ZeSG(ydnix1h+(rQR+Uk)2%n<-+zVyED9s!A5 z0xC=}2$1i~zi?lv0qEjXJR>5`H9~9k?|+vT8$F5k7@)4E;ZS%mk-~2LrT@Hc&H?kL@)kvQAb;!8DZ=BW`l_YceesX$w${q2XMn@H+ zzE6&biX^KK`=d#2?t|5gLDPx3I&}bA-jAWFL@Z-z9zm;9@z-uOrcHZw&Z+3JG&@vc}?L?aS=am81SimAQn1S zbXh7E#~qS>t+JVsZ~XC4^%qa&k*qUozwq`>`VCt;w~QzL-_L5(gER->BuDnJd|3m- zPv&xPmSzOVR5yny8(s9ygL5J)OKXX4@SR)WPOZefZB5N5NBLzkj8FsXXIOYv;L3zF zRZU@P!th>!;r*gu=B8{P2>b5lwC3MzM+X0xEWijtG)`#w=q8aAqQ;9hSJh;OsjvlI ze4CD`Aps82VThrMYLc28PC9$MeB6yQBR8>NbcH&9p8B(`zLzG+%`m69?Q=5jLcN{Ygt?IeB0DK71SOk z=|qx*#y!$-$RR$!lbQMf=Up*WZp*E3#T2fyLwS_7gp?8bV+ZidnxwfVi#_Q$Z>F)^ zmO+DOwv#`7O%8UL!<~e0nPi!u*pf3L>xV6}2@~T#kD*feNvu-t9_*X3O_5n= zhS3Sx6j~!*PKZ9B_$v=_yjI8FjB*z@os&^G@7kalPdUpu-WvYPwpbvZP1UjD<+`JM zvVXzKntdz$M2nz;(Sg6k`*ycy=?Wry8AJ#8k8JGsa9XG61^o6ETa%A;ErO*AKK~U{ zZbjiR%L&pY4`{+pXja9chc5(vlhdF>DW%T`7qmu`l|9wv=~9px%P&t6g~S|mNefZb z?Aocngb4#RJGh_Z!{d3*A0MZe-Lqu(LL5cN+X2o7-_kK_>VB>YL2scBmO0xTnk^e; zCrXfFr8=1}=?X77yF7gQJ_-7*YQl}E>9Kf*UgmPoE3VO5u>*1W>1D)Ckp?yNOE%_4fIb*n-_ z@nxZ|AQK089JuTouH`#f2t&~ENN0cnG={?M^NOSPq_W$X8;wjD5?KuUJyI+!?oT^r znDy1;N0It*%tWv3{lKFkGns?C0Ih(ADf2!?5Dcjfncyy~&QK=1=|bNTlZlfW1t^TJ z>K+i;fiIKGu}dps!}0~ez1-Dy?&njHoO}2m9VGh1_xfa(EfhvM21uaav%__hbsU*& z1ON<8!a~ANBO-qm%HU`8@M5C?j|h7DC@h3u(d^YhzC*_gsvSBCdY(_(h|nn58VHe@ z7Km}5tfE-=;#vSQry&H&fib|TY(puHAbySv6#E&|s59a;Fx09a>~Wt)X^hWfD)Itg zE`S83wDiAn_tlBw_AJ)-%p;Kc@i?@-2c?H+-~nFh6Nlw~JfAb49*%I&4OM2kBA^xB z<^)6muy<&GG_gbzyPBX{#+SxU9^%RbKV07(yQMd60RlLIRHNg_bn8cg4|Nl;{x^{E zt<@?{@DpgWiwf@dMR!r0Vze@-L&P;4QHx!9Kpcpau9f@6Ll#2&^6>29;HhlZsuu8W z9hM->>i*Z(i}0||X8x+2A8kG@^>`)~)Lu+Ie3fmZBfrn<5il6a?!MxO|-SjGDtCAZ-R?bS$aqsJl1^;_MbB&u$ND=h*9H`)vv;+zC;`P)yyzN z0zrHV7L1OgNTn?+kY8j(c49B778f5LR8kTWCp3NiNc_aOzSg( zxGYx!_dTa3ySp!EW8u=tFoAUf9f%g{cxa zt0P||dz`3m5@UF<3mZMF@iC{BQFFT?I{Od!A2B+Z385-4m3I4*YuhPZY$SAo(Z>#I zME@4~M9H>E&;-K+ntoV$D$6We;nW1tMwCbyIz}^vk*3*_pv;Aa{&L5G__hATQ5G|G zKqK-5psbU-kyP{yo&T(6Xsoy-lwt5L$>2B1BAY!#)0)cSdP%ZQ?c)5MMXEB)`BZ6@ z+VnDczc+!{_59#v5`+D!H|%OO&z^{MoGt8}Pr^ee8L)-L{dqMTYZLc}EzY&Hg;3g} z%4p)Y0h$UQm@@uzWxV&PQj=%QI`XyCcxKydTDgZFr!scLG=NpMr7YWb77Q0hHrKYH zp;USi!Vu`PeJu}jMWl~G0oNljVgmLcM4Urg2Q@(Ysa2Z1kIP=5Nt<-v>9Pa1Ef{4G z-2Q_M2KS+8`wL2h%3Uv5a23sEnzp=={3-jva&(ppg6p=^NJ0L{%AaI!F=!O6hJwA@ zpHKVrhx0!A1a%s^)2Ou zNK9G0SXsQZ<|wr-lSYmgTa6bZBS~?N^5uFVKDV=ckf)i@(`5r&MD}|<>gf}GSK4#oQ5c6!}FR>RX$FPewUO8?)%fam^=C~ZTpD>`=F=r729Lz$rpBx_UqqE zCF$cu9`?nelP!LXEYX55OO-oAZo{) z1B&?LU1$+b^zD?RuOc8E%>^B!6c|$d@i@DaSiLEgxKVf=h+XfbCa8Q4*E(+@gm6oL z-pIJKiww4a4}N)&x;phSz4@$KJ@l8C~<})Ix?@rlcvbHc2zdVxUj8x*aBlOh}-oEbJIK1%?pXi>n-Db$T{hk83NbC$ZLu@`i29`NRkEa3!Pn4l&`n zf`YvpOv#k-*9&K`T|^yqWvrIiceoWXChgz2wcAa$INRSfHg}tJ+g;);%pZ}olyuj# zS4wj)%7Lw8UXu{_^9o;QRrBu@I~>TL_$d$j#(M9d`#LDb<=3;uXeUfT&%BD>bbg5IrEfca zaGWR4OqBrL;?)XBOZfZHM-c)I9D?G zDrr>kF{$5S^e>_SH&-~ERrsq&I+9un=p5S8B(RjTq)f1yK9@41N^KTL2YsM>iTQwV zYye!rYoS^X@JM*!%?JkWm5v)N#aOL8X2`CpNQ1(``TyPak(Y~y5Xt>BJVdh3Bb5K@2fYk(wCWkB)rW5v- z-HI_pcgV5JnKJg=Bb=gcdnYgp#;YZtgEWCI&wAhXDW2IGv)SQouDc2_5sz7Tl9vcG zTWYmU$j5c`*eo6WicW6vf78yBGr<+|u_m9-)-L&B-)+pkRdQ`odONRsU((v2lSMvs zX*G{<89cQt$^CTs&YgGP{&3#*{9sGYiao4%F}TY0uD z=YPg6lRriOALktg_u^9Wj^Np_7PssdrxI$<1Eayh{chBL_)9g$kekbZrO$KAnd7_8 zcNp&3dJB|(h(+tupK>rm$vL5=x6id0|GsmT+?ex|n~JbRPT!k@O@5};W=8ML2_am` zLaP<$;!82CB(xzbwpZ>9=gFI8N>T(Mh~DGRz*M}V)>gYQGH#nQFu4p?IP7>n^oBg_ zW&kd=EQo6M>2}(*>t7M`_v4N}(ZhXW41e9(Jk8|IbwcB?Xs$ zdX10S!Yl0%>UL#Tl6$}4mqQ0#&xHIph1H(I!(Y|~EzM*W(eac>p4kYq(V3V<%X8~3 z`m3Th*Vo?`IE*aKNxF({EeV=S`-L(xCfX^0>Cs&E=fH)&h