diff --git a/benchmarks/test_benchmark_compile_pages.py b/benchmarks/test_benchmark_compile_pages.py index 33404bca7..4448bca45 100644 --- a/benchmarks/test_benchmark_compile_pages.py +++ b/benchmarks/test_benchmark_compile_pages.py @@ -130,7 +130,6 @@ def render_multiple_pages(app, num: int): def AppWithOnePage(): """A reflex app with one page.""" - import reflex_chakra as rc from rxconfig import config # type: ignore import reflex as rx @@ -145,7 +144,7 @@ def AppWithOnePage(): def index() -> rx.Component: return rx.center( - rc.input( + rx.input( id="token", value=State.router.session.client_token, is_read_only=True ), rx.vstack( diff --git a/integration/test_background_task.py b/integration/test_background_task.py index 3764f67b4..b97791b0a 100644 --- a/integration/test_background_task.py +++ b/integration/test_background_task.py @@ -13,7 +13,6 @@ def BackgroundTask(): import asyncio import pytest - import reflex_chakra as rc import reflex as rx from reflex.state import ImmutableStateError @@ -116,11 +115,11 @@ def BackgroundTask(): def index() -> rx.Component: return rx.vstack( - rc.input( + rx.input( id="token", value=State.router.session.client_token, is_read_only=True ), rx.heading(State.counter, id="counter"), - rc.input( + rx.input( id="iterations", placeholder="Iterations", value=State.iterations.to_string(), # type: ignore diff --git a/integration/test_client_storage.py b/integration/test_client_storage.py index e88d2fa14..e4a38a15b 100644 --- a/integration/test_client_storage.py +++ b/integration/test_client_storage.py @@ -17,8 +17,6 @@ from . import utils def ClientSide(): """App for testing client-side state.""" - import reflex_chakra as rc - import reflex as rx class ClientSideState(rx.State): @@ -72,18 +70,18 @@ def ClientSide(): def index(): return rx.fragment( - rc.input( + rx.input( value=ClientSideState.router.session.client_token, is_read_only=True, id="token", ), - rc.input( + rx.input( placeholder="state var", value=ClientSideState.state_var, on_change=ClientSideState.set_state_var, # type: ignore id="state_var", ), - rc.input( + rx.input( placeholder="input value", value=ClientSideState.input_value, on_change=ClientSideState.set_input_value, # type: ignore @@ -313,7 +311,6 @@ async def test_client_side_state( # no cookies should be set yet! assert not driver.get_cookies() local_storage_items = local_storage.items() - local_storage_items.pop("chakra-ui-color-mode", None) local_storage_items.pop("last_compiled_time", None) assert not local_storage_items @@ -429,7 +426,6 @@ async def test_client_side_state( assert f"{sub_state_name}.c3" not in cookie_info_map(driver) local_storage_items = local_storage.items() - local_storage_items.pop("chakra-ui-color-mode", None) local_storage_items.pop("last_compiled_time", None) assert local_storage_items.pop(f"{sub_state_name}.l1") == "l1 value" assert local_storage_items.pop(f"{sub_state_name}.l2") == "l2 value" diff --git a/integration/test_dynamic_routes.py b/integration/test_dynamic_routes.py index 235232461..dc75eac6f 100644 --- a/integration/test_dynamic_routes.py +++ b/integration/test_dynamic_routes.py @@ -17,8 +17,6 @@ def DynamicRoute(): """App for testing dynamic routes.""" from typing import List - import reflex_chakra as rc - import reflex as rx class DynamicState(rx.State): @@ -41,13 +39,13 @@ def DynamicRoute(): def index(): return rx.fragment( - rc.input( + rx.input( value=DynamicState.router.session.client_token, is_read_only=True, id="token", ), - rc.input(value=rx.State.page_id, is_read_only=True, id="page_id"), # type: ignore - rc.input( + rx.input(value=rx.State.page_id, is_read_only=True, id="page_id"), # type: ignore + rx.input( value=DynamicState.router.page.raw_path, is_read_only=True, id="raw_path", @@ -60,10 +58,10 @@ def DynamicRoute(): id="link_page_next", # type: ignore ), rx.link("missing", href="/missing", id="link_missing"), - rc.list( + rx.list( # type: ignore rx.foreach( DynamicState.order, # type: ignore - lambda i: rc.list_item(rx.text(i)), + lambda i: rx.list_item(rx.text(i)), ), ), ) diff --git a/integration/test_event_actions.py b/integration/test_event_actions.py index 5845d80f1..499478a1c 100644 --- a/integration/test_event_actions.py +++ b/integration/test_event_actions.py @@ -16,8 +16,6 @@ def TestEventAction(): """App for testing event_actions.""" from typing import List, Optional - import reflex_chakra as rc - import reflex as rx class EventActionState(rx.State): @@ -55,7 +53,7 @@ def TestEventAction(): def index(): return rx.vstack( - rc.input( + rx.input( value=EventActionState.router.session.client_token, is_read_only=True, id="token", @@ -148,10 +146,10 @@ def TestEventAction(): 200 ).stop_propagation, ), - rc.list( + rx.list( # type: ignore rx.foreach( EventActionState.order, # type: ignore - rc.list_item, + rx.list_item, ), ), on_click=EventActionState.on_click("outer"), # type: ignore diff --git a/integration/test_event_chain.py b/integration/test_event_chain.py index cdef575a1..740fc71a3 100644 --- a/integration/test_event_chain.py +++ b/integration/test_event_chain.py @@ -18,8 +18,6 @@ def EventChain(): import time from typing import List - import reflex_chakra as rc - import reflex as rx # repeated here since the outer global isn't exported into the App module @@ -129,7 +127,7 @@ def EventChain(): app = rx.App(state=rx.State) - token_input = rc.input( + token_input = rx.input( value=State.router.session.client_token, is_read_only=True, id="token" ) @@ -137,7 +135,7 @@ def EventChain(): def index(): return rx.fragment( token_input, - rc.input(value=State.interim_value, is_read_only=True, id="interim_value"), + rx.input(value=State.interim_value, is_read_only=True, id="interim_value"), rx.button( "Return Event", id="return_event", diff --git a/integration/test_form_submit.py b/integration/test_form_submit.py index acc6bca96..a020a7e15 100644 --- a/integration/test_form_submit.py +++ b/integration/test_form_submit.py @@ -20,8 +20,6 @@ def FormSubmit(form_component): """ from typing import Dict, List - import reflex_chakra as rc - import reflex as rx class FormState(rx.State): @@ -37,28 +35,29 @@ def FormSubmit(form_component): @app.add_page def index(): return rx.vstack( - rc.input( + rx.input( value=FormState.router.session.client_token, is_read_only=True, id="token", ), eval(form_component)( rx.vstack( - rc.input(id="name_input"), - rx.hstack(rc.pin_input(length=4, id="pin_input")), - rc.number_input(id="number_input"), + rx.input(id="name_input"), rx.checkbox(id="bool_input"), rx.switch(id="bool_input2"), rx.checkbox(id="bool_input3"), rx.switch(id="bool_input4"), rx.slider(id="slider_input", default_value=[50], width="100%"), - rc.range_slider(id="range_input"), rx.radio(["option1", "option2"], id="radio_input"), rx.radio(FormState.var_options, id="radio_input_var"), - rc.select(["option1", "option2"], id="select_input"), - rc.select(FormState.var_options, id="select_input_var"), + rx.select( + ["option1", "option2"], + name="select_input", + default_value="option1", + ), + rx.select(FormState.var_options, id="select_input_var"), rx.text_area(id="text_area_input"), - rc.input( + rx.input( id="debounce_input", debounce_timeout=0, on_change=rx.console_log, @@ -81,8 +80,6 @@ def FormSubmitName(form_component): """ from typing import Dict, List - import reflex_chakra as rc - import reflex as rx class FormState(rx.State): @@ -98,22 +95,19 @@ def FormSubmitName(form_component): @app.add_page def index(): return rx.vstack( - rc.input( + rx.input( value=FormState.router.session.client_token, is_read_only=True, id="token", ), eval(form_component)( rx.vstack( - rc.input(name="name_input"), - rx.hstack(rc.pin_input(length=4, name="pin_input")), - rc.number_input(name="number_input"), + rx.input(name="name_input"), rx.checkbox(name="bool_input"), rx.switch(name="bool_input2"), rx.checkbox(name="bool_input3"), rx.switch(name="bool_input4"), rx.slider(name="slider_input", default_value=[50], width="100%"), - rc.range_slider(name="range_input"), rx.radio(FormState.options, name="radio_input"), rx.select( FormState.options, @@ -121,21 +115,13 @@ def FormSubmitName(form_component): default_value=FormState.options[0], ), rx.text_area(name="text_area_input"), - rc.input_group( - rc.input_left_element(rx.icon(tag="chevron_right")), - rc.input( - name="debounce_input", - debounce_timeout=0, - on_change=rx.console_log, - ), - rc.input_right_element(rx.icon(tag="chevron_left")), - ), - rc.button_group( - rx.button("Submit", type_="submit"), - rx.icon_button(FormState.val, icon=rx.icon(tag="plus")), - variant="outline", - is_attached=True, + rx.input( + name="debounce_input", + debounce_timeout=0, + on_change=rx.console_log, ), + rx.button("Submit", type_="submit"), + rx.icon_button(FormState.val, icon=rx.icon(tag="plus")), ), on_submit=FormState.form_submit, custom_attrs={"action": "/invalid"}, @@ -152,16 +138,12 @@ def FormSubmitName(form_component): functools.partial(FormSubmitName, form_component="rx.form.root"), functools.partial(FormSubmit, form_component="rx.el.form"), functools.partial(FormSubmitName, form_component="rx.el.form"), - functools.partial(FormSubmit, form_component="rc.form"), - functools.partial(FormSubmitName, form_component="rc.form"), ], ids=[ "id-radix", "name-radix", "id-html", "name-html", - "id-chakra", - "name-chakra", ], ) def form_submit(request, tmp_path_factory) -> Generator[AppHarness, None, None]: @@ -224,16 +206,6 @@ async def test_submit(driver, form_submit: AppHarness): name_input = driver.find_element(by, "name_input") name_input.send_keys("foo") - pin_inputs = driver.find_elements(By.CLASS_NAME, "chakra-pin-input") - pin_values = ["8", "1", "6", "4"] - for i, pin_input in enumerate(pin_inputs): - pin_input.send_keys(pin_values[i]) - - number_input = driver.find_element(By.CLASS_NAME, "chakra-numberinput") - buttons = number_input.find_elements(By.XPATH, "//div[@role='button']") - for _ in range(3): - buttons[1].click() - checkbox_input = driver.find_element(By.XPATH, "//button[@role='checkbox']") checkbox_input.click() @@ -275,15 +247,12 @@ async def test_submit(driver, form_submit: AppHarness): print(form_data) assert form_data["name_input"] == "foo" - assert form_data["pin_input"] == pin_values - assert form_data["number_input"] == "-3" assert form_data["bool_input"] assert form_data["bool_input2"] assert not form_data.get("bool_input3", False) assert not form_data.get("bool_input4", False) assert form_data["slider_input"] == "50" - assert form_data["range_input"] == ["25", "75"] assert form_data["radio_input"] == "option2" assert form_data["select_input"] == "option1" assert form_data["text_area_input"] == "Some\nText" diff --git a/integration/test_server_side_event.py b/integration/test_server_side_event.py index 8687b0f32..ee5e8dbc0 100644 --- a/integration/test_server_side_event.py +++ b/integration/test_server_side_event.py @@ -11,8 +11,6 @@ from reflex.testing import AppHarness def ServerSideEvent(): """App with inputs set via event handlers and set_value.""" - import reflex_chakra as rc - import reflex as rx class SSState(rx.State): @@ -41,12 +39,12 @@ def ServerSideEvent(): @app.add_page def index(): return rx.fragment( - rc.input( + rx.input( id="token", value=SSState.router.session.client_token, is_read_only=True ), - rc.input(default_value="a", id="a"), - rc.input(default_value="b", id="b"), - rc.input(default_value="c", id="c"), + rx.input(default_value="a", id="a"), + rx.input(default_value="b", id="b"), + rx.input(default_value="c", id="c"), rx.button( "Clear Immediate", id="clear_immediate", diff --git a/integration/test_table.py b/integration/test_table.py index d28798a98..09004a874 100644 --- a/integration/test_table.py +++ b/integration/test_table.py @@ -10,91 +10,47 @@ from reflex.testing import AppHarness def Table(): """App using table component.""" - from typing import List - - import reflex_chakra as rc - import reflex as rx - class TableState(rx.State): - rows: List[List[str]] = [ - ["John", "30", "New York"], - ["Jane", "31", "San Fransisco"], - ["Joe", "32", "Los Angeles"], - ] - - headers: List[str] = ["Name", "Age", "Location"] - - footers: List[str] = ["footer1", "footer2", "footer3"] - - caption: str = "random caption" - app = rx.App(state=rx.State) @app.add_page def index(): return rx.center( - rc.input( + rx.input( id="token", - value=TableState.router.session.client_token, + value=rx.State.router.session.client_token, is_read_only=True, ), - rc.table_container( - rc.table( - headers=TableState.headers, - rows=TableState.rows, - footers=TableState.footers, - caption=TableState.caption, - variant="striped", - color_scheme="blue", - width="100%", + rx.table.root( + rx.table.header( + rx.table.row( + rx.table.column_header_cell("Name"), + rx.table.column_header_cell("Age"), + rx.table.column_header_cell("Location"), + ), ), + rx.table.body( + rx.table.row( + rx.table.row_header_cell("John"), + rx.table.cell(30), + rx.table.cell("New York"), + ), + rx.table.row( + rx.table.row_header_cell("Jane"), + rx.table.cell(31), + rx.table.cell("San Fransisco"), + ), + rx.table.row( + rx.table.row_header_cell("Joe"), + rx.table.cell(32), + rx.table.cell("Los Angeles"), + ), + ), + width="100%", ), ) - @app.add_page - def another(): - return rx.center( - rc.table_container( - rc.table( # type: ignore - rc.thead( # type: ignore - rc.tr( # type: ignore - rc.th("Name"), - rc.th("Age"), - rc.th("Location"), - ) - ), - rc.tbody( # type: ignore - rc.tr( # type: ignore - rc.td("John"), - rc.td(30), - rc.td("New York"), - ), - rc.tr( # type: ignore - rc.td("Jane"), - rc.td(31), - rc.td("San Francisco"), - ), - rc.tr( # type: ignore - rc.td("Joe"), - rc.td(32), - rc.td("Los Angeles"), - ), - ), - rc.tfoot( # type: ignore - rc.tr( - rc.td("footer1"), - rc.td("footer2"), - rc.td("footer3"), - ) # type: ignore - ), - rc.table_caption("random caption"), - variant="striped", - color_scheme="teal", - ) - ) - ) - @pytest.fixture() def table(tmp_path_factory) -> Generator[AppHarness, None, None]: @@ -138,23 +94,20 @@ def driver(table: AppHarness): driver.quit() -@pytest.mark.parametrize("route", ["", "/another"]) -def test_table(driver, table: AppHarness, route): +def test_table(driver, table: AppHarness): """Test that a table component is rendered properly. Args: driver: Selenium WebDriver open to the app table: Harness for Table app - route: Page route or path. """ - driver.get(f"{table.frontend_url}/{route}") assert table.app_instance is not None, "app is not running" thead = driver.find_element(By.TAG_NAME, "thead") # poll till page is fully loaded. table.poll_for_content(element=thead) # check headers - assert thead.find_element(By.TAG_NAME, "tr").text == "NAME AGE LOCATION" + assert thead.find_element(By.TAG_NAME, "tr").text == "Name Age Location" # check first row value assert ( driver.find_element(By.TAG_NAME, "tbody") @@ -162,12 +115,3 @@ def test_table(driver, table: AppHarness, route): .text == "John 30 New York" ) - # check footer - assert ( - driver.find_element(By.TAG_NAME, "tfoot") - .find_element(By.TAG_NAME, "tr") - .text.lower() - == "footer1 footer2 footer3" - ) - # check caption - assert driver.find_element(By.TAG_NAME, "caption").text == "random caption" diff --git a/integration/test_tailwind.py b/integration/test_tailwind.py index 491eba538..bda664a1c 100644 --- a/integration/test_tailwind.py +++ b/integration/test_tailwind.py @@ -27,8 +27,6 @@ def TailwindApp( """ from pathlib import Path - import reflex_chakra as rc - import reflex as rx class UnusedState(rx.State): @@ -36,7 +34,7 @@ def TailwindApp( def index(): return rx.el.div( - rc.text(paragraph_text, class_name=paragraph_class_name), + rx.text(paragraph_text, class_name=paragraph_class_name), rx.el.p(paragraph_text, class_name=paragraph_class_name), rx.text(paragraph_text, as_="p", class_name=paragraph_class_name), rx.el.div("Test external stylesheet", class_name="external"), diff --git a/integration/test_upload.py b/integration/test_upload.py index 1f8b39efc..813313462 100644 --- a/integration/test_upload.py +++ b/integration/test_upload.py @@ -16,8 +16,6 @@ def UploadFile(): """App for testing dynamic routes.""" from typing import Dict, List - import reflex_chakra as rc - import reflex as rx class UploadState(rx.State): @@ -46,7 +44,7 @@ def UploadFile(): def index(): return rx.vstack( - rc.input( + rx.input( value=UploadState.router.session.client_token, is_read_only=True, id="token", diff --git a/integration/test_var_operations.py b/integration/test_var_operations.py index 2feb80ae0..3542bdf39 100644 --- a/integration/test_var_operations.py +++ b/integration/test_var_operations.py @@ -14,8 +14,6 @@ def VarOperations(): """App with var operations.""" from typing import Dict, List - import reflex_chakra as rc - import reflex as rx from reflex.ivars.base import LiteralVar from reflex.ivars.sequence import ArrayVar @@ -552,10 +550,7 @@ def VarOperations(): VarOperationState.html_str, id="html_str", ), - rc.highlight( - "second", - query=[VarOperationState.str_var2], - ), + rx.el.mark("second"), rx.text(ArrayVar.range(2, 5).join(","), id="list_join_range1"), rx.text(ArrayVar.range(2, 10, 2).join(","), id="list_join_range2"), rx.text(ArrayVar.range(5, 0, -1).join(","), id="list_join_range3"), diff --git a/poetry.lock b/poetry.lock index 6604a361a..6fe39acc4 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,9 +1,10 @@ -# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.4.2 and should not be changed by hand. [[package]] name = "alembic" version = "1.13.2" description = "A database migration tool for SQLAlchemy." +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -25,6 +26,7 @@ tz = ["backports.zoneinfo"] name = "annotated-types" version = "0.7.0" description = "Reusable constraint types to use with typing.Annotated" +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -39,6 +41,7 @@ typing-extensions = {version = ">=4.0.0", markers = "python_version < \"3.9\""} name = "anyio" version = "4.4.0" description = "High level compatibility layer for multiple asynchronous event loop implementations" +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -61,6 +64,7 @@ trio = ["trio (>=0.23)"] name = "async-timeout" version = "4.0.3" description = "Timeout context manager for asyncio programs" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -72,6 +76,7 @@ files = [ name = "asynctest" version = "0.13.0" description = "Enhance the standard unittest package with features for testing asyncio libraries" +category = "dev" optional = false python-versions = ">=3.5" files = [ @@ -83,6 +88,7 @@ files = [ name = "attrs" version = "24.2.0" description = "Classes Without Boilerplate" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -102,6 +108,7 @@ tests-mypy = ["mypy (>=1.11.1)", "pytest-mypy-plugins"] name = "backports-tarfile" version = "1.2.0" description = "Backport of CPython tarfile module" +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -117,6 +124,7 @@ testing = ["jaraco.test", "pytest (!=8.0.*)", "pytest (>=6,!=8.1.*)", "pytest-ch name = "bidict" version = "0.23.1" description = "The bidirectional mapping library for Python." +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -128,6 +136,7 @@ files = [ name = "build" version = "1.2.2" description = "A simple, correct Python build frontend" +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -153,6 +162,7 @@ virtualenv = ["virtualenv (>=20.0.35)"] name = "certifi" version = "2024.8.30" description = "Python package for providing Mozilla's CA Bundle." +category = "main" optional = false python-versions = ">=3.6" files = [ @@ -164,6 +174,7 @@ files = [ name = "cffi" version = "1.17.1" description = "Foreign Function Interface for Python calling C code." +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -243,6 +254,7 @@ pycparser = "*" name = "cfgv" version = "3.4.0" description = "Validate configuration and produce human readable error messages." +category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -254,6 +266,7 @@ files = [ name = "charset-normalizer" version = "3.3.2" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." +category = "main" optional = false python-versions = ">=3.7.0" files = [ @@ -353,6 +366,7 @@ files = [ name = "click" version = "8.1.7" description = "Composable command line interface toolkit" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -367,6 +381,7 @@ colorama = {version = "*", markers = "platform_system == \"Windows\""} name = "colorama" version = "0.4.6" description = "Cross-platform colored terminal text." +category = "main" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" files = [ @@ -378,6 +393,7 @@ files = [ name = "coverage" version = "7.6.1" description = "Code coverage measurement for Python" +category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -465,6 +481,7 @@ toml = ["tomli"] name = "cryptography" version = "43.0.1" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -514,6 +531,7 @@ test-randomorder = ["pytest-randomly"] name = "darglint" version = "1.8.1" description = "A utility for ensuring Google-style docstrings stay up to date with the source code." +category = "dev" optional = false python-versions = ">=3.6,<4.0" files = [ @@ -525,6 +543,7 @@ files = [ name = "dill" version = "0.3.8" description = "serialize all of Python" +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -540,6 +559,7 @@ profile = ["gprof2dot (>=2022.7.29)"] name = "distlib" version = "0.3.8" description = "Distribution utilities" +category = "dev" optional = false python-versions = "*" files = [ @@ -551,6 +571,7 @@ files = [ name = "distro" version = "1.9.0" description = "Distro - an OS platform information API" +category = "main" optional = false python-versions = ">=3.6" files = [ @@ -562,14 +583,19 @@ files = [ name = "docutils" version = "0.20.1" description = "Docutils -- Python Documentation Utilities" +category = "main" optional = false -python-versions = "*" -files = [] +python-versions = ">=3.7" +files = [ + {file = "docutils-0.20.1-py3-none-any.whl", hash = "sha256:96f387a2c5562db4476f09f13bbab2192e764cac08ebbf3a34a95d9b1e4a59d6"}, + {file = "docutils-0.20.1.tar.gz", hash = "sha256:f08a4e276c3a1583a86dce3e34aba3fe04d02bba2dd51ed16106244e8a923e3b"}, +] [[package]] name = "exceptiongroup" version = "1.2.2" description = "Backport of PEP 654 (exception groups)" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -584,6 +610,7 @@ test = ["pytest (>=6)"] name = "fastapi" version = "0.114.0" description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production" +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -604,6 +631,7 @@ standard = ["email-validator (>=2.0.0)", "fastapi-cli[standard] (>=0.0.5)", "htt name = "filelock" version = "3.15.4" description = "A platform independent file lock." +category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -620,6 +648,7 @@ typing = ["typing-extensions (>=4.8)"] name = "greenlet" version = "3.0.3" description = "Lightweight in-process concurrent programming" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -691,6 +720,7 @@ test = ["objgraph", "psutil"] name = "gunicorn" version = "23.0.0" description = "WSGI HTTP Server for UNIX" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -712,6 +742,7 @@ tornado = ["tornado (>=0.2)"] name = "h11" version = "0.14.0" description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -723,6 +754,7 @@ files = [ name = "httpcore" version = "1.0.5" description = "A minimal low-level HTTP client." +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -737,13 +769,14 @@ h11 = ">=0.13,<0.15" [package.extras] asyncio = ["anyio (>=4.0,<5.0)"] http2 = ["h2 (>=3,<5)"] -socks = ["socksio (==1.*)"] +socks = ["socksio (>=1.0.0,<2.0.0)"] trio = ["trio (>=0.22.0,<0.26.0)"] [[package]] name = "httpx" version = "0.27.2" description = "The next generation HTTP client." +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -754,21 +787,22 @@ files = [ [package.dependencies] anyio = "*" certifi = "*" -httpcore = "==1.*" +httpcore = ">=1.0.0,<2.0.0" idna = "*" sniffio = "*" [package.extras] brotli = ["brotli", "brotlicffi"] -cli = ["click (==8.*)", "pygments (==2.*)", "rich (>=10,<14)"] +cli = ["click (>=8.0.0,<9.0.0)", "pygments (>=2.0.0,<3.0.0)", "rich (>=10,<14)"] http2 = ["h2 (>=3,<5)"] -socks = ["socksio (==1.*)"] +socks = ["socksio (>=1.0.0,<2.0.0)"] zstd = ["zstandard (>=0.18.0)"] [[package]] name = "identify" version = "2.6.0" description = "File identification library for Python" +category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -783,6 +817,7 @@ license = ["ukkonen"] name = "idna" version = "3.8" description = "Internationalized Domain Names in Applications (IDNA)" +category = "main" optional = false python-versions = ">=3.6" files = [ @@ -794,6 +829,7 @@ files = [ name = "importlib-metadata" version = "8.4.0" description = "Read metadata from Python packages" +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -813,6 +849,7 @@ test = ["flufl.flake8", "importlib-resources (>=1.3)", "jaraco.test (>=5.4)", "p name = "importlib-resources" version = "6.4.4" description = "Read resources from Python packages" +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -835,6 +872,7 @@ type = ["pytest-mypy"] name = "iniconfig" version = "2.0.0" description = "brain-dead simple config-ini parsing" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -846,6 +884,7 @@ files = [ name = "jaraco-classes" version = "3.4.0" description = "Utility functions for Python class constructs" +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -864,6 +903,7 @@ testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-ena name = "jaraco-context" version = "6.0.1" description = "Useful decorators and context managers" +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -882,6 +922,7 @@ test = ["portend", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-c name = "jaraco-functools" version = "4.0.2" description = "Functools like those found in stdlib" +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -900,6 +941,7 @@ test = ["jaraco.classes", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "p name = "jeepney" version = "0.8.0" description = "Low-level, pure Python DBus protocol wrapper." +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -915,6 +957,7 @@ trio = ["async_generator", "trio"] name = "jinja2" version = "3.1.4" description = "A very fast and expressive template engine." +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -932,6 +975,7 @@ i18n = ["Babel (>=2.7)"] name = "keyring" version = "25.3.0" description = "Store and access your passwords safely." +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -958,6 +1002,7 @@ test = ["pyfakefs", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest- name = "lazy-loader" version = "0.4" description = "Makes it easy to load subpackages and functions on demand." +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -977,6 +1022,7 @@ test = ["pytest (>=7.4)", "pytest-cov (>=4.1)"] name = "mako" version = "1.3.5" description = "A super-fast templating language that borrows the best ideas from the existing templating languages." +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -996,6 +1042,7 @@ testing = ["pytest"] name = "markdown-it-py" version = "3.0.0" description = "Python port of markdown-it. Markdown parsing, done right!" +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -1020,6 +1067,7 @@ testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] name = "markupsafe" version = "2.1.5" description = "Safely add untrusted strings to HTML/XML markup." +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1089,6 +1137,7 @@ files = [ name = "mdurl" version = "0.1.2" description = "Markdown URL utilities" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1100,6 +1149,7 @@ files = [ name = "more-itertools" version = "10.5.0" description = "More routines for operating on iterables, beyond itertools" +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -1111,6 +1161,7 @@ files = [ name = "nh3" version = "0.2.18" description = "Python bindings to the ammonia HTML sanitization library." +category = "main" optional = false python-versions = "*" files = [ @@ -1136,6 +1187,7 @@ files = [ name = "nodeenv" version = "1.9.1" description = "Node.js virtual environment builder" +category = "dev" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" files = [ @@ -1147,6 +1199,7 @@ files = [ name = "numpy" version = "1.24.4" description = "Fundamental package for array computing in Python" +category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -1184,6 +1237,7 @@ files = [ name = "numpy" version = "2.0.2" description = "Fundamental package for array computing in Python" +category = "dev" optional = false python-versions = ">=3.9" files = [ @@ -1238,6 +1292,7 @@ files = [ name = "numpy" version = "2.1.1" description = "Fundamental package for array computing in Python" +category = "dev" optional = false python-versions = ">=3.10" files = [ @@ -1300,6 +1355,7 @@ files = [ name = "outcome" version = "1.3.0.post0" description = "Capture the outcome of Python function calls." +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1314,6 +1370,7 @@ attrs = ">=19.2.0" name = "packaging" version = "24.1" description = "Core utilities for Python packages" +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -1325,6 +1382,7 @@ files = [ name = "pandas" version = "1.5.3" description = "Powerful data structures for data analysis, time series, and statistics" +category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -1369,6 +1427,7 @@ test = ["hypothesis (>=5.5.3)", "pytest (>=6.0)", "pytest-xdist (>=1.31)"] name = "pandas" version = "2.2.2" description = "Powerful data structures for data analysis, time series, and statistics" +category = "dev" optional = false python-versions = ">=3.9" files = [ @@ -1442,6 +1501,7 @@ xml = ["lxml (>=4.9.2)"] name = "pillow" version = "10.4.0" description = "Python Imaging Library (Fork)" +category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -1539,6 +1599,7 @@ xmp = ["defusedxml"] name = "pip" version = "24.2" description = "The PyPA recommended tool for installing Python packages." +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -1550,6 +1611,7 @@ files = [ name = "pipdeptree" version = "2.16.2" description = "Command line utility to show dependency tree of packages." +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -1568,6 +1630,7 @@ test = ["covdefaults (>=2.3)", "diff-cover (>=8.0.1)", "pytest (>=7.4.3)", "pyte name = "pkginfo" version = "1.10.0" description = "Query metadata from sdists / bdists / installed packages." +category = "main" optional = false python-versions = ">=3.6" files = [ @@ -1582,6 +1645,7 @@ testing = ["pytest", "pytest-cov", "wheel"] name = "platformdirs" version = "4.2.2" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -1598,6 +1662,7 @@ type = ["mypy (>=1.8)"] name = "plotly" version = "5.24.0" description = "An open-source, interactive data visualization library for Python" +category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -1613,6 +1678,7 @@ tenacity = ">=6.2.0" name = "pluggy" version = "1.5.0" description = "plugin and hook calling mechanisms for python" +category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -1628,6 +1694,7 @@ testing = ["pytest", "pytest-benchmark"] name = "pre-commit" version = "3.5.0" description = "A framework for managing and maintaining multi-language pre-commit hooks." +category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -1646,6 +1713,7 @@ virtualenv = ">=20.10.0" name = "psutil" version = "6.0.0" description = "Cross-platform lib for process and system monitoring in Python." +category = "main" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" files = [ @@ -1675,6 +1743,7 @@ test = ["enum34", "ipaddress", "mock", "pywin32", "wmi"] name = "py-cpuinfo" version = "9.0.0" description = "Get CPU info with pure Python" +category = "dev" optional = false python-versions = "*" files = [ @@ -1686,6 +1755,7 @@ files = [ name = "pycparser" version = "2.22" description = "C parser in Python" +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -1697,6 +1767,7 @@ files = [ name = "pydantic" version = "2.9.0" description = "Data validation using Python type hints" +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -1720,6 +1791,7 @@ email = ["email-validator (>=2.0.0)"] name = "pydantic-core" version = "2.23.2" description = "Core functionality for Pydantic validation and serialization" +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -1821,6 +1893,7 @@ typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" name = "pygments" version = "2.18.0" description = "Pygments is a syntax highlighting package written in Python." +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -1835,6 +1908,7 @@ windows-terminal = ["colorama (>=0.4.6)"] name = "pyproject-hooks" version = "1.1.0" description = "Wrappers to call pyproject.toml-based build backend hooks." +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1846,6 +1920,7 @@ files = [ name = "pyright" version = "1.1.334" description = "Command line wrapper for pyright" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1864,6 +1939,7 @@ dev = ["twine (>=3.4.1)"] name = "pysocks" version = "1.7.1" description = "A Python SOCKS client module. See https://github.com/Anorov/PySocks for more information." +category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ @@ -1876,6 +1952,7 @@ files = [ name = "pytest" version = "7.4.4" description = "pytest: simple powerful testing with Python" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1898,6 +1975,7 @@ testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "no name = "pytest-asyncio" version = "0.21.2" description = "Pytest support for asyncio" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1916,6 +1994,7 @@ testing = ["coverage (>=6.2)", "flaky (>=3.5.0)", "hypothesis (>=5.7.1)", "mypy name = "pytest-benchmark" version = "4.0.0" description = "A ``pytest`` fixture for benchmarking code. It will group the tests into rounds that are calibrated to the chosen timer." +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1936,6 +2015,7 @@ histogram = ["pygal", "pygaljs"] name = "pytest-cov" version = "4.1.0" description = "Pytest plugin for measuring coverage." +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1954,6 +2034,7 @@ testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtuale name = "pytest-mock" version = "3.14.0" description = "Thin-wrapper around the mock package for easier use with pytest" +category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -1971,6 +2052,7 @@ dev = ["pre-commit", "pytest-asyncio", "tox"] name = "python-dateutil" version = "2.9.0.post0" description = "Extensions to the standard Python datetime module" +category = "main" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" files = [ @@ -1985,6 +2067,7 @@ six = ">=1.5" name = "python-engineio" version = "4.9.1" description = "Engine.IO server and client for Python" +category = "main" optional = false python-versions = ">=3.6" files = [ @@ -2004,6 +2087,7 @@ docs = ["sphinx"] name = "python-multipart" version = "0.0.9" description = "A streaming multipart parser for Python" +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -2018,6 +2102,7 @@ dev = ["atomicwrites (==1.4.1)", "attrs (==23.2.0)", "coverage (==7.4.1)", "hatc name = "python-socketio" version = "5.11.4" description = "Socket.IO server and client for Python" +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -2038,6 +2123,7 @@ docs = ["sphinx"] name = "pytz" version = "2024.1" description = "World timezone definitions, modern and historical" +category = "dev" optional = false python-versions = "*" files = [ @@ -2049,6 +2135,7 @@ files = [ name = "pywin32-ctypes" version = "0.2.3" description = "A (partial) reimplementation of pywin32 using ctypes/cffi" +category = "main" optional = false python-versions = ">=3.6" files = [ @@ -2060,6 +2147,7 @@ files = [ name = "pyyaml" version = "6.0.2" description = "YAML parser and emitter for Python" +category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -2122,6 +2210,7 @@ files = [ name = "readme-renderer" version = "43.0" description = "readme_renderer is a library for rendering readme descriptions for Warehouse" +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -2141,6 +2230,7 @@ md = ["cmarkgfm (>=0.8.0)"] name = "redis" version = "5.0.8" description = "Python client for Redis database and key-value store" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -2157,13 +2247,14 @@ ocsp = ["cryptography (>=36.0.1)", "pyopenssl (==20.0.1)", "requests (>=2.26.0)" [[package]] name = "reflex-chakra" -version = "0.6.0a5" +version = "0.6.0a6" description = "reflex using chakra components" +category = "main" optional = false python-versions = "<4.0,>=3.8" files = [ - {file = "reflex_chakra-0.6.0a5-py3-none-any.whl", hash = "sha256:9d502ddf3bd606baef4f0ff6453dcefb5e19dab4cfaf7c23df069fab70687a63"}, - {file = "reflex_chakra-0.6.0a5.tar.gz", hash = "sha256:b44e73478462052f09c9677d3fd7726891b4af87711b4b6a2171f010049308c9"}, + {file = "reflex_chakra-0.6.0a6-py3-none-any.whl", hash = "sha256:a526f5db8a457c68c24317d007172e77b323a34abf03f05eeb8d92c014563bfb"}, + {file = "reflex_chakra-0.6.0a6.tar.gz", hash = "sha256:124a780ea96d36d31f46b4c97bb0c0f111f2fb6e0d66a142e1577dad740f6e35"}, ] [package.dependencies] @@ -2173,6 +2264,7 @@ reflex = ">=0.6.0a" name = "reflex-hosting-cli" version = "0.1.13" description = "Reflex Hosting CLI" +category = "main" optional = false python-versions = "<4.0,>=3.8" files = [ @@ -2196,6 +2288,7 @@ websockets = ">=10.4" name = "requests" version = "2.32.3" description = "Python HTTP for Humans." +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -2217,6 +2310,7 @@ use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] name = "requests-toolbelt" version = "1.0.0" description = "A utility belt for advanced users of python-requests" +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ @@ -2231,6 +2325,7 @@ requests = ">=2.0.1,<3.0.0" name = "rfc3986" version = "2.0.0" description = "Validating URI References per RFC 3986" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -2245,6 +2340,7 @@ idna2008 = ["idna"] name = "rich" version = "13.8.0" description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" +category = "main" optional = false python-versions = ">=3.7.0" files = [ @@ -2264,6 +2360,7 @@ jupyter = ["ipywidgets (>=7.5.1,<9)"] name = "ruff" version = "0.4.10" description = "An extremely fast Python linter and code formatter, written in Rust." +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -2290,6 +2387,7 @@ files = [ name = "secretstorage" version = "3.3.3" description = "Python bindings to FreeDesktop.org Secret Service API" +category = "main" optional = false python-versions = ">=3.6" files = [ @@ -2305,6 +2403,7 @@ jeepney = ">=0.6" name = "selenium" version = "4.24.0" description = "Official Python bindings for Selenium WebDriver" +category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -2324,6 +2423,7 @@ websocket-client = ">=1.8,<2.0" name = "setuptools" version = "70.1.1" description = "Easily download, build, install, upgrade, and uninstall Python packages" +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -2339,6 +2439,7 @@ testing = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "importlib-metad name = "shellingham" version = "1.5.4" description = "Tool to Detect Surrounding Shell" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -2350,6 +2451,7 @@ files = [ name = "simple-websocket" version = "1.0.0" description = "Simple WebSocket server and client for Python" +category = "main" optional = false python-versions = ">=3.6" files = [ @@ -2367,6 +2469,7 @@ docs = ["sphinx"] name = "six" version = "1.16.0" description = "Python 2 and 3 compatibility utilities" +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" files = [ @@ -2378,6 +2481,7 @@ files = [ name = "sniffio" version = "1.3.1" description = "Sniff out which async library your code is running under" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -2389,6 +2493,7 @@ files = [ name = "sortedcontainers" version = "2.4.0" description = "Sorted Containers -- Sorted List, Sorted Dict, Sorted Set" +category = "dev" optional = false python-versions = "*" files = [ @@ -2400,6 +2505,7 @@ files = [ name = "sqlalchemy" version = "2.0.34" description = "Database Abstraction Library" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -2455,7 +2561,7 @@ files = [ ] [package.dependencies] -greenlet = {version = "!=0.4.17", markers = "python_version < \"3.13\" and (platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"win32\" or platform_machine == \"WIN32\")"} +greenlet = {version = "!=0.4.17", markers = "python_version < \"3.13\" and platform_machine == \"aarch64\" or python_version < \"3.13\" and platform_machine == \"ppc64le\" or python_version < \"3.13\" and platform_machine == \"x86_64\" or python_version < \"3.13\" and platform_machine == \"amd64\" or python_version < \"3.13\" and platform_machine == \"AMD64\" or python_version < \"3.13\" and platform_machine == \"win32\" or python_version < \"3.13\" and platform_machine == \"WIN32\""} typing-extensions = ">=4.6.0" [package.extras] @@ -2487,6 +2593,7 @@ sqlcipher = ["sqlcipher3_binary"] name = "sqlmodel" version = "0.0.22" description = "SQLModel, SQL databases in Python, designed for simplicity, compatibility, and robustness." +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -2502,6 +2609,7 @@ SQLAlchemy = ">=2.0.14,<2.1.0" name = "starlette" version = "0.38.4" description = "The little ASGI library that shines." +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -2520,6 +2628,7 @@ full = ["httpx (>=0.22.0)", "itsdangerous", "jinja2", "python-multipart (>=0.0.7 name = "starlette-admin" version = "0.14.1" description = "Fast, beautiful and extensible administrative interface framework for Starlette/FastApi applications" +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -2543,6 +2652,7 @@ test = ["aiomysql (>=0.1.1,<0.3.0)", "aiosqlite (>=0.17.0,<0.21.0)", "arrow (>=1 name = "tabulate" version = "0.9.0" description = "Pretty-print tabular data" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -2557,6 +2667,7 @@ widechars = ["wcwidth"] name = "tenacity" version = "9.0.0" description = "Retry code until it succeeds" +category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -2572,6 +2683,7 @@ test = ["pytest", "tornado (>=4.5)", "typeguard"] name = "toml" version = "0.10.2" description = "Python Library for Tom's Obvious, Minimal Language" +category = "dev" optional = false python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" files = [ @@ -2583,6 +2695,7 @@ files = [ name = "tomli" version = "2.0.1" description = "A lil' TOML parser" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -2594,6 +2707,7 @@ files = [ name = "tomlkit" version = "0.13.2" description = "Style preserving TOML library" +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -2605,6 +2719,7 @@ files = [ name = "trio" version = "0.26.2" description = "A friendly Python library for async concurrency and I/O" +category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -2625,6 +2740,7 @@ sortedcontainers = "*" name = "trio-websocket" version = "0.11.1" description = "WebSocket library for Trio" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -2641,6 +2757,7 @@ wsproto = ">=0.14" name = "twine" version = "5.1.1" description = "Collection of utilities for publishing packages on PyPI" +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -2663,6 +2780,7 @@ urllib3 = ">=1.26.0" name = "typer" version = "0.12.5" description = "Typer, build great CLIs. Easy to code. Based on Python type hints." +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -2680,6 +2798,7 @@ typing-extensions = ">=3.7.4.3" name = "typing-extensions" version = "4.12.2" description = "Backported and Experimental Type Hints for Python 3.8+" +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -2691,6 +2810,7 @@ files = [ name = "tzdata" version = "2024.1" description = "Provider of IANA time zone data" +category = "main" optional = false python-versions = ">=2" files = [ @@ -2702,6 +2822,7 @@ files = [ name = "urllib3" version = "2.2.2" description = "HTTP library with thread-safe connection pooling, file post, and more." +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -2722,6 +2843,7 @@ zstd = ["zstandard (>=0.18.0)"] name = "uvicorn" version = "0.30.6" description = "The lightning-fast ASGI server." +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -2741,6 +2863,7 @@ standard = ["colorama (>=0.4)", "httptools (>=0.5.0)", "python-dotenv (>=0.13)", name = "virtualenv" version = "20.26.3" description = "Virtual Python Environment builder" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -2761,6 +2884,7 @@ test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess name = "websocket-client" version = "1.8.0" description = "WebSocket client for Python with low level API options" +category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -2777,6 +2901,7 @@ test = ["websockets"] name = "websockets" version = "13.0.1" description = "An implementation of the WebSocket Protocol (RFC 6455 & 7692)" +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -2872,6 +2997,7 @@ files = [ name = "wheel" version = "0.44.0" description = "A built-package format for Python" +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -2886,6 +3012,7 @@ test = ["pytest (>=6.0.0)", "setuptools (>=65)"] name = "wrapt" version = "1.16.0" description = "Module for decorators, wrappers and monkey patching." +category = "main" optional = false python-versions = ">=3.6" files = [ @@ -2965,6 +3092,7 @@ files = [ name = "wsproto" version = "1.2.0" description = "WebSockets state-machine based protocol implementation" +category = "main" optional = false python-versions = ">=3.7.0" files = [ @@ -2979,6 +3107,7 @@ h11 = ">=0.9.0,<1" name = "zipp" version = "3.20.1" description = "Backport of pathlib-compatible object wrapper for zip files" +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -2997,4 +3126,4 @@ type = ["pytest-mypy"] [metadata] lock-version = "2.0" python-versions = "^3.8" -content-hash = "0bd98b6baa48d41ec50bfa0903d89005129211f6e429d7b62504ee880f8ba523" +content-hash = "7a8990e432a404802c3ace9a81c3c8c33cdd60596f26cdc38e2de424cb1126dd" diff --git a/pyproject.toml b/pyproject.toml index c8598a479..9643b69e5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -59,7 +59,7 @@ httpx = ">=0.25.1,<1.0" twine = ">=4.0.0,<6.0" tomlkit = ">=0.12.4,<1.0" lazy_loader = ">=0.4" -reflex-chakra = ">=0.6.0a" +reflex-chakra = ">=0.6.0a6" [tool.poetry.group.dev.dependencies] pytest = ">=7.1.2,<8.0" diff --git a/reflex/.templates/jinja/web/pages/_app.js.jinja2 b/reflex/.templates/jinja/web/pages/_app.js.jinja2 index d4322b171..654f7a2a4 100644 --- a/reflex/.templates/jinja/web/pages/_app.js.jinja2 +++ b/reflex/.templates/jinja/web/pages/_app.js.jinja2 @@ -27,7 +27,7 @@ function AppWrap({children}) { export default function MyApp({ Component, pageProps }) { return ( - + diff --git a/reflex/.templates/web/components/reflex/chakra_color_mode_provider.js b/reflex/.templates/web/components/reflex/chakra_color_mode_provider.js deleted file mode 100644 index ad41d5134..000000000 --- a/reflex/.templates/web/components/reflex/chakra_color_mode_provider.js +++ /dev/null @@ -1,36 +0,0 @@ -import { useColorMode as chakraUseColorMode } from "@chakra-ui/react"; -import { useTheme } from "next-themes"; -import { useEffect, useState } from "react"; -import { ColorModeContext, defaultColorMode } from "/utils/context.js"; - -export default function ChakraColorModeProvider({ children }) { - const { theme, resolvedTheme, setTheme } = useTheme(); - const { colorMode, toggleColorMode } = chakraUseColorMode(); - const [resolvedColorMode, setResolvedColorMode] = useState(colorMode); - - useEffect(() => { - if (colorMode != resolvedTheme) { - toggleColorMode(); - } - setResolvedColorMode(resolvedTheme); - }, [theme, resolvedTheme]); - - const rawColorMode = colorMode; - const setColorMode = (mode) => { - const allowedModes = ["light", "dark", "system"]; - if (!allowedModes.includes(mode)) { - console.error( - `Invalid color mode "${mode}". Defaulting to "${defaultColorMode}".` - ); - mode = defaultColorMode; - } - setTheme(mode); - }; - return ( - - {children} - - ); -} diff --git a/reflex/__init__.pyi b/reflex/__init__.pyi index 22c7298fe..b11da5fb0 100644 --- a/reflex/__init__.pyi +++ b/reflex/__init__.pyi @@ -132,6 +132,7 @@ from .components.radix.themes.layout.container import container as container from .components.radix.themes.layout.flex import flex as flex from .components.radix.themes.layout.grid import grid as grid from .components.radix.themes.layout.list import list_item as list_item +from .components.radix.themes.layout.list import list_ns as list # noqa from .components.radix.themes.layout.list import ordered_list as ordered_list from .components.radix.themes.layout.list import unordered_list as unordered_list from .components.radix.themes.layout.section import section as section diff --git a/reflex/app.py b/reflex/app.py index 9e5c2541a..43edee983 100644 --- a/reflex/app.py +++ b/reflex/app.py @@ -9,6 +9,7 @@ import copy import functools import inspect import io +import json import multiprocessing import os import platform @@ -1096,6 +1097,7 @@ class App(MiddlewareMixin, LifespanMixin, Base): if delta: # When the state is modified reset dirty status and emit the delta to the frontend. state._clean() + print(dir(state.router)) await self.event_namespace.emit_update( update=StateUpdate(delta=delta), sid=state.router.session.session_id, @@ -1531,8 +1533,9 @@ class EventNamespace(AsyncNamespace): sid: The Socket.IO session id. data: The event data. """ + fields = json.loads(data) # Get the event. - event = Event.parse_raw(data) + event = Event(**{k: v for k, v in fields.items() if k != "handler"}) self.token_to_sid[event.token] = sid self.sid_to_token[sid] = event.token diff --git a/reflex/components/component.py b/reflex/components/component.py index e838c86d1..c2ca7c001 100644 --- a/reflex/components/component.py +++ b/reflex/components/component.py @@ -4,6 +4,7 @@ from __future__ import annotations import copy import typing +import warnings from abc import ABC, abstractmethod from functools import lru_cache, wraps from hashlib import md5 @@ -169,6 +170,8 @@ ComponentStyle = Dict[ ] ComponentChild = Union[types.PrimitiveType, Var, BaseComponent] +warnings.filterwarnings("ignore", message="fields may not start with an underscore") + class Component(BaseComponent, ABC): """A component with style, event trigger and other props.""" @@ -195,7 +198,7 @@ class Component(BaseComponent, ABC): class_name: Any = None # Special component props. - special_props: Set[ImmutableVar] = set() + special_props: List[ImmutableVar] = [] # Whether the component should take the focus once the page is loaded autofocus: bool = False @@ -655,7 +658,7 @@ class Component(BaseComponent, ABC): """ # Create the base tag. tag = Tag( - name=self.tag if not self.alias else self.alias, + name=(self.tag if not self.alias else self.alias) or "", special_props=self.special_props, ) @@ -2244,7 +2247,7 @@ class StatefulComponent(BaseComponent): Returns: The tag to render. """ - return dict(Tag(name=self.tag)) + return dict(Tag(name=self.tag or "")) def __str__(self) -> str: """Represent the component in React. diff --git a/reflex/components/core/upload.py b/reflex/components/core/upload.py index b6fe1024a..7501934d8 100644 --- a/reflex/components/core/upload.py +++ b/reflex/components/core/upload.py @@ -247,9 +247,9 @@ class Upload(MemoizationLeaf): } # The file input to use. upload = Input.create(type="file") - upload.special_props = { + upload.special_props = [ ImmutableVar(_var_name="{...getInputProps()}", _var_type=None) - } + ] # The dropzone to use. zone = Box.create( @@ -257,9 +257,9 @@ class Upload(MemoizationLeaf): *children, **{k: v for k, v in props.items() if k not in supported_props}, ) - zone.special_props = { + zone.special_props = [ ImmutableVar(_var_name="{...getRootProps()}", _var_type=None) - } + ] # Create the component. upload_props["id"] = props.get("id", DEFAULT_UPLOAD_ID) diff --git a/reflex/components/el/elements/metadata.py b/reflex/components/el/elements/metadata.py index fc8e1d9d6..df5e1e901 100644 --- a/reflex/components/el/elements/metadata.py +++ b/reflex/components/el/elements/metadata.py @@ -1,6 +1,6 @@ """Element classes. This is an auto-generated file. Do not edit. See ../generate.py.""" -from typing import Set, Union +from typing import List, Union from reflex.components.el.element import Element from reflex.ivars.base import ImmutableVar @@ -90,9 +90,9 @@ class StyleEl(Element): # noqa: E742 media: Var[Union[str, int, bool]] - special_props: Set[ImmutableVar] = { + special_props: List[ImmutableVar] = [ ImmutableVar.create_safe("suppressHydrationWarning") - } + ] base = Base.create diff --git a/reflex/components/markdown/markdown.py b/reflex/components/markdown/markdown.py index b44ca25dd..83f647f4c 100644 --- a/reflex/components/markdown/markdown.py +++ b/reflex/components/markdown/markdown.py @@ -195,17 +195,17 @@ class Markdown(Component): if tag not in self.component_map: raise ValueError(f"No markdown component found for tag: {tag}.") - special_props = {_PROPS_IN_TAG} + special_props = [_PROPS_IN_TAG] children = [_CHILDREN] # For certain tags, the props from the markdown renderer are not actually valid for the component. if tag in NO_PROPS_TAGS: - special_props = set() + special_props = [] # If the children are set as a prop, don't pass them as children. children_prop = props.pop("children", None) if children_prop is not None: - special_props.add( + special_props.append( ImmutableVar.create_safe(f"children={{{str(children_prop)}}}") ) children = [] diff --git a/reflex/components/moment/moment.py b/reflex/components/moment/moment.py index 958ba6c57..54411f870 100644 --- a/reflex/components/moment/moment.py +++ b/reflex/components/moment/moment.py @@ -1,26 +1,27 @@ """Moment component for humanized date rendering.""" +import dataclasses from typing import List, Optional -from reflex.base import Base from reflex.components.component import Component, NoSSRComponent from reflex.event import EventHandler from reflex.utils.imports import ImportDict from reflex.vars import Var -class MomentDelta(Base): +@dataclasses.dataclass(frozen=True) +class MomentDelta: """A delta used for add/subtract prop in Moment.""" - years: Optional[int] - quarters: Optional[int] - months: Optional[int] - weeks: Optional[int] - days: Optional[int] - hours: Optional[int] - minutess: Optional[int] - seconds: Optional[int] - milliseconds: Optional[int] + years: Optional[int] = dataclasses.field(default=None) + quarters: Optional[int] = dataclasses.field(default=None) + months: Optional[int] = dataclasses.field(default=None) + weeks: Optional[int] = dataclasses.field(default=None) + days: Optional[int] = dataclasses.field(default=None) + hours: Optional[int] = dataclasses.field(default=None) + minutess: Optional[int] = dataclasses.field(default=None) + seconds: Optional[int] = dataclasses.field(default=None) + milliseconds: Optional[int] = dataclasses.field(default=None) class Moment(NoSSRComponent): diff --git a/reflex/components/moment/moment.pyi b/reflex/components/moment/moment.pyi index 168a239d7..2a19bcd01 100644 --- a/reflex/components/moment/moment.pyi +++ b/reflex/components/moment/moment.pyi @@ -3,9 +3,9 @@ # ------------------- DO NOT EDIT ---------------------- # This file was generated by `reflex/utils/pyi_generator.py`! # ------------------------------------------------------ +import dataclasses from typing import Any, Callable, Dict, Optional, Union, overload -from reflex.base import Base from reflex.components.component import NoSSRComponent from reflex.event import EventHandler, EventSpec from reflex.ivars.base import ImmutableVar @@ -13,7 +13,8 @@ from reflex.style import Style from reflex.utils.imports import ImportDict from reflex.vars import Var -class MomentDelta(Base): +@dataclasses.dataclass(frozen=True) +class MomentDelta: years: Optional[int] quarters: Optional[int] months: Optional[int] diff --git a/reflex/components/plotly/plotly.py b/reflex/components/plotly/plotly.py index ed7040d1c..c226e6e95 100644 --- a/reflex/components/plotly/plotly.py +++ b/reflex/components/plotly/plotly.py @@ -267,7 +267,7 @@ const extractPoints = (points) => { template_dict = LiteralVar.create({"layout": {"template": self.template}}) merge_dicts.append(template_dict.without_data()) if merge_dicts: - tag.special_props.add( + tag.special_props.append( # Merge all dictionaries and spread the result over props. ImmutableVar.create_safe( f"{{...mergician({str(figure)}," @@ -276,5 +276,5 @@ const extractPoints = (points) => { ) else: # Spread the figure dict over props, nothing to merge. - tag.special_props.add(ImmutableVar.create_safe(f"{{...{str(figure)}}}")) + tag.special_props.append(ImmutableVar.create_safe(f"{{...{str(figure)}}}")) return tag diff --git a/reflex/components/props.py b/reflex/components/props.py index 2966e9727..8736eca72 100644 --- a/reflex/components/props.py +++ b/reflex/components/props.py @@ -23,3 +23,20 @@ class PropsBase(Base): return LiteralObjectVar.create( {format.to_camel_case(key): value for key, value in self.dict().items()} ).json() + + def dict(self, *args, **kwargs): + """Convert the object to a dictionary. + + Keys will be converted to camelCase. + + Args: + *args: Arguments to pass to the parent class. + **kwargs: Keyword arguments to pass to the parent class. + + Returns: + The object as a dictionary. + """ + return { + format.to_camel_case(key): value + for key, value in super().dict(*args, **kwargs).items() + } diff --git a/reflex/components/radix/__init__.pyi b/reflex/components/radix/__init__.pyi index 8ba6c242b..f4e81666a 100644 --- a/reflex/components/radix/__init__.pyi +++ b/reflex/components/radix/__init__.pyi @@ -55,6 +55,7 @@ from .themes.layout.container import container as container from .themes.layout.flex import flex as flex from .themes.layout.grid import grid as grid from .themes.layout.list import list_item as list_item +from .themes.layout.list import list_ns as list # noqa from .themes.layout.list import ordered_list as ordered_list from .themes.layout.list import unordered_list as unordered_list from .themes.layout.section import section as section diff --git a/reflex/components/radix/themes/base.py b/reflex/components/radix/themes/base.py index 5a7fb2d99..82bb3c036 100644 --- a/reflex/components/radix/themes/base.py +++ b/reflex/components/radix/themes/base.py @@ -262,26 +262,6 @@ class ThemePanel(RadixThemesComponent): """ return {"react": "useEffect"} - def add_hooks(self) -> list[str]: - """Add a hook on the ThemePanel to clear chakra-ui-color-mode. - - Returns: - The hooks to render. - """ - # The panel freezes the tab if the user color preference differs from the - # theme "appearance", so clear it out when theme panel is used. - return [ - """ - useEffect(() => { - if (typeof window !== 'undefined') { - window.onbeforeunload = () => { - localStorage.removeItem('chakra-ui-color-mode'); - } - window.onbeforeunload(); - } - }, [])""" - ] - class RadixThemesColorModeProvider(Component): """Next-themes integration for radix themes components.""" diff --git a/reflex/components/radix/themes/base.pyi b/reflex/components/radix/themes/base.pyi index c79557024..fd0b7e092 100644 --- a/reflex/components/radix/themes/base.pyi +++ b/reflex/components/radix/themes/base.pyi @@ -583,7 +583,6 @@ class Theme(RadixThemesComponent): class ThemePanel(RadixThemesComponent): def add_imports(self) -> dict[str, str]: ... - def add_hooks(self) -> list[str]: ... @overload @classmethod def create( # type: ignore diff --git a/reflex/components/radix/themes/layout/__init__.pyi b/reflex/components/radix/themes/layout/__init__.pyi index 1420299e0..6712a3068 100644 --- a/reflex/components/radix/themes/layout/__init__.pyi +++ b/reflex/components/radix/themes/layout/__init__.pyi @@ -9,6 +9,7 @@ from .container import container as container from .flex import flex as flex from .grid import grid as grid from .list import list_item as list_item +from .list import list_ns as list # noqa from .list import ordered_list as ordered_list from .list import unordered_list as unordered_list from .section import section as section diff --git a/reflex/components/sonner/toast.py b/reflex/components/sonner/toast.py index c58b4a106..c93b07044 100644 --- a/reflex/components/sonner/toast.py +++ b/reflex/components/sonner/toast.py @@ -171,12 +171,12 @@ class ToastProps(PropsBase): d["cancel"] = self.cancel if isinstance(self.cancel, dict): d["cancel"] = ToastAction(**self.cancel) - if "on_dismiss" in d: - d["on_dismiss"] = format.format_queue_events( + if "onDismiss" in d: + d["onDismiss"] = format.format_queue_events( self.on_dismiss, _toast_callback_signature ) - if "on_auto_close" in d: - d["on_auto_close"] = format.format_queue_events( + if "onAutoClose" in d: + d["onAutoClose"] = format.format_queue_events( self.on_auto_close, _toast_callback_signature ) return d diff --git a/reflex/components/tags/cond_tag.py b/reflex/components/tags/cond_tag.py index 3143890c4..7bdf9a3c7 100644 --- a/reflex/components/tags/cond_tag.py +++ b/reflex/components/tags/cond_tag.py @@ -1,19 +1,22 @@ """Tag to conditionally render components.""" +import dataclasses from typing import Any, Dict, Optional from reflex.components.tags.tag import Tag +from reflex.ivars.base import LiteralVar from reflex.vars import Var +@dataclasses.dataclass() class CondTag(Tag): """A conditional tag.""" # The condition to determine which component to render. - cond: Var[Any] + cond: Var[Any] = dataclasses.field(default_factory=lambda: LiteralVar.create(True)) # The code to render if the condition is true. - true_value: Dict + true_value: Dict = dataclasses.field(default_factory=dict) # The code to render if the condition is false. - false_value: Optional[Dict] + false_value: Optional[Dict] = None diff --git a/reflex/components/tags/iter_tag.py b/reflex/components/tags/iter_tag.py index ee7a63628..ff6925f56 100644 --- a/reflex/components/tags/iter_tag.py +++ b/reflex/components/tags/iter_tag.py @@ -2,31 +2,36 @@ from __future__ import annotations +import dataclasses import inspect from typing import TYPE_CHECKING, Any, Callable, List, Tuple, Type, Union, get_args from reflex.components.tags.tag import Tag from reflex.ivars.base import ImmutableVar -from reflex.vars import Var +from reflex.ivars.sequence import LiteralArrayVar +from reflex.vars import Var, get_unique_variable_name if TYPE_CHECKING: from reflex.components.component import Component +@dataclasses.dataclass() class IterTag(Tag): """An iterator tag.""" # The var to iterate over. - iterable: Var[List] + iterable: Var[List] = dataclasses.field( + default_factory=lambda: LiteralArrayVar.create([]) + ) # The component render function for each item in the iterable. - render_fn: Callable + render_fn: Callable = dataclasses.field(default_factory=lambda: lambda x: x) # The name of the arg var. - arg_var_name: str + arg_var_name: str = dataclasses.field(default_factory=get_unique_variable_name) # The name of the index var. - index_var_name: str + index_var_name: str = dataclasses.field(default_factory=get_unique_variable_name) def get_iterable_var_type(self) -> Type: """Get the type of the iterable var. diff --git a/reflex/components/tags/match_tag.py b/reflex/components/tags/match_tag.py index c2f6649d5..b67ed62cc 100644 --- a/reflex/components/tags/match_tag.py +++ b/reflex/components/tags/match_tag.py @@ -1,19 +1,22 @@ """Tag to conditionally match cases.""" +import dataclasses from typing import Any, List from reflex.components.tags.tag import Tag +from reflex.ivars.base import LiteralVar from reflex.vars import Var +@dataclasses.dataclass() class MatchTag(Tag): """A match tag.""" # The condition to determine which case to match. - cond: Var[Any] + cond: Var[Any] = dataclasses.field(default_factory=lambda: LiteralVar.create(True)) # The list of match cases to be matched. - match_cases: List[Any] + match_cases: List[Any] = dataclasses.field(default_factory=list) # The catchall case to match. - default: Any + default: Any = dataclasses.field(default=LiteralVar.create(None)) diff --git a/reflex/components/tags/tag.py b/reflex/components/tags/tag.py index 810da30f9..8c97d72c5 100644 --- a/reflex/components/tags/tag.py +++ b/reflex/components/tags/tag.py @@ -2,22 +2,23 @@ from __future__ import annotations -from typing import Any, Dict, List, Optional, Set, Tuple, Union +import dataclasses +from typing import Any, Dict, List, Optional, Tuple, Union -from reflex.base import Base from reflex.event import EventChain from reflex.ivars.base import ImmutableVar, LiteralVar from reflex.utils import format, types -class Tag(Base): +@dataclasses.dataclass() +class Tag: """A React tag.""" # The name of the tag. name: str = "" # The props of the tag. - props: Dict[str, Any] = {} + props: Dict[str, Any] = dataclasses.field(default_factory=dict) # The inner contents of the tag. contents: str = "" @@ -26,25 +27,18 @@ class Tag(Base): args: Optional[Tuple[str, ...]] = None # Special props that aren't key value pairs. - special_props: Set[ImmutableVar] = set() + special_props: List[ImmutableVar] = dataclasses.field(default_factory=list) # The children components. - children: List[Any] = [] + children: List[Any] = dataclasses.field(default_factory=list) - def __init__(self, *args, **kwargs): - """Initialize the tag. - - Args: - *args: Args to initialize the tag. - **kwargs: Kwargs to initialize the tag. - """ - # Convert any props to vars. - if "props" in kwargs: - kwargs["props"] = { - name: LiteralVar.create(value) - for name, value in kwargs["props"].items() - } - super().__init__(*args, **kwargs) + def __post_init__(self): + """Post initialize the tag.""" + object.__setattr__( + self, + "props", + {name: LiteralVar.create(value) for name, value in self.props.items()}, + ) def format_props(self) -> List: """Format the tag's props. @@ -54,6 +48,29 @@ class Tag(Base): """ return format.format_props(*self.special_props, **self.props) + def set(self, **kwargs: Any): + """Set the tag's fields. + + Args: + kwargs: The fields to set. + + Returns: + The tag with the fields + """ + for name, value in kwargs.items(): + setattr(self, name, value) + + return self + + def __iter__(self): + """Iterate over the tag's fields. + + Yields: + Tuple[str, Any]: The field name and value. + """ + for field in dataclasses.fields(self): + yield field.name, getattr(self, field.name) + def add_props(self, **kwargs: Optional[Any]) -> Tag: """Add props to the tag. diff --git a/reflex/constants/base.py b/reflex/constants/base.py index d5407902e..df64a1006 100644 --- a/reflex/constants/base.py +++ b/reflex/constants/base.py @@ -117,7 +117,9 @@ class Templates(SimpleNamespace): REFLEX_BUILD_POLL_URL = REFLEX_BUILD_BACKEND + "/api/init/{reflex_init_token}" # The URL to fetch the generation's reflex code - REFLEX_BUILD_CODE_URL = REFLEX_BUILD_BACKEND + "/api/gen/{generation_hash}" + REFLEX_BUILD_CODE_URL = ( + REFLEX_BUILD_BACKEND + "/api/gen/{generation_hash}/refactored" + ) class Dirs(SimpleNamespace): """Folders used by the template system of Reflex.""" diff --git a/reflex/event.py b/reflex/event.py index 8384f06a8..73fecfc03 100644 --- a/reflex/event.py +++ b/reflex/event.py @@ -2,6 +2,7 @@ from __future__ import annotations +import dataclasses import inspect import types import urllib.parse @@ -18,7 +19,6 @@ from typing import ( ) from reflex import constants -from reflex.base import Base from reflex.ivars.base import ImmutableVar, LiteralVar from reflex.ivars.function import FunctionStringVar, FunctionVar from reflex.ivars.object import ObjectVar @@ -33,7 +33,11 @@ except ImportError: from typing_extensions import Annotated -class Event(Base): +@dataclasses.dataclass( + init=True, + frozen=True, +) +class Event: """An event that describes any state change in the app.""" # The token to specify the client that the event is for. @@ -43,10 +47,10 @@ class Event(Base): name: str # The routing data where event occurred - router_data: Dict[str, Any] = {} + router_data: Dict[str, Any] = dataclasses.field(default_factory=dict) # The event payload. - payload: Dict[str, Any] = {} + payload: Dict[str, Any] = dataclasses.field(default_factory=dict) @property def substate_token(self) -> str: @@ -81,11 +85,15 @@ def background(fn): return fn -class EventActionsMixin(Base): +@dataclasses.dataclass( + init=True, + frozen=True, +) +class EventActionsMixin: """Mixin for DOM event actions.""" # Whether to `preventDefault` or `stopPropagation` on the event. - event_actions: Dict[str, Union[bool, int]] = {} + event_actions: Dict[str, Union[bool, int]] = dataclasses.field(default_factory=dict) @property def stop_propagation(self): @@ -94,8 +102,9 @@ class EventActionsMixin(Base): Returns: New EventHandler-like with stopPropagation set to True. """ - return self.copy( - update={"event_actions": {"stopPropagation": True, **self.event_actions}}, + return dataclasses.replace( + self, + event_actions={"stopPropagation": True, **self.event_actions}, ) @property @@ -105,8 +114,9 @@ class EventActionsMixin(Base): Returns: New EventHandler-like with preventDefault set to True. """ - return self.copy( - update={"event_actions": {"preventDefault": True, **self.event_actions}}, + return dataclasses.replace( + self, + event_actions={"preventDefault": True, **self.event_actions}, ) def throttle(self, limit_ms: int): @@ -118,8 +128,9 @@ class EventActionsMixin(Base): Returns: New EventHandler-like with throttle set to limit_ms. """ - return self.copy( - update={"event_actions": {"throttle": limit_ms, **self.event_actions}}, + return dataclasses.replace( + self, + event_actions={"throttle": limit_ms, **self.event_actions}, ) def debounce(self, delay_ms: int): @@ -131,26 +142,25 @@ class EventActionsMixin(Base): Returns: New EventHandler-like with debounce set to delay_ms. """ - return self.copy( - update={"event_actions": {"debounce": delay_ms, **self.event_actions}}, + return dataclasses.replace( + self, + event_actions={"debounce": delay_ms, **self.event_actions}, ) +@dataclasses.dataclass( + init=True, + frozen=True, +) class EventHandler(EventActionsMixin): """An event handler responds to an event to update the state.""" # The function to call in response to the event. - fn: Any + fn: Any = dataclasses.field(default=None) # The full name of the state class this event handler is attached to. # Empty string means this event handler is a server side event. - state_full_name: str = "" - - class Config: - """The Pydantic config.""" - - # Needed to allow serialization of Callable. - frozen = True + state_full_name: str = dataclasses.field(default="") @classmethod def __class_getitem__(cls, args_spec: str) -> Annotated: @@ -215,6 +225,10 @@ class EventHandler(EventActionsMixin): ) +@dataclasses.dataclass( + init=True, + frozen=True, +) class EventSpec(EventActionsMixin): """An event specification. @@ -223,19 +237,37 @@ class EventSpec(EventActionsMixin): """ # The event handler. - handler: EventHandler + handler: EventHandler = dataclasses.field(default=None) # type: ignore # The handler on the client to process event. - client_handler_name: str = "" + client_handler_name: str = dataclasses.field(default="") # The arguments to pass to the function. - args: Tuple[Tuple[ImmutableVar, ImmutableVar], ...] = () + args: Tuple[Tuple[ImmutableVar, ImmutableVar], ...] = dataclasses.field( + default_factory=tuple + ) - class Config: - """The Pydantic config.""" + def __init__( + self, + handler: EventHandler, + event_actions: Dict[str, Union[bool, int]] | None = None, + client_handler_name: str = "", + args: Tuple[Tuple[ImmutableVar, ImmutableVar], ...] = tuple(), + ): + """Initialize an EventSpec. - # Required to allow tuple fields. - frozen = True + 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 tuple()) def with_args( self, args: Tuple[Tuple[ImmutableVar, ImmutableVar], ...] @@ -286,6 +318,9 @@ class EventSpec(EventActionsMixin): 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. @@ -305,10 +340,13 @@ class CallableEventSpec(EventSpec): if fn is not None: default_event_spec = fn() super().__init__( - fn=fn, # type: ignore - **default_event_spec.dict(), + 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) @@ -332,12 +370,16 @@ class CallableEventSpec(EventSpec): 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: List[EventSpec] + events: List[EventSpec] = dataclasses.field(default_factory=list) - args_spec: Optional[Callable] + args_spec: Optional[Callable] = dataclasses.field(default=None) # These chains can be used for their side effects when no other events are desired. @@ -345,14 +387,22 @@ stop_propagation = EventChain(events=[], args_spec=lambda: []).stop_propagation prevent_default = EventChain(events=[], args_spec=lambda: []).prevent_default -class Target(Base): +@dataclasses.dataclass( + init=True, + frozen=True, +) +class Target: """A Javascript event target.""" checked: bool = False value: Any = None -class FrontendEvent(Base): +@dataclasses.dataclass( + init=True, + frozen=True, +) +class FrontendEvent: """A Javascript event.""" target: Target = Target() @@ -360,7 +410,11 @@ class FrontendEvent(Base): value: Any = None -class FileUpload(Base): +@dataclasses.dataclass( + init=True, + frozen=True, +) +class FileUpload: """Class to represent a file upload.""" upload_id: Optional[str] = None diff --git a/reflex/ivars/base.py b/reflex/ivars/base.py index 18beaad61..0c18a7fe3 100644 --- a/reflex/ivars/base.py +++ b/reflex/ivars/base.py @@ -421,6 +421,9 @@ class ImmutableVar(Var, Generic[VAR_TYPE]): if issubclass(output, (ObjectVar, Base)): return ToObjectOperation.create(self, var_type or dict) + if dataclasses.is_dataclass(output): + return ToObjectOperation.create(self, var_type or dict) + if issubclass(output, FunctionVar): # if fixed_type is not None and not issubclass(fixed_type, Callable): # raise TypeError( @@ -479,7 +482,11 @@ class ImmutableVar(Var, Generic[VAR_TYPE]): ): return self.to(NumberVar, self._var_type) - if all(inspect.isclass(t) and issubclass(t, Base) for t in inner_types): + if all( + inspect.isclass(t) + and (issubclass(t, Base) or dataclasses.is_dataclass(t)) + for t in inner_types + ): return self.to(ObjectVar, self._var_type) return self.to(ObjectVar, self._var_type) @@ -982,6 +989,16 @@ class LiteralVar(ImmutableVar): ) return LiteralVar.create(serialized_value, _var_data=_var_data) + if dataclasses.is_dataclass(value) and not isinstance(value, type): + return LiteralObjectVar.create( + { + k: (None if callable(v) else v) + for k, v in dataclasses.asdict(value).items() + }, + _var_type=type(value), + _var_data=_var_data, + ) + raise TypeError( f"Unsupported type {type(value)} for LiteralVar. Tried to create a LiteralVar from {value}." ) diff --git a/reflex/middleware/hydrate_middleware.py b/reflex/middleware/hydrate_middleware.py index b5694e22f..46b524cd7 100644 --- a/reflex/middleware/hydrate_middleware.py +++ b/reflex/middleware/hydrate_middleware.py @@ -2,6 +2,7 @@ from __future__ import annotations +import dataclasses from typing import TYPE_CHECKING, Optional from reflex import constants @@ -14,6 +15,7 @@ if TYPE_CHECKING: from reflex.app import App +@dataclasses.dataclass(init=True) class HydrateMiddleware(Middleware): """Middleware to handle initial app hydration.""" diff --git a/reflex/middleware/middleware.py b/reflex/middleware/middleware.py index 76cbcfe9a..ef9de0bde 100644 --- a/reflex/middleware/middleware.py +++ b/reflex/middleware/middleware.py @@ -2,10 +2,9 @@ from __future__ import annotations -from abc import ABC +from abc import ABC, abstractmethod from typing import TYPE_CHECKING, Optional -from reflex.base import Base from reflex.event import Event from reflex.state import BaseState, StateUpdate @@ -13,9 +12,10 @@ if TYPE_CHECKING: from reflex.app import App -class Middleware(Base, ABC): +class Middleware(ABC): """Middleware to preprocess and postprocess requests.""" + @abstractmethod async def preprocess( self, app: App, state: BaseState, event: Event ) -> Optional[StateUpdate]: diff --git a/reflex/state.py b/reflex/state.py index b815fd44f..dd69522fa 100644 --- a/reflex/state.py +++ b/reflex/state.py @@ -5,8 +5,10 @@ from __future__ import annotations import asyncio import contextlib import copy +import dataclasses import functools import inspect +import json import os import uuid from abc import ABC, abstractmethod @@ -83,13 +85,15 @@ var = immutable_computed_var TOO_LARGE_SERIALIZED_STATE = 100 * 1024 # 100kb -class HeaderData(Base): +@dataclasses.dataclass(frozen=True) +class HeaderData: """An object containing headers data.""" host: str = "" origin: str = "" upgrade: str = "" connection: str = "" + cookie: str = "" pragma: str = "" cache_control: str = "" user_agent: str = "" @@ -105,13 +109,16 @@ class HeaderData(Base): Args: router_data: the router_data dict. """ - super().__init__() if router_data: for k, v in router_data.get(constants.RouteVar.HEADERS, {}).items(): - setattr(self, format.to_snake_case(k), v) + object.__setattr__(self, format.to_snake_case(k), v) + else: + for k in dataclasses.fields(self): + object.__setattr__(self, k.name, "") -class PageData(Base): +@dataclasses.dataclass(frozen=True) +class PageData: """An object containing page data.""" host: str = "" # repeated with self.headers.origin (remove or keep the duplicate?) @@ -119,7 +126,7 @@ class PageData(Base): raw_path: str = "" full_path: str = "" full_raw_path: str = "" - params: dict = {} + params: dict = dataclasses.field(default_factory=dict) def __init__(self, router_data: Optional[dict] = None): """Initalize the PageData object based on router_data. @@ -127,17 +134,34 @@ class PageData(Base): Args: router_data: the router_data dict. """ - super().__init__() if router_data: - self.host = router_data.get(constants.RouteVar.HEADERS, {}).get("origin") - self.path = router_data.get(constants.RouteVar.PATH, "") - self.raw_path = router_data.get(constants.RouteVar.ORIGIN, "") - self.full_path = f"{self.host}{self.path}" - self.full_raw_path = f"{self.host}{self.raw_path}" - self.params = router_data.get(constants.RouteVar.QUERY, {}) + object.__setattr__( + self, + "host", + router_data.get(constants.RouteVar.HEADERS, {}).get("origin", ""), + ) + object.__setattr__( + self, "path", router_data.get(constants.RouteVar.PATH, "") + ) + object.__setattr__( + self, "raw_path", router_data.get(constants.RouteVar.ORIGIN, "") + ) + object.__setattr__(self, "full_path", f"{self.host}{self.path}") + object.__setattr__(self, "full_raw_path", f"{self.host}{self.raw_path}") + object.__setattr__( + self, "params", router_data.get(constants.RouteVar.QUERY, {}) + ) + else: + object.__setattr__(self, "host", "") + object.__setattr__(self, "path", "") + object.__setattr__(self, "raw_path", "") + object.__setattr__(self, "full_path", "") + object.__setattr__(self, "full_raw_path", "") + object.__setattr__(self, "params", {}) -class SessionData(Base): +@dataclasses.dataclass(frozen=True, init=False) +class SessionData: """An object containing session data.""" client_token: str = "" @@ -150,19 +174,24 @@ class SessionData(Base): Args: router_data: the router_data dict. """ - super().__init__() if router_data: - self.client_token = router_data.get(constants.RouteVar.CLIENT_TOKEN, "") - self.client_ip = router_data.get(constants.RouteVar.CLIENT_IP, "") - self.session_id = router_data.get(constants.RouteVar.SESSION_ID, "") + client_token = router_data.get(constants.RouteVar.CLIENT_TOKEN, "") + client_ip = router_data.get(constants.RouteVar.CLIENT_IP, "") + session_id = router_data.get(constants.RouteVar.SESSION_ID, "") + else: + client_token = client_ip = session_id = "" + object.__setattr__(self, "client_token", client_token) + object.__setattr__(self, "client_ip", client_ip) + object.__setattr__(self, "session_id", session_id) -class RouterData(Base): +@dataclasses.dataclass(frozen=True, init=False) +class RouterData: """An object containing RouterData.""" - session: SessionData = SessionData() - headers: HeaderData = HeaderData() - page: PageData = PageData() + session: SessionData = dataclasses.field(default_factory=SessionData) + headers: HeaderData = dataclasses.field(default_factory=HeaderData) + page: PageData = dataclasses.field(default_factory=PageData) def __init__(self, router_data: Optional[dict] = None): """Initialize the RouterData object. @@ -170,10 +199,30 @@ class RouterData(Base): Args: router_data: the router_data dict. """ - super().__init__() - self.session = SessionData(router_data) - self.headers = HeaderData(router_data) - self.page = PageData(router_data) + object.__setattr__(self, "session", SessionData(router_data)) + object.__setattr__(self, "headers", HeaderData(router_data)) + object.__setattr__(self, "page", PageData(router_data)) + + def toJson(self) -> str: + """Convert the object to a JSON string. + + Returns: + The JSON string. + """ + return json.dumps(dataclasses.asdict(self)) + + +@serializer +def serialize_routerdata(value: RouterData) -> str: + """Serialize a RouterData instance. + + Args: + value: The RouterData to serialize. + + Returns: + The serialized RouterData. + """ + return value.toJson() def _no_chain_background_task( @@ -249,10 +298,11 @@ def _split_substate_key(substate_key: str) -> tuple[str, str]: return token, state_name +@dataclasses.dataclass(frozen=True, init=False) class EventHandlerSetVar(EventHandler): """A special event handler to wrap setvar functionality.""" - state_cls: Type[BaseState] + state_cls: Type[BaseState] = dataclasses.field(init=False) def __init__(self, state_cls: Type[BaseState]): """Initialize the EventHandlerSetVar. @@ -263,8 +313,8 @@ class EventHandlerSetVar(EventHandler): super().__init__( fn=type(self).setvar, state_full_name=state_cls.get_full_name(), - state_cls=state_cls, # type: ignore ) + object.__setattr__(self, "state_cls", state_cls) def setvar(self, var_name: str, value: Any): """Set the state variable to the value of the event. @@ -1826,8 +1876,13 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow): self.dirty_vars.update(self._always_dirty_computed_vars) self._mark_dirty() + def dictify(value: Any): + if dataclasses.is_dataclass(value) and not isinstance(value, type): + return dataclasses.asdict(value) + return value + base_vars = { - prop_name: self.get_value(getattr(self, prop_name)) + prop_name: dictify(self.get_value(getattr(self, prop_name))) for prop_name in self.base_vars } if initial and include_computed: @@ -1907,9 +1962,6 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow): return state -EventHandlerSetVar.update_forward_refs() - - class State(BaseState): """The app Base State.""" @@ -2341,18 +2393,29 @@ class StateProxy(wrapt.ObjectProxy): self._self_mutable = original_mutable -class StateUpdate(Base): +@dataclasses.dataclass( + frozen=True, +) +class StateUpdate: """A state update sent to the frontend.""" # The state delta. - delta: Delta = {} + delta: Delta = dataclasses.field(default_factory=dict) # Events to be added to the event queue. - events: List[Event] = [] + events: List[Event] = dataclasses.field(default_factory=list) # Whether this is the final state update for the event. final: bool = True + def json(self) -> str: + """Convert the state update to a JSON string. + + Returns: + The state update as a JSON string. + """ + return json.dumps(dataclasses.asdict(self)) + class StateManager(Base, ABC): """A class to manage many client states.""" diff --git a/reflex/style.py b/reflex/style.py index b29160cb2..5e5503e66 100644 --- a/reflex/style.py +++ b/reflex/style.py @@ -290,7 +290,6 @@ def _format_emotion_style_pseudo_selector(key: str) -> str: """ prefix = None if key.startswith("_"): - # Handle pseudo selectors in chakra style format. prefix = "&:" key = key[1:] if key.startswith(":"): diff --git a/reflex/utils/exceptions.py b/reflex/utils/exceptions.py index dbab3fb6d..891ebe047 100644 --- a/reflex/utils/exceptions.py +++ b/reflex/utils/exceptions.py @@ -91,3 +91,7 @@ class EventFnArgMismatch(ReflexError, TypeError): class DynamicRouteArgShadowsStateVar(ReflexError, NameError): """Raised when a dynamic route arg shadows a state var.""" + + +class GeneratedCodeHasNoFunctionDefs(ReflexError): + """Raised when refactored code generated with flexgen has no functions defined.""" diff --git a/reflex/utils/format.py b/reflex/utils/format.py index 0e18a8495..ddfb6b05a 100644 --- a/reflex/utils/format.py +++ b/reflex/utils/format.py @@ -2,6 +2,7 @@ from __future__ import annotations +import dataclasses import inspect import json import os @@ -623,6 +624,14 @@ def format_state(value: Any, key: Optional[str] = None) -> Any: if isinstance(value, dict): return {k: format_state(v, k) for k, v in value.items()} + # Hand dataclasses. + if dataclasses.is_dataclass(value): + if isinstance(value, type): + raise TypeError( + f"Cannot format state of type {type(value)}. Please provide an instance of the dataclass." + ) + return {k: format_state(v, k) for k, v in dataclasses.asdict(value).items()} + # Handle lists, sets, typles. if isinstance(value, types.StateIterBases): return [format_state(v) for v in value] diff --git a/reflex/utils/imports.py b/reflex/utils/imports.py index d58c2bf3f..8f53ed07a 100644 --- a/reflex/utils/imports.py +++ b/reflex/utils/imports.py @@ -2,10 +2,9 @@ from __future__ import annotations +import dataclasses from collections import defaultdict -from typing import Dict, List, Optional, Tuple, Union - -from reflex.base import Base +from typing import DefaultDict, Dict, List, Optional, Tuple, Union def merge_imports( @@ -19,12 +18,22 @@ def merge_imports( Returns: The merged import dicts. """ - all_imports = defaultdict(list) + 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() ): - all_imports[lib].extend(fields) + 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 @@ -75,7 +84,8 @@ def collapse_imports( } -class ImportVar(Base): +@dataclasses.dataclass(order=True, frozen=True) +class ImportVar: """An import var.""" # The name of the import tag. @@ -111,73 +121,6 @@ class ImportVar(Base): else: return self.tag or "" - def __lt__(self, other: ImportVar) -> bool: - """Compare two ImportVar objects. - - Args: - other: The other ImportVar object to compare. - - Returns: - Whether this ImportVar object is less than the other. - """ - return ( - self.tag, - self.is_default, - self.alias, - self.install, - self.render, - self.transpile, - ) < ( - other.tag, - other.is_default, - other.alias, - other.install, - other.render, - other.transpile, - ) - - def __eq__(self, other: ImportVar) -> bool: - """Check if two ImportVar objects are equal. - - Args: - other: The other ImportVar object to compare. - - Returns: - Whether the two ImportVar objects are equal. - """ - return ( - self.tag, - self.is_default, - self.alias, - self.install, - self.render, - self.transpile, - ) == ( - other.tag, - other.is_default, - other.alias, - other.install, - other.render, - other.transpile, - ) - - def __hash__(self) -> int: - """Hash the ImportVar object. - - Returns: - The hash of the ImportVar object. - """ - return hash( - ( - self.tag, - self.is_default, - self.alias, - self.install, - self.render, - self.transpile, - ) - ) - ImportTypes = Union[str, ImportVar, List[Union[str, ImportVar]], List[ImportVar]] ImportDict = Dict[str, ImportTypes] diff --git a/reflex/utils/prerequisites.py b/reflex/utils/prerequisites.py index e061207f4..78139034b 100644 --- a/reflex/utils/prerequisites.py +++ b/reflex/utils/prerequisites.py @@ -2,6 +2,7 @@ from __future__ import annotations +import dataclasses import functools import glob import importlib @@ -15,7 +16,7 @@ import shutil import stat import sys import tempfile -import textwrap +import time import zipfile from datetime import datetime from fileinput import FileInput @@ -32,17 +33,18 @@ from redis import exceptions from redis.asyncio import Redis from reflex import constants, model -from reflex.base import Base from reflex.compiler import templates from reflex.config import Config, get_config from reflex.utils import console, net, path_ops, processes +from reflex.utils.exceptions import GeneratedCodeHasNoFunctionDefs from reflex.utils.format import format_library_name from reflex.utils.registry import _get_best_registry CURRENTLY_INSTALLING_NODE = False -class Template(Base): +@dataclasses.dataclass(frozen=True) +class Template: """A template for a Reflex app.""" name: str @@ -51,7 +53,8 @@ class Template(Base): demo_url: str -class CpuInfo(Base): +@dataclasses.dataclass(frozen=True) +class CpuInfo: """Model to save cpu info.""" manufacturer_id: Optional[str] @@ -1278,7 +1281,7 @@ def fetch_app_templates(version: str) -> dict[str, Template]: None, ) return { - tp["name"]: Template.parse_obj(tp) + tp["name"]: Template(**tp) for tp in templates_data if not tp["hidden"] and tp["code_url"] is not None } @@ -1435,19 +1438,37 @@ def initialize_main_module_index_from_generation(app_name: str, generation_hash: Args: app_name: The name of the app. generation_hash: The generation hash from reflex.build. + + Raises: + GeneratedCodeHasNoFunctionDefs: If the fetched code has no function definitions + (the refactored reflex code is expected to have at least one root function defined). """ # Download the reflex code for the generation. - resp = net.get( - constants.Templates.REFLEX_BUILD_CODE_URL.format( - generation_hash=generation_hash + url = constants.Templates.REFLEX_BUILD_CODE_URL.format( + generation_hash=generation_hash + ) + resp = net.get(url) + while resp.status_code == httpx.codes.SERVICE_UNAVAILABLE: + console.debug("Waiting for the code to be generated...") + time.sleep(1) + resp = net.get(url) + resp.raise_for_status() + + # Determine the name of the last function, which renders the generated code. + defined_funcs = re.findall(r"def ([a-zA-Z_]+)\(", resp.text) + if not defined_funcs: + raise GeneratedCodeHasNoFunctionDefs( + f"No function definitions found in generated code from {url!r}." ) - ).raise_for_status() + render_func_name = defined_funcs[-1] def replace_content(_match): return "\n".join( [ - "def index() -> rx.Component:", - textwrap.indent("return " + resp.text, " "), + resp.text, + "", + "" "def index() -> rx.Component:", + f" return {render_func_name}()", "", "", ], diff --git a/reflex/utils/pyi_generator.py b/reflex/utils/pyi_generator.py index cc60418a9..bd0d9f118 100644 --- a/reflex/utils/pyi_generator.py +++ b/reflex/utils/pyi_generator.py @@ -903,7 +903,13 @@ class PyiGenerator: # construct the import statement and handle special cases for aliases sub_mod_attrs_imports = [ f"from .{path} import {mod if not isinstance(mod, tuple) else mod[0]} as {mod if not isinstance(mod, tuple) else mod[1]}" - + (" # type: ignore" if mod in pyright_ignore_imports else "") + + ( + " # type: ignore" + if mod in pyright_ignore_imports + else " # noqa" # ignore ruff formatting here for cases like rx.list. + if isinstance(mod, tuple) + else "" + ) for mod, path in sub_mod_attrs.items() ] sub_mod_attrs_imports.append("") diff --git a/reflex/utils/telemetry.py b/reflex/utils/telemetry.py index e027ed81a..03e2b943b 100644 --- a/reflex/utils/telemetry.py +++ b/reflex/utils/telemetry.py @@ -3,6 +3,7 @@ from __future__ import annotations import asyncio +import dataclasses import multiprocessing import platform import warnings @@ -144,7 +145,7 @@ def _prepare_event(event: str, **kwargs) -> dict: "python_version": get_python_version(), "cpu_count": get_cpu_count(), "memory": get_memory(), - "cpu_info": dict(cpuinfo) if cpuinfo else {}, + "cpu_info": dataclasses.asdict(cpuinfo) if cpuinfo else {}, **additional_fields, }, "timestamp": stamp, diff --git a/reflex/utils/types.py b/reflex/utils/types.py index cd54ad59d..b65bb626b 100644 --- a/reflex/utils/types.py +++ b/reflex/utils/types.py @@ -3,6 +3,7 @@ from __future__ import annotations import contextlib +import dataclasses import inspect import sys import types @@ -486,7 +487,11 @@ def is_valid_var_type(type_: Type) -> bool: if is_union(type_): return all((is_valid_var_type(arg) for arg in get_args(type_))) - return _issubclass(type_, StateVar) or serializers.has_serializer(type_) + return ( + _issubclass(type_, StateVar) + or serializers.has_serializer(type_) + or dataclasses.is_dataclass(type_) + ) def is_backend_base_variable(name: str, cls: Type) -> bool: diff --git a/tests/components/datadisplay/test_table.py b/tests/components/datadisplay/test_table.py deleted file mode 100644 index 8740d4b8c..000000000 --- a/tests/components/datadisplay/test_table.py +++ /dev/null @@ -1,177 +0,0 @@ -import sys -from typing import List, Tuple - -import pytest -from reflex_chakra.components.datadisplay.table import Tbody, Tfoot, Thead - -from reflex.state import BaseState - -PYTHON_GT_V38 = sys.version_info.major >= 3 and sys.version_info.minor > 8 - - -class TableState(BaseState): - """Test State class.""" - - rows_List_List_str: List[List[str]] = [["random", "row"]] - rows_List_List: List[List] = [["random", "row"]] - rows_List_str: List[str] = ["random", "row"] - rows_Tuple_List_str: Tuple[List[str]] = (["random", "row"],) - rows_Tuple_List: Tuple[List] = ["random", "row"] # type: ignore - rows_Tuple_str_str: Tuple[str, str] = ( - "random", - "row", - ) - rows_Tuple_Tuple_str_str: Tuple[Tuple[str, str]] = ( - ( - "random", - "row", - ), - ) - rows_Tuple_Tuple: Tuple[Tuple] = ( - ( - "random", - "row", - ), - ) - rows_str: str = "random, row" - headers_List_str: List[str] = ["header1", "header2"] - headers_Tuple_str_str: Tuple[str, str] = ( - "header1", - "header2", - ) - headers_str: str = "headers1, headers2" - footers_List_str: List[str] = ["footer1", "footer2"] - footers_Tuple_str_str: Tuple[str, str] = ( - "footer1", - "footer2", - ) - footers_str: str = "footer1, footer2" - - if sys.version_info.major >= 3 and sys.version_info.minor > 8: - rows_list_list_str: list[list[str]] = [["random", "row"]] - rows_list_list: list[list] = [["random", "row"]] - rows_list_str: list[str] = ["random", "row"] - rows_tuple_list_str: tuple[list[str]] = (["random", "row"],) - rows_tuple_list: tuple[list] = ["random", "row"] # type: ignore - rows_tuple_str_str: tuple[str, str] = ( - "random", - "row", - ) - rows_tuple_tuple_str_str: tuple[tuple[str, str]] = ( - ( - "random", - "row", - ), - ) - rows_tuple_tuple: tuple[tuple] = ( - ( - "random", - "row", - ), - ) - - -valid_extras = ( - [ - TableState.rows_list_list_str, - TableState.rows_list_list, - TableState.rows_tuple_list_str, - TableState.rows_tuple_list, - TableState.rows_tuple_tuple_str_str, - TableState.rows_tuple_tuple, - ] - if PYTHON_GT_V38 - else [] -) -invalid_extras = ( - [TableState.rows_list_str, TableState.rows_tuple_str_str] if PYTHON_GT_V38 else [] -) - - -@pytest.mark.parametrize( - "rows", - [ - [["random", "row"]], - TableState.rows_List_List_str, - TableState.rows_List_List, - TableState.rows_Tuple_List_str, - TableState.rows_Tuple_List, - TableState.rows_Tuple_Tuple_str_str, - TableState.rows_Tuple_Tuple, - *valid_extras, - ], -) -def test_create_table_body_with_valid_rows_prop(rows): - render_dict = Tbody.create(rows=rows).render() - assert render_dict["name"] == "Tbody" - assert len(render_dict["children"]) == 1 - - -@pytest.mark.parametrize( - "rows", - [ - ["random", "row"], - "random, rows", - TableState.rows_List_str, - TableState.rows_Tuple_str_str, - TableState.rows_str, - *invalid_extras, - ], -) -def test_create_table_body_with_invalid_rows_prop(rows): - with pytest.raises(TypeError): - Tbody.create(rows=rows) - - -@pytest.mark.parametrize( - "headers", - [ - ["random", "header"], - TableState.headers_List_str, - TableState.headers_Tuple_str_str, - ], -) -def test_create_table_head_with_valid_headers_prop(headers): - render_dict = Thead.create(headers=headers).render() - assert render_dict["name"] == "Thead" - assert len(render_dict["children"]) == 1 - assert render_dict["children"][0]["name"] == "Tr" - - -@pytest.mark.parametrize( - "headers", - [ - "random, header", - TableState.headers_str, - ], -) -def test_create_table_head_with_invalid_headers_prop(headers): - with pytest.raises(TypeError): - Thead.create(headers=headers) - - -@pytest.mark.parametrize( - "footers", - [ - ["random", "footers"], - TableState.footers_List_str, - TableState.footers_Tuple_str_str, - ], -) -def test_create_table_footer_with_valid_footers_prop(footers): - render_dict = Tfoot.create(footers=footers).render() - assert render_dict["name"] == "Tfoot" - assert len(render_dict["children"]) == 1 - assert render_dict["children"][0]["name"] == "Tr" - - -@pytest.mark.parametrize( - "footers", - [ - "random, footers", - TableState.footers_str, - ], -) -def test_create_table_footer_with_invalid_footers_prop(footers): - with pytest.raises(TypeError): - Tfoot.create(footers=footers) diff --git a/tests/components/forms/test_form.py b/tests/components/forms/test_form.py index efb31b97a..0611c6994 100644 --- a/tests/components/forms/test_form.py +++ b/tests/components/forms/test_form.py @@ -1,5 +1,4 @@ -from reflex_chakra.components.forms.form import Form - +from reflex.components.radix.primitives.form import Form from reflex.event import EventChain from reflex.ivars.base import ImmutableVar diff --git a/tests/components/media/test_icon.py b/tests/components/media/test_icon.py deleted file mode 100644 index 6a152c587..000000000 --- a/tests/components/media/test_icon.py +++ /dev/null @@ -1,55 +0,0 @@ -import pytest -from reflex_chakra.components.media.icon import ICON_LIST, Icon - -from reflex.utils import format - - -def test_no_tag_errors(): - """Test that an icon without a tag raises an error.""" - with pytest.raises(AttributeError): - Icon.create() - - -def test_children_errors(): - """Test that an icon with children raises an error.""" - with pytest.raises(AttributeError): - Icon.create("child", tag="search") - - -@pytest.mark.parametrize( - "tag", - ICON_LIST, -) -def test_valid_icon(tag: str): - """Test that a valid icon does not raise an error. - - Args: - tag: The icon tag. - """ - icon = Icon.create(tag=tag) - assert icon.tag == format.to_title_case(tag) + "Icon" - - -@pytest.mark.parametrize("tag", ["", " ", "invalid", 123]) -def test_invalid_icon(tag): - """Test that an invalid icon raises an error. - - Args: - tag: The icon tag. - """ - with pytest.raises(ValueError): - Icon.create(tag=tag) - - -@pytest.mark.parametrize( - "tag", - ["Check", "Close", "eDit"], -) -def test_tag_with_capital(tag: str): - """Test that an icon that tag with capital does not raise an error. - - Args: - tag: The icon tag. - """ - icon = Icon.create(tag=tag) - assert icon.tag == format.to_title_case(tag) + "Icon" diff --git a/tests/components/test_component.py b/tests/components/test_component.py index 3ce5decad..79a72b997 100644 --- a/tests/components/test_component.py +++ b/tests/components/test_component.py @@ -2,8 +2,6 @@ from contextlib import nullcontext from typing import Any, Dict, List, Optional, Type, Union import pytest -import reflex_chakra as rc -from reflex_chakra.components.layout.box import Box import reflex as rx from reflex.base import Base @@ -16,6 +14,7 @@ from reflex.components.component import ( StatefulComponent, custom_component, ) +from reflex.components.radix.themes.layout.box import Box from reflex.constants import EventTriggers from reflex.event import EventChain, EventHandler, parse_args_spec from reflex.ivars.base import ImmutableVar, LiteralVar @@ -638,21 +637,21 @@ def test_component_create_unallowed_types(children, test_component): "props": [], "contents": "", "args": None, - "special_props": set(), + "special_props": [], "children": [ { "name": "RadixThemesText", "props": ['as={"p"}'], "contents": "", "args": None, - "special_props": set(), + "special_props": [], "children": [ { "name": "", "props": [], "contents": '{"first_text"}', "args": None, - "special_props": set(), + "special_props": [], "children": [], "autofocus": False, } @@ -680,13 +679,13 @@ def test_component_create_unallowed_types(children, test_component): "contents": '{"first_text"}', "name": "", "props": [], - "special_props": set(), + "special_props": [], } ], "contents": "", "name": "RadixThemesText", "props": ['as={"p"}'], - "special_props": set(), + "special_props": [], }, { "args": None, @@ -699,19 +698,19 @@ def test_component_create_unallowed_types(children, test_component): "contents": '{"second_text"}', "name": "", "props": [], - "special_props": set(), + "special_props": [], } ], "contents": "", "name": "RadixThemesText", "props": ['as={"p"}'], - "special_props": set(), + "special_props": [], }, ], "contents": "", "name": "Fragment", "props": [], - "special_props": set(), + "special_props": [], }, ), ( @@ -731,13 +730,13 @@ def test_component_create_unallowed_types(children, test_component): "contents": '{"first_text"}', "name": "", "props": [], - "special_props": set(), + "special_props": [], } ], "contents": "", "name": "RadixThemesText", "props": ['as={"p"}'], - "special_props": set(), + "special_props": [], }, { "args": None, @@ -758,31 +757,31 @@ def test_component_create_unallowed_types(children, test_component): "contents": '{"second_text"}', "name": "", "props": [], - "special_props": set(), + "special_props": [], } ], "contents": "", "name": "RadixThemesText", "props": ['as={"p"}'], - "special_props": set(), + "special_props": [], } ], "contents": "", "name": "Fragment", "props": [], - "special_props": set(), + "special_props": [], } ], "contents": "", "name": "RadixThemesBox", "props": [], - "special_props": set(), + "special_props": [], }, ], "contents": "", "name": "Fragment", "props": [], - "special_props": set(), + "special_props": [], }, ), ], @@ -1114,8 +1113,8 @@ def test_component_with_only_valid_children(fixture, request): [ (rx.text("hi"), '\n {"hi"}\n'), ( - rx.box(rc.heading("test", size="md")), - '\n \n {"test"}\n\n', + rx.box(rx.heading("test", size="3")), + '\n \n {"test"}\n\n', ), ], ) @@ -1290,12 +1289,12 @@ class EventState(rx.State): id="fstring-class_name", ), pytest.param( - rx.fragment(special_props={TEST_VAR}), + rx.fragment(special_props=[TEST_VAR]), [TEST_VAR], id="direct-special_props", ), pytest.param( - rx.fragment(special_props={LiteralVar.create(f"foo{TEST_VAR}bar")}), + rx.fragment(special_props=[LiteralVar.create(f"foo{TEST_VAR}bar")]), [FORMATTED_TEST_VAR], id="fstring-special_props", ), diff --git a/tests/components/typography/test_markdown.py b/tests/components/typography/test_markdown.py index 2ef6fbce4..5e9abbb1f 100644 --- a/tests/components/typography/test_markdown.py +++ b/tests/components/typography/test_markdown.py @@ -1,5 +1,4 @@ import pytest -import reflex_chakra as rc import reflex as rx from reflex.components.markdown import Markdown @@ -37,9 +36,7 @@ def test_get_component(tag, expected): def test_set_component_map(): """Test setting the component map.""" component_map = { - "h1": lambda value: rx.box( - rc.heading(value, as_="h1", size="2xl"), padding="1em" - ), + "h1": lambda value: rx.box(rx.heading(value, as_="h1"), padding="1em"), "p": lambda value: rx.box(rx.text(value), padding="1em"), } md = Markdown.create("# Hello", component_map=component_map) diff --git a/tests/test_app.py b/tests/test_app.py index 6767abe35..a2a0cede7 100644 --- a/tests/test_app.py +++ b/tests/test_app.py @@ -1,5 +1,6 @@ from __future__ import annotations +import dataclasses import functools import io import json @@ -13,7 +14,6 @@ from typing import Generator, List, Tuple, Type from unittest.mock import AsyncMock import pytest -import reflex_chakra as rc import sqlmodel from fastapi import FastAPI, UploadFile from starlette_admin.auth import AuthProvider @@ -268,8 +268,6 @@ def test_add_page_set_route_dynamic(index_page, windows_platform: bool): app = App(state=EmptyState) assert app.state is not None route = "/test/[dynamic]" - if windows_platform: - route.lstrip("/").replace("/", "\\") assert app.pages == {} app.add_page(index_page, route=route) assert app.pages.keys() == {"test/[dynamic]"} @@ -937,8 +935,6 @@ def test_dynamic_arg_shadow( """ arg_name = "counter" route = f"/test/[{arg_name}]" - if windows_platform: - route.lstrip("/").replace("/", "\\") app = app_module_mock.app = App(state=DynamicState) assert app.state is not None with pytest.raises(NameError): @@ -964,9 +960,6 @@ def test_multiple_dynamic_args( arg_name = "my_arg" route = f"/test/[{arg_name}]" route2 = f"/test2/[{arg_name}]" - if windows_platform: - route = route.lstrip("/").replace("/", "\\") - route2 = route2.lstrip("/").replace("/", "\\") app = app_module_mock.app = App(state=EmptyState) app.add_page(index_page, route=route) app.add_page(index_page, route=route2) @@ -994,8 +987,6 @@ async def test_dynamic_route_var_route_change_completed_on_load( """ arg_name = "dynamic" route = f"/test/[{arg_name}]" - if windows_platform: - route.lstrip("/").replace("/", "\\") app = app_module_mock.app = App(state=DynamicState) assert app.state is not None assert arg_name not in app.state.vars @@ -1062,7 +1053,7 @@ async def test_dynamic_route_var_route_change_completed_on_load( f"comp_{arg_name}": exp_val, constants.CompileVars.IS_HYDRATED: False, # "side_effect_counter": exp_index, - "router": exp_router, + "router": dataclasses.asdict(exp_router), } }, events=[ @@ -1320,13 +1311,13 @@ def test_app_wrap_priority(compilable_app: tuple[App, Path]): tag = "Fragment1" def _get_app_wrap_components(self) -> dict[tuple[int, str], Component]: - return {(99, "Box"): rc.box()} + return {(99, "Box"): rx.box()} class Fragment2(Component): tag = "Fragment2" def _get_app_wrap_components(self) -> dict[tuple[int, str], Component]: - return {(50, "Text"): rc.text()} + return {(50, "Text"): rx.text()} class Fragment3(Component): tag = "Fragment3" @@ -1346,19 +1337,17 @@ def test_app_wrap_priority(compilable_app: tuple[App, Path]): assert ( "function AppWrap({children}) {" "return (" - "" - "" - "" - "" + "" + '' + "" "" "" "{children}" "" "" - "" - "" - "" - "" + "" + "" + "" ")" "}" ) in "".join(app_js_lines) diff --git a/tests/test_state.py b/tests/test_state.py index 29944840e..3da74fc89 100644 --- a/tests/test_state.py +++ b/tests/test_state.py @@ -2,6 +2,7 @@ from __future__ import annotations import asyncio import copy +import dataclasses import datetime import functools import json @@ -58,6 +59,7 @@ formatted_router = { "origin": "", "upgrade": "", "connection": "", + "cookie": "", "pragma": "", "cache_control": "", "user_agent": "", @@ -865,8 +867,10 @@ def test_get_headers(test_state, router_data, router_data_headers): router_data: The router data fixture. router_data_headers: The expected headers. """ + print(router_data_headers) test_state.router = RouterData(router_data) - assert test_state.router.headers.dict() == { + print(test_state.router.headers) + assert dataclasses.asdict(test_state.router.headers) == { format.to_snake_case(k): v for k, v in router_data_headers.items() } @@ -1908,19 +1912,21 @@ async def test_state_proxy(grandchild_state: GrandchildState, mock_app: rx.App): mock_app.event_namespace.emit.assert_called_once() mcall = mock_app.event_namespace.emit.mock_calls[0] assert mcall.args[0] == str(SocketEvent.EVENT) - assert json.loads(mcall.args[1]) == StateUpdate( - delta={ - parent_state.get_full_name(): { - "upper": "", - "sum": 3.14, - }, - grandchild_state.get_full_name(): { - "value2": "42", - }, - GrandchildState3.get_full_name(): { - "computed": "", - }, - } + assert json.loads(mcall.args[1]) == dataclasses.asdict( + StateUpdate( + delta={ + parent_state.get_full_name(): { + "upper": "", + "sum": 3.14, + }, + grandchild_state.get_full_name(): { + "value2": "42", + }, + GrandchildState3.get_full_name(): { + "computed": "", + }, + } + ) ) assert mcall.kwargs["to"] == grandchild_state.router.session.session_id diff --git a/tests/utils/test_format.py b/tests/utils/test_format.py index 8c559f141..286748943 100644 --- a/tests/utils/test_format.py +++ b/tests/utils/test_format.py @@ -553,6 +553,7 @@ formatted_router = { "origin": "", "upgrade": "", "connection": "", + "cookie": "", "pragma": "", "cache_control": "", "user_agent": "", diff --git a/tests/utils/test_imports.py b/tests/utils/test_imports.py index e9be5c1be..c30d1d85c 100644 --- a/tests/utils/test_imports.py +++ b/tests/utils/test_imports.py @@ -54,17 +54,21 @@ def test_import_var(import_var, expected_name): ( {"react": {"Component"}}, {"react": {"Component"}, "react-dom": {"render"}}, - {"react": {"Component"}, "react-dom": {"render"}}, + {"react": {ImportVar("Component")}, "react-dom": {ImportVar("render")}}, ), ( {"react": {"Component"}, "next/image": {"Image"}}, {"react": {"Component"}, "react-dom": {"render"}}, - {"react": {"Component"}, "react-dom": {"render"}, "next/image": {"Image"}}, + { + "react": {ImportVar("Component")}, + "react-dom": {ImportVar("render")}, + "next/image": {ImportVar("Image")}, + }, ), ( {"react": {"Component"}}, {"": {"some/custom.css"}}, - {"react": {"Component"}, "": {"some/custom.css"}}, + {"react": {ImportVar("Component")}, "": {ImportVar("some/custom.css")}}, ), ], )