From afd52a87ddc6a5fa009fc6c36f1c61b0d97c782a Mon Sep 17 00:00:00 2001 From: Elijah Ahianyo Date: Sun, 22 Sep 2024 21:44:16 +0000 Subject: [PATCH 01/39] Make `reflex init --ai` use light mode (#3963) --- reflex/utils/prerequisites.py | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/reflex/utils/prerequisites.py b/reflex/utils/prerequisites.py index 6433cd346..7173e4872 100644 --- a/reflex/utils/prerequisites.py +++ b/reflex/utils/prerequisites.py @@ -1481,13 +1481,20 @@ def initialize_main_module_index_from_generation(app_name: str, generation_hash: main_module_path = Path(app_name, app_name + constants.Ext.PY) main_module_code = main_module_path.read_text() - main_module_path.write_text( - re.sub( - r"def index\(\).*:\n([^\n]\s+.*\n+)+", - replace_content, - main_module_code, - ) + + main_module_code = re.sub( + r"def index\(\).*:\n([^\n]\s+.*\n+)+", + replace_content, + main_module_code, ) + # Make the app use light mode until flexgen enforces the conversion of + # tailwind colors to radix colors. + main_module_code = re.sub( + r"app\s*=\s*rx\.App\(\s*\)", + 'app = rx.App(theme=rx.theme(color_mode="light"))', + main_module_code, + ) + main_module_path.write_text(main_module_code) def format_address_width(address_width) -> int | None: From 61332fdba18640a03201a2329199717195b87db7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Brand=C3=A9ho?= Date: Sun, 22 Sep 2024 23:44:44 +0200 Subject: [PATCH 02/39] add some more tests (#3965) --- .../components/datadisplay/test_dataeditor.py | 11 ++++++ tests/components/recharts/test_polar.py | 38 +++++++++++++++++++ 2 files changed, 49 insertions(+) create mode 100644 tests/components/datadisplay/test_dataeditor.py create mode 100644 tests/components/recharts/test_polar.py diff --git a/tests/components/datadisplay/test_dataeditor.py b/tests/components/datadisplay/test_dataeditor.py new file mode 100644 index 000000000..63b156e74 --- /dev/null +++ b/tests/components/datadisplay/test_dataeditor.py @@ -0,0 +1,11 @@ +from reflex.components.datadisplay.dataeditor import DataEditor + + +def test_dataeditor(): + editor_wrapper = DataEditor.create().render() + editor = editor_wrapper["children"][0] + assert editor_wrapper["name"] == "div" + assert editor_wrapper["props"] == [ + 'css={({ ["width"] : "100%", ["height"] : "100%" })}' + ] + assert editor["name"] == "DataEditor" diff --git a/tests/components/recharts/test_polar.py b/tests/components/recharts/test_polar.py new file mode 100644 index 000000000..4e4af0f49 --- /dev/null +++ b/tests/components/recharts/test_polar.py @@ -0,0 +1,38 @@ +from reflex.components.recharts import ( + Pie, + PolarAngleAxis, + PolarGrid, + PolarRadiusAxis, + Radar, + RadialBar, +) + + +def test_pie(): + pie = Pie.create().render() + assert pie["name"] == "RechartsPie" + + +def test_radar(): + radar = Radar.create().render() + assert radar["name"] == "RechartsRadar" + + +def test_radialbar(): + radialbar = RadialBar.create().render() + assert radialbar["name"] == "RechartsRadialBar" + + +def test_polarangleaxis(): + polarangleaxis = PolarAngleAxis.create().render() + assert polarangleaxis["name"] == "RechartsPolarAngleAxis" + + +def test_polargrid(): + polargrid = PolarGrid.create().render() + assert polargrid["name"] == "RechartsPolarGrid" + + +def test_polarradiusaxis(): + polarradiusaxis = PolarRadiusAxis.create().render() + assert polarradiusaxis["name"] == "RechartsPolarRadiusAxis" From ee3b0e614c9ffbfe4bd420ce7253190c94a48bd8 Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Mon, 23 Sep 2024 12:40:14 -0700 Subject: [PATCH 03/39] fix set value logix for client state (#3966) --- reflex/experimental/client_state.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/reflex/experimental/client_state.py b/reflex/experimental/client_state.py index 9601ca58b..438ca0a23 100644 --- a/reflex/experimental/client_state.py +++ b/reflex/experimental/client_state.py @@ -3,6 +3,7 @@ from __future__ import annotations import dataclasses +import re import sys from typing import Any, Callable, Union @@ -174,15 +175,15 @@ class ClientStateVar(Var): else self._setter_name ) if value is not NoValue: - import re - # This is a hack to make it work like an EventSpec taking an arg value_str = str(LiteralVar.create(value)) - # remove patterns of ["*"] from the value_str using regex - arg = re.sub(r"\[\".*\"\]", "", value_str) - - setter = f"({arg}) => {setter}({str(value)})" + if value_str.startswith("_"): + # remove patterns of ["*"] from the value_str using regex + arg = re.sub(r"\[\".*\"\]", "", value_str) + setter = f"(({arg}) => {setter}({value_str}))" + else: + setter = f"(() => {setter}({value_str}))" return Var( _js_expr=setter, _var_data=VarData(imports=_refs_import if self._global_ref else {}), From 47c9938d958f9438c0e170dd0e9bf1cc0acd49fd Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Mon, 23 Sep 2024 16:36:58 -0700 Subject: [PATCH 04/39] use is true for bool var (#3973) --- reflex/vars/sequence.py | 8 -------- tests/test_var.py | 2 +- 2 files changed, 1 insertion(+), 9 deletions(-) diff --git a/reflex/vars/sequence.py b/reflex/vars/sequence.py index ca8967d33..6145c980c 100644 --- a/reflex/vars/sequence.py +++ b/reflex/vars/sequence.py @@ -194,14 +194,6 @@ class StringVar(Var[str]): """ return string_strip_operation(self) - def bool(self): - """Boolean conversion. - - Returns: - The boolean value of the string. - """ - return self.length() != 0 - def reversed(self) -> StringVar: """Reverse the string. diff --git a/tests/test_var.py b/tests/test_var.py index e9998c456..31bf80568 100644 --- a/tests/test_var.py +++ b/tests/test_var.py @@ -968,7 +968,7 @@ def test_all_number_operations(): [ (Var.create(False), "false"), (Var.create(True), "true"), - (Var.create("false"), '("false".split("").length !== 0)'), + (Var.create("false"), 'isTrue("false")'), (Var.create([1, 2, 3]), "isTrue([1, 2, 3])"), (Var.create({"a": 1, "b": 2}), 'isTrue(({ ["a"] : 1, ["b"] : 2 }))'), (Var("mysterious_var"), "isTrue(mysterious_var)"), From a5ad5203df9fae74d3512766933441057b235e2d Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Mon, 23 Sep 2024 18:13:55 -0700 Subject: [PATCH 05/39] suggest bool() for wrong values (#3975) --- reflex/components/component.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/reflex/components/component.py b/reflex/components/component.py index e6bdfef06..b2f3d196f 100644 --- a/reflex/components/component.py +++ b/reflex/components/component.py @@ -448,8 +448,16 @@ class Component(BaseComponent, ABC): and not types._issubclass(passed_type, expected_type, value) ): value_name = value._js_expr if isinstance(value, Var) else value + + additional_info = ( + " You can call `.bool()` on the value to convert it to a boolean." + if expected_type is bool and isinstance(value, Var) + else "" + ) + raise TypeError( f"Invalid var passed for prop {type(self).__name__}.{key}, expected type {expected_type}, got value {value_name} of type {passed_type}." + + additional_info ) # Check if the key is an event trigger. if key in component_specific_triggers: From 00d995d971f9eec6a62d1c3d3de697dbb8f0fe22 Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Mon, 23 Sep 2024 18:14:28 -0700 Subject: [PATCH 06/39] [ENG-3833] handle object in is bool (#3974) * handle object in is bool * use if statements --- reflex/.templates/web/utils/state.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/reflex/.templates/web/utils/state.js b/reflex/.templates/web/utils/state.js index 66b50b1b4..78e671809 100644 --- a/reflex/.templates/web/utils/state.js +++ b/reflex/.templates/web/utils/state.js @@ -832,7 +832,9 @@ export const useEventLoop = ( * @returns True if the value is truthy, false otherwise. */ export const isTrue = (val) => { - return Array.isArray(val) ? val.length > 0 : !!val; + if (Array.isArray(val)) return val.length > 0; + if (val === Object(val)) return Object.keys(val).length > 0; + return Boolean(val); }; /** From 2883e541a986797d1d1d78c79caf91535323c68d Mon Sep 17 00:00:00 2001 From: Masen Furer Date: Mon, 23 Sep 2024 18:15:16 -0700 Subject: [PATCH 07/39] Bring back py3.9 support with a deprecation warning. (#3976) * Revert "ruff formatting to unbreak `main` CI (#3964)" This reverts commit f9be184b86ae878245bcda326eb8fd2b6fdf7fa9. * Revert "bump python>=3.10 for 0.6.0 (#3956)" This reverts commit fe1833c5e16695c715a9895d7bed5e4165c6bc43. * drop python3.8 support * relock dependencies * Raise warning when < py310 is used * Move python version check to reflex version check function Avoid spammy deprecation warnings by only emitting warning once per project, per reflex version, per reinit. * Remove other references to python3.8 --- .github/workflows/benchmarks.yml | 10 +- .github/workflows/integration_tests.yml | 7 +- .github/workflows/integration_tests_wsl.yml | 4 +- .github/workflows/unit_tests.yml | 6 +- CONTRIBUTING.md | 4 +- README.md | 2 +- docs/de/README.md | 2 +- docs/es/README.md | 2 +- docs/in/README.md | 2 +- docs/it/README.md | 2 +- docs/ja/README.md | 2 +- docs/kr/README.md | 2 +- docs/pe/README.md | 2 +- docs/pt/pt_br/README.md | 2 +- docs/tr/README.md | 2 +- docs/zh/zh_cn/README.md | 2 +- docs/zh/zh_tw/README.md | 2 +- integration/init-test/Dockerfile | 2 +- poetry.lock | 490 ++++++++++-------- pyproject.toml | 4 +- .../custom_components/pyproject.toml.jinja2 | 2 +- reflex/app.py | 8 +- reflex/app_module_for_backend.py | 2 +- reflex/components/core/breakpoints.py | 4 +- reflex/event.py | 4 +- reflex/state.py | 1 - reflex/utils/prerequisites.py | 27 +- reflex/utils/telemetry.py | 2 +- reflex/utils/types.py | 2 +- reflex/vars/base.py | 2 +- tests/compiler/test_compiler.py | 2 +- tests/components/media/test_image.py | 1 - tests/components/test_component.py | 2 - tests/test_var.py | 3 - 34 files changed, 349 insertions(+), 264 deletions(-) diff --git a/.github/workflows/benchmarks.yml b/.github/workflows/benchmarks.yml index 0971c728b..aac67f7a6 100644 --- a/.github/workflows/benchmarks.yml +++ b/.github/workflows/benchmarks.yml @@ -81,11 +81,15 @@ jobs: matrix: # Show OS combos first in GUI os: [ubuntu-latest, windows-latest, macos-12] - python-version: ['3.10.13', '3.11.5', '3.12.0'] + python-version: ['3.9.18', '3.10.13', '3.11.5', '3.12.0'] exclude: - os: windows-latest python-version: '3.10.13' + - os: windows-latest + python-version: '3.9.18' # keep only one python version for MacOS + - os: macos-latest + python-version: '3.9.18' - os: macos-latest python-version: '3.10.13' - os: macos-12 @@ -93,7 +97,9 @@ jobs: include: - os: windows-latest python-version: '3.10.11' - + - os: windows-latest + python-version: '3.9.13' + runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/integration_tests.yml b/.github/workflows/integration_tests.yml index 289a7b2f1..e9fecc81a 100644 --- a/.github/workflows/integration_tests.yml +++ b/.github/workflows/integration_tests.yml @@ -43,14 +43,17 @@ jobs: matrix: # Show OS combos first in GUI os: [ubuntu-latest, windows-latest, macos-12] - python-version: ['3.10.13', '3.11.5', '3.12.0'] + python-version: ['3.9.18', '3.10.13', '3.11.5', '3.12.0'] exclude: - os: windows-latest python-version: '3.10.13' + - os: windows-latest + python-version: '3.9.18' include: - os: windows-latest python-version: '3.10.11' - + - os: windows-latest + python-version: '3.9.13' runs-on: ${{ matrix.os }} steps: diff --git a/.github/workflows/integration_tests_wsl.yml b/.github/workflows/integration_tests_wsl.yml index ea62268b1..7a743252b 100644 --- a/.github/workflows/integration_tests_wsl.yml +++ b/.github/workflows/integration_tests_wsl.yml @@ -38,14 +38,14 @@ jobs: - uses: Vampire/setup-wsl@v3 with: - distribution: Ubuntu-24.04 + distribution: Ubuntu-24.04 - name: Install Python shell: wsl-bash {0} run: | apt update apt install -y python3 python3-pip curl dos2unix zip unzip - + - name: Install Poetry shell: wsl-bash {0} run: | diff --git a/.github/workflows/unit_tests.yml b/.github/workflows/unit_tests.yml index f15434193..333c54cb3 100644 --- a/.github/workflows/unit_tests.yml +++ b/.github/workflows/unit_tests.yml @@ -28,14 +28,18 @@ jobs: fail-fast: false matrix: os: [ubuntu-latest, windows-latest, macos-12] - python-version: ['3.10.13', '3.11.5', '3.12.0'] + python-version: ['3.9.18', '3.10.13', '3.11.5', '3.12.0'] # Windows is a bit behind on Python version availability in Github exclude: - os: windows-latest python-version: '3.10.13' + - os: windows-latest + python-version: '3.9.18' include: - os: windows-latest python-version: '3.10.11' + - os: windows-latest + python-version: '3.9.13' runs-on: ${{ matrix.os }} # Service containers to run with `runner-job` services: diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6db8e1a04..af195997a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -8,7 +8,7 @@ Here is a quick guide on how to run Reflex repo locally so you can start contrib **Prerequisites:** -- Python >= 3.10 +- Python >= 3.9 - Poetry version >= 1.4.0 and add it to your path (see [Poetry Docs](https://python-poetry.org/docs/#installation) for more info). **1. Fork this repository:** @@ -87,7 +87,7 @@ poetry run ruff format . ``` Consider installing git pre-commit hooks so Ruff, Pyright, Darglint and `make_pyi` will run automatically before each commit. -Note that pre-commit will only be installed when you use a Python version >= 3.8. +Note that pre-commit will only be installed when you use a Python version >= 3.9. ``` bash pre-commit install diff --git a/README.md b/README.md index d3f22bb54..c249aea9f 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,7 @@ See our [architecture page](https://reflex.dev/blog/2024-03-21-reflex-architectu ## ⚙️ Installation -Open a terminal and run (Requires Python 3.10+): +Open a terminal and run (Requires Python 3.9+): ```bash pip install reflex diff --git a/docs/de/README.md b/docs/de/README.md index 6d2d69e94..9931c24cc 100644 --- a/docs/de/README.md +++ b/docs/de/README.md @@ -34,7 +34,7 @@ Auf unserer [Architektur-Seite](https://reflex.dev/blog/2024-03-21-reflex-archit ## ⚙️ Installation -Öffne ein Terminal und führe den folgenden Befehl aus (benötigt Python 3.10+): +Öffne ein Terminal und führe den folgenden Befehl aus (benötigt Python 3.9+): ```bash pip install reflex diff --git a/docs/es/README.md b/docs/es/README.md index 538192e4b..15ce63335 100644 --- a/docs/es/README.md +++ b/docs/es/README.md @@ -35,7 +35,7 @@ Consulta nuestra [página de arquitectura](https://reflex.dev/blog/2024-03-21-re ## ⚙️ Instalación -Abra un terminal y ejecute (Requiere Python 3.10+): +Abra un terminal y ejecute (Requiere Python 3.9+): ```bash pip install reflex diff --git a/docs/in/README.md b/docs/in/README.md index 81b1106ff..ebc4155f4 100644 --- a/docs/in/README.md +++ b/docs/in/README.md @@ -35,7 +35,7 @@ Reflex के अंदर के कामकाज को जानने क ## ⚙️ इंस्टॉलेशन (Installation) -एक टर्मिनल खोलें और चलाएं (Python 3.10+ की आवश्यकता है): +एक टर्मिनल खोलें और चलाएं (Python 3.9+ की आवश्यकता है): ```bash pip install reflex diff --git a/docs/it/README.md b/docs/it/README.md index cd6f24dd8..92438f696 100644 --- a/docs/it/README.md +++ b/docs/it/README.md @@ -22,7 +22,7 @@ ## ⚙️ Installazione -Apri un terminale ed esegui (Richiede Python 3.10+): +Apri un terminale ed esegui (Richiede Python 3.9+): ```bash pip install reflex diff --git a/docs/ja/README.md b/docs/ja/README.md index f58fb70ad..0a7ab0d53 100644 --- a/docs/ja/README.md +++ b/docs/ja/README.md @@ -37,7 +37,7 @@ Reflex がどのように動作しているかを知るには、[アーキテク ## ⚙️ インストール -ターミナルを開いて以下のコマンドを実行してください。(Python 3.8 以上が必要です。): +ターミナルを開いて以下のコマンドを実行してください。(Python 3.9 以上が必要です。): ```bash pip install reflex diff --git a/docs/kr/README.md b/docs/kr/README.md index 57bb43794..a92fcd0c5 100644 --- a/docs/kr/README.md +++ b/docs/kr/README.md @@ -20,7 +20,7 @@ --- ## ⚙️ 설치 -터미널을 열고 실행하세요. (Python 3.10+ 필요): +터미널을 열고 실행하세요. (Python 3.9+ 필요): ```bash pip install reflex diff --git a/docs/pe/README.md b/docs/pe/README.md index 867b543bc..3a0ba044b 100644 --- a/docs/pe/README.md +++ b/docs/pe/README.md @@ -34,7 +34,7 @@ ## ⚙️ Installation - نصب و راه اندازی -یک ترمینال را باز کنید و اجرا کنید (نیازمند Python 3.10+): +یک ترمینال را باز کنید و اجرا کنید (نیازمند Python 3.9+): ```bash pip install reflex diff --git a/docs/pt/pt_br/README.md b/docs/pt/pt_br/README.md index 8abfaebde..184b668bc 100644 --- a/docs/pt/pt_br/README.md +++ b/docs/pt/pt_br/README.md @@ -21,7 +21,7 @@ --- ## ⚙️ Instalação -Abra um terminal e execute (Requer Python 3.10+): +Abra um terminal e execute (Requer Python 3.9+): ```bash pip install reflex diff --git a/docs/tr/README.md b/docs/tr/README.md index afb8ae5b9..376547e01 100644 --- a/docs/tr/README.md +++ b/docs/tr/README.md @@ -24,7 +24,7 @@ ## ⚙️ Kurulum -Bir terminal açın ve çalıştırın (Python 3.10+ gerekir): +Bir terminal açın ve çalıştırın (Python 3.9+ gerekir): ```bash pip install reflex diff --git a/docs/zh/zh_cn/README.md b/docs/zh/zh_cn/README.md index c39db0c2d..e114bc1e2 100644 --- a/docs/zh/zh_cn/README.md +++ b/docs/zh/zh_cn/README.md @@ -34,7 +34,7 @@ Reflex 是一个使用纯Python构建全栈web应用的库。 ## ⚙️ 安装 -打开一个终端并且运行(要求Python3.8+): +打开一个终端并且运行(要求Python3.9+): ```bash pip install reflex diff --git a/docs/zh/zh_tw/README.md b/docs/zh/zh_tw/README.md index 6161e17d0..83f6b2ae2 100644 --- a/docs/zh/zh_tw/README.md +++ b/docs/zh/zh_tw/README.md @@ -36,7 +36,7 @@ Reflex 是一個可以用純 Python 構建全端網頁應用程式的函式庫 ## ⚙️ 安裝 -開啟一個終端機並且執行 (需要 Python 3.10+): +開啟一個終端機並且執行 (需要 Python 3.9+): ```bash pip install reflex diff --git a/integration/init-test/Dockerfile b/integration/init-test/Dockerfile index e5d2a0820..f30466e7f 100644 --- a/integration/init-test/Dockerfile +++ b/integration/init-test/Dockerfile @@ -1,4 +1,4 @@ -FROM python:3.10 +FROM python:3.9 ARG USERNAME=kerrigan RUN useradd -m $USERNAME diff --git a/poetry.lock b/poetry.lock index d3a63110b..c587977e9 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,14 +1,14 @@ -# 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.8.2 and should not be changed by hand. [[package]] name = "alembic" -version = "1.13.2" +version = "1.13.3" description = "A database migration tool for SQLAlchemy." optional = false python-versions = ">=3.8" files = [ - {file = "alembic-1.13.2-py3-none-any.whl", hash = "sha256:6b8733129a6224a9a711e17c99b08462dbf7cc9670ba8f2e2ae9af860ceb1953"}, - {file = "alembic-1.13.2.tar.gz", hash = "sha256:1ff0ae32975f4fd96028c39ed9bb3c867fe3af956bd7bb37343b54c9fe7445ef"}, + {file = "alembic-1.13.3-py3-none-any.whl", hash = "sha256:908e905976d15235fae59c9ac42c4c5b75cfcefe3d27c0fbf7ae15a37715d80e"}, + {file = "alembic-1.13.3.tar.gz", hash = "sha256:203503117415561e203aa14541740643a611f641517f0209fcae63e9fa09f1a2"}, ] [package.dependencies] @@ -32,13 +32,13 @@ files = [ [[package]] name = "anyio" -version = "4.5.0" +version = "4.6.0" description = "High level compatibility layer for multiple asynchronous event loop implementations" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "anyio-4.5.0-py3-none-any.whl", hash = "sha256:fdeb095b7cc5a5563175eedd926ec4ae55413bb4be5770c424af0ba46ccb4a78"}, - {file = "anyio-4.5.0.tar.gz", hash = "sha256:c5a275fe5ca0afd788001f58fca1e69e29ce706d746e317d660e21f70c530ef9"}, + {file = "anyio-4.6.0-py3-none-any.whl", hash = "sha256:c7d2e9d63e31599eeb636c8c5c03a7e108d73b345f064f1c19fdc87b79036a9a"}, + {file = "anyio-4.6.0.tar.gz", hash = "sha256:137b4559cbb034c477165047febb6ff83f390fc3b20bf181c1fc0a728cb8beeb"}, ] [package.dependencies] @@ -616,77 +616,84 @@ typing = ["typing-extensions (>=4.12.2)"] [[package]] name = "greenlet" -version = "3.1.0" +version = "3.1.1" description = "Lightweight in-process concurrent programming" optional = false python-versions = ">=3.7" files = [ - {file = "greenlet-3.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a814dc3100e8a046ff48faeaa909e80cdb358411a3d6dd5293158425c684eda8"}, - {file = "greenlet-3.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a771dc64fa44ebe58d65768d869fcfb9060169d203446c1d446e844b62bdfdca"}, - {file = "greenlet-3.1.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0e49a65d25d7350cca2da15aac31b6f67a43d867448babf997fe83c7505f57bc"}, - {file = "greenlet-3.1.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2cd8518eade968bc52262d8c46727cfc0826ff4d552cf0430b8d65aaf50bb91d"}, - {file = "greenlet-3.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:76dc19e660baea5c38e949455c1181bc018893f25372d10ffe24b3ed7341fb25"}, - {file = "greenlet-3.1.0-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c0a5b1c22c82831f56f2f7ad9bbe4948879762fe0d59833a4a71f16e5fa0f682"}, - {file = "greenlet-3.1.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:2651dfb006f391bcb240635079a68a261b227a10a08af6349cba834a2141efa1"}, - {file = "greenlet-3.1.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:3e7e6ef1737a819819b1163116ad4b48d06cfdd40352d813bb14436024fcda99"}, - {file = "greenlet-3.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:ffb08f2a1e59d38c7b8b9ac8083c9c8b9875f0955b1e9b9b9a965607a51f8e54"}, - {file = "greenlet-3.1.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9730929375021ec90f6447bff4f7f5508faef1c02f399a1953870cdb78e0c345"}, - {file = "greenlet-3.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:713d450cf8e61854de9420fb7eea8ad228df4e27e7d4ed465de98c955d2b3fa6"}, - {file = "greenlet-3.1.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4c3446937be153718250fe421da548f973124189f18fe4575a0510b5c928f0cc"}, - {file = "greenlet-3.1.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1ddc7bcedeb47187be74208bc652d63d6b20cb24f4e596bd356092d8000da6d6"}, - {file = "greenlet-3.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:44151d7b81b9391ed759a2f2865bbe623ef00d648fed59363be2bbbd5154656f"}, - {file = "greenlet-3.1.0-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6cea1cca3be76c9483282dc7760ea1cc08a6ecec1f0b6ca0a94ea0d17432da19"}, - {file = "greenlet-3.1.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:619935a44f414274a2c08c9e74611965650b730eb4efe4b2270f91df5e4adf9a"}, - {file = "greenlet-3.1.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:221169d31cada333a0c7fd087b957c8f431c1dba202c3a58cf5a3583ed973e9b"}, - {file = "greenlet-3.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:01059afb9b178606b4b6e92c3e710ea1635597c3537e44da69f4531e111dd5e9"}, - {file = "greenlet-3.1.0-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:24fc216ec7c8be9becba8b64a98a78f9cd057fd2dc75ae952ca94ed8a893bf27"}, - {file = "greenlet-3.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3d07c28b85b350564bdff9f51c1c5007dfb2f389385d1bc23288de51134ca303"}, - {file = "greenlet-3.1.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:243a223c96a4246f8a30ea470c440fe9db1f5e444941ee3c3cd79df119b8eebf"}, - {file = "greenlet-3.1.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:26811df4dc81271033a7836bc20d12cd30938e6bd2e9437f56fa03da81b0f8fc"}, - {file = "greenlet-3.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c9d86401550b09a55410f32ceb5fe7efcd998bd2dad9e82521713cb148a4a15f"}, - {file = "greenlet-3.1.0-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:26d9c1c4f1748ccac0bae1dbb465fb1a795a75aba8af8ca871503019f4285e2a"}, - {file = "greenlet-3.1.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:cd468ec62257bb4544989402b19d795d2305eccb06cde5da0eb739b63dc04665"}, - {file = "greenlet-3.1.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a53dfe8f82b715319e9953330fa5c8708b610d48b5c59f1316337302af5c0811"}, - {file = "greenlet-3.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:28fe80a3eb673b2d5cc3b12eea468a5e5f4603c26aa34d88bf61bba82ceb2f9b"}, - {file = "greenlet-3.1.0-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:76b3e3976d2a452cba7aa9e453498ac72240d43030fdc6d538a72b87eaff52fd"}, - {file = "greenlet-3.1.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:655b21ffd37a96b1e78cc48bf254f5ea4b5b85efaf9e9e2a526b3c9309d660ca"}, - {file = "greenlet-3.1.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c6f4c2027689093775fd58ca2388d58789009116844432d920e9147f91acbe64"}, - {file = "greenlet-3.1.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:76e5064fd8e94c3f74d9fd69b02d99e3cdb8fc286ed49a1f10b256e59d0d3a0b"}, - {file = "greenlet-3.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6a4bf607f690f7987ab3291406e012cd8591a4f77aa54f29b890f9c331e84989"}, - {file = "greenlet-3.1.0-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:037d9ac99540ace9424cb9ea89f0accfaff4316f149520b4ae293eebc5bded17"}, - {file = "greenlet-3.1.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:90b5bbf05fe3d3ef697103850c2ce3374558f6fe40fd57c9fac1bf14903f50a5"}, - {file = "greenlet-3.1.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:726377bd60081172685c0ff46afbc600d064f01053190e4450857483c4d44484"}, - {file = "greenlet-3.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:d46d5069e2eeda111d6f71970e341f4bd9aeeee92074e649ae263b834286ecc0"}, - {file = "greenlet-3.1.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:81eeec4403a7d7684b5812a8aaa626fa23b7d0848edb3a28d2eb3220daddcbd0"}, - {file = "greenlet-3.1.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4a3dae7492d16e85ea6045fd11cb8e782b63eac8c8d520c3a92c02ac4573b0a6"}, - {file = "greenlet-3.1.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4b5ea3664eed571779403858d7cd0a9b0ebf50d57d2cdeafc7748e09ef8cd81a"}, - {file = "greenlet-3.1.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a22f4e26400f7f48faef2d69c20dc055a1f3043d330923f9abe08ea0aecc44df"}, - {file = "greenlet-3.1.0-cp37-cp37m-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:13ff8c8e54a10472ce3b2a2da007f915175192f18e6495bad50486e87c7f6637"}, - {file = "greenlet-3.1.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:f9671e7282d8c6fcabc32c0fb8d7c0ea8894ae85cee89c9aadc2d7129e1a9954"}, - {file = "greenlet-3.1.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:184258372ae9e1e9bddce6f187967f2e08ecd16906557c4320e3ba88a93438c3"}, - {file = "greenlet-3.1.0-cp37-cp37m-win32.whl", hash = "sha256:a0409bc18a9f85321399c29baf93545152d74a49d92f2f55302f122007cfda00"}, - {file = "greenlet-3.1.0-cp37-cp37m-win_amd64.whl", hash = "sha256:9eb4a1d7399b9f3c7ac68ae6baa6be5f9195d1d08c9ddc45ad559aa6b556bce6"}, - {file = "greenlet-3.1.0-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:a8870983af660798dc1b529e1fd6f1cefd94e45135a32e58bd70edd694540f33"}, - {file = "greenlet-3.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cfcfb73aed40f550a57ea904629bdaf2e562c68fa1164fa4588e752af6efdc3f"}, - {file = "greenlet-3.1.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f9482c2ed414781c0af0b35d9d575226da6b728bd1a720668fa05837184965b7"}, - {file = "greenlet-3.1.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d58ec349e0c2c0bc6669bf2cd4982d2f93bf067860d23a0ea1fe677b0f0b1e09"}, - {file = "greenlet-3.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd65695a8df1233309b701dec2539cc4b11e97d4fcc0f4185b4a12ce54db0491"}, - {file = "greenlet-3.1.0-cp38-cp38-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:665b21e95bc0fce5cab03b2e1d90ba9c66c510f1bb5fdc864f3a377d0f553f6b"}, - {file = "greenlet-3.1.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:d3c59a06c2c28a81a026ff11fbf012081ea34fb9b7052f2ed0366e14896f0a1d"}, - {file = "greenlet-3.1.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:5415b9494ff6240b09af06b91a375731febe0090218e2898d2b85f9b92abcda0"}, - {file = "greenlet-3.1.0-cp38-cp38-win32.whl", hash = "sha256:1544b8dd090b494c55e60c4ff46e238be44fdc472d2589e943c241e0169bcea2"}, - {file = "greenlet-3.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:7f346d24d74c00b6730440f5eb8ec3fe5774ca8d1c9574e8e57c8671bb51b910"}, - {file = "greenlet-3.1.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:db1b3ccb93488328c74e97ff888604a8b95ae4f35f4f56677ca57a4fc3a4220b"}, - {file = "greenlet-3.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:44cd313629ded43bb3b98737bba2f3e2c2c8679b55ea29ed73daea6b755fe8e7"}, - {file = "greenlet-3.1.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fad7a051e07f64e297e6e8399b4d6a3bdcad3d7297409e9a06ef8cbccff4f501"}, - {file = "greenlet-3.1.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c3967dcc1cd2ea61b08b0b276659242cbce5caca39e7cbc02408222fb9e6ff39"}, - {file = "greenlet-3.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d45b75b0f3fd8d99f62eb7908cfa6d727b7ed190737dec7fe46d993da550b81a"}, - {file = "greenlet-3.1.0-cp39-cp39-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2d004db911ed7b6218ec5c5bfe4cf70ae8aa2223dffbb5b3c69e342bb253cb28"}, - {file = "greenlet-3.1.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b9505a0c8579899057cbefd4ec34d865ab99852baf1ff33a9481eb3924e2da0b"}, - {file = "greenlet-3.1.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5fd6e94593f6f9714dbad1aaba734b5ec04593374fa6638df61592055868f8b8"}, - {file = "greenlet-3.1.0-cp39-cp39-win32.whl", hash = "sha256:d0dd943282231480aad5f50f89bdf26690c995e8ff555f26d8a5b9887b559bcc"}, - {file = "greenlet-3.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:ac0adfdb3a21dc2a24ed728b61e72440d297d0fd3a577389df566651fcd08f97"}, - {file = "greenlet-3.1.0.tar.gz", hash = "sha256:b395121e9bbe8d02a750886f108d540abe66075e61e22f7353d9acb0b81be0f0"}, + {file = "greenlet-3.1.1-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:0bbae94a29c9e5c7e4a2b7f0aae5c17e8e90acbfd3bf6270eeba60c39fce3563"}, + {file = "greenlet-3.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0fde093fb93f35ca72a556cf72c92ea3ebfda3d79fc35bb19fbe685853869a83"}, + {file = "greenlet-3.1.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:36b89d13c49216cadb828db8dfa6ce86bbbc476a82d3a6c397f0efae0525bdd0"}, + {file = "greenlet-3.1.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:94b6150a85e1b33b40b1464a3f9988dcc5251d6ed06842abff82e42632fac120"}, + {file = "greenlet-3.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:93147c513fac16385d1036b7e5b102c7fbbdb163d556b791f0f11eada7ba65dc"}, + {file = "greenlet-3.1.1-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:da7a9bff22ce038e19bf62c4dd1ec8391062878710ded0a845bcf47cc0200617"}, + {file = "greenlet-3.1.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b2795058c23988728eec1f36a4e5e4ebad22f8320c85f3587b539b9ac84128d7"}, + {file = "greenlet-3.1.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:ed10eac5830befbdd0c32f83e8aa6288361597550ba669b04c48f0f9a2c843c6"}, + {file = "greenlet-3.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:77c386de38a60d1dfb8e55b8c1101d68c79dfdd25c7095d51fec2dd800892b80"}, + {file = "greenlet-3.1.1-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:e4d333e558953648ca09d64f13e6d8f0523fa705f51cae3f03b5983489958c70"}, + {file = "greenlet-3.1.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:09fc016b73c94e98e29af67ab7b9a879c307c6731a2c9da0db5a7d9b7edd1159"}, + {file = "greenlet-3.1.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d5e975ca70269d66d17dd995dafc06f1b06e8cb1ec1e9ed54c1d1e4a7c4cf26e"}, + {file = "greenlet-3.1.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3b2813dc3de8c1ee3f924e4d4227999285fd335d1bcc0d2be6dc3f1f6a318ec1"}, + {file = "greenlet-3.1.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e347b3bfcf985a05e8c0b7d462ba6f15b1ee1c909e2dcad795e49e91b152c383"}, + {file = "greenlet-3.1.1-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9e8f8c9cb53cdac7ba9793c276acd90168f416b9ce36799b9b885790f8ad6c0a"}, + {file = "greenlet-3.1.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:62ee94988d6b4722ce0028644418d93a52429e977d742ca2ccbe1c4f4a792511"}, + {file = "greenlet-3.1.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:1776fd7f989fc6b8d8c8cb8da1f6b82c5814957264d1f6cf818d475ec2bf6395"}, + {file = "greenlet-3.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:48ca08c771c268a768087b408658e216133aecd835c0ded47ce955381105ba39"}, + {file = "greenlet-3.1.1-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:4afe7ea89de619adc868e087b4d2359282058479d7cfb94970adf4b55284574d"}, + {file = "greenlet-3.1.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f406b22b7c9a9b4f8aa9d2ab13d6ae0ac3e85c9a809bd590ad53fed2bf70dc79"}, + {file = "greenlet-3.1.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c3a701fe5a9695b238503ce5bbe8218e03c3bcccf7e204e455e7462d770268aa"}, + {file = "greenlet-3.1.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2846930c65b47d70b9d178e89c7e1a69c95c1f68ea5aa0a58646b7a96df12441"}, + {file = "greenlet-3.1.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:99cfaa2110534e2cf3ba31a7abcac9d328d1d9f1b95beede58294a60348fba36"}, + {file = "greenlet-3.1.1-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1443279c19fca463fc33e65ef2a935a5b09bb90f978beab37729e1c3c6c25fe9"}, + {file = "greenlet-3.1.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b7cede291382a78f7bb5f04a529cb18e068dd29e0fb27376074b6d0317bf4dd0"}, + {file = "greenlet-3.1.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:23f20bb60ae298d7d8656c6ec6db134bca379ecefadb0b19ce6f19d1f232a942"}, + {file = "greenlet-3.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:7124e16b4c55d417577c2077be379514321916d5790fa287c9ed6f23bd2ffd01"}, + {file = "greenlet-3.1.1-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:05175c27cb459dcfc05d026c4232f9de8913ed006d42713cb8a5137bd49375f1"}, + {file = "greenlet-3.1.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:935e943ec47c4afab8965954bf49bfa639c05d4ccf9ef6e924188f762145c0ff"}, + {file = "greenlet-3.1.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:667a9706c970cb552ede35aee17339a18e8f2a87a51fba2ed39ceeeb1004798a"}, + {file = "greenlet-3.1.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b8a678974d1f3aa55f6cc34dc480169d58f2e6d8958895d68845fa4ab566509e"}, + {file = "greenlet-3.1.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:efc0f674aa41b92da8c49e0346318c6075d734994c3c4e4430b1c3f853e498e4"}, + {file = "greenlet-3.1.1-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0153404a4bb921f0ff1abeb5ce8a5131da56b953eda6e14b88dc6bbc04d2049e"}, + {file = "greenlet-3.1.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:275f72decf9932639c1c6dd1013a1bc266438eb32710016a1c742df5da6e60a1"}, + {file = "greenlet-3.1.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:c4aab7f6381f38a4b42f269057aee279ab0fc7bf2e929e3d4abfae97b682a12c"}, + {file = "greenlet-3.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:b42703b1cf69f2aa1df7d1030b9d77d3e584a70755674d60e710f0af570f3761"}, + {file = "greenlet-3.1.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f1695e76146579f8c06c1509c7ce4dfe0706f49c6831a817ac04eebb2fd02011"}, + {file = "greenlet-3.1.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7876452af029456b3f3549b696bb36a06db7c90747740c5302f74a9e9fa14b13"}, + {file = "greenlet-3.1.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4ead44c85f8ab905852d3de8d86f6f8baf77109f9da589cb4fa142bd3b57b475"}, + {file = "greenlet-3.1.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8320f64b777d00dd7ccdade271eaf0cad6636343293a25074cc5566160e4de7b"}, + {file = "greenlet-3.1.1-cp313-cp313t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6510bf84a6b643dabba74d3049ead221257603a253d0a9873f55f6a59a65f822"}, + {file = "greenlet-3.1.1-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:04b013dc07c96f83134b1e99888e7a79979f1a247e2a9f59697fa14b5862ed01"}, + {file = "greenlet-3.1.1-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:411f015496fec93c1c8cd4e5238da364e1da7a124bcb293f085bf2860c32c6f6"}, + {file = "greenlet-3.1.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:47da355d8687fd65240c364c90a31569a133b7b60de111c255ef5b606f2ae291"}, + {file = "greenlet-3.1.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:98884ecf2ffb7d7fe6bd517e8eb99d31ff7855a840fa6d0d63cd07c037f6a981"}, + {file = "greenlet-3.1.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f1d4aeb8891338e60d1ab6127af1fe45def5259def8094b9c7e34690c8858803"}, + {file = "greenlet-3.1.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db32b5348615a04b82240cc67983cb315309e88d444a288934ee6ceaebcad6cc"}, + {file = "greenlet-3.1.1-cp37-cp37m-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dcc62f31eae24de7f8dce72134c8651c58000d3b1868e01392baea7c32c247de"}, + {file = "greenlet-3.1.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:1d3755bcb2e02de341c55b4fca7a745a24a9e7212ac953f6b3a48d117d7257aa"}, + {file = "greenlet-3.1.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:b8da394b34370874b4572676f36acabac172602abf054cbc4ac910219f3340af"}, + {file = "greenlet-3.1.1-cp37-cp37m-win32.whl", hash = "sha256:a0dfc6c143b519113354e780a50381508139b07d2177cb6ad6a08278ec655798"}, + {file = "greenlet-3.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:54558ea205654b50c438029505def3834e80f0869a70fb15b871c29b4575ddef"}, + {file = "greenlet-3.1.1-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:346bed03fe47414091be4ad44786d1bd8bef0c3fcad6ed3dee074a032ab408a9"}, + {file = "greenlet-3.1.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dfc59d69fc48664bc693842bd57acfdd490acafda1ab52c7836e3fc75c90a111"}, + {file = "greenlet-3.1.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d21e10da6ec19b457b82636209cbe2331ff4306b54d06fa04b7c138ba18c8a81"}, + {file = "greenlet-3.1.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:37b9de5a96111fc15418819ab4c4432e4f3c2ede61e660b1e33971eba26ef9ba"}, + {file = "greenlet-3.1.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6ef9ea3f137e5711f0dbe5f9263e8c009b7069d8a1acea822bd5e9dae0ae49c8"}, + {file = "greenlet-3.1.1-cp38-cp38-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:85f3ff71e2e60bd4b4932a043fbbe0f499e263c628390b285cb599154a3b03b1"}, + {file = "greenlet-3.1.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:95ffcf719966dd7c453f908e208e14cde192e09fde6c7186c8f1896ef778d8cd"}, + {file = "greenlet-3.1.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:03a088b9de532cbfe2ba2034b2b85e82df37874681e8c470d6fb2f8c04d7e4b7"}, + {file = "greenlet-3.1.1-cp38-cp38-win32.whl", hash = "sha256:8b8b36671f10ba80e159378df9c4f15c14098c4fd73a36b9ad715f057272fbef"}, + {file = "greenlet-3.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:7017b2be767b9d43cc31416aba48aab0d2309ee31b4dbf10a1d38fb7972bdf9d"}, + {file = "greenlet-3.1.1-cp39-cp39-macosx_11_0_universal2.whl", hash = "sha256:396979749bd95f018296af156201d6211240e7a23090f50a8d5d18c370084dc3"}, + {file = "greenlet-3.1.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca9d0ff5ad43e785350894d97e13633a66e2b50000e8a183a50a88d834752d42"}, + {file = "greenlet-3.1.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f6ff3b14f2df4c41660a7dec01045a045653998784bf8cfcb5a525bdffffbc8f"}, + {file = "greenlet-3.1.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:94ebba31df2aa506d7b14866fed00ac141a867e63143fe5bca82a8e503b36437"}, + {file = "greenlet-3.1.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:73aaad12ac0ff500f62cebed98d8789198ea0e6f233421059fa68a5aa7220145"}, + {file = "greenlet-3.1.1-cp39-cp39-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:63e4844797b975b9af3a3fb8f7866ff08775f5426925e1e0bbcfe7932059a12c"}, + {file = "greenlet-3.1.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:7939aa3ca7d2a1593596e7ac6d59391ff30281ef280d8632fa03d81f7c5f955e"}, + {file = "greenlet-3.1.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d0028e725ee18175c6e422797c407874da24381ce0690d6b9396c204c7f7276e"}, + {file = "greenlet-3.1.1-cp39-cp39-win32.whl", hash = "sha256:5e06afd14cbaf9e00899fae69b24a32f2196c19de08fcb9f4779dd4f004e5e7c"}, + {file = "greenlet-3.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:3319aa75e0e0639bc15ff54ca327e8dc7a6fe404003496e3c6925cd3142e0e22"}, + {file = "greenlet-3.1.1.tar.gz", hash = "sha256:4ce3ac6cdb6adf7946475d7ef31777c26d94bccc377e070a7986bd2d5c515467"}, ] [package.extras] @@ -921,13 +928,13 @@ i18n = ["Babel (>=2.7)"] [[package]] name = "keyring" -version = "25.4.0" +version = "25.4.1" description = "Store and access your passwords safely." optional = false python-versions = ">=3.8" files = [ - {file = "keyring-25.4.0-py3-none-any.whl", hash = "sha256:a2a630d5c9bef5d3f0968d15ef4e42b894a83e17494edcb67b154c36491c9920"}, - {file = "keyring-25.4.0.tar.gz", hash = "sha256:ae8263fd9264c94f91ad82d098f8a5bb1b7fa71ce0a72388dc4fc0be3f6a034e"}, + {file = "keyring-25.4.1-py3-none-any.whl", hash = "sha256:5426f817cf7f6f007ba5ec722b1bcad95a75b27d780343772ad76b17cb47b0bf"}, + {file = "keyring-25.4.1.tar.gz", hash = "sha256:b07ebc55f3e8ed86ac81dd31ef14e81ace9dd9c3d4b5d77a6e9a2016d0d71a1b"}, ] [package.dependencies] @@ -1137,6 +1144,60 @@ files = [ {file = "nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f"}, ] +[[package]] +name = "numpy" +version = "2.0.2" +description = "Fundamental package for array computing in Python" +optional = false +python-versions = ">=3.9" +files = [ + {file = "numpy-2.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:51129a29dbe56f9ca83438b706e2e69a39892b5eda6cedcb6b0c9fdc9b0d3ece"}, + {file = "numpy-2.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f15975dfec0cf2239224d80e32c3170b1d168335eaedee69da84fbe9f1f9cd04"}, + {file = "numpy-2.0.2-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:8c5713284ce4e282544c68d1c3b2c7161d38c256d2eefc93c1d683cf47683e66"}, + {file = "numpy-2.0.2-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:becfae3ddd30736fe1889a37f1f580e245ba79a5855bff5f2a29cb3ccc22dd7b"}, + {file = "numpy-2.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2da5960c3cf0df7eafefd806d4e612c5e19358de82cb3c343631188991566ccd"}, + {file = "numpy-2.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:496f71341824ed9f3d2fd36cf3ac57ae2e0165c143b55c3a035ee219413f3318"}, + {file = "numpy-2.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a61ec659f68ae254e4d237816e33171497e978140353c0c2038d46e63282d0c8"}, + {file = "numpy-2.0.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d731a1c6116ba289c1e9ee714b08a8ff882944d4ad631fd411106a30f083c326"}, + {file = "numpy-2.0.2-cp310-cp310-win32.whl", hash = "sha256:984d96121c9f9616cd33fbd0618b7f08e0cfc9600a7ee1d6fd9b239186d19d97"}, + {file = "numpy-2.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:c7b0be4ef08607dd04da4092faee0b86607f111d5ae68036f16cc787e250a131"}, + {file = "numpy-2.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:49ca4decb342d66018b01932139c0961a8f9ddc7589611158cb3c27cbcf76448"}, + {file = "numpy-2.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:11a76c372d1d37437857280aa142086476136a8c0f373b2e648ab2c8f18fb195"}, + {file = "numpy-2.0.2-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:807ec44583fd708a21d4a11d94aedf2f4f3c3719035c76a2bbe1fe8e217bdc57"}, + {file = "numpy-2.0.2-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:8cafab480740e22f8d833acefed5cc87ce276f4ece12fdaa2e8903db2f82897a"}, + {file = "numpy-2.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a15f476a45e6e5a3a79d8a14e62161d27ad897381fecfa4a09ed5322f2085669"}, + {file = "numpy-2.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:13e689d772146140a252c3a28501da66dfecd77490b498b168b501835041f951"}, + {file = "numpy-2.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9ea91dfb7c3d1c56a0e55657c0afb38cf1eeae4544c208dc465c3c9f3a7c09f9"}, + {file = "numpy-2.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c1c9307701fec8f3f7a1e6711f9089c06e6284b3afbbcd259f7791282d660a15"}, + {file = "numpy-2.0.2-cp311-cp311-win32.whl", hash = "sha256:a392a68bd329eafac5817e5aefeb39038c48b671afd242710b451e76090e81f4"}, + {file = "numpy-2.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:286cd40ce2b7d652a6f22efdfc6d1edf879440e53e76a75955bc0c826c7e64dc"}, + {file = "numpy-2.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:df55d490dea7934f330006d0f81e8551ba6010a5bf035a249ef61a94f21c500b"}, + {file = "numpy-2.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8df823f570d9adf0978347d1f926b2a867d5608f434a7cff7f7908c6570dcf5e"}, + {file = "numpy-2.0.2-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:9a92ae5c14811e390f3767053ff54eaee3bf84576d99a2456391401323f4ec2c"}, + {file = "numpy-2.0.2-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:a842d573724391493a97a62ebbb8e731f8a5dcc5d285dfc99141ca15a3302d0c"}, + {file = "numpy-2.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c05e238064fc0610c840d1cf6a13bf63d7e391717d247f1bf0318172e759e692"}, + {file = "numpy-2.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0123ffdaa88fa4ab64835dcbde75dcdf89c453c922f18dced6e27c90d1d0ec5a"}, + {file = "numpy-2.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:96a55f64139912d61de9137f11bf39a55ec8faec288c75a54f93dfd39f7eb40c"}, + {file = "numpy-2.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ec9852fb39354b5a45a80bdab5ac02dd02b15f44b3804e9f00c556bf24b4bded"}, + {file = "numpy-2.0.2-cp312-cp312-win32.whl", hash = "sha256:671bec6496f83202ed2d3c8fdc486a8fc86942f2e69ff0e986140339a63bcbe5"}, + {file = "numpy-2.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:cfd41e13fdc257aa5778496b8caa5e856dc4896d4ccf01841daee1d96465467a"}, + {file = "numpy-2.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9059e10581ce4093f735ed23f3b9d283b9d517ff46009ddd485f1747eb22653c"}, + {file = "numpy-2.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:423e89b23490805d2a5a96fe40ec507407b8ee786d66f7328be214f9679df6dd"}, + {file = "numpy-2.0.2-cp39-cp39-macosx_14_0_arm64.whl", hash = "sha256:2b2955fa6f11907cf7a70dab0d0755159bca87755e831e47932367fc8f2f2d0b"}, + {file = "numpy-2.0.2-cp39-cp39-macosx_14_0_x86_64.whl", hash = "sha256:97032a27bd9d8988b9a97a8c4d2c9f2c15a81f61e2f21404d7e8ef00cb5be729"}, + {file = "numpy-2.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1e795a8be3ddbac43274f18588329c72939870a16cae810c2b73461c40718ab1"}, + {file = "numpy-2.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f26b258c385842546006213344c50655ff1555a9338e2e5e02a0756dc3e803dd"}, + {file = "numpy-2.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5fec9451a7789926bcf7c2b8d187292c9f93ea30284802a0ab3f5be8ab36865d"}, + {file = "numpy-2.0.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:9189427407d88ff25ecf8f12469d4d39d35bee1db5d39fc5c168c6f088a6956d"}, + {file = "numpy-2.0.2-cp39-cp39-win32.whl", hash = "sha256:905d16e0c60200656500c95b6b8dca5d109e23cb24abc701d41c02d74c6b3afa"}, + {file = "numpy-2.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:a3f4ab0caa7f053f6797fcd4e1e25caee367db3112ef2b6ef82d749530768c73"}, + {file = "numpy-2.0.2-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:7f0a0c6f12e07fa94133c8a67404322845220c06a9e80e85999afe727f7438b8"}, + {file = "numpy-2.0.2-pp39-pypy39_pp73-macosx_14_0_x86_64.whl", hash = "sha256:312950fdd060354350ed123c0e25a71327d3711584beaef30cdaa93320c392d4"}, + {file = "numpy-2.0.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:26df23238872200f63518dd2aa984cfca675d82469535dc7162dc2ee52d9dd5c"}, + {file = "numpy-2.0.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:a46288ec55ebbd58947d31d72be2c63cbf839f0a63b49cb755022310792a3385"}, + {file = "numpy-2.0.2.tar.gz", hash = "sha256:883c987dee1880e2a864ab0dc9892292582510604156762362d9326444636e78"}, +] + [[package]] name = "numpy" version = "2.1.1" @@ -1226,40 +1287,53 @@ files = [ [[package]] name = "pandas" -version = "2.2.2" +version = "2.2.3" description = "Powerful data structures for data analysis, time series, and statistics" optional = false python-versions = ">=3.9" files = [ - {file = "pandas-2.2.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:90c6fca2acf139569e74e8781709dccb6fe25940488755716d1d354d6bc58bce"}, - {file = "pandas-2.2.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c7adfc142dac335d8c1e0dcbd37eb8617eac386596eb9e1a1b77791cf2498238"}, - {file = "pandas-2.2.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4abfe0be0d7221be4f12552995e58723c7422c80a659da13ca382697de830c08"}, - {file = "pandas-2.2.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8635c16bf3d99040fdf3ca3db669a7250ddf49c55dc4aa8fe0ae0fa8d6dcc1f0"}, - {file = "pandas-2.2.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:40ae1dffb3967a52203105a077415a86044a2bea011b5f321c6aa64b379a3f51"}, - {file = "pandas-2.2.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8e5a0b00e1e56a842f922e7fae8ae4077aee4af0acb5ae3622bd4b4c30aedf99"}, - {file = "pandas-2.2.2-cp310-cp310-win_amd64.whl", hash = "sha256:ddf818e4e6c7c6f4f7c8a12709696d193976b591cc7dc50588d3d1a6b5dc8772"}, - {file = "pandas-2.2.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:696039430f7a562b74fa45f540aca068ea85fa34c244d0deee539cb6d70aa288"}, - {file = "pandas-2.2.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8e90497254aacacbc4ea6ae5e7a8cd75629d6ad2b30025a4a8b09aa4faf55151"}, - {file = "pandas-2.2.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:58b84b91b0b9f4bafac2a0ac55002280c094dfc6402402332c0913a59654ab2b"}, - {file = "pandas-2.2.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d2123dc9ad6a814bcdea0f099885276b31b24f7edf40f6cdbc0912672e22eee"}, - {file = "pandas-2.2.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:2925720037f06e89af896c70bca73459d7e6a4be96f9de79e2d440bd499fe0db"}, - {file = "pandas-2.2.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:0cace394b6ea70c01ca1595f839cf193df35d1575986e484ad35c4aeae7266c1"}, - {file = "pandas-2.2.2-cp311-cp311-win_amd64.whl", hash = "sha256:873d13d177501a28b2756375d59816c365e42ed8417b41665f346289adc68d24"}, - {file = "pandas-2.2.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:9dfde2a0ddef507a631dc9dc4af6a9489d5e2e740e226ad426a05cabfbd7c8ef"}, - {file = "pandas-2.2.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:e9b79011ff7a0f4b1d6da6a61aa1aa604fb312d6647de5bad20013682d1429ce"}, - {file = "pandas-2.2.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1cb51fe389360f3b5a4d57dbd2848a5f033350336ca3b340d1c53a1fad33bcad"}, - {file = "pandas-2.2.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eee3a87076c0756de40b05c5e9a6069c035ba43e8dd71c379e68cab2c20f16ad"}, - {file = "pandas-2.2.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:3e374f59e440d4ab45ca2fffde54b81ac3834cf5ae2cdfa69c90bc03bde04d76"}, - {file = "pandas-2.2.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:43498c0bdb43d55cb162cdc8c06fac328ccb5d2eabe3cadeb3529ae6f0517c32"}, - {file = "pandas-2.2.2-cp312-cp312-win_amd64.whl", hash = "sha256:d187d355ecec3629624fccb01d104da7d7f391db0311145817525281e2804d23"}, - {file = "pandas-2.2.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:0ca6377b8fca51815f382bd0b697a0814c8bda55115678cbc94c30aacbb6eff2"}, - {file = "pandas-2.2.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9057e6aa78a584bc93a13f0a9bf7e753a5e9770a30b4d758b8d5f2a62a9433cd"}, - {file = "pandas-2.2.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:001910ad31abc7bf06f49dcc903755d2f7f3a9186c0c040b827e522e9cef0863"}, - {file = "pandas-2.2.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:66b479b0bd07204e37583c191535505410daa8df638fd8e75ae1b383851fe921"}, - {file = "pandas-2.2.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:a77e9d1c386196879aa5eb712e77461aaee433e54c68cf253053a73b7e49c33a"}, - {file = "pandas-2.2.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:92fd6b027924a7e178ac202cfbe25e53368db90d56872d20ffae94b96c7acc57"}, - {file = "pandas-2.2.2-cp39-cp39-win_amd64.whl", hash = "sha256:640cef9aa381b60e296db324337a554aeeb883ead99dc8f6c18e81a93942f5f4"}, - {file = "pandas-2.2.2.tar.gz", hash = "sha256:9e79019aba43cb4fda9e4d983f8e88ca0373adbb697ae9c6c43093218de28b54"}, + {file = "pandas-2.2.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1948ddde24197a0f7add2bdc4ca83bf2b1ef84a1bc8ccffd95eda17fd836ecb5"}, + {file = "pandas-2.2.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:381175499d3802cde0eabbaf6324cce0c4f5d52ca6f8c377c29ad442f50f6348"}, + {file = "pandas-2.2.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d9c45366def9a3dd85a6454c0e7908f2b3b8e9c138f5dc38fed7ce720d8453ed"}, + {file = "pandas-2.2.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:86976a1c5b25ae3f8ccae3a5306e443569ee3c3faf444dfd0f41cda24667ad57"}, + {file = "pandas-2.2.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:b8661b0238a69d7aafe156b7fa86c44b881387509653fdf857bebc5e4008ad42"}, + {file = "pandas-2.2.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:37e0aced3e8f539eccf2e099f65cdb9c8aa85109b0be6e93e2baff94264bdc6f"}, + {file = "pandas-2.2.3-cp310-cp310-win_amd64.whl", hash = "sha256:56534ce0746a58afaf7942ba4863e0ef81c9c50d3f0ae93e9497d6a41a057645"}, + {file = "pandas-2.2.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:66108071e1b935240e74525006034333f98bcdb87ea116de573a6a0dccb6c039"}, + {file = "pandas-2.2.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7c2875855b0ff77b2a64a0365e24455d9990730d6431b9e0ee18ad8acee13dbd"}, + {file = "pandas-2.2.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cd8d0c3be0515c12fed0bdbae072551c8b54b7192c7b1fda0ba56059a0179698"}, + {file = "pandas-2.2.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c124333816c3a9b03fbeef3a9f230ba9a737e9e5bb4060aa2107a86cc0a497fc"}, + {file = "pandas-2.2.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:63cc132e40a2e084cf01adf0775b15ac515ba905d7dcca47e9a251819c575ef3"}, + {file = "pandas-2.2.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:29401dbfa9ad77319367d36940cd8a0b3a11aba16063e39632d98b0e931ddf32"}, + {file = "pandas-2.2.3-cp311-cp311-win_amd64.whl", hash = "sha256:3fc6873a41186404dad67245896a6e440baacc92f5b716ccd1bc9ed2995ab2c5"}, + {file = "pandas-2.2.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b1d432e8d08679a40e2a6d8b2f9770a5c21793a6f9f47fdd52c5ce1948a5a8a9"}, + {file = "pandas-2.2.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a5a1595fe639f5988ba6a8e5bc9649af3baf26df3998a0abe56c02609392e0a4"}, + {file = "pandas-2.2.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5de54125a92bb4d1c051c0659e6fcb75256bf799a732a87184e5ea503965bce3"}, + {file = "pandas-2.2.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fffb8ae78d8af97f849404f21411c95062db1496aeb3e56f146f0355c9989319"}, + {file = "pandas-2.2.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6dfcb5ee8d4d50c06a51c2fffa6cff6272098ad6540aed1a76d15fb9318194d8"}, + {file = "pandas-2.2.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:062309c1b9ea12a50e8ce661145c6aab431b1e99530d3cd60640e255778bd43a"}, + {file = "pandas-2.2.3-cp312-cp312-win_amd64.whl", hash = "sha256:59ef3764d0fe818125a5097d2ae867ca3fa64df032331b7e0917cf5d7bf66b13"}, + {file = "pandas-2.2.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f00d1345d84d8c86a63e476bb4955e46458b304b9575dcf71102b5c705320015"}, + {file = "pandas-2.2.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3508d914817e153ad359d7e069d752cdd736a247c322d932eb89e6bc84217f28"}, + {file = "pandas-2.2.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:22a9d949bfc9a502d320aa04e5d02feab689d61da4e7764b62c30b991c42c5f0"}, + {file = "pandas-2.2.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f3a255b2c19987fbbe62a9dfd6cff7ff2aa9ccab3fc75218fd4b7530f01efa24"}, + {file = "pandas-2.2.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:800250ecdadb6d9c78eae4990da62743b857b470883fa27f652db8bdde7f6659"}, + {file = "pandas-2.2.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6374c452ff3ec675a8f46fd9ab25c4ad0ba590b71cf0656f8b6daa5202bca3fb"}, + {file = "pandas-2.2.3-cp313-cp313-win_amd64.whl", hash = "sha256:61c5ad4043f791b61dd4752191d9f07f0ae412515d59ba8f005832a532f8736d"}, + {file = "pandas-2.2.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:3b71f27954685ee685317063bf13c7709a7ba74fc996b84fc6821c59b0f06468"}, + {file = "pandas-2.2.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:38cf8125c40dae9d5acc10fa66af8ea6fdf760b2714ee482ca691fc66e6fcb18"}, + {file = "pandas-2.2.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ba96630bc17c875161df3818780af30e43be9b166ce51c9a18c1feae342906c2"}, + {file = "pandas-2.2.3-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1db71525a1538b30142094edb9adc10be3f3e176748cd7acc2240c2f2e5aa3a4"}, + {file = "pandas-2.2.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:15c0e1e02e93116177d29ff83e8b1619c93ddc9c49083f237d4312337a61165d"}, + {file = "pandas-2.2.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:ad5b65698ab28ed8d7f18790a0dc58005c7629f227be9ecc1072aa74c0c1d43a"}, + {file = "pandas-2.2.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:bc6b93f9b966093cb0fd62ff1a7e4c09e6d546ad7c1de191767baffc57628f39"}, + {file = "pandas-2.2.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5dbca4c1acd72e8eeef4753eeca07de9b1db4f398669d5994086f788a5d7cc30"}, + {file = "pandas-2.2.3-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8cd6d7cc958a3910f934ea8dbdf17b2364827bb4dafc38ce6eef6bb3d65ff09c"}, + {file = "pandas-2.2.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:99df71520d25fade9db7c1076ac94eb994f4d2673ef2aa2e86ee039b6746d20c"}, + {file = "pandas-2.2.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:31d0ced62d4ea3e231a9f228366919a5ea0b07440d9d4dac345376fd8e1477ea"}, + {file = "pandas-2.2.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:7eee9e7cea6adf3e3d24e304ac6b8300646e2a5d1cd3a3c2abed9101b0846761"}, + {file = "pandas-2.2.3-cp39-cp39-win_amd64.whl", hash = "sha256:4850ba03528b6dd51d6c5d273c46f183f39a9baf3f0143e566b89450965b105e"}, + {file = "pandas-2.2.3.tar.gz", hash = "sha256:4f18ba62b61d7e192368b84517265a99b4d7ee8912f8708660fb4a366cc82667"}, ] [package.dependencies] @@ -1861,18 +1935,15 @@ docs = ["sphinx"] [[package]] name = "python-multipart" -version = "0.0.9" +version = "0.0.10" description = "A streaming multipart parser for Python" optional = false python-versions = ">=3.8" files = [ - {file = "python_multipart-0.0.9-py3-none-any.whl", hash = "sha256:97ca7b8ea7b05f977dc3849c3ba99d51689822fab725c3703af7c866a0c2b215"}, - {file = "python_multipart-0.0.9.tar.gz", hash = "sha256:03f54688c663f1b7977105f021043b0793151e4cb1c1a9d4a11fc13d622c4026"}, + {file = "python_multipart-0.0.10-py3-none-any.whl", hash = "sha256:2b06ad9e8d50c7a8db80e3b56dab590137b323410605af2be20d62a5f1ba1dc8"}, + {file = "python_multipart-0.0.10.tar.gz", hash = "sha256:46eb3c6ce6fdda5fb1a03c7e11d490e407c6930a2703fe7aef4da71c374688fa"}, ] -[package.extras] -dev = ["atomicwrites (==1.4.1)", "attrs (==23.2.0)", "coverage (==7.4.1)", "hatch", "invoke (==2.2.0)", "more-itertools (==10.2.0)", "pbr (==6.0.0)", "pluggy (==1.4.0)", "py (==1.11.0)", "pytest (==8.0.0)", "pytest-cov (==4.1.0)", "pytest-timeout (==2.2.0)", "pyyaml (==6.0.1)", "ruff (==0.2.1)"] - [[package]] name = "python-socketio" version = "5.11.4" @@ -2161,13 +2232,13 @@ jeepney = ">=0.6" [[package]] name = "selenium" -version = "4.24.0" +version = "4.25.0" description = "Official Python bindings for Selenium WebDriver" optional = false python-versions = ">=3.8" files = [ - {file = "selenium-4.24.0-py3-none-any.whl", hash = "sha256:42c23f60753d5415b261b236cecbd69bd4eb5271e1563915f546b443cb6b71c6"}, - {file = "selenium-4.24.0.tar.gz", hash = "sha256:88281e5b5b90fe231868905d5ea745b9ee5e30db280b33498cc73fb0fa06d571"}, + {file = "selenium-4.25.0-py3-none-any.whl", hash = "sha256:3798d2d12b4a570bc5790163ba57fef10b2afee958bf1d80f2a3cf07c4141f33"}, + {file = "selenium-4.25.0.tar.gz", hash = "sha256:95d08d3b82fb353f3c474895154516604c7f0e6a9a565ae6498ef36c9bac6921"}, ] [package.dependencies] @@ -2358,17 +2429,18 @@ SQLAlchemy = ">=2.0.14,<2.1.0" [[package]] name = "starlette" -version = "0.38.5" +version = "0.38.6" description = "The little ASGI library that shines." optional = false python-versions = ">=3.8" files = [ - {file = "starlette-0.38.5-py3-none-any.whl", hash = "sha256:632f420a9d13e3ee2a6f18f437b0a9f1faecb0bc42e1942aa2ea0e379a4c4206"}, - {file = "starlette-0.38.5.tar.gz", hash = "sha256:04a92830a9b6eb1442c766199d62260c3d4dc9c4f9188360626b1e0273cb7077"}, + {file = "starlette-0.38.6-py3-none-any.whl", hash = "sha256:4517a1409e2e73ee4951214ba012052b9e16f60e90d73cfb06192c19203bbb05"}, + {file = "starlette-0.38.6.tar.gz", hash = "sha256:863a1588f5574e70a821dadefb41e4881ea451a47a3cd1b4df359d4ffefe5ead"}, ] [package.dependencies] anyio = ">=3.4.0,<5" +typing-extensions = {version = ">=3.10.0", markers = "python_version < \"3.10\""} [package.extras] full = ["httpx (>=0.22.0)", "itsdangerous", "jinja2", "python-multipart (>=0.0.7)", "pyyaml"] @@ -2632,97 +2704,97 @@ test = ["websockets"] [[package]] name = "websockets" -version = "13.0.1" +version = "13.1" description = "An implementation of the WebSocket Protocol (RFC 6455 & 7692)" optional = false python-versions = ">=3.8" files = [ - {file = "websockets-13.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:1841c9082a3ba4a05ea824cf6d99570a6a2d8849ef0db16e9c826acb28089e8f"}, - {file = "websockets-13.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c5870b4a11b77e4caa3937142b650fbbc0914a3e07a0cf3131f35c0587489c1c"}, - {file = "websockets-13.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f1d3d1f2eb79fe7b0fb02e599b2bf76a7619c79300fc55f0b5e2d382881d4f7f"}, - {file = "websockets-13.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:15c7d62ee071fa94a2fc52c2b472fed4af258d43f9030479d9c4a2de885fd543"}, - {file = "websockets-13.0.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6724b554b70d6195ba19650fef5759ef11346f946c07dbbe390e039bcaa7cc3d"}, - {file = "websockets-13.0.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:56a952fa2ae57a42ba7951e6b2605e08a24801a4931b5644dfc68939e041bc7f"}, - {file = "websockets-13.0.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:17118647c0ea14796364299e942c330d72acc4b248e07e639d34b75067b3cdd8"}, - {file = "websockets-13.0.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:64a11aae1de4c178fa653b07d90f2fb1a2ed31919a5ea2361a38760192e1858b"}, - {file = "websockets-13.0.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:0617fd0b1d14309c7eab6ba5deae8a7179959861846cbc5cb528a7531c249448"}, - {file = "websockets-13.0.1-cp310-cp310-win32.whl", hash = "sha256:11f9976ecbc530248cf162e359a92f37b7b282de88d1d194f2167b5e7ad80ce3"}, - {file = "websockets-13.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:c3c493d0e5141ec055a7d6809a28ac2b88d5b878bb22df8c621ebe79a61123d0"}, - {file = "websockets-13.0.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:699ba9dd6a926f82a277063603fc8d586b89f4cb128efc353b749b641fcddda7"}, - {file = "websockets-13.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cf2fae6d85e5dc384bf846f8243ddaa9197f3a1a70044f59399af001fd1f51d4"}, - {file = "websockets-13.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:52aed6ef21a0f1a2a5e310fb5c42d7555e9c5855476bbd7173c3aa3d8a0302f2"}, - {file = "websockets-13.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8eb2b9a318542153674c6e377eb8cb9ca0fc011c04475110d3477862f15d29f0"}, - {file = "websockets-13.0.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5df891c86fe68b2c38da55b7aea7095beca105933c697d719f3f45f4220a5e0e"}, - {file = "websockets-13.0.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fac2d146ff30d9dd2fcf917e5d147db037a5c573f0446c564f16f1f94cf87462"}, - {file = "websockets-13.0.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b8ac5b46fd798bbbf2ac6620e0437c36a202b08e1f827832c4bf050da081b501"}, - {file = "websockets-13.0.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:46af561eba6f9b0848b2c9d2427086cabadf14e0abdd9fde9d72d447df268418"}, - {file = "websockets-13.0.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b5a06d7f60bc2fc378a333978470dfc4e1415ee52f5f0fce4f7853eb10c1e9df"}, - {file = "websockets-13.0.1-cp311-cp311-win32.whl", hash = "sha256:556e70e4f69be1082e6ef26dcb70efcd08d1850f5d6c5f4f2bcb4e397e68f01f"}, - {file = "websockets-13.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:67494e95d6565bf395476e9d040037ff69c8b3fa356a886b21d8422ad86ae075"}, - {file = "websockets-13.0.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:f9c9e258e3d5efe199ec23903f5da0eeaad58cf6fccb3547b74fd4750e5ac47a"}, - {file = "websockets-13.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:6b41a1b3b561f1cba8321fb32987552a024a8f67f0d05f06fcf29f0090a1b956"}, - {file = "websockets-13.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f73e676a46b0fe9426612ce8caeca54c9073191a77c3e9d5c94697aef99296af"}, - {file = "websockets-13.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f613289f4a94142f914aafad6c6c87903de78eae1e140fa769a7385fb232fdf"}, - {file = "websockets-13.0.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0f52504023b1480d458adf496dc1c9e9811df4ba4752f0bc1f89ae92f4f07d0c"}, - {file = "websockets-13.0.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:139add0f98206cb74109faf3611b7783ceafc928529c62b389917a037d4cfdf4"}, - {file = "websockets-13.0.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:47236c13be337ef36546004ce8c5580f4b1150d9538b27bf8a5ad8edf23ccfab"}, - {file = "websockets-13.0.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:c44ca9ade59b2e376612df34e837013e2b273e6c92d7ed6636d0556b6f4db93d"}, - {file = "websockets-13.0.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:9bbc525f4be3e51b89b2a700f5746c2a6907d2e2ef4513a8daafc98198b92237"}, - {file = "websockets-13.0.1-cp312-cp312-win32.whl", hash = "sha256:3624fd8664f2577cf8de996db3250662e259bfbc870dd8ebdcf5d7c6ac0b5185"}, - {file = "websockets-13.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:0513c727fb8adffa6d9bf4a4463b2bade0186cbd8c3604ae5540fae18a90cb99"}, - {file = "websockets-13.0.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:1ee4cc030a4bdab482a37462dbf3ffb7e09334d01dd37d1063be1136a0d825fa"}, - {file = "websockets-13.0.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:dbb0b697cc0655719522406c059eae233abaa3243821cfdfab1215d02ac10231"}, - {file = "websockets-13.0.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:acbebec8cb3d4df6e2488fbf34702cbc37fc39ac7abf9449392cefb3305562e9"}, - {file = "websockets-13.0.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63848cdb6fcc0bf09d4a155464c46c64ffdb5807ede4fb251da2c2692559ce75"}, - {file = "websockets-13.0.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:872afa52a9f4c414d6955c365b6588bc4401272c629ff8321a55f44e3f62b553"}, - {file = "websockets-13.0.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:05e70fec7c54aad4d71eae8e8cab50525e899791fc389ec6f77b95312e4e9920"}, - {file = "websockets-13.0.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e82db3756ccb66266504f5a3de05ac6b32f287faacff72462612120074103329"}, - {file = "websockets-13.0.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:4e85f46ce287f5c52438bb3703d86162263afccf034a5ef13dbe4318e98d86e7"}, - {file = "websockets-13.0.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f3fea72e4e6edb983908f0db373ae0732b275628901d909c382aae3b592589f2"}, - {file = "websockets-13.0.1-cp313-cp313-win32.whl", hash = "sha256:254ecf35572fca01a9f789a1d0f543898e222f7b69ecd7d5381d8d8047627bdb"}, - {file = "websockets-13.0.1-cp313-cp313-win_amd64.whl", hash = "sha256:ca48914cdd9f2ccd94deab5bcb5ac98025a5ddce98881e5cce762854a5de330b"}, - {file = "websockets-13.0.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:b74593e9acf18ea5469c3edaa6b27fa7ecf97b30e9dabd5a94c4c940637ab96e"}, - {file = "websockets-13.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:132511bfd42e77d152c919147078460c88a795af16b50e42a0bd14f0ad71ddd2"}, - {file = "websockets-13.0.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:165bedf13556f985a2aa064309baa01462aa79bf6112fbd068ae38993a0e1f1b"}, - {file = "websockets-13.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e801ca2f448850685417d723ec70298feff3ce4ff687c6f20922c7474b4746ae"}, - {file = "websockets-13.0.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:30d3a1f041360f029765d8704eae606781e673e8918e6b2c792e0775de51352f"}, - {file = "websockets-13.0.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:67648f5e50231b5a7f6d83b32f9c525e319f0ddc841be0de64f24928cd75a603"}, - {file = "websockets-13.0.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:4f0426d51c8f0926a4879390f53c7f5a855e42d68df95fff6032c82c888b5f36"}, - {file = "websockets-13.0.1-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:ef48e4137e8799998a343706531e656fdec6797b80efd029117edacb74b0a10a"}, - {file = "websockets-13.0.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:249aab278810bee585cd0d4de2f08cfd67eed4fc75bde623be163798ed4db2eb"}, - {file = "websockets-13.0.1-cp38-cp38-win32.whl", hash = "sha256:06c0a667e466fcb56a0886d924b5f29a7f0886199102f0a0e1c60a02a3751cb4"}, - {file = "websockets-13.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1f3cf6d6ec1142412d4535adabc6bd72a63f5f148c43fe559f06298bc21953c9"}, - {file = "websockets-13.0.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:1fa082ea38d5de51dd409434edc27c0dcbd5fed2b09b9be982deb6f0508d25bc"}, - {file = "websockets-13.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4a365bcb7be554e6e1f9f3ed64016e67e2fa03d7b027a33e436aecf194febb63"}, - {file = "websockets-13.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:10a0dc7242215d794fb1918f69c6bb235f1f627aaf19e77f05336d147fce7c37"}, - {file = "websockets-13.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:59197afd478545b1f73367620407b0083303569c5f2d043afe5363676f2697c9"}, - {file = "websockets-13.0.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7d20516990d8ad557b5abeb48127b8b779b0b7e6771a265fa3e91767596d7d97"}, - {file = "websockets-13.0.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a1a2e272d067030048e1fe41aa1ec8cfbbaabce733b3d634304fa2b19e5c897f"}, - {file = "websockets-13.0.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:ad327ac80ba7ee61da85383ca8822ff808ab5ada0e4a030d66703cc025b021c4"}, - {file = "websockets-13.0.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:518f90e6dd089d34eaade01101fd8a990921c3ba18ebbe9b0165b46ebff947f0"}, - {file = "websockets-13.0.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:68264802399aed6fe9652e89761031acc734fc4c653137a5911c2bfa995d6d6d"}, - {file = "websockets-13.0.1-cp39-cp39-win32.whl", hash = "sha256:a5dc0c42ded1557cc7c3f0240b24129aefbad88af4f09346164349391dea8e58"}, - {file = "websockets-13.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:b448a0690ef43db5ef31b3a0d9aea79043882b4632cfc3eaab20105edecf6097"}, - {file = "websockets-13.0.1-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:faef9ec6354fe4f9a2c0bbb52fb1ff852effc897e2a4501e25eb3a47cb0a4f89"}, - {file = "websockets-13.0.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:03d3f9ba172e0a53e37fa4e636b86cc60c3ab2cfee4935e66ed1d7acaa4625ad"}, - {file = "websockets-13.0.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d450f5a7a35662a9b91a64aefa852f0c0308ee256122f5218a42f1d13577d71e"}, - {file = "websockets-13.0.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3f55b36d17ac50aa8a171b771e15fbe1561217510c8768af3d546f56c7576cdc"}, - {file = "websockets-13.0.1-pp310-pypy310_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14b9c006cac63772b31abbcd3e3abb6228233eec966bf062e89e7fa7ae0b7333"}, - {file = "websockets-13.0.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:b79915a1179a91f6c5f04ece1e592e2e8a6bd245a0e45d12fd56b2b59e559a32"}, - {file = "websockets-13.0.1-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:f40de079779acbcdbb6ed4c65af9f018f8b77c5ec4e17a4b737c05c2db554491"}, - {file = "websockets-13.0.1-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:80e4ba642fc87fa532bac07e5ed7e19d56940b6af6a8c61d4429be48718a380f"}, - {file = "websockets-13.0.1-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2a02b0161c43cc9e0232711eff846569fad6ec836a7acab16b3cf97b2344c060"}, - {file = "websockets-13.0.1-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6aa74a45d4cdc028561a7d6ab3272c8b3018e23723100b12e58be9dfa5a24491"}, - {file = "websockets-13.0.1-pp38-pypy38_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:00fd961943b6c10ee6f0b1130753e50ac5dcd906130dcd77b0003c3ab797d026"}, - {file = "websockets-13.0.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:d93572720d781331fb10d3da9ca1067817d84ad1e7c31466e9f5e59965618096"}, - {file = "websockets-13.0.1-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:71e6e5a3a3728886caee9ab8752e8113670936a193284be9d6ad2176a137f376"}, - {file = "websockets-13.0.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:c4a6343e3b0714e80da0b0893543bf9a5b5fa71b846ae640e56e9abc6fbc4c83"}, - {file = "websockets-13.0.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a678532018e435396e37422a95e3ab87f75028ac79570ad11f5bf23cd2a7d8c"}, - {file = "websockets-13.0.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d6716c087e4aa0b9260c4e579bb82e068f84faddb9bfba9906cb87726fa2e870"}, - {file = "websockets-13.0.1-pp39-pypy39_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e33505534f3f673270dd67f81e73550b11de5b538c56fe04435d63c02c3f26b5"}, - {file = "websockets-13.0.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:acab3539a027a85d568c2573291e864333ec9d912675107d6efceb7e2be5d980"}, - {file = "websockets-13.0.1-py3-none-any.whl", hash = "sha256:b80f0c51681c517604152eb6a572f5a9378f877763231fddb883ba2f968e8817"}, - {file = "websockets-13.0.1.tar.gz", hash = "sha256:4d6ece65099411cfd9a48d13701d7438d9c34f479046b34c50ff60bb8834e43e"}, + {file = "websockets-13.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:f48c749857f8fb598fb890a75f540e3221d0976ed0bf879cf3c7eef34151acee"}, + {file = "websockets-13.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c7e72ce6bda6fb9409cc1e8164dd41d7c91466fb599eb047cfda72fe758a34a7"}, + {file = "websockets-13.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f779498eeec470295a2b1a5d97aa1bc9814ecd25e1eb637bd9d1c73a327387f6"}, + {file = "websockets-13.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4676df3fe46956fbb0437d8800cd5f2b6d41143b6e7e842e60554398432cf29b"}, + {file = "websockets-13.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a7affedeb43a70351bb811dadf49493c9cfd1ed94c9c70095fd177e9cc1541fa"}, + {file = "websockets-13.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1971e62d2caa443e57588e1d82d15f663b29ff9dfe7446d9964a4b6f12c1e700"}, + {file = "websockets-13.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:5f2e75431f8dc4a47f31565a6e1355fb4f2ecaa99d6b89737527ea917066e26c"}, + {file = "websockets-13.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:58cf7e75dbf7e566088b07e36ea2e3e2bd5676e22216e4cad108d4df4a7402a0"}, + {file = "websockets-13.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:c90d6dec6be2c7d03378a574de87af9b1efea77d0c52a8301dd831ece938452f"}, + {file = "websockets-13.1-cp310-cp310-win32.whl", hash = "sha256:730f42125ccb14602f455155084f978bd9e8e57e89b569b4d7f0f0c17a448ffe"}, + {file = "websockets-13.1-cp310-cp310-win_amd64.whl", hash = "sha256:5993260f483d05a9737073be197371940c01b257cc45ae3f1d5d7adb371b266a"}, + {file = "websockets-13.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:61fc0dfcda609cda0fc9fe7977694c0c59cf9d749fbb17f4e9483929e3c48a19"}, + {file = "websockets-13.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ceec59f59d092c5007e815def4ebb80c2de330e9588e101cf8bd94c143ec78a5"}, + {file = "websockets-13.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c1dca61c6db1166c48b95198c0b7d9c990b30c756fc2923cc66f68d17dc558fd"}, + {file = "websockets-13.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:308e20f22c2c77f3f39caca508e765f8725020b84aa963474e18c59accbf4c02"}, + {file = "websockets-13.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:62d516c325e6540e8a57b94abefc3459d7dab8ce52ac75c96cad5549e187e3a7"}, + {file = "websockets-13.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:87c6e35319b46b99e168eb98472d6c7d8634ee37750d7693656dc766395df096"}, + {file = "websockets-13.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:5f9fee94ebafbc3117c30be1844ed01a3b177bb6e39088bc6b2fa1dc15572084"}, + {file = "websockets-13.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:7c1e90228c2f5cdde263253fa5db63e6653f1c00e7ec64108065a0b9713fa1b3"}, + {file = "websockets-13.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6548f29b0e401eea2b967b2fdc1c7c7b5ebb3eeb470ed23a54cd45ef078a0db9"}, + {file = "websockets-13.1-cp311-cp311-win32.whl", hash = "sha256:c11d4d16e133f6df8916cc5b7e3e96ee4c44c936717d684a94f48f82edb7c92f"}, + {file = "websockets-13.1-cp311-cp311-win_amd64.whl", hash = "sha256:d04f13a1d75cb2b8382bdc16ae6fa58c97337253826dfe136195b7f89f661557"}, + {file = "websockets-13.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:9d75baf00138f80b48f1eac72ad1535aac0b6461265a0bcad391fc5aba875cfc"}, + {file = "websockets-13.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:9b6f347deb3dcfbfde1c20baa21c2ac0751afaa73e64e5b693bb2b848efeaa49"}, + {file = "websockets-13.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:de58647e3f9c42f13f90ac7e5f58900c80a39019848c5547bc691693098ae1bd"}, + {file = "websockets-13.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a1b54689e38d1279a51d11e3467dd2f3a50f5f2e879012ce8f2d6943f00e83f0"}, + {file = "websockets-13.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cf1781ef73c073e6b0f90af841aaf98501f975d306bbf6221683dd594ccc52b6"}, + {file = "websockets-13.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d23b88b9388ed85c6faf0e74d8dec4f4d3baf3ecf20a65a47b836d56260d4b9"}, + {file = "websockets-13.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3c78383585f47ccb0fcf186dcb8a43f5438bd7d8f47d69e0b56f71bf431a0a68"}, + {file = "websockets-13.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:d6d300f8ec35c24025ceb9b9019ae9040c1ab2f01cddc2bcc0b518af31c75c14"}, + {file = "websockets-13.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a9dcaf8b0cc72a392760bb8755922c03e17a5a54e08cca58e8b74f6902b433cf"}, + {file = "websockets-13.1-cp312-cp312-win32.whl", hash = "sha256:2f85cf4f2a1ba8f602298a853cec8526c2ca42a9a4b947ec236eaedb8f2dc80c"}, + {file = "websockets-13.1-cp312-cp312-win_amd64.whl", hash = "sha256:38377f8b0cdeee97c552d20cf1865695fcd56aba155ad1b4ca8779a5b6ef4ac3"}, + {file = "websockets-13.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a9ab1e71d3d2e54a0aa646ab6d4eebfaa5f416fe78dfe4da2839525dc5d765c6"}, + {file = "websockets-13.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b9d7439d7fab4dce00570bb906875734df13d9faa4b48e261c440a5fec6d9708"}, + {file = "websockets-13.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:327b74e915cf13c5931334c61e1a41040e365d380f812513a255aa804b183418"}, + {file = "websockets-13.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:325b1ccdbf5e5725fdcb1b0e9ad4d2545056479d0eee392c291c1bf76206435a"}, + {file = "websockets-13.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:346bee67a65f189e0e33f520f253d5147ab76ae42493804319b5716e46dddf0f"}, + {file = "websockets-13.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:91a0fa841646320ec0d3accdff5b757b06e2e5c86ba32af2e0815c96c7a603c5"}, + {file = "websockets-13.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:18503d2c5f3943e93819238bf20df71982d193f73dcecd26c94514f417f6b135"}, + {file = "websockets-13.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:a9cd1af7e18e5221d2878378fbc287a14cd527fdd5939ed56a18df8a31136bb2"}, + {file = "websockets-13.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:70c5be9f416aa72aab7a2a76c90ae0a4fe2755c1816c153c1a2bcc3333ce4ce6"}, + {file = "websockets-13.1-cp313-cp313-win32.whl", hash = "sha256:624459daabeb310d3815b276c1adef475b3e6804abaf2d9d2c061c319f7f187d"}, + {file = "websockets-13.1-cp313-cp313-win_amd64.whl", hash = "sha256:c518e84bb59c2baae725accd355c8dc517b4a3ed8db88b4bc93c78dae2974bf2"}, + {file = "websockets-13.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:c7934fd0e920e70468e676fe7f1b7261c1efa0d6c037c6722278ca0228ad9d0d"}, + {file = "websockets-13.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:149e622dc48c10ccc3d2760e5f36753db9cacf3ad7bc7bbbfd7d9c819e286f23"}, + {file = "websockets-13.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a569eb1b05d72f9bce2ebd28a1ce2054311b66677fcd46cf36204ad23acead8c"}, + {file = "websockets-13.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:95df24ca1e1bd93bbca51d94dd049a984609687cb2fb08a7f2c56ac84e9816ea"}, + {file = "websockets-13.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d8dbb1bf0c0a4ae8b40bdc9be7f644e2f3fb4e8a9aca7145bfa510d4a374eeb7"}, + {file = "websockets-13.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:035233b7531fb92a76beefcbf479504db8c72eb3bff41da55aecce3a0f729e54"}, + {file = "websockets-13.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:e4450fc83a3df53dec45922b576e91e94f5578d06436871dce3a6be38e40f5db"}, + {file = "websockets-13.1-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:463e1c6ec853202dd3657f156123d6b4dad0c546ea2e2e38be2b3f7c5b8e7295"}, + {file = "websockets-13.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:6d6855bbe70119872c05107e38fbc7f96b1d8cb047d95c2c50869a46c65a8e96"}, + {file = "websockets-13.1-cp38-cp38-win32.whl", hash = "sha256:204e5107f43095012b00f1451374693267adbb832d29966a01ecc4ce1db26faf"}, + {file = "websockets-13.1-cp38-cp38-win_amd64.whl", hash = "sha256:485307243237328c022bc908b90e4457d0daa8b5cf4b3723fd3c4a8012fce4c6"}, + {file = "websockets-13.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:9b37c184f8b976f0c0a231a5f3d6efe10807d41ccbe4488df8c74174805eea7d"}, + {file = "websockets-13.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:163e7277e1a0bd9fb3c8842a71661ad19c6aa7bb3d6678dc7f89b17fbcc4aeb7"}, + {file = "websockets-13.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4b889dbd1342820cc210ba44307cf75ae5f2f96226c0038094455a96e64fb07a"}, + {file = "websockets-13.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:586a356928692c1fed0eca68b4d1c2cbbd1ca2acf2ac7e7ebd3b9052582deefa"}, + {file = "websockets-13.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7bd6abf1e070a6b72bfeb71049d6ad286852e285f146682bf30d0296f5fbadfa"}, + {file = "websockets-13.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d2aad13a200e5934f5a6767492fb07151e1de1d6079c003ab31e1823733ae79"}, + {file = "websockets-13.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:df01aea34b6e9e33572c35cd16bae5a47785e7d5c8cb2b54b2acdb9678315a17"}, + {file = "websockets-13.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:e54affdeb21026329fb0744ad187cf812f7d3c2aa702a5edb562b325191fcab6"}, + {file = "websockets-13.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:9ef8aa8bdbac47f4968a5d66462a2a0935d044bf35c0e5a8af152d58516dbeb5"}, + {file = "websockets-13.1-cp39-cp39-win32.whl", hash = "sha256:deeb929efe52bed518f6eb2ddc00cc496366a14c726005726ad62c2dd9017a3c"}, + {file = "websockets-13.1-cp39-cp39-win_amd64.whl", hash = "sha256:7c65ffa900e7cc958cd088b9a9157a8141c991f8c53d11087e6fb7277a03f81d"}, + {file = "websockets-13.1-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:5dd6da9bec02735931fccec99d97c29f47cc61f644264eb995ad6c0c27667238"}, + {file = "websockets-13.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:2510c09d8e8df777177ee3d40cd35450dc169a81e747455cc4197e63f7e7bfe5"}, + {file = "websockets-13.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f1c3cf67185543730888b20682fb186fc8d0fa6f07ccc3ef4390831ab4b388d9"}, + {file = "websockets-13.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bcc03c8b72267e97b49149e4863d57c2d77f13fae12066622dc78fe322490fe6"}, + {file = "websockets-13.1-pp310-pypy310_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:004280a140f220c812e65f36944a9ca92d766b6cc4560be652a0a3883a79ed8a"}, + {file = "websockets-13.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:e2620453c075abeb0daa949a292e19f56de518988e079c36478bacf9546ced23"}, + {file = "websockets-13.1-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:9156c45750b37337f7b0b00e6248991a047be4aa44554c9886fe6bdd605aab3b"}, + {file = "websockets-13.1-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:80c421e07973a89fbdd93e6f2003c17d20b69010458d3a8e37fb47874bd67d51"}, + {file = "websockets-13.1-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:82d0ba76371769d6a4e56f7e83bb8e81846d17a6190971e38b5de108bde9b0d7"}, + {file = "websockets-13.1-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e9875a0143f07d74dc5e1ded1c4581f0d9f7ab86c78994e2ed9e95050073c94d"}, + {file = "websockets-13.1-pp38-pypy38_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a11e38ad8922c7961447f35c7b17bffa15de4d17c70abd07bfbe12d6faa3e027"}, + {file = "websockets-13.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:4059f790b6ae8768471cddb65d3c4fe4792b0ab48e154c9f0a04cefaabcd5978"}, + {file = "websockets-13.1-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:25c35bf84bf7c7369d247f0b8cfa157f989862c49104c5cf85cb5436a641d93e"}, + {file = "websockets-13.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:83f91d8a9bb404b8c2c41a707ac7f7f75b9442a0a876df295de27251a856ad09"}, + {file = "websockets-13.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7a43cfdcddd07f4ca2b1afb459824dd3c6d53a51410636a2c7fc97b9a8cf4842"}, + {file = "websockets-13.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:48a2ef1381632a2f0cb4efeff34efa97901c9fbc118e01951ad7cfc10601a9bb"}, + {file = "websockets-13.1-pp39-pypy39_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:459bf774c754c35dbb487360b12c5727adab887f1622b8aed5755880a21c4a20"}, + {file = "websockets-13.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:95858ca14a9f6fa8413d29e0a585b31b278388aa775b8a81fa24830123874678"}, + {file = "websockets-13.1-py3-none-any.whl", hash = "sha256:a9a396a6ad26130cdae92ae10c36af09d9bfe6cafe69670fd3b6da9b07b4044f"}, + {file = "websockets-13.1.tar.gz", hash = "sha256:a3b3366087c1bc0a2795111edcadddb8b3b59509d5db5d7ea3fdd69f954a8878"}, ] [[package]] @@ -2853,5 +2925,5 @@ type = ["pytest-mypy"] [metadata] lock-version = "2.0" -python-versions = "^3.10" -content-hash = "1c8904971be7061c2b33400d2b5f461e526703586e5b7f9eb08c2fbfda1a23ab" +python-versions = "^3.9" +content-hash = "83a12dac424352fe71847b80f3cf08d0c3fb68da088561cc6630d6476a60627d" diff --git a/pyproject.toml b/pyproject.toml index 203ccc917..6a58f4700 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -26,7 +26,7 @@ packages = [ ] [tool.poetry.dependencies] -python = "^3.10" +python = "^3.9" dill = ">=0.3.8,<0.4" fastapi = ">=0.96.0,!=0.111.0,!=0.111.1" gunicorn = ">=20.1.0,<24.0" @@ -88,7 +88,7 @@ build-backend = "poetry.core.masonry.api" [tool.pyright] [tool.ruff] -target-version = "py310" +target-version = "py39" lint.select = ["B", "D", "E", "F", "I", "SIM", "W"] lint.ignore = ["B008", "D203", "D205", "D213", "D401", "D406", "D407", "E501", "F403", "F405", "F541"] lint.pydocstyle.convention = "google" diff --git a/reflex/.templates/jinja/custom_components/pyproject.toml.jinja2 b/reflex/.templates/jinja/custom_components/pyproject.toml.jinja2 index abfd998fd..a5239f33b 100644 --- a/reflex/.templates/jinja/custom_components/pyproject.toml.jinja2 +++ b/reflex/.templates/jinja/custom_components/pyproject.toml.jinja2 @@ -8,7 +8,7 @@ version = "0.0.1" description = "Reflex custom component {{ module_name }}" readme = "README.md" license = { text = "Apache-2.0" } -requires-python = ">=3.10" +requires-python = ">=3.9" authors = [{ name = "", email = "YOUREMAIL@domain.com" }] keywords = ["reflex","reflex-custom-components"] diff --git a/reflex/app.py b/reflex/app.py index 323a3c8f8..63334997c 100644 --- a/reflex/app.py +++ b/reflex/app.py @@ -606,10 +606,7 @@ class App(MiddlewareMixin, LifespanMixin, Base): for route in self.pages: replaced_route = replace_brackets_with_keywords(route) for rw, r, nr in zip( - replaced_route.split("/"), - route.split("/"), - new_route.split("/"), - strict=False, + replaced_route.split("/"), route.split("/"), new_route.split("/") ): if rw in segments and r != nr: # If the slugs in the segments of both routes are not the same, then the route is invalid @@ -958,7 +955,7 @@ class App(MiddlewareMixin, LifespanMixin, Base): # Prepopulate the global ExecutorSafeFunctions class with input data required by the compile functions. # This is required for multiprocessing to work, in presence of non-picklable inputs. - for route, component in zip(self.pages, page_components, strict=False): + for route, component in zip(self.pages, page_components): ExecutorSafeFunctions.COMPILE_PAGE_ARGS_BY_ROUTE[route] = ( route, component, @@ -1172,7 +1169,6 @@ class App(MiddlewareMixin, LifespanMixin, Base): FRONTEND_ARG_SPEC, BACKEND_ARG_SPEC, ], - strict=False, ): if hasattr(handler_fn, "__name__"): _fn_name = handler_fn.__name__ diff --git a/reflex/app_module_for_backend.py b/reflex/app_module_for_backend.py index cae136354..8109fc3d6 100644 --- a/reflex/app_module_for_backend.py +++ b/reflex/app_module_for_backend.py @@ -15,7 +15,7 @@ if constants.CompileVars.APP != "app": telemetry.send("compile") app_module = get_app(reload=False) app = getattr(app_module, constants.CompileVars.APP) -# For py3.8 and py3.9 compatibility when redis is used, we MUST add any decorator pages +# For py3.9 compatibility when redis is used, we MUST add any decorator pages # before compiling the app in a thread to avoid event loop error (REF-2172). app._apply_decorated_pages() compile_future = ThreadPoolExecutor(max_workers=1).submit(app._compile) diff --git a/reflex/components/core/breakpoints.py b/reflex/components/core/breakpoints.py index decd2727e..4b2372a70 100644 --- a/reflex/components/core/breakpoints.py +++ b/reflex/components/core/breakpoints.py @@ -82,9 +82,7 @@ class Breakpoints(Dict[K, V]): return Breakpoints( { k: v - for k, v in zip( - ["initial", *breakpoint_names], thresholds, strict=False - ) + for k, v in zip(["initial", *breakpoint_names], thresholds) if v is not None } ) diff --git a/reflex/event.py b/reflex/event.py index fe3a6bfb4..ac0c713ab 100644 --- a/reflex/event.py +++ b/reflex/event.py @@ -217,7 +217,7 @@ class EventHandler(EventActionsMixin): raise EventHandlerTypeError( f"Arguments to event handlers must be Vars or JSON-serializable. Got {arg} of type {type(arg)}." ) from e - payload = tuple(zip(fn_args, values, strict=False)) + payload = tuple(zip(fn_args, values)) # Return the event spec. return EventSpec( @@ -310,7 +310,7 @@ class EventSpec(EventActionsMixin): raise EventHandlerTypeError( f"Arguments to event handlers must be Vars or JSON-serializable. Got {arg} of type {type(arg)}." ) from e - new_payload = tuple(zip(fn_args, values, strict=False)) + new_payload = tuple(zip(fn_args, values)) return self.with_args(self.args + new_payload) diff --git a/reflex/state.py b/reflex/state.py index 5223be7e5..8b32d1a07 100644 --- a/reflex/state.py +++ b/reflex/state.py @@ -1316,7 +1316,6 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow): for part1, part2 in zip( cls.get_full_name().split("."), other.get_full_name().split("."), - strict=False, ): if part1 != part2: break diff --git a/reflex/utils/prerequisites.py b/reflex/utils/prerequisites.py index 7173e4872..f86da0c53 100644 --- a/reflex/utils/prerequisites.py +++ b/reflex/utils/prerequisites.py @@ -74,6 +74,18 @@ def get_web_dir() -> Path: return workdir +def _python_version_check(): + """Emit deprecation warning for deprecated python versions.""" + # Check for end-of-life python versions. + if sys.version_info < (3, 10): + console.deprecate( + feature_name="Support for Python 3.9 and older", + reason="please upgrade to Python 3.10 or newer", + deprecation_version="0.6.0", + removal_version="0.7.0", + ) + + def check_latest_package_version(package_name: str): """Check if the latest version of the package is installed. @@ -86,15 +98,16 @@ def check_latest_package_version(package_name: str): url = f"https://pypi.org/pypi/{package_name}/json" response = net.get(url) latest_version = response.json()["info"]["version"] - if ( - version.parse(current_version) < version.parse(latest_version) - and not get_or_set_last_reflex_version_check_datetime() - ): - # only show a warning when the host version is outdated and - # the last_version_check_datetime is not set in reflex.json + if get_or_set_last_reflex_version_check_datetime(): + # Versions were already checked and saved in reflex.json, no need to warn again + return + if version.parse(current_version) < version.parse(latest_version): + # Show a warning when the host version is older than PyPI version console.warn( f"Your version ({current_version}) of {package_name} is out of date. Upgrade to {latest_version} with 'pip install {package_name} --upgrade'" ) + # Check for depreacted python versions + _python_version_check() except Exception: pass @@ -291,7 +304,7 @@ def get_compiled_app(reload: bool = False, export: bool = False) -> ModuleType: """ app_module = get_app(reload=reload) app = getattr(app_module, constants.CompileVars.APP) - # For py3.8 and py3.9 compatibility when redis is used, we MUST add any decorator pages + # For py3.9 compatibility when redis is used, we MUST add any decorator pages # before compiling the app in a thread to avoid event loop error (REF-2172). app._apply_decorated_pages() app._compile(export=export) diff --git a/reflex/utils/telemetry.py b/reflex/utils/telemetry.py index b2507e656..af3994ff8 100644 --- a/reflex/utils/telemetry.py +++ b/reflex/utils/telemetry.py @@ -121,7 +121,7 @@ def _prepare_event(event: str, **kwargs) -> dict: return {} if UTC is None: - # for python 3.10 + # for python 3.9 & 3.10 stamp = datetime.utcnow().isoformat() else: # for python 3.11 & 3.12 diff --git a/reflex/utils/types.py b/reflex/utils/types.py index ed74131a0..63238f67b 100644 --- a/reflex/utils/types.py +++ b/reflex/utils/types.py @@ -632,7 +632,7 @@ def validate_parameter_literals(func): annotations = {param[0]: param[1].annotation for param in func_params} # validate args - for param, arg in zip(annotations, args, strict=False): + for param, arg in zip(annotations, args): if annotations[param] is inspect.Parameter.empty: continue validate_literal(param, arg, annotations[param], func.__name__) diff --git a/reflex/vars/base.py b/reflex/vars/base.py index eb5362090..4faa38be7 100644 --- a/reflex/vars/base.py +++ b/reflex/vars/base.py @@ -2667,7 +2667,7 @@ def generic_type_to_actual_type_map( # call recursively for nested generic types and merge the results return { k: v - for generic_arg, actual_arg in zip(generic_args, actual_args, strict=False) + for generic_arg, actual_arg in zip(generic_args, actual_args) for k, v in generic_type_to_actual_type_map(generic_arg, actual_arg).items() } diff --git a/tests/compiler/test_compiler.py b/tests/compiler/test_compiler.py index 9ff4e1f14..63014cf33 100644 --- a/tests/compiler/test_compiler.py +++ b/tests/compiler/test_compiler.py @@ -100,7 +100,7 @@ def test_compile_imports(import_dict: ParsedImportDict, test_dicts: List[dict]): test_dicts: The expected output. """ imports = utils.compile_imports(import_dict) - for import_dict, test_dict in zip(imports, test_dicts, strict=False): + for import_dict, test_dict in zip(imports, test_dicts): assert import_dict["lib"] == test_dict["lib"] assert import_dict["default"] == test_dict["default"] assert sorted(import_dict["rest"]) == test_dict["rest"] # type: ignore diff --git a/tests/components/media/test_image.py b/tests/components/media/test_image.py index e3d924235..f8618347c 100644 --- a/tests/components/media/test_image.py +++ b/tests/components/media/test_image.py @@ -1,4 +1,3 @@ -# PIL is only available in python 3.8+ import numpy as np import PIL import pytest diff --git a/tests/components/test_component.py b/tests/components/test_component.py index e6a1a40cb..4dda81896 100644 --- a/tests/components/test_component.py +++ b/tests/components/test_component.py @@ -1403,7 +1403,6 @@ def test_get_vars(component, exp_vars): for comp_var, exp_var in zip( comp_vars, sorted(exp_vars, key=lambda v: v._js_expr), - strict=False, ): # print(str(comp_var), str(exp_var)) # print(comp_var._get_all_var_data(), exp_var._get_all_var_data()) @@ -1793,7 +1792,6 @@ def test_custom_component_declare_event_handlers_in_fields(): for v1, v2 in zip( parse_args_spec(test_triggers[trigger_name]), parse_args_spec(custom_triggers[trigger_name]), - strict=False, ): assert v1.equals(v2) diff --git a/tests/test_var.py b/tests/test_var.py index 31bf80568..227b01d85 100644 --- a/tests/test_var.py +++ b/tests/test_var.py @@ -184,7 +184,6 @@ def ChildWithRuntimeOnlyVar(StateWithRuntimeOnlyVar): "state.local", "local2", ], - strict=False, ), ) def test_full_name(prop, expected): @@ -202,7 +201,6 @@ def test_full_name(prop, expected): zip( test_vars, ["prop1", "key", "state.value", "state.local", "local2"], - strict=False, ), ) def test_str(prop, expected): @@ -249,7 +247,6 @@ def test_default_value(prop, expected): "state.set_local", "set_local2", ], - strict=False, ), ) def test_get_setter(prop, expected): From 2c4310d9ff4136416bbfe7e255a3d7ab610ccac7 Mon Sep 17 00:00:00 2001 From: Andrew Davies <6566948+minimav@users.noreply.github.com> Date: Tue, 24 Sep 2024 02:18:05 +0100 Subject: [PATCH 08/39] Use tailwind typography plugin by default (#3593) Co-authored-by: Khaleel Al-Adhami --- reflex/components/core/html.py | 2 +- reflex/config.py | 2 +- tests/components/core/test_html.py | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/reflex/components/core/html.py b/reflex/components/core/html.py index cfe46e591..a732e7828 100644 --- a/reflex/components/core/html.py +++ b/reflex/components/core/html.py @@ -40,7 +40,7 @@ class Html(Div): given_class_name = props.pop("class_name", []) if isinstance(given_class_name, str): given_class_name = [given_class_name] - props["class_name"] = ["rx-Html", *given_class_name] + props["class_name"] = ["rx-Html", "prose", *given_class_name] # Create the component. return super().create(**props) diff --git a/reflex/config.py b/reflex/config.py index 06d8c2193..fadd82482 100644 --- a/reflex/config.py +++ b/reflex/config.py @@ -194,7 +194,7 @@ class Config(Base): cors_allowed_origins: List[str] = ["*"] # Tailwind config. - tailwind: Optional[Dict[str, Any]] = {} + tailwind: Optional[Dict[str, Any]] = {"plugins": ["@tailwindcss/typography"]} # Timeout when launching the gunicorn server. TODO(rename this to backend_timeout?) timeout: int = 120 diff --git a/tests/components/core/test_html.py b/tests/components/core/test_html.py index 4847e1d5a..cc6530cb6 100644 --- a/tests/components/core/test_html.py +++ b/tests/components/core/test_html.py @@ -19,7 +19,7 @@ def test_html_create(): assert str(html.dangerouslySetInnerHTML) == '({ ["__html"] : "

Hello !

" })' # type: ignore assert ( str(html) - == '
Hello !

" })}/>' + == '
Hello !

" })}/>' ) @@ -37,5 +37,5 @@ def test_html_fstring_create(): ) assert ( str(html) - == f'
' # type: ignore + == f'
' # type: ignore ) From 4e4d36a86789f1d5ea8acfb0a208863b5b338684 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Brand=C3=A9ho?= Date: Tue, 24 Sep 2024 23:29:56 +0200 Subject: [PATCH 09/39] re add removed method with better behaviour and tests (#3986) --- reflex/page.py | 19 +++++++++++++++++++ tests/test_page.py | 31 ++++++++++++++++++++++++++++++- 2 files changed, 49 insertions(+), 1 deletion(-) diff --git a/reflex/page.py b/reflex/page.py index 27a3c4fa8..52f0c8efc 100644 --- a/reflex/page.py +++ b/reflex/page.py @@ -63,3 +63,22 @@ def page( return render_fn return decorator + + +def get_decorated_pages(omit_implicit_routes=True) -> list[dict[str, Any]]: + """Get the decorated pages. + + Args: + omit_implicit_routes: Whether to omit pages where the route will be implicitely guessed later. + + Returns: + The decorated pages. + """ + return sorted( + [ + page_data + for _, page_data in DECORATED_PAGES[get_config().app_name] + if not omit_implicit_routes or "route" in page_data + ], + key=lambda x: x.get("route", ""), + ) diff --git a/tests/test_page.py b/tests/test_page.py index 9cbd2886d..e1dd70905 100644 --- a/tests/test_page.py +++ b/tests/test_page.py @@ -1,6 +1,6 @@ from reflex import text from reflex.config import get_config -from reflex.page import DECORATED_PAGES, page +from reflex.page import DECORATED_PAGES, get_decorated_pages, page def test_page_decorator(): @@ -48,3 +48,32 @@ def test_page_decorator_with_kwargs(): } DECORATED_PAGES.clear() + + +def test_get_decorated_pages(): + assert get_decorated_pages() == [] + + def foo_(): + return text("foo") + + page()(foo_) + + assert get_decorated_pages() == [] + assert get_decorated_pages(omit_implicit_routes=False) == [{}] + + page(route="foo2")(foo_) + + assert get_decorated_pages() == [{"route": "foo2"}] + assert get_decorated_pages(omit_implicit_routes=False) == [{}, {"route": "foo2"}] + + page(route="foo3", title="Foo3")(foo_) + + assert get_decorated_pages() == [ + {"route": "foo2"}, + {"route": "foo3", "title": "Foo3"}, + ] + assert get_decorated_pages(omit_implicit_routes=False) == [ + {}, + {"route": "foo2"}, + {"route": "foo3", "title": "Foo3"}, + ] From d0ad5cd15e8575b7c09b34b68d54925efbc2e275 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Brand=C3=A9ho?= Date: Wed, 25 Sep 2024 02:34:41 +0200 Subject: [PATCH 10/39] use svg elements instead of raw html for logo (#3978) * use svg elements instead of raw html for logo * avoid duplication --- reflex/components/datadisplay/logo.py | 40 ++++++++++++++------------- 1 file changed, 21 insertions(+), 19 deletions(-) diff --git a/reflex/components/datadisplay/logo.py b/reflex/components/datadisplay/logo.py index c00c8ebae..beb9b9d10 100644 --- a/reflex/components/datadisplay/logo.py +++ b/reflex/components/datadisplay/logo.py @@ -12,31 +12,33 @@ def logo(**props): Returns: The logo component. """ - light_mode_logo = """ - - - - - - -""" - dark_mode_logo = """ - - - - - - -""" + def logo_path(d): + return rx.el.svg.path( + d=d, + fill=rx.color_mode_cond("#110F1F", "white"), + ) + + paths = [ + "M0 11.5999V0.399902H8.96V4.8799H6.72V2.6399H2.24V4.8799H6.72V7.1199H2.24V11.5999H0ZM6.72 11.5999V7.1199H8.96V11.5999H6.72Z", + "M11.2 11.5999V0.399902H17.92V2.6399H13.44V4.8799H17.92V7.1199H13.44V9.3599H17.92V11.5999H11.2Z", + "M20.16 11.5999V0.399902H26.88V2.6399H22.4V4.8799H26.88V7.1199H22.4V11.5999H20.16Z", + "M29.12 11.5999V0.399902H31.36V9.3599H35.84V11.5999H29.12Z", + "M38.08 11.5999V0.399902H44.8V2.6399H40.32V4.8799H44.8V7.1199H40.32V9.3599H44.8V11.5999H38.08Z", + "M47.04 4.8799V0.399902H49.28V4.8799H47.04ZM53.76 4.8799V0.399902H56V4.8799H53.76ZM49.28 7.1199V4.8799H53.76V7.1199H49.28ZM47.04 11.5999V7.1199H49.28V11.5999H47.04ZM53.76 11.5999V7.1199H56V11.5999H53.76Z", + ] return rx.center( rx.link( rx.hstack( "Built with ", - rx.color_mode_cond( - rx.html(light_mode_logo), - rx.html(dark_mode_logo), + rx.el.svg( + *[logo_path(d) for d in paths], + width="56", + height="12", + viewBox="0 0 56 12", + fill="none", + xmlns="http://www.w3.org/2000/svg", ), text_align="center", align="center", From 001b8c4222b9202b60cbd2a09aac3d13ad8d90b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Brand=C3=A9ho?= Date: Wed, 25 Sep 2024 02:36:58 +0200 Subject: [PATCH 11/39] can run with granian by setting REFLEX_USE_GRANIAN var (#3919) * can run with granian by setting REFLEX_USE_GRANIAN var * granian also useable for prod mode * adjust reload paths for granian * move uvicorn / granian logic to their own function * fix prod mode --- reflex/utils/exec.py | 163 ++++++++++++++++++++++++++++++++++++++----- 1 file changed, 147 insertions(+), 16 deletions(-) diff --git a/reflex/utils/exec.py b/reflex/utils/exec.py index 417d6fbe7..013fa8734 100644 --- a/reflex/utils/exec.py +++ b/reflex/utils/exec.py @@ -172,6 +172,33 @@ def run_frontend_prod(root: Path, port: str, backend_present=True): ) +def should_use_granian(): + """Whether to use Granian for backend. + + Returns: + True if Granian should be used. + """ + return os.getenv("REFLEX_USE_GRANIAN", "0") == "1" + + +def get_app_module(): + """Get the app module for the backend. + + Returns: + The app module for the backend. + """ + return f"reflex.app_module_for_backend:{constants.CompileVars.APP}" + + +def get_granian_target(): + """Get the Granian target for the backend. + + Returns: + The Granian target for the backend. + """ + return get_app_module() + f".{constants.CompileVars.API}" + + def run_backend( host: str, port: int, @@ -184,24 +211,76 @@ def run_backend( port: The app port loglevel: The log level. """ - import uvicorn - - config = get_config() - app_module = f"reflex.app_module_for_backend:{constants.CompileVars.APP}" - web_dir = get_web_dir() # Create a .nocompile file to skip compile for backend. if web_dir.exists(): (web_dir / constants.NOCOMPILE_FILE).touch() - # Run the backend in development mode. + if should_use_granian(): + run_granian_backend(host, port, loglevel) + else: + run_uvicorn_backend(host, port, loglevel) + + +def run_uvicorn_backend(host, port, loglevel): + """Run the backend in development mode using Uvicorn. + + Args: + host: The app host + port: The app port + loglevel: The log level. + """ + import uvicorn + uvicorn.run( - app=f"{app_module}.{constants.CompileVars.API}", + app=f"{get_app_module()}.{constants.CompileVars.API}", host=host, port=port, log_level=loglevel.value, reload=True, - reload_dirs=[config.app_name], + reload_dirs=[get_config().app_name], + ) + + +def run_granian_backend(host, port, loglevel): + """Run the backend in development mode using Granian. + + Args: + host: The app host + port: The app port + loglevel: The log level. + """ + console.debug("Using Granian for backend") + try: + from granian import Granian # type: ignore + from granian.constants import Interfaces # type: ignore + from granian.log import LogLevels # type: ignore + + Granian( + target=get_granian_target(), + address=host, + port=port, + interface=Interfaces.ASGI, + log_level=LogLevels(loglevel.value), + reload=True, + reload_paths=[Path(get_config().app_name)], + reload_ignore_dirs=[".web"], + ).serve() + except ImportError: + console.error( + 'InstallError: REFLEX_USE_GRANIAN is set but `granian` is not installed. (run `pip install "granian>=1.6.0"`)' + ) + os._exit(1) + + +def _get_backend_workers(): + from reflex.utils import processes + + config = get_config() + return ( + processes.get_num_workers() + if not config.gunicorn_workers + else config.gunicorn_workers ) @@ -212,6 +291,20 @@ def run_backend_prod( ): """Run the backend. + Args: + host: The app host + port: The app port + loglevel: The log level. + """ + if should_use_granian(): + run_granian_backend_prod(host, port, loglevel) + else: + run_uvicorn_backend_prod(host, port, loglevel) + + +def run_uvicorn_backend_prod(host, port, loglevel): + """Run the backend in production mode using Uvicorn. + Args: host: The app host port: The app port @@ -220,14 +313,11 @@ def run_backend_prod( from reflex.utils import processes config = get_config() - num_workers = ( - processes.get_num_workers() - if not config.gunicorn_workers - else config.gunicorn_workers - ) + + app_module = get_app_module() + RUN_BACKEND_PROD = f"gunicorn --worker-class {config.gunicorn_worker_class} --preload --timeout {config.timeout} --log-level critical".split() RUN_BACKEND_PROD_WINDOWS = f"uvicorn --timeout-keep-alive {config.timeout}".split() - app_module = f"reflex.app_module_for_backend:{constants.CompileVars.APP}" command = ( [ *RUN_BACKEND_PROD_WINDOWS, @@ -243,7 +333,7 @@ def run_backend_prod( "--bind", f"{host}:{port}", "--threads", - str(num_workers), + str(_get_backend_workers()), f"{app_module}()", ] ) @@ -252,7 +342,7 @@ def run_backend_prod( "--log-level", loglevel.value, "--workers", - str(num_workers), + str(_get_backend_workers()), ] processes.new_process( command, @@ -262,6 +352,47 @@ def run_backend_prod( ) +def run_granian_backend_prod(host, port, loglevel): + """Run the backend in production mode using Granian. + + Args: + host: The app host + port: The app port + loglevel: The log level. + """ + from reflex.utils import processes + + try: + from granian.constants import Interfaces # type: ignore + + command = [ + "granian", + "--workers", + str(_get_backend_workers()), + "--log-level", + "critical", + "--host", + host, + "--port", + str(port), + "--interface", + str(Interfaces.ASGI), + get_granian_target(), + ] + processes.new_process( + command, + run=True, + show_logs=True, + env={ + constants.SKIP_COMPILE_ENV_VAR: "yes" + }, # skip compile for prod backend + ) + except ImportError: + console.error( + 'InstallError: REFLEX_USE_GRANIAN is set but `granian` is not installed. (run `pip install "granian>=1.6.0"`)' + ) + + def output_system_info(): """Show system information if the loglevel is in DEBUG.""" if console._LOG_LEVEL > constants.LogLevel.DEBUG: From 46be46d6eaea76381a84380dd11654611333a7b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Brand=C3=A9ho?= Date: Wed, 25 Sep 2024 02:38:12 +0200 Subject: [PATCH 12/39] allow link as metatags (#3980) * allow link as metatags * remove stray print & fix nit --- reflex/compiler/utils.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/reflex/compiler/utils.py b/reflex/compiler/utils.py index 3b643718f..1808f787a 100644 --- a/reflex/compiler/utils.py +++ b/reflex/compiler/utils.py @@ -429,11 +429,11 @@ def add_meta( Returns: The component with the metadata added. """ - meta_tags = [Meta.create(**item) for item in meta] - - children: list[Any] = [ - Title.create(title), + meta_tags = [ + item if isinstance(item, Component) else Meta.create(**item) for item in meta ] + + children: list[Any] = [Title.create(title)] if description: children.append(Description.create(content=description)) children.append(Image.create(content=image)) From 5e738fd62bbac46467d52558d1742d94e0d8a9e1 Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Tue, 24 Sep 2024 17:38:49 -0700 Subject: [PATCH 13/39] don't camel case keys of dicts in style (#3982) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * don't camel case keys of dicts in style * change tests to fit the code 😎 * respect objectvars as true dicts --- reflex/components/component.py | 2 +- reflex/style.py | 14 ++++++++++++-- tests/test_style.py | 6 +++--- 3 files changed, 16 insertions(+), 6 deletions(-) diff --git a/reflex/components/component.py b/reflex/components/component.py index b2f3d196f..e89da6900 100644 --- a/reflex/components/component.py +++ b/reflex/components/component.py @@ -477,7 +477,7 @@ class Component(BaseComponent, ABC): # Merge styles, the later ones overriding keys in the earlier ones. style = {k: v for style_dict in style for k, v in style_dict.items()} - if isinstance(style, Breakpoints): + if isinstance(style, (Breakpoints, Var)): style = { # Assign the Breakpoints to the self-referential selector to avoid squashing down to a regular dict. "&": style, diff --git a/reflex/style.py b/reflex/style.py index e63cb820f..4ebd42ac3 100644 --- a/reflex/style.py +++ b/reflex/style.py @@ -13,6 +13,7 @@ from reflex.utils.imports import ImportVar from reflex.vars import VarData from reflex.vars.base import CallableVar, LiteralVar, Var from reflex.vars.function import FunctionVar +from reflex.vars.object import ObjectVar SYSTEM_COLOR_MODE: str = "system" LIGHT_COLOR_MODE: str = "light" @@ -188,7 +189,16 @@ def convert( out[k] = return_value for key, value in style_dict.items(): - keys = format_style_key(key) + keys = ( + format_style_key(key) + if not isinstance(value, (dict, ObjectVar)) + or ( + isinstance(value, Breakpoints) + and all(not isinstance(v, dict) for v in value.values()) + ) + else (key,) + ) + if isinstance(value, Var): return_val = value new_var_data = value._get_all_var_data() @@ -283,7 +293,7 @@ def _format_emotion_style_pseudo_selector(key: str) -> str: """Format a pseudo selector for emotion CSS-in-JS. Args: - key: Underscore-prefixed or colon-prefixed pseudo selector key (_hover). + key: Underscore-prefixed or colon-prefixed pseudo selector key (_hover/:hover). Returns: A self-referential pseudo selector key (&:hover). diff --git a/tests/test_style.py b/tests/test_style.py index 19106df75..e1d652798 100644 --- a/tests/test_style.py +++ b/tests/test_style.py @@ -15,9 +15,9 @@ test_style = [ ({"a": 1}, {"a": 1}), ({"a": LiteralVar.create("abc")}, {"a": "abc"}), ({"test_case": 1}, {"testCase": 1}), - ({"test_case": {"a": 1}}, {"testCase": {"a": 1}}), - ({":test_case": {"a": 1}}, {":testCase": {"a": 1}}), - ({"::test_case": {"a": 1}}, {"::testCase": {"a": 1}}), + ({"test_case": {"a": 1}}, {"test_case": {"a": 1}}), + ({":test_case": {"a": 1}}, {":test_case": {"a": 1}}), + ({"::test_case": {"a": 1}}, {"::test_case": {"a": 1}}), ( {"::-webkit-scrollbar": {"display": "none"}}, {"::-webkit-scrollbar": {"display": "none"}}, From fc5d352431a05e7ed568754aa733fdd4da01dc95 Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Wed, 25 Sep 2024 09:55:16 -0700 Subject: [PATCH 14/39] add basic integration test for dynamic components (#3990) * add basic integration test * fix docstrings * dang it darglint --- integration/test_dynamic_components.py | 152 +++++++++++++++++++++++++ 1 file changed, 152 insertions(+) create mode 100644 integration/test_dynamic_components.py diff --git a/integration/test_dynamic_components.py b/integration/test_dynamic_components.py new file mode 100644 index 000000000..31080223f --- /dev/null +++ b/integration/test_dynamic_components.py @@ -0,0 +1,152 @@ +"""Integration tests for var operations.""" + +import time +from typing import Callable, Generator, TypeVar + +import pytest +from selenium.webdriver.common.by import By + +from reflex.testing import AppHarness + +# pyright: reportOptionalMemberAccess=false, reportGeneralTypeIssues=false, reportUnknownMemberType=false + + +def DynamicComponents(): + """App with var operations.""" + import reflex as rx + + class DynamicComponentsState(rx.State): + button: rx.Component = rx.button( + "Click me", + custom_attrs={ + "id": "button", + }, + ) + + def got_clicked(self): + self.button = rx.button( + "Clicked", + custom_attrs={ + "id": "button", + }, + ) + + @rx.var + def client_token_component(self) -> rx.Component: + return rx.vstack( + rx.el.input( + custom_attrs={ + "id": "token", + }, + value=self.router.session.client_token, + is_read_only=True, + ), + rx.button( + "Update", + custom_attrs={ + "id": "update", + }, + on_click=DynamicComponentsState.got_clicked, + ), + ) + + app = rx.App() + + @app.add_page + def index(): + return rx.vstack( + DynamicComponentsState.client_token_component, + DynamicComponentsState.button, + ) + + +@pytest.fixture(scope="module") +def dynamic_components(tmp_path_factory) -> Generator[AppHarness, None, None]: + """Start VarOperations app at tmp_path via AppHarness. + + Args: + tmp_path_factory: pytest tmp_path_factory fixture + + Yields: + running AppHarness instance + """ + with AppHarness.create( + root=tmp_path_factory.mktemp("dynamic_components"), + app_source=DynamicComponents, # type: ignore + ) as harness: + assert harness.app_instance is not None, "app is not running" + yield harness + + +T = TypeVar("T") + + +def poll_for_result( + f: Callable[[], T], exception=Exception, max_attempts=5, seconds_between_attempts=1 +) -> T: + """Poll for a result from a function. + + Args: + f: function to call + exception: exception to catch + max_attempts: maximum number of attempts + seconds_between_attempts: seconds to wait between + + Returns: + Result of the function + + Raises: + AssertionError: if the function does not return a value + """ + attempts = 0 + while attempts < max_attempts: + try: + return f() + except exception: + attempts += 1 + time.sleep(seconds_between_attempts) + raise AssertionError("Function did not return a value") + + +@pytest.fixture +def driver(dynamic_components: AppHarness): + """Get an instance of the browser open to the dynamic components app. + + Args: + dynamic_components: AppHarness for the dynamic components + + Yields: + WebDriver instance. + """ + driver = dynamic_components.frontend() + try: + token_input = poll_for_result(lambda: driver.find_element(By.ID, "token")) + assert token_input + # wait for the backend connection to send the token + token = dynamic_components.poll_for_value(token_input) + assert token is not None + + yield driver + finally: + driver.quit() + + +def test_dynamic_components(driver, dynamic_components: AppHarness): + """Test that the var operations produce the right results. + + Args: + driver: selenium WebDriver open to the app + dynamic_components: AppHarness for the dynamic components + """ + button = poll_for_result(lambda: driver.find_element(By.ID, "button")) + assert button + assert button.text == "Click me" + + update_button = driver.find_element(By.ID, "update") + assert update_button + update_button.click() + + assert ( + dynamic_components.poll_for_content(button, exp_not_equal="Click me") + == "Clicked" + ) From 3eeb0bde9c1c541224cccb39798db272a838715b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Brand=C3=A9ho?= Date: Wed, 25 Sep 2024 18:56:15 +0200 Subject: [PATCH 15/39] bump nextjs version (#3992) --- reflex/constants/installer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/reflex/constants/installer.py b/reflex/constants/installer.py index 6f31b08f1..e01e5ae69 100644 --- a/reflex/constants/installer.py +++ b/reflex/constants/installer.py @@ -115,7 +115,7 @@ class PackageJson(SimpleNamespace): "@emotion/react": "11.11.1", "axios": "1.6.0", "json5": "2.2.3", - "next": "14.0.1", + "next": "14.2.13", "next-sitemap": "4.1.8", "next-themes": "0.2.1", "react": "18.2.0", From 982c43d5953825fd3739e47497002d08f6c792c4 Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Wed, 25 Sep 2024 09:57:16 -0700 Subject: [PATCH 16/39] use bundled radix ui for dynamic components (#3993) --- reflex/components/dynamic.py | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/reflex/components/dynamic.py b/reflex/components/dynamic.py index 6ae78161f..ad044d54f 100644 --- a/reflex/components/dynamic.py +++ b/reflex/components/dynamic.py @@ -57,12 +57,20 @@ def load_dynamic_serializer(): ) ] = None + libs_in_window = [ + "react", + "@radix-ui/themes", + ] + imports = {} for lib, names in component._get_all_imports().items(): if ( not lib.startswith((".", "/")) and not lib.startswith("http") - and lib != "react" + and all( + not lib.startswith(lib_in_window) + for lib_in_window in libs_in_window + ) ): imports[get_cdn_url(lib)] = names else: @@ -83,10 +91,16 @@ def load_dynamic_serializer(): ) + "]" ) - elif 'from "react"' in line: - module_code_lines[ix] = line.replace( - "import ", "const ", 1 - ).replace(' from "react"', " = window.__reflex.react", 1) + else: + for lib in libs_in_window: + if f'from "{lib}"' in line: + module_code_lines[ix] = ( + line.replace("import ", "const ", 1) + .replace( + f' from "{lib}"', f" = window.__reflex['{lib}']", 1 + ) + .replace(" as ", ": ") + ) if line.startswith("export function"): module_code_lines[ix] = line.replace( "export function", "export default function", 1 From 74d1c47ce219c79354a77de36b86a943f26e513b Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Wed, 25 Sep 2024 09:57:29 -0700 Subject: [PATCH 17/39] allow classname to be state vars (#3991) * allow classname to be state vars * simplify join with all literal string vars * add test case and avoid concat var operation if it's not necessary * remove silly print statement * simplify case where there's no var * don't automatically do class name str to literal var --- reflex/components/component.py | 8 +++- .../components/radix/themes/layout/stack.py | 2 +- reflex/vars/sequence.py | 43 +++++++++++++++++++ tests/components/test_component.py | 10 +++++ 4 files changed, 61 insertions(+), 2 deletions(-) diff --git a/reflex/components/component.py b/reflex/components/component.py index e89da6900..7ee9b0d3a 100644 --- a/reflex/components/component.py +++ b/reflex/components/component.py @@ -55,6 +55,7 @@ from reflex.utils.imports import ( ) from reflex.vars import VarData from reflex.vars.base import LiteralVar, Var +from reflex.vars.sequence import LiteralArrayVar class BaseComponent(Base, ABC): @@ -496,7 +497,12 @@ class Component(BaseComponent, ABC): # Convert class_name to str if it's list class_name = kwargs.get("class_name", "") if isinstance(class_name, (List, tuple)): - kwargs["class_name"] = " ".join(class_name) + if any(isinstance(c, Var) for c in class_name): + kwargs["class_name"] = LiteralArrayVar.create( + class_name, _var_type=List[str] + ).join(" ") + else: + kwargs["class_name"] = " ".join(class_name) # Construct the component. super().__init__(*args, **kwargs) diff --git a/reflex/components/radix/themes/layout/stack.py b/reflex/components/radix/themes/layout/stack.py index cb513cbfb..94bba4fb6 100644 --- a/reflex/components/radix/themes/layout/stack.py +++ b/reflex/components/radix/themes/layout/stack.py @@ -33,7 +33,7 @@ class Stack(Flex): """ # Apply the default classname given_class_name = props.pop("class_name", []) - if isinstance(given_class_name, str): + if not isinstance(given_class_name, list): given_class_name = [given_class_name] props["class_name"] = ["rx-Stack", *given_class_name] diff --git a/reflex/vars/sequence.py b/reflex/vars/sequence.py index 6145c980c..15c7411a6 100644 --- a/reflex/vars/sequence.py +++ b/reflex/vars/sequence.py @@ -592,6 +592,29 @@ class LiteralStringVar(LiteralVar, StringVar): else: return only_string.to(StringVar, only_string._var_type) + if len( + literal_strings := [ + s + for s in filtered_strings_and_vals + if isinstance(s, (str, LiteralStringVar)) + ] + ) == len(filtered_strings_and_vals): + return LiteralStringVar.create( + "".join( + s._var_value if isinstance(s, LiteralStringVar) else s + for s in literal_strings + ), + _var_type=_var_type, + _var_data=VarData.merge( + _var_data, + *( + s._get_all_var_data() + for s in filtered_strings_and_vals + if isinstance(s, Var) + ), + ), + ) + concat_result = ConcatVarOperation.create( *filtered_strings_and_vals, _var_data=_var_data, @@ -736,6 +759,26 @@ class ArrayVar(Var[ARRAY_VAR_TYPE]): """ if not isinstance(sep, (StringVar, str)): raise_unsupported_operand_types("join", (type(self), type(sep))) + if ( + isinstance(self, LiteralArrayVar) + and ( + len( + args := [ + x + for x in self._var_value + if isinstance(x, (LiteralStringVar, str)) + ] + ) + == len(self._var_value) + ) + and isinstance(sep, (LiteralStringVar, str)) + ): + sep_str = sep._var_value if isinstance(sep, LiteralStringVar) else sep + return LiteralStringVar.create( + sep_str.join( + i._var_value if isinstance(i, LiteralStringVar) else i for i in args + ) + ) return array_join_operation(self, sep) def reverse(self) -> ArrayVar[ARRAY_VAR_TYPE]: diff --git a/tests/components/test_component.py b/tests/components/test_component.py index 4dda81896..73d3f611b 100644 --- a/tests/components/test_component.py +++ b/tests/components/test_component.py @@ -1288,6 +1288,16 @@ class EventState(rx.State): [FORMATTED_TEST_VAR], id="fstring-class_name", ), + pytest.param( + rx.fragment(class_name=f"foo{TEST_VAR}bar other-class"), + [LiteralVar.create(f"{FORMATTED_TEST_VAR} other-class")], + id="fstring-dual-class_name", + ), + pytest.param( + rx.fragment(class_name=[TEST_VAR, "other-class"]), + [LiteralVar.create([TEST_VAR, "other-class"]).join(" ")], + id="fstring-dual-class_name", + ), pytest.param( rx.fragment(special_props=[TEST_VAR]), [TEST_VAR], From e1538b75f8ab5639fe609176e9c26ae218e35485 Mon Sep 17 00:00:00 2001 From: Masen Furer Date: Wed, 25 Sep 2024 11:31:45 -0700 Subject: [PATCH 18/39] bump to 0.6.1 for further dev (#3995) * bump version to 0.6.1dev1 * bump reflex-chakra requirement to 0.6.0 * update poetry file --------- Co-authored-by: Khaleel Al-Adhami --- poetry.lock | 20 ++++++++------------ pyproject.toml | 4 ++-- 2 files changed, 10 insertions(+), 14 deletions(-) diff --git a/poetry.lock b/poetry.lock index c587977e9..7a836a021 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.8.2 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. [[package]] name = "alembic" @@ -2087,13 +2087,13 @@ ocsp = ["cryptography (>=36.0.1)", "pyopenssl (==20.0.1)", "requests (>=2.26.0)" [[package]] name = "reflex-chakra" -version = "0.6.0a7" +version = "0.6.0" description = "reflex using chakra components" optional = false python-versions = "<4.0,>=3.8" files = [ - {file = "reflex_chakra-0.6.0a7-py3-none-any.whl", hash = "sha256:d693aee7323af13ce491165ffb4fe392344939e45516991671d5601727f749f4"}, - {file = "reflex_chakra-0.6.0a7.tar.gz", hash = "sha256:fe17282e439fdfdfd507e24857a615258ef9789f15049aa0e70239fbcb4d5fbf"}, + {file = "reflex_chakra-0.6.0-py3-none-any.whl", hash = "sha256:eca1593fca67289e05591dd21fbcc8632c119d64a08bdc41fd995055a114cc91"}, + {file = "reflex_chakra-0.6.0.tar.gz", hash = "sha256:db1c7b48f1ba547bf91e5af103fce6fc7191d7225b414ebfbada7d983e33dd87"}, ] [package.dependencies] @@ -2334,9 +2334,7 @@ python-versions = ">=3.7" files = [ {file = "SQLAlchemy-2.0.35-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:67219632be22f14750f0d1c70e62f204ba69d28f62fd6432ba05ab295853de9b"}, {file = "SQLAlchemy-2.0.35-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4668bd8faf7e5b71c0319407b608f278f279668f358857dbfd10ef1954ac9f90"}, - {file = "SQLAlchemy-2.0.35-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb8bea573863762bbf45d1e13f87c2d2fd32cee2dbd50d050f83f87429c9e1ea"}, {file = "SQLAlchemy-2.0.35-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f552023710d4b93d8fb29a91fadf97de89c5926c6bd758897875435f2a939f33"}, - {file = "SQLAlchemy-2.0.35-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:016b2e665f778f13d3c438651dd4de244214b527a275e0acf1d44c05bc6026a9"}, {file = "SQLAlchemy-2.0.35-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:7befc148de64b6060937231cbff8d01ccf0bfd75aa26383ffdf8d82b12ec04ff"}, {file = "SQLAlchemy-2.0.35-cp310-cp310-win32.whl", hash = "sha256:22b83aed390e3099584b839b93f80a0f4a95ee7f48270c97c90acd40ee646f0b"}, {file = "SQLAlchemy-2.0.35-cp310-cp310-win_amd64.whl", hash = "sha256:a29762cd3d116585278ffb2e5b8cc311fb095ea278b96feef28d0b423154858e"}, @@ -2373,9 +2371,7 @@ files = [ {file = "SQLAlchemy-2.0.35-cp38-cp38-win_amd64.whl", hash = "sha256:0375a141e1c0878103eb3d719eb6d5aa444b490c96f3fedab8471c7f6ffe70ee"}, {file = "SQLAlchemy-2.0.35-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ccae5de2a0140d8be6838c331604f91d6fafd0735dbdcee1ac78fc8fbaba76b4"}, {file = "SQLAlchemy-2.0.35-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:2a275a806f73e849e1c309ac11108ea1a14cd7058577aba962cd7190e27c9e3c"}, - {file = "SQLAlchemy-2.0.35-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:732e026240cdd1c1b2e3ac515c7a23820430ed94292ce33806a95869c46bd139"}, {file = "SQLAlchemy-2.0.35-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:890da8cd1941fa3dab28c5bac3b9da8502e7e366f895b3b8e500896f12f94d11"}, - {file = "SQLAlchemy-2.0.35-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:c0d8326269dbf944b9201911b0d9f3dc524d64779a07518199a58384c3d37a44"}, {file = "SQLAlchemy-2.0.35-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:b76d63495b0508ab9fc23f8152bac63205d2a704cd009a2b0722f4c8e0cba8e0"}, {file = "SQLAlchemy-2.0.35-cp39-cp39-win32.whl", hash = "sha256:69683e02e8a9de37f17985905a5eca18ad651bf592314b4d3d799029797d0eb3"}, {file = "SQLAlchemy-2.0.35-cp39-cp39-win_amd64.whl", hash = "sha256:aee110e4ef3c528f3abbc3c2018c121e708938adeeff9006428dd7c8555e9b3f"}, @@ -2618,13 +2614,13 @@ files = [ [[package]] name = "tzdata" -version = "2024.1" +version = "2024.2" description = "Provider of IANA time zone data" optional = false python-versions = ">=2" files = [ - {file = "tzdata-2024.1-py2.py3-none-any.whl", hash = "sha256:9068bc196136463f5245e51efda838afa15aaeca9903f49050dfa2679db4d252"}, - {file = "tzdata-2024.1.tar.gz", hash = "sha256:2674120f8d891909751c38abcdfd386ac0a5a1127954fbc332af6b5ceae07efd"}, + {file = "tzdata-2024.2-py2.py3-none-any.whl", hash = "sha256:a48093786cdcde33cad18c2555e8532f34422074448fbc874186f0abd79565cd"}, + {file = "tzdata-2024.2.tar.gz", hash = "sha256:7d85cc416e9382e69095b7bdf4afd9e3880418a2413feec7069d533d6b4e31cc"}, ] [[package]] @@ -2926,4 +2922,4 @@ type = ["pytest-mypy"] [metadata] lock-version = "2.0" python-versions = "^3.9" -content-hash = "83a12dac424352fe71847b80f3cf08d0c3fb68da088561cc6630d6476a60627d" +content-hash = "df66545bb3f79e356e3a91866306f8b18b800aeadf1f98ac184644f986a83d95" diff --git a/pyproject.toml b/pyproject.toml index 6a58f4700..c8a62e195 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "reflex" -version = "0.6.0a1" +version = "0.6.1dev1" description = "Web apps in pure Python." license = "Apache-2.0" authors = [ @@ -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.0a6" +reflex-chakra = ">=0.6.0" [tool.poetry.group.dev.dependencies] pytest = ">=7.1.2,<8.0" From 9d8b737b1aa9ed486d1a1cfaf9726dbcf4e11eae Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Wed, 25 Sep 2024 13:11:04 -0700 Subject: [PATCH 19/39] hash the state file name (#4000) * hash the state file name * forgot to digest my food oop --- reflex/state.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/reflex/state.py b/reflex/state.py index 8b32d1a07..f0f3e1453 100644 --- a/reflex/state.py +++ b/reflex/state.py @@ -12,6 +12,7 @@ import os import uuid from abc import ABC, abstractmethod from collections import defaultdict +from hashlib import md5 from pathlib import Path from types import FunctionType, MethodType from typing import ( @@ -2704,7 +2705,9 @@ class StateManagerDisk(StateManager): Returns: The path for the token. """ - return (self.states_directory / f"{token}.pkl").absolute() + return ( + self.states_directory / f"{md5(token.encode()).hexdigest()}.pkl" + ).absolute() async def load_state(self, token: str, root_state: BaseState) -> BaseState: """Load a state object based on the provided token. From b07fba72e9c6049ab35f37644ad2b15aa68b9b24 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Brand=C3=A9ho?= Date: Thu, 26 Sep 2024 00:57:08 +0200 Subject: [PATCH 20/39] add missing message when running in backend_only (#4002) * add missing message when running in backend_only * actually pass loglevel to backend_cmd --- reflex/reflex.py | 4 ++-- reflex/utils/exec.py | 22 +++++++++++++++++++--- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/reflex/reflex.py b/reflex/reflex.py index c90e328e4..07c1dff5b 100644 --- a/reflex/reflex.py +++ b/reflex/reflex.py @@ -247,13 +247,13 @@ def _run( # In prod mode, run the backend on a separate thread. if backend and env == constants.Env.PROD: - commands.append((backend_cmd, backend_host, backend_port)) + commands.append((backend_cmd, backend_host, backend_port, loglevel, frontend)) # Start the frontend and backend. with processes.run_concurrently_context(*commands): # In dev mode, run the backend on the main thread. if backend and env == constants.Env.DEV: - backend_cmd(backend_host, int(backend_port)) + backend_cmd(backend_host, int(backend_port), loglevel, frontend) # The windows uvicorn bug workaround # https://github.com/reflex-dev/reflex/issues/2335 if constants.IS_WINDOWS and exec.frontend_process: diff --git a/reflex/utils/exec.py b/reflex/utils/exec.py index 013fa8734..82fb8d9b3 100644 --- a/reflex/utils/exec.py +++ b/reflex/utils/exec.py @@ -60,6 +60,13 @@ def kill(proc_pid: int): process.kill() +def notify_backend(): + """Output a string notifying where the backend is running.""" + console.print( + f"Backend running at: [bold green]http://0.0.0.0:{get_config().backend_port}[/bold green]" + ) + + # run_process_and_launch_url is assumed to be used # only to launch the frontend # If this is not the case, might have to change the logic @@ -103,9 +110,7 @@ def run_process_and_launch_url(run_command: list[str], backend_present=True): f"App running at: [bold green]{url}[/bold green]{' (Frontend-only mode)' if not backend_present else ''}" ) if backend_present: - console.print( - f"Backend running at: [bold green]http://0.0.0.0:{get_config().backend_port}[/bold green]" - ) + notify_backend() first_run = False else: console.print("New packages detected: Updating app...") @@ -203,6 +208,7 @@ def run_backend( host: str, port: int, loglevel: constants.LogLevel = constants.LogLevel.ERROR, + frontend_present: bool = False, ): """Run the backend. @@ -210,11 +216,16 @@ def run_backend( host: The app host port: The app port loglevel: The log level. + frontend_present: Whether the frontend is present. """ web_dir = get_web_dir() # Create a .nocompile file to skip compile for backend. if web_dir.exists(): (web_dir / constants.NOCOMPILE_FILE).touch() + + if not frontend_present: + notify_backend() + # Run the backend in development mode. if should_use_granian(): run_granian_backend(host, port, loglevel) @@ -288,6 +299,7 @@ def run_backend_prod( host: str, port: int, loglevel: constants.LogLevel = constants.LogLevel.ERROR, + frontend_present: bool = False, ): """Run the backend. @@ -295,7 +307,11 @@ def run_backend_prod( host: The app host port: The app port loglevel: The log level. + frontend_present: Whether the frontend is present. """ + if not frontend_present: + notify_backend() + if should_use_granian(): run_granian_backend_prod(host, port, loglevel) else: From 3f538865b5c4f3d777f528e0c8958e1da9f7fa9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Brand=C3=A9ho?= Date: Thu, 26 Sep 2024 01:22:52 +0200 Subject: [PATCH 21/39] reorganize all tests in a single top folder (#3981) * lift node version restraint to allow more recent version if already installed * add node test for latest version * change python version * use purple for debug logs * update workflow * add playwright dev dependency * update workflow * change test * oops * improve test * update test * fix tests * mv units tests to a subfolder * reorganize tests * fix install * update test_state * revert node changes and only keep new tests organization * move integration tests in tests/integration * fix integration workflow * fix dockerfile workflow * fix dockerfile workflow 2 * fix shared_state --- .github/workflows/check_node_latest.yml | 39 +++ .github/workflows/integration_app_harness.yml | 2 +- .../workflows/reflex_init_in_docker_test.yml | 4 +- .github/workflows/unit_tests.yml | 6 +- .pre-commit-config.yaml | 2 +- CONTRIBUTING.md | 2 +- poetry.lock | 239 ++++++++++++------ pyproject.toml | 2 + reflex/utils/console.py | 2 +- .../integration}/__init__.py | 0 .../integration}/conftest.py | 0 .../integration}/init-test/Dockerfile | 0 .../init-test/in_docker_test_script.sh | 2 +- .../integration}/shared/state.py | 0 .../integration}/test_background_task.py | 0 .../integration}/test_call_script.py | 0 .../integration}/test_client_storage.py | 0 .../integration}/test_component_state.py | 0 .../integration}/test_computed_vars.py | 0 .../integration}/test_connection_banner.py | 0 .../integration}/test_deploy_url.py | 0 .../integration}/test_dynamic_components.py | 0 .../integration}/test_dynamic_routes.py | 0 .../integration}/test_event_actions.py | 0 .../integration}/test_event_chain.py | 0 .../integration}/test_exception_handlers.py | 0 .../integration}/test_form_submit.py | 0 .../integration}/test_input.py | 0 .../integration}/test_large_state.py | 0 .../integration}/test_lifespan.py | 0 .../integration}/test_login_flow.py | 0 .../integration}/test_media.py | 0 .../integration}/test_navigation.py | 0 .../integration}/test_server_side_event.py | 0 .../integration}/test_shared_state.py | 2 +- .../integration}/test_state_inheritance.py | 0 .../integration}/test_table.py | 0 .../integration}/test_tailwind.py | 0 .../integration}/test_upload.py | 0 .../integration}/test_urls.py | 0 .../integration}/test_var_operations.py | 0 {integration => tests/integration}/utils.py | 0 tests/test_node_version.py | 73 ++++++ tests/units/__init__.py | 5 + tests/{ => units}/compiler/__init__.py | 0 tests/{ => units}/compiler/test_compiler.py | 0 .../compiler/test_compiler_utils.py | 0 tests/{ => units}/components/__init__.py | 0 .../{ => units}/components/base/test_bare.py | 0 .../{ => units}/components/base/test_link.py | 0 .../components/base/test_script.py | 0 tests/{ => units}/components/core/__init__.py | 0 .../components/core/test_banner.py | 0 .../components/core/test_colors.py | 0 .../{ => units}/components/core/test_cond.py | 0 .../components/core/test_debounce.py | 0 .../components/core/test_foreach.py | 0 .../{ => units}/components/core/test_html.py | 0 .../{ => units}/components/core/test_match.py | 0 .../components/core/test_responsive.py | 0 .../components/core/test_upload.py | 0 .../components/datadisplay/__init__.py | 0 .../components/datadisplay/conftest.py | 0 .../components/datadisplay/test_code.py | 0 .../components/datadisplay/test_dataeditor.py | 0 .../components/datadisplay/test_datatable.py | 0 tests/{ => units}/components/el/test_html.py | 0 .../{ => units}/components/forms/__init__.py | 0 .../{ => units}/components/forms/test_form.py | 0 .../components/forms/test_uploads.py | 0 .../components/graphing/__init__.py | 0 .../components/graphing/test_plotly.py | 0 .../components/graphing/test_recharts.py | 0 .../{ => units}/components/layout/__init__.py | 0 .../components/lucide/test_icon.py | 0 .../{ => units}/components/media/__init__.py | 0 .../components/media/test_image.py | 0 .../components/radix/test_icon_button.py | 0 .../components/radix/test_layout.py | 0 .../components/recharts/test_cartesian.py | 0 .../components/recharts/test_polar.py | 0 .../{ => units}/components/test_component.py | 0 .../test_component_future_annotations.py | 0 .../components/test_component_state.py | 0 tests/{ => units}/components/test_tag.py | 0 .../components/typography/__init__.py | 0 .../components/typography/test_markdown.py | 0 tests/{ => units}/conftest.py | 0 .../{ => units}/experimental/custom_script.js | 0 tests/{ => units}/experimental/test_assets.py | 0 tests/{ => units}/middleware/__init__.py | 0 tests/{ => units}/middleware/conftest.py | 0 .../middleware/test_hydrate_middleware.py | 0 tests/{ => units}/states/__init__.py | 0 tests/{ => units}/states/mutation.py | 0 tests/{ => units}/states/upload.py | 0 tests/{ => units}/test_app.py | 0 .../{ => units}/test_attribute_access_type.py | 0 tests/{ => units}/test_base.py | 0 tests/{ => units}/test_config.py | 0 tests/{ => units}/test_db_config.py | 0 tests/{ => units}/test_event.py | 0 tests/{ => units}/test_health_endpoint.py | 0 tests/{ => units}/test_model.py | 0 tests/{ => units}/test_page.py | 0 tests/{ => units}/test_prerequisites.py | 0 tests/{ => units}/test_route.py | 0 tests/{ => units}/test_sqlalchemy.py | 0 tests/{ => units}/test_state.py | 20 +- tests/{ => units}/test_state_tree.py | 0 tests/{ => units}/test_style.py | 0 tests/{ => units}/test_telemetry.py | 0 tests/{ => units}/test_testing.py | 0 tests/{ => units}/test_var.py | 0 tests/{ => units}/utils/__init__.py | 0 tests/{ => units}/utils/test_format.py | 2 +- tests/{ => units}/utils/test_imports.py | 0 tests/{ => units}/utils/test_serializers.py | 0 tests/{ => units}/utils/test_types.py | 0 tests/{ => units}/utils/test_utils.py | 0 tests/{ => units}/vars/test_base.py | 0 121 files changed, 306 insertions(+), 96 deletions(-) create mode 100644 .github/workflows/check_node_latest.yml rename {integration => tests/integration}/__init__.py (100%) rename {integration => tests/integration}/conftest.py (100%) rename {integration => tests/integration}/init-test/Dockerfile (100%) rename {integration => tests/integration}/init-test/in_docker_test_script.sh (96%) rename {integration => tests/integration}/shared/state.py (100%) rename {integration => tests/integration}/test_background_task.py (100%) rename {integration => tests/integration}/test_call_script.py (100%) rename {integration => tests/integration}/test_client_storage.py (100%) rename {integration => tests/integration}/test_component_state.py (100%) rename {integration => tests/integration}/test_computed_vars.py (100%) rename {integration => tests/integration}/test_connection_banner.py (100%) rename {integration => tests/integration}/test_deploy_url.py (100%) rename {integration => tests/integration}/test_dynamic_components.py (100%) rename {integration => tests/integration}/test_dynamic_routes.py (100%) rename {integration => tests/integration}/test_event_actions.py (100%) rename {integration => tests/integration}/test_event_chain.py (100%) rename {integration => tests/integration}/test_exception_handlers.py (100%) rename {integration => tests/integration}/test_form_submit.py (100%) rename {integration => tests/integration}/test_input.py (100%) rename {integration => tests/integration}/test_large_state.py (100%) rename {integration => tests/integration}/test_lifespan.py (100%) rename {integration => tests/integration}/test_login_flow.py (100%) rename {integration => tests/integration}/test_media.py (100%) rename {integration => tests/integration}/test_navigation.py (100%) rename {integration => tests/integration}/test_server_side_event.py (100%) rename {integration => tests/integration}/test_shared_state.py (96%) rename {integration => tests/integration}/test_state_inheritance.py (100%) rename {integration => tests/integration}/test_table.py (100%) rename {integration => tests/integration}/test_tailwind.py (100%) rename {integration => tests/integration}/test_upload.py (100%) rename {integration => tests/integration}/test_urls.py (100%) rename {integration => tests/integration}/test_var_operations.py (100%) rename {integration => tests/integration}/utils.py (100%) create mode 100644 tests/test_node_version.py create mode 100644 tests/units/__init__.py rename tests/{ => units}/compiler/__init__.py (100%) rename tests/{ => units}/compiler/test_compiler.py (100%) rename tests/{ => units}/compiler/test_compiler_utils.py (100%) rename tests/{ => units}/components/__init__.py (100%) rename tests/{ => units}/components/base/test_bare.py (100%) rename tests/{ => units}/components/base/test_link.py (100%) rename tests/{ => units}/components/base/test_script.py (100%) rename tests/{ => units}/components/core/__init__.py (100%) rename tests/{ => units}/components/core/test_banner.py (100%) rename tests/{ => units}/components/core/test_colors.py (100%) rename tests/{ => units}/components/core/test_cond.py (100%) rename tests/{ => units}/components/core/test_debounce.py (100%) rename tests/{ => units}/components/core/test_foreach.py (100%) rename tests/{ => units}/components/core/test_html.py (100%) rename tests/{ => units}/components/core/test_match.py (100%) rename tests/{ => units}/components/core/test_responsive.py (100%) rename tests/{ => units}/components/core/test_upload.py (100%) rename tests/{ => units}/components/datadisplay/__init__.py (100%) rename tests/{ => units}/components/datadisplay/conftest.py (100%) rename tests/{ => units}/components/datadisplay/test_code.py (100%) rename tests/{ => units}/components/datadisplay/test_dataeditor.py (100%) rename tests/{ => units}/components/datadisplay/test_datatable.py (100%) rename tests/{ => units}/components/el/test_html.py (100%) rename tests/{ => units}/components/forms/__init__.py (100%) rename tests/{ => units}/components/forms/test_form.py (100%) rename tests/{ => units}/components/forms/test_uploads.py (100%) rename tests/{ => units}/components/graphing/__init__.py (100%) rename tests/{ => units}/components/graphing/test_plotly.py (100%) rename tests/{ => units}/components/graphing/test_recharts.py (100%) rename tests/{ => units}/components/layout/__init__.py (100%) rename tests/{ => units}/components/lucide/test_icon.py (100%) rename tests/{ => units}/components/media/__init__.py (100%) rename tests/{ => units}/components/media/test_image.py (100%) rename tests/{ => units}/components/radix/test_icon_button.py (100%) rename tests/{ => units}/components/radix/test_layout.py (100%) rename tests/{ => units}/components/recharts/test_cartesian.py (100%) rename tests/{ => units}/components/recharts/test_polar.py (100%) rename tests/{ => units}/components/test_component.py (100%) rename tests/{ => units}/components/test_component_future_annotations.py (100%) rename tests/{ => units}/components/test_component_state.py (100%) rename tests/{ => units}/components/test_tag.py (100%) rename tests/{ => units}/components/typography/__init__.py (100%) rename tests/{ => units}/components/typography/test_markdown.py (100%) rename tests/{ => units}/conftest.py (100%) rename tests/{ => units}/experimental/custom_script.js (100%) rename tests/{ => units}/experimental/test_assets.py (100%) rename tests/{ => units}/middleware/__init__.py (100%) rename tests/{ => units}/middleware/conftest.py (100%) rename tests/{ => units}/middleware/test_hydrate_middleware.py (100%) rename tests/{ => units}/states/__init__.py (100%) rename tests/{ => units}/states/mutation.py (100%) rename tests/{ => units}/states/upload.py (100%) rename tests/{ => units}/test_app.py (100%) rename tests/{ => units}/test_attribute_access_type.py (100%) rename tests/{ => units}/test_base.py (100%) rename tests/{ => units}/test_config.py (100%) rename tests/{ => units}/test_db_config.py (100%) rename tests/{ => units}/test_event.py (100%) rename tests/{ => units}/test_health_endpoint.py (100%) rename tests/{ => units}/test_model.py (100%) rename tests/{ => units}/test_page.py (100%) rename tests/{ => units}/test_prerequisites.py (100%) rename tests/{ => units}/test_route.py (100%) rename tests/{ => units}/test_sqlalchemy.py (100%) rename tests/{ => units}/test_state.py (99%) rename tests/{ => units}/test_state_tree.py (100%) rename tests/{ => units}/test_style.py (100%) rename tests/{ => units}/test_telemetry.py (100%) rename tests/{ => units}/test_testing.py (100%) rename tests/{ => units}/test_var.py (100%) rename tests/{ => units}/utils/__init__.py (100%) rename tests/{ => units}/utils/test_format.py (99%) rename tests/{ => units}/utils/test_imports.py (100%) rename tests/{ => units}/utils/test_serializers.py (100%) rename tests/{ => units}/utils/test_types.py (100%) rename tests/{ => units}/utils/test_utils.py (100%) rename tests/{ => units}/vars/test_base.py (100%) diff --git a/.github/workflows/check_node_latest.yml b/.github/workflows/check_node_latest.yml new file mode 100644 index 000000000..945786c05 --- /dev/null +++ b/.github/workflows/check_node_latest.yml @@ -0,0 +1,39 @@ +name: integration-node-latest + +on: + push: + branches: + - main + pull_request: + branches: + - main + +env: + TELEMETRY_ENABLED: false + +jobs: + check_latest_node: + runs-on: ubuntu-latest + strategy: + matrix: + python-version: ['3.12'] + node-version: ['node'] + steps: + - uses: actions/checkout@v4 + - uses: ./.github/actions/setup_build_env + with: + python-version: ${{ matrix.python-version }} + run-poetry-install: true + create-venv-at-path: .venv + - uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node-version }} + - run: | + poetry run uv pip install pyvirtualdisplay pillow + poetry run playwright install --with-deps + - run: | + # poetry run pytest tests/test_node_version.py + poetry run pytest tests/integration + + + diff --git a/.github/workflows/integration_app_harness.yml b/.github/workflows/integration_app_harness.yml index 93412aa71..c11e6e01c 100644 --- a/.github/workflows/integration_app_harness.yml +++ b/.github/workflows/integration_app_harness.yml @@ -51,7 +51,7 @@ jobs: SCREENSHOT_DIR: /tmp/screenshots REDIS_URL: ${{ matrix.state_manager == 'redis' && 'redis://localhost:6379' || '' }} run: | - poetry run pytest integration + poetry run pytest tests/integration - uses: actions/upload-artifact@v4 name: Upload failed test screenshots if: always() diff --git a/.github/workflows/reflex_init_in_docker_test.yml b/.github/workflows/reflex_init_in_docker_test.yml index b34efd2f6..ce56a1310 100644 --- a/.github/workflows/reflex_init_in_docker_test.yml +++ b/.github/workflows/reflex_init_in_docker_test.yml @@ -28,5 +28,5 @@ jobs: # Run reflex init in a docker container # cwd is repo root - docker build -f integration/init-test/Dockerfile -t reflex-init-test integration/init-test - docker run --rm -v $(pwd):/reflex-repo/ reflex-init-test /reflex-repo/integration/init-test/in_docker_test_script.sh + docker build -f tests/integration/init-test/Dockerfile -t reflex-init-test tests/integration/init-test + docker run --rm -v $(pwd):/reflex-repo/ reflex-init-test /reflex-repo/tests/integration/init-test/in_docker_test_script.sh diff --git a/.github/workflows/unit_tests.yml b/.github/workflows/unit_tests.yml index 333c54cb3..09e489949 100644 --- a/.github/workflows/unit_tests.yml +++ b/.github/workflows/unit_tests.yml @@ -65,17 +65,17 @@ jobs: - name: Run unit tests run: | export PYTHONUNBUFFERED=1 - poetry run pytest tests --cov --no-cov-on-fail --cov-report= + poetry run pytest tests/units --cov --no-cov-on-fail --cov-report= - name: Run unit tests w/ redis if: ${{ matrix.os == 'ubuntu-latest' }} run: | export PYTHONUNBUFFERED=1 export REDIS_URL=redis://localhost:6379 - poetry run pytest tests --cov --no-cov-on-fail --cov-report= + poetry run pytest tests/units --cov --no-cov-on-fail --cov-report= # Change to explicitly install v1 when reflex-hosting-cli is compatible with v2 - name: Run unit tests w/ pydantic v1 run: | export PYTHONUNBUFFERED=1 poetry run uv pip install "pydantic~=1.10" - poetry run pytest tests --cov --no-cov-on-fail --cov-report= + poetry run pytest tests/units --cov --no-cov-on-fail --cov-report= - run: poetry run coverage html diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index fbd88c485..609364a6e 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -6,7 +6,7 @@ repos: rev: v0.4.10 hooks: - id: ruff-format - args: [integration, reflex, tests] + args: [reflex, tests] - id: ruff args: ["--fix", "--exit-non-zero-on-fix"] exclude: '^integration/benchmarks/' diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index af195997a..fc8398013 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -69,7 +69,7 @@ In your `reflex` directory run make sure all the unit tests are still passing us This will fail if code coverage is below 70%. ``` bash -poetry run pytest tests --cov --no-cov-on-fail --cov-report= +poetry run pytest tests/units --cov --no-cov-on-fail --cov-report= ``` Next make sure all the following tests pass. This ensures that every new change has proper documentation and type checking. diff --git a/poetry.lock b/poetry.lock index 7a836a021..f94a3832a 100644 --- a/poetry.lock +++ b/poetry.lock @@ -616,84 +616,69 @@ typing = ["typing-extensions (>=4.12.2)"] [[package]] name = "greenlet" -version = "3.1.1" +version = "3.0.3" description = "Lightweight in-process concurrent programming" optional = false python-versions = ">=3.7" files = [ - {file = "greenlet-3.1.1-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:0bbae94a29c9e5c7e4a2b7f0aae5c17e8e90acbfd3bf6270eeba60c39fce3563"}, - {file = "greenlet-3.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0fde093fb93f35ca72a556cf72c92ea3ebfda3d79fc35bb19fbe685853869a83"}, - {file = "greenlet-3.1.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:36b89d13c49216cadb828db8dfa6ce86bbbc476a82d3a6c397f0efae0525bdd0"}, - {file = "greenlet-3.1.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:94b6150a85e1b33b40b1464a3f9988dcc5251d6ed06842abff82e42632fac120"}, - {file = "greenlet-3.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:93147c513fac16385d1036b7e5b102c7fbbdb163d556b791f0f11eada7ba65dc"}, - {file = "greenlet-3.1.1-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:da7a9bff22ce038e19bf62c4dd1ec8391062878710ded0a845bcf47cc0200617"}, - {file = "greenlet-3.1.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b2795058c23988728eec1f36a4e5e4ebad22f8320c85f3587b539b9ac84128d7"}, - {file = "greenlet-3.1.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:ed10eac5830befbdd0c32f83e8aa6288361597550ba669b04c48f0f9a2c843c6"}, - {file = "greenlet-3.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:77c386de38a60d1dfb8e55b8c1101d68c79dfdd25c7095d51fec2dd800892b80"}, - {file = "greenlet-3.1.1-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:e4d333e558953648ca09d64f13e6d8f0523fa705f51cae3f03b5983489958c70"}, - {file = "greenlet-3.1.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:09fc016b73c94e98e29af67ab7b9a879c307c6731a2c9da0db5a7d9b7edd1159"}, - {file = "greenlet-3.1.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d5e975ca70269d66d17dd995dafc06f1b06e8cb1ec1e9ed54c1d1e4a7c4cf26e"}, - {file = "greenlet-3.1.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3b2813dc3de8c1ee3f924e4d4227999285fd335d1bcc0d2be6dc3f1f6a318ec1"}, - {file = "greenlet-3.1.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e347b3bfcf985a05e8c0b7d462ba6f15b1ee1c909e2dcad795e49e91b152c383"}, - {file = "greenlet-3.1.1-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9e8f8c9cb53cdac7ba9793c276acd90168f416b9ce36799b9b885790f8ad6c0a"}, - {file = "greenlet-3.1.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:62ee94988d6b4722ce0028644418d93a52429e977d742ca2ccbe1c4f4a792511"}, - {file = "greenlet-3.1.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:1776fd7f989fc6b8d8c8cb8da1f6b82c5814957264d1f6cf818d475ec2bf6395"}, - {file = "greenlet-3.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:48ca08c771c268a768087b408658e216133aecd835c0ded47ce955381105ba39"}, - {file = "greenlet-3.1.1-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:4afe7ea89de619adc868e087b4d2359282058479d7cfb94970adf4b55284574d"}, - {file = "greenlet-3.1.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f406b22b7c9a9b4f8aa9d2ab13d6ae0ac3e85c9a809bd590ad53fed2bf70dc79"}, - {file = "greenlet-3.1.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c3a701fe5a9695b238503ce5bbe8218e03c3bcccf7e204e455e7462d770268aa"}, - {file = "greenlet-3.1.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2846930c65b47d70b9d178e89c7e1a69c95c1f68ea5aa0a58646b7a96df12441"}, - {file = "greenlet-3.1.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:99cfaa2110534e2cf3ba31a7abcac9d328d1d9f1b95beede58294a60348fba36"}, - {file = "greenlet-3.1.1-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1443279c19fca463fc33e65ef2a935a5b09bb90f978beab37729e1c3c6c25fe9"}, - {file = "greenlet-3.1.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b7cede291382a78f7bb5f04a529cb18e068dd29e0fb27376074b6d0317bf4dd0"}, - {file = "greenlet-3.1.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:23f20bb60ae298d7d8656c6ec6db134bca379ecefadb0b19ce6f19d1f232a942"}, - {file = "greenlet-3.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:7124e16b4c55d417577c2077be379514321916d5790fa287c9ed6f23bd2ffd01"}, - {file = "greenlet-3.1.1-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:05175c27cb459dcfc05d026c4232f9de8913ed006d42713cb8a5137bd49375f1"}, - {file = "greenlet-3.1.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:935e943ec47c4afab8965954bf49bfa639c05d4ccf9ef6e924188f762145c0ff"}, - {file = "greenlet-3.1.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:667a9706c970cb552ede35aee17339a18e8f2a87a51fba2ed39ceeeb1004798a"}, - {file = "greenlet-3.1.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b8a678974d1f3aa55f6cc34dc480169d58f2e6d8958895d68845fa4ab566509e"}, - {file = "greenlet-3.1.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:efc0f674aa41b92da8c49e0346318c6075d734994c3c4e4430b1c3f853e498e4"}, - {file = "greenlet-3.1.1-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0153404a4bb921f0ff1abeb5ce8a5131da56b953eda6e14b88dc6bbc04d2049e"}, - {file = "greenlet-3.1.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:275f72decf9932639c1c6dd1013a1bc266438eb32710016a1c742df5da6e60a1"}, - {file = "greenlet-3.1.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:c4aab7f6381f38a4b42f269057aee279ab0fc7bf2e929e3d4abfae97b682a12c"}, - {file = "greenlet-3.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:b42703b1cf69f2aa1df7d1030b9d77d3e584a70755674d60e710f0af570f3761"}, - {file = "greenlet-3.1.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f1695e76146579f8c06c1509c7ce4dfe0706f49c6831a817ac04eebb2fd02011"}, - {file = "greenlet-3.1.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7876452af029456b3f3549b696bb36a06db7c90747740c5302f74a9e9fa14b13"}, - {file = "greenlet-3.1.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4ead44c85f8ab905852d3de8d86f6f8baf77109f9da589cb4fa142bd3b57b475"}, - {file = "greenlet-3.1.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8320f64b777d00dd7ccdade271eaf0cad6636343293a25074cc5566160e4de7b"}, - {file = "greenlet-3.1.1-cp313-cp313t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6510bf84a6b643dabba74d3049ead221257603a253d0a9873f55f6a59a65f822"}, - {file = "greenlet-3.1.1-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:04b013dc07c96f83134b1e99888e7a79979f1a247e2a9f59697fa14b5862ed01"}, - {file = "greenlet-3.1.1-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:411f015496fec93c1c8cd4e5238da364e1da7a124bcb293f085bf2860c32c6f6"}, - {file = "greenlet-3.1.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:47da355d8687fd65240c364c90a31569a133b7b60de111c255ef5b606f2ae291"}, - {file = "greenlet-3.1.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:98884ecf2ffb7d7fe6bd517e8eb99d31ff7855a840fa6d0d63cd07c037f6a981"}, - {file = "greenlet-3.1.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f1d4aeb8891338e60d1ab6127af1fe45def5259def8094b9c7e34690c8858803"}, - {file = "greenlet-3.1.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db32b5348615a04b82240cc67983cb315309e88d444a288934ee6ceaebcad6cc"}, - {file = "greenlet-3.1.1-cp37-cp37m-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dcc62f31eae24de7f8dce72134c8651c58000d3b1868e01392baea7c32c247de"}, - {file = "greenlet-3.1.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:1d3755bcb2e02de341c55b4fca7a745a24a9e7212ac953f6b3a48d117d7257aa"}, - {file = "greenlet-3.1.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:b8da394b34370874b4572676f36acabac172602abf054cbc4ac910219f3340af"}, - {file = "greenlet-3.1.1-cp37-cp37m-win32.whl", hash = "sha256:a0dfc6c143b519113354e780a50381508139b07d2177cb6ad6a08278ec655798"}, - {file = "greenlet-3.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:54558ea205654b50c438029505def3834e80f0869a70fb15b871c29b4575ddef"}, - {file = "greenlet-3.1.1-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:346bed03fe47414091be4ad44786d1bd8bef0c3fcad6ed3dee074a032ab408a9"}, - {file = "greenlet-3.1.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dfc59d69fc48664bc693842bd57acfdd490acafda1ab52c7836e3fc75c90a111"}, - {file = "greenlet-3.1.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d21e10da6ec19b457b82636209cbe2331ff4306b54d06fa04b7c138ba18c8a81"}, - {file = "greenlet-3.1.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:37b9de5a96111fc15418819ab4c4432e4f3c2ede61e660b1e33971eba26ef9ba"}, - {file = "greenlet-3.1.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6ef9ea3f137e5711f0dbe5f9263e8c009b7069d8a1acea822bd5e9dae0ae49c8"}, - {file = "greenlet-3.1.1-cp38-cp38-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:85f3ff71e2e60bd4b4932a043fbbe0f499e263c628390b285cb599154a3b03b1"}, - {file = "greenlet-3.1.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:95ffcf719966dd7c453f908e208e14cde192e09fde6c7186c8f1896ef778d8cd"}, - {file = "greenlet-3.1.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:03a088b9de532cbfe2ba2034b2b85e82df37874681e8c470d6fb2f8c04d7e4b7"}, - {file = "greenlet-3.1.1-cp38-cp38-win32.whl", hash = "sha256:8b8b36671f10ba80e159378df9c4f15c14098c4fd73a36b9ad715f057272fbef"}, - {file = "greenlet-3.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:7017b2be767b9d43cc31416aba48aab0d2309ee31b4dbf10a1d38fb7972bdf9d"}, - {file = "greenlet-3.1.1-cp39-cp39-macosx_11_0_universal2.whl", hash = "sha256:396979749bd95f018296af156201d6211240e7a23090f50a8d5d18c370084dc3"}, - {file = "greenlet-3.1.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca9d0ff5ad43e785350894d97e13633a66e2b50000e8a183a50a88d834752d42"}, - {file = "greenlet-3.1.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f6ff3b14f2df4c41660a7dec01045a045653998784bf8cfcb5a525bdffffbc8f"}, - {file = "greenlet-3.1.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:94ebba31df2aa506d7b14866fed00ac141a867e63143fe5bca82a8e503b36437"}, - {file = "greenlet-3.1.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:73aaad12ac0ff500f62cebed98d8789198ea0e6f233421059fa68a5aa7220145"}, - {file = "greenlet-3.1.1-cp39-cp39-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:63e4844797b975b9af3a3fb8f7866ff08775f5426925e1e0bbcfe7932059a12c"}, - {file = "greenlet-3.1.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:7939aa3ca7d2a1593596e7ac6d59391ff30281ef280d8632fa03d81f7c5f955e"}, - {file = "greenlet-3.1.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d0028e725ee18175c6e422797c407874da24381ce0690d6b9396c204c7f7276e"}, - {file = "greenlet-3.1.1-cp39-cp39-win32.whl", hash = "sha256:5e06afd14cbaf9e00899fae69b24a32f2196c19de08fcb9f4779dd4f004e5e7c"}, - {file = "greenlet-3.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:3319aa75e0e0639bc15ff54ca327e8dc7a6fe404003496e3c6925cd3142e0e22"}, - {file = "greenlet-3.1.1.tar.gz", hash = "sha256:4ce3ac6cdb6adf7946475d7ef31777c26d94bccc377e070a7986bd2d5c515467"}, + {file = "greenlet-3.0.3-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:9da2bd29ed9e4f15955dd1595ad7bc9320308a3b766ef7f837e23ad4b4aac31a"}, + {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d353cadd6083fdb056bb46ed07e4340b0869c305c8ca54ef9da3421acbdf6881"}, + {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dca1e2f3ca00b84a396bc1bce13dd21f680f035314d2379c4160c98153b2059b"}, + {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3ed7fb269f15dc662787f4119ec300ad0702fa1b19d2135a37c2c4de6fadfd4a"}, + {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd4f49ae60e10adbc94b45c0b5e6a179acc1736cf7a90160b404076ee283cf83"}, + {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:73a411ef564e0e097dbe7e866bb2dda0f027e072b04da387282b02c308807405"}, + {file = "greenlet-3.0.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:7f362975f2d179f9e26928c5b517524e89dd48530a0202570d55ad6ca5d8a56f"}, + {file = "greenlet-3.0.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:649dde7de1a5eceb258f9cb00bdf50e978c9db1b996964cd80703614c86495eb"}, + {file = "greenlet-3.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:68834da854554926fbedd38c76e60c4a2e3198c6fbed520b106a8986445caaf9"}, + {file = "greenlet-3.0.3-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:b1b5667cced97081bf57b8fa1d6bfca67814b0afd38208d52538316e9422fc61"}, + {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:52f59dd9c96ad2fc0d5724107444f76eb20aaccb675bf825df6435acb7703559"}, + {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:afaff6cf5200befd5cec055b07d1c0a5a06c040fe5ad148abcd11ba6ab9b114e"}, + {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fe754d231288e1e64323cfad462fcee8f0288654c10bdf4f603a39ed923bef33"}, + {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2797aa5aedac23af156bbb5a6aa2cd3427ada2972c828244eb7d1b9255846379"}, + {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b7f009caad047246ed379e1c4dbcb8b020f0a390667ea74d2387be2998f58a22"}, + {file = "greenlet-3.0.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:c5e1536de2aad7bf62e27baf79225d0d64360d4168cf2e6becb91baf1ed074f3"}, + {file = "greenlet-3.0.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:894393ce10ceac937e56ec00bb71c4c2f8209ad516e96033e4b3b1de270e200d"}, + {file = "greenlet-3.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:1ea188d4f49089fc6fb283845ab18a2518d279c7cd9da1065d7a84e991748728"}, + {file = "greenlet-3.0.3-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:70fb482fdf2c707765ab5f0b6655e9cfcf3780d8d87355a063547b41177599be"}, + {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d4d1ac74f5c0c0524e4a24335350edad7e5f03b9532da7ea4d3c54d527784f2e"}, + {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:149e94a2dd82d19838fe4b2259f1b6b9957d5ba1b25640d2380bea9c5df37676"}, + {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:15d79dd26056573940fcb8c7413d84118086f2ec1a8acdfa854631084393efcc"}, + {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:881b7db1ebff4ba09aaaeae6aa491daeb226c8150fc20e836ad00041bcb11230"}, + {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fcd2469d6a2cf298f198f0487e0a5b1a47a42ca0fa4dfd1b6862c999f018ebbf"}, + {file = "greenlet-3.0.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:1f672519db1796ca0d8753f9e78ec02355e862d0998193038c7073045899f305"}, + {file = "greenlet-3.0.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:2516a9957eed41dd8f1ec0c604f1cdc86758b587d964668b5b196a9db5bfcde6"}, + {file = "greenlet-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:bba5387a6975598857d86de9eac14210a49d554a77eb8261cc68b7d082f78ce2"}, + {file = "greenlet-3.0.3-cp37-cp37m-macosx_11_0_universal2.whl", hash = "sha256:5b51e85cb5ceda94e79d019ed36b35386e8c37d22f07d6a751cb659b180d5274"}, + {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:daf3cb43b7cf2ba96d614252ce1684c1bccee6b2183a01328c98d36fcd7d5cb0"}, + {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:99bf650dc5d69546e076f413a87481ee1d2d09aaaaaca058c9251b6d8c14783f"}, + {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2dd6e660effd852586b6a8478a1d244b8dc90ab5b1321751d2ea15deb49ed414"}, + {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e3391d1e16e2a5a1507d83e4a8b100f4ee626e8eca43cf2cadb543de69827c4c"}, + {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e1f145462f1fa6e4a4ae3c0f782e580ce44d57c8f2c7aae1b6fa88c0b2efdb41"}, + {file = "greenlet-3.0.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:1a7191e42732df52cb5f39d3527217e7ab73cae2cb3694d241e18f53d84ea9a7"}, + {file = "greenlet-3.0.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:0448abc479fab28b00cb472d278828b3ccca164531daab4e970a0458786055d6"}, + {file = "greenlet-3.0.3-cp37-cp37m-win32.whl", hash = "sha256:b542be2440edc2d48547b5923c408cbe0fc94afb9f18741faa6ae970dbcb9b6d"}, + {file = "greenlet-3.0.3-cp37-cp37m-win_amd64.whl", hash = "sha256:01bc7ea167cf943b4c802068e178bbf70ae2e8c080467070d01bfa02f337ee67"}, + {file = "greenlet-3.0.3-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:1996cb9306c8595335bb157d133daf5cf9f693ef413e7673cb07e3e5871379ca"}, + {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3ddc0f794e6ad661e321caa8d2f0a55ce01213c74722587256fb6566049a8b04"}, + {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c9db1c18f0eaad2f804728c67d6c610778456e3e1cc4ab4bbd5eeb8e6053c6fc"}, + {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7170375bcc99f1a2fbd9c306f5be8764eaf3ac6b5cb968862cad4c7057756506"}, + {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b66c9c1e7ccabad3a7d037b2bcb740122a7b17a53734b7d72a344ce39882a1b"}, + {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:098d86f528c855ead3479afe84b49242e174ed262456c342d70fc7f972bc13c4"}, + {file = "greenlet-3.0.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:81bb9c6d52e8321f09c3d165b2a78c680506d9af285bfccbad9fb7ad5a5da3e5"}, + {file = "greenlet-3.0.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:fd096eb7ffef17c456cfa587523c5f92321ae02427ff955bebe9e3c63bc9f0da"}, + {file = "greenlet-3.0.3-cp38-cp38-win32.whl", hash = "sha256:d46677c85c5ba00a9cb6f7a00b2bfa6f812192d2c9f7d9c4f6a55b60216712f3"}, + {file = "greenlet-3.0.3-cp38-cp38-win_amd64.whl", hash = "sha256:419b386f84949bf0e7c73e6032e3457b82a787c1ab4a0e43732898a761cc9dbf"}, + {file = "greenlet-3.0.3-cp39-cp39-macosx_11_0_universal2.whl", hash = "sha256:da70d4d51c8b306bb7a031d5cff6cc25ad253affe89b70352af5f1cb68e74b53"}, + {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:086152f8fbc5955df88382e8a75984e2bb1c892ad2e3c80a2508954e52295257"}, + {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d73a9fe764d77f87f8ec26a0c85144d6a951a6c438dfe50487df5595c6373eac"}, + {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b7dcbe92cc99f08c8dd11f930de4d99ef756c3591a5377d1d9cd7dd5e896da71"}, + {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1551a8195c0d4a68fac7a4325efac0d541b48def35feb49d803674ac32582f61"}, + {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:64d7675ad83578e3fc149b617a444fab8efdafc9385471f868eb5ff83e446b8b"}, + {file = "greenlet-3.0.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b37eef18ea55f2ffd8f00ff8fe7c8d3818abd3e25fb73fae2ca3b672e333a7a6"}, + {file = "greenlet-3.0.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:77457465d89b8263bca14759d7c1684df840b6811b2499838cc5b040a8b5b113"}, + {file = "greenlet-3.0.3-cp39-cp39-win32.whl", hash = "sha256:57e8974f23e47dac22b83436bdcf23080ade568ce77df33159e019d161ce1d1e"}, + {file = "greenlet-3.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:c5ee858cfe08f34712f548c3c363e807e7186f03ad7a5039ebadb29e8c6be067"}, + {file = "greenlet-3.0.3.tar.gz", hash = "sha256:43374442353259554ce33599da8b692d5aa96f8976d567d4badf263371fbe491"}, ] [package.extras] @@ -1527,6 +1512,26 @@ docs = ["furo (>=2024.8.6)", "proselint (>=0.14)", "sphinx (>=8.0.2)", "sphinx-a test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=8.3.2)", "pytest-cov (>=5)", "pytest-mock (>=3.14)"] type = ["mypy (>=1.11.2)"] +[[package]] +name = "playwright" +version = "1.47.0" +description = "A high-level API to automate web browsers" +optional = false +python-versions = ">=3.8" +files = [ + {file = "playwright-1.47.0-py3-none-macosx_10_13_x86_64.whl", hash = "sha256:f205df24edb925db1a4ab62f1ab0da06f14bb69e382efecfb0deedc4c7f4b8cd"}, + {file = "playwright-1.47.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:7fc820faf6885f69a52ba4ec94124e575d3c4a4003bf29200029b4a4f2b2d0ab"}, + {file = "playwright-1.47.0-py3-none-macosx_11_0_universal2.whl", hash = "sha256:8e212dc472ff19c7d46ed7e900191c7a786ce697556ac3f1615986ec3aa00341"}, + {file = "playwright-1.47.0-py3-none-manylinux1_x86_64.whl", hash = "sha256:a1935672531963e4b2a321de5aa59b982fb92463ee6e1032dd7326378e462955"}, + {file = "playwright-1.47.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e0a1b61473d6f7f39c5d77d4800b3cbefecb03344c90b98f3fbcae63294ad249"}, + {file = "playwright-1.47.0-py3-none-win32.whl", hash = "sha256:1b977ed81f6bba5582617684a21adab9bad5676d90a357ebf892db7bdf4a9974"}, + {file = "playwright-1.47.0-py3-none-win_amd64.whl", hash = "sha256:0ec1056042d2e86088795a503347407570bffa32cbe20748e5d4c93dba085280"}, +] + +[package.dependencies] +greenlet = "3.0.3" +pyee = "12.0.0" + [[package]] name = "plotly" version = "5.24.1" @@ -1750,6 +1755,23 @@ files = [ [package.dependencies] typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" +[[package]] +name = "pyee" +version = "12.0.0" +description = "A rough port of Node.js's EventEmitter to Python with a few tricks of its own" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyee-12.0.0-py3-none-any.whl", hash = "sha256:7b14b74320600049ccc7d0e0b1becd3b4bd0a03c745758225e31a59f4095c990"}, + {file = "pyee-12.0.0.tar.gz", hash = "sha256:c480603f4aa2927d4766eb41fa82793fe60a82cbfdb8d688e0d08c55a534e145"}, +] + +[package.dependencies] +typing-extensions = "*" + +[package.extras] +dev = ["black", "build", "flake8", "flake8-black", "isort", "jupyter-console", "mkdocs", "mkdocs-include-markdown-plugin", "mkdocstrings[python]", "pytest", "pytest-asyncio", "pytest-trio", "sphinx", "toml", "tox", "trio", "trio", "trio-typing", "twine", "twisted", "validate-pyproject[all]"] + [[package]] name = "pygments" version = "2.18.0" @@ -1845,6 +1867,24 @@ pytest = ">=7.0.0" docs = ["sphinx (>=5.3)", "sphinx-rtd-theme (>=1.0)"] testing = ["coverage (>=6.2)", "flaky (>=3.5.0)", "hypothesis (>=5.7.1)", "mypy (>=0.931)", "pytest-trio (>=0.7.0)"] +[[package]] +name = "pytest-base-url" +version = "2.1.0" +description = "pytest plugin for URL based testing" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pytest_base_url-2.1.0-py3-none-any.whl", hash = "sha256:3ad15611778764d451927b2a53240c1a7a591b521ea44cebfe45849d2d2812e6"}, + {file = "pytest_base_url-2.1.0.tar.gz", hash = "sha256:02748589a54f9e63fcbe62301d6b0496da0d10231b753e950c63e03aee745d45"}, +] + +[package.dependencies] +pytest = ">=7.0.0" +requests = ">=2.9" + +[package.extras] +test = ["black (>=22.1.0)", "flake8 (>=4.0.1)", "pre-commit (>=2.17.0)", "pytest-localserver (>=0.7.1)", "tox (>=3.24.5)"] + [[package]] name = "pytest-benchmark" version = "4.0.0" @@ -1900,6 +1940,23 @@ pytest = ">=6.2.5" [package.extras] dev = ["pre-commit", "pytest-asyncio", "tox"] +[[package]] +name = "pytest-playwright" +version = "0.5.2" +description = "A pytest wrapper with fixtures for Playwright to automate web browsers" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pytest_playwright-0.5.2-py3-none-any.whl", hash = "sha256:2c5720591364a1cdf66610b972ff8492512bc380953e043c85f705b78b2ed582"}, + {file = "pytest_playwright-0.5.2.tar.gz", hash = "sha256:c6d603df9e6c50b35f057b0528e11d41c0963283e98c257267117f5ed6ba1924"}, +] + +[package.dependencies] +playwright = ">=1.18" +pytest = ">=6.2.4,<9.0.0" +pytest-base-url = ">=1.0.0,<3.0.0" +python-slugify = ">=6.0.0,<9.0.0" + [[package]] name = "python-dateutil" version = "2.9.0.post0" @@ -1944,6 +2001,23 @@ files = [ {file = "python_multipart-0.0.10.tar.gz", hash = "sha256:46eb3c6ce6fdda5fb1a03c7e11d490e407c6930a2703fe7aef4da71c374688fa"}, ] +[[package]] +name = "python-slugify" +version = "8.0.4" +description = "A Python slugify application that also handles Unicode" +optional = false +python-versions = ">=3.7" +files = [ + {file = "python-slugify-8.0.4.tar.gz", hash = "sha256:59202371d1d05b54a9e7720c5e038f928f45daaffe41dd10822f3907b937c856"}, + {file = "python_slugify-8.0.4-py2.py3-none-any.whl", hash = "sha256:276540b79961052b66b7d116620b36518847f52d5fd9e3a70164fc8c50faa6b8"}, +] + +[package.dependencies] +text-unidecode = ">=1.3" + +[package.extras] +unidecode = ["Unidecode (>=1.1.1)"] + [[package]] name = "python-socketio" version = "5.11.4" @@ -2334,7 +2408,9 @@ python-versions = ">=3.7" files = [ {file = "SQLAlchemy-2.0.35-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:67219632be22f14750f0d1c70e62f204ba69d28f62fd6432ba05ab295853de9b"}, {file = "SQLAlchemy-2.0.35-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4668bd8faf7e5b71c0319407b608f278f279668f358857dbfd10ef1954ac9f90"}, + {file = "SQLAlchemy-2.0.35-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb8bea573863762bbf45d1e13f87c2d2fd32cee2dbd50d050f83f87429c9e1ea"}, {file = "SQLAlchemy-2.0.35-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f552023710d4b93d8fb29a91fadf97de89c5926c6bd758897875435f2a939f33"}, + {file = "SQLAlchemy-2.0.35-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:016b2e665f778f13d3c438651dd4de244214b527a275e0acf1d44c05bc6026a9"}, {file = "SQLAlchemy-2.0.35-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:7befc148de64b6060937231cbff8d01ccf0bfd75aa26383ffdf8d82b12ec04ff"}, {file = "SQLAlchemy-2.0.35-cp310-cp310-win32.whl", hash = "sha256:22b83aed390e3099584b839b93f80a0f4a95ee7f48270c97c90acd40ee646f0b"}, {file = "SQLAlchemy-2.0.35-cp310-cp310-win_amd64.whl", hash = "sha256:a29762cd3d116585278ffb2e5b8cc311fb095ea278b96feef28d0b423154858e"}, @@ -2371,7 +2447,9 @@ files = [ {file = "SQLAlchemy-2.0.35-cp38-cp38-win_amd64.whl", hash = "sha256:0375a141e1c0878103eb3d719eb6d5aa444b490c96f3fedab8471c7f6ffe70ee"}, {file = "SQLAlchemy-2.0.35-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ccae5de2a0140d8be6838c331604f91d6fafd0735dbdcee1ac78fc8fbaba76b4"}, {file = "SQLAlchemy-2.0.35-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:2a275a806f73e849e1c309ac11108ea1a14cd7058577aba962cd7190e27c9e3c"}, + {file = "SQLAlchemy-2.0.35-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:732e026240cdd1c1b2e3ac515c7a23820430ed94292ce33806a95869c46bd139"}, {file = "SQLAlchemy-2.0.35-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:890da8cd1941fa3dab28c5bac3b9da8502e7e366f895b3b8e500896f12f94d11"}, + {file = "SQLAlchemy-2.0.35-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:c0d8326269dbf944b9201911b0d9f3dc524d64779a07518199a58384c3d37a44"}, {file = "SQLAlchemy-2.0.35-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:b76d63495b0508ab9fc23f8152bac63205d2a704cd009a2b0722f4c8e0cba8e0"}, {file = "SQLAlchemy-2.0.35-cp39-cp39-win32.whl", hash = "sha256:69683e02e8a9de37f17985905a5eca18ad651bf592314b4d3d799029797d0eb3"}, {file = "SQLAlchemy-2.0.35-cp39-cp39-win_amd64.whl", hash = "sha256:aee110e4ef3c528f3abbc3c2018c121e708938adeeff9006428dd7c8555e9b3f"}, @@ -2493,6 +2571,17 @@ files = [ doc = ["reno", "sphinx"] test = ["pytest", "tornado (>=4.5)", "typeguard"] +[[package]] +name = "text-unidecode" +version = "1.3" +description = "The most basic Text::Unidecode port" +optional = false +python-versions = "*" +files = [ + {file = "text-unidecode-1.3.tar.gz", hash = "sha256:bad6603bb14d279193107714b288be206cac565dfa49aa5b105294dd5c4aab93"}, + {file = "text_unidecode-1.3-py2.py3-none-any.whl", hash = "sha256:1311f10e8b895935241623731c2ba64f4c455287888b18189350b67134a822e8"}, +] + [[package]] name = "toml" version = "0.10.2" @@ -2922,4 +3011,4 @@ type = ["pytest-mypy"] [metadata] lock-version = "2.0" python-versions = "^3.9" -content-hash = "df66545bb3f79e356e3a91866306f8b18b800aeadf1f98ac184644f986a83d95" +content-hash = "adccd071775567aeefe219261aeb9e222906c865745f03edb1e770edc79c44ac" diff --git a/pyproject.toml b/pyproject.toml index c8a62e195..335fc440e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -77,6 +77,8 @@ asynctest = ">=0.13.0,<1.0" pre-commit = ">=3.2.1" selenium = ">=4.11.0,<5.0" pytest-benchmark = ">=4.0.0,<5.0" +playwright = ">=1.46.0" +pytest-playwright = ">=0.5.1" [tool.poetry.scripts] reflex = "reflex.reflex:cli" diff --git a/reflex/utils/console.py b/reflex/utils/console.py index 69500b32f..20c699e20 100644 --- a/reflex/utils/console.py +++ b/reflex/utils/console.py @@ -58,7 +58,7 @@ def debug(msg: str, **kwargs): kwargs: Keyword arguments to pass to the print function. """ if is_debug(): - msg_ = f"[blue]Debug: {msg}[/blue]" + msg_ = f"[purple]Debug: {msg}[/purple]" if progress := kwargs.pop("progress", None): progress.console.print(msg_, **kwargs) else: diff --git a/integration/__init__.py b/tests/integration/__init__.py similarity index 100% rename from integration/__init__.py rename to tests/integration/__init__.py diff --git a/integration/conftest.py b/tests/integration/conftest.py similarity index 100% rename from integration/conftest.py rename to tests/integration/conftest.py diff --git a/integration/init-test/Dockerfile b/tests/integration/init-test/Dockerfile similarity index 100% rename from integration/init-test/Dockerfile rename to tests/integration/init-test/Dockerfile diff --git a/integration/init-test/in_docker_test_script.sh b/tests/integration/init-test/in_docker_test_script.sh similarity index 96% rename from integration/init-test/in_docker_test_script.sh rename to tests/integration/init-test/in_docker_test_script.sh index 4e898ac99..31d245588 100755 --- a/integration/init-test/in_docker_test_script.sh +++ b/tests/integration/init-test/in_docker_test_script.sh @@ -13,7 +13,7 @@ function do_export () { reflex init --template "$template" reflex export ( - cd "$SCRIPTPATH/../.." + cd "$SCRIPTPATH/../../.." scripts/integration.sh ~/"$template" dev pkill -9 -f 'next-server|python3' || true sleep 10 diff --git a/integration/shared/state.py b/tests/integration/shared/state.py similarity index 100% rename from integration/shared/state.py rename to tests/integration/shared/state.py diff --git a/integration/test_background_task.py b/tests/integration/test_background_task.py similarity index 100% rename from integration/test_background_task.py rename to tests/integration/test_background_task.py diff --git a/integration/test_call_script.py b/tests/integration/test_call_script.py similarity index 100% rename from integration/test_call_script.py rename to tests/integration/test_call_script.py diff --git a/integration/test_client_storage.py b/tests/integration/test_client_storage.py similarity index 100% rename from integration/test_client_storage.py rename to tests/integration/test_client_storage.py diff --git a/integration/test_component_state.py b/tests/integration/test_component_state.py similarity index 100% rename from integration/test_component_state.py rename to tests/integration/test_component_state.py diff --git a/integration/test_computed_vars.py b/tests/integration/test_computed_vars.py similarity index 100% rename from integration/test_computed_vars.py rename to tests/integration/test_computed_vars.py diff --git a/integration/test_connection_banner.py b/tests/integration/test_connection_banner.py similarity index 100% rename from integration/test_connection_banner.py rename to tests/integration/test_connection_banner.py diff --git a/integration/test_deploy_url.py b/tests/integration/test_deploy_url.py similarity index 100% rename from integration/test_deploy_url.py rename to tests/integration/test_deploy_url.py diff --git a/integration/test_dynamic_components.py b/tests/integration/test_dynamic_components.py similarity index 100% rename from integration/test_dynamic_components.py rename to tests/integration/test_dynamic_components.py diff --git a/integration/test_dynamic_routes.py b/tests/integration/test_dynamic_routes.py similarity index 100% rename from integration/test_dynamic_routes.py rename to tests/integration/test_dynamic_routes.py diff --git a/integration/test_event_actions.py b/tests/integration/test_event_actions.py similarity index 100% rename from integration/test_event_actions.py rename to tests/integration/test_event_actions.py diff --git a/integration/test_event_chain.py b/tests/integration/test_event_chain.py similarity index 100% rename from integration/test_event_chain.py rename to tests/integration/test_event_chain.py diff --git a/integration/test_exception_handlers.py b/tests/integration/test_exception_handlers.py similarity index 100% rename from integration/test_exception_handlers.py rename to tests/integration/test_exception_handlers.py diff --git a/integration/test_form_submit.py b/tests/integration/test_form_submit.py similarity index 100% rename from integration/test_form_submit.py rename to tests/integration/test_form_submit.py diff --git a/integration/test_input.py b/tests/integration/test_input.py similarity index 100% rename from integration/test_input.py rename to tests/integration/test_input.py diff --git a/integration/test_large_state.py b/tests/integration/test_large_state.py similarity index 100% rename from integration/test_large_state.py rename to tests/integration/test_large_state.py diff --git a/integration/test_lifespan.py b/tests/integration/test_lifespan.py similarity index 100% rename from integration/test_lifespan.py rename to tests/integration/test_lifespan.py diff --git a/integration/test_login_flow.py b/tests/integration/test_login_flow.py similarity index 100% rename from integration/test_login_flow.py rename to tests/integration/test_login_flow.py diff --git a/integration/test_media.py b/tests/integration/test_media.py similarity index 100% rename from integration/test_media.py rename to tests/integration/test_media.py diff --git a/integration/test_navigation.py b/tests/integration/test_navigation.py similarity index 100% rename from integration/test_navigation.py rename to tests/integration/test_navigation.py diff --git a/integration/test_server_side_event.py b/tests/integration/test_server_side_event.py similarity index 100% rename from integration/test_server_side_event.py rename to tests/integration/test_server_side_event.py diff --git a/integration/test_shared_state.py b/tests/integration/test_shared_state.py similarity index 96% rename from integration/test_shared_state.py rename to tests/integration/test_shared_state.py index a1a3d6080..6f59c5142 100644 --- a/integration/test_shared_state.py +++ b/tests/integration/test_shared_state.py @@ -12,7 +12,7 @@ from reflex.testing import AppHarness, WebDriver def SharedStateApp(): """Test that shared state works as expected.""" import reflex as rx - from integration.shared.state import SharedState + from tests.integration.shared.state import SharedState class State(SharedState): pass diff --git a/integration/test_state_inheritance.py b/tests/integration/test_state_inheritance.py similarity index 100% rename from integration/test_state_inheritance.py rename to tests/integration/test_state_inheritance.py diff --git a/integration/test_table.py b/tests/integration/test_table.py similarity index 100% rename from integration/test_table.py rename to tests/integration/test_table.py diff --git a/integration/test_tailwind.py b/tests/integration/test_tailwind.py similarity index 100% rename from integration/test_tailwind.py rename to tests/integration/test_tailwind.py diff --git a/integration/test_upload.py b/tests/integration/test_upload.py similarity index 100% rename from integration/test_upload.py rename to tests/integration/test_upload.py diff --git a/integration/test_urls.py b/tests/integration/test_urls.py similarity index 100% rename from integration/test_urls.py rename to tests/integration/test_urls.py diff --git a/integration/test_var_operations.py b/tests/integration/test_var_operations.py similarity index 100% rename from integration/test_var_operations.py rename to tests/integration/test_var_operations.py diff --git a/integration/utils.py b/tests/integration/utils.py similarity index 100% rename from integration/utils.py rename to tests/integration/utils.py diff --git a/tests/test_node_version.py b/tests/test_node_version.py new file mode 100644 index 000000000..9555eb524 --- /dev/null +++ b/tests/test_node_version.py @@ -0,0 +1,73 @@ +"""Test for latest node version being able to run reflex.""" + +from __future__ import annotations + +from typing import Any, Generator + +import httpx +import pytest +from playwright.sync_api import Page, expect + +from reflex.testing import AppHarness + + +def TestNodeVersionApp(): + """A test app for node latest version.""" + import reflex as rx + from reflex.utils.prerequisites import get_node_version + + class TestNodeVersionConfig(rx.Config): + pass + + class TestNodeVersionState(rx.State): + @rx.var + def node_version(self) -> str: + return str(get_node_version()) + + app = rx.App() + + @app.add_page + def index(): + return rx.heading("Node Version check v", TestNodeVersionState.node_version) + + +@pytest.fixture() +def node_version_app(tmp_path) -> Generator[AppHarness, Any, None]: + """Fixture to start TestNodeVersionApp app at tmp_path via AppHarness. + + Args: + tmp_path: pytest tmp_path fixture + + Yields: + running AppHarness instance + """ + with AppHarness.create( + root=tmp_path, + app_source=TestNodeVersionApp, # type: ignore + ) as harness: + yield harness + + +def test_node_version(node_version_app: AppHarness, page: Page): + """Test for latest node version being able to run reflex. + + Args: + node_version_app: running AppHarness instance + page: playwright page instance + """ + + def get_latest_node_version(): + response = httpx.get("https://nodejs.org/dist/index.json") + versions = response.json() + + # Assuming the first entry in the API response is the most recent version + if versions: + latest_version = versions[0]["version"] + return latest_version + return None + + assert node_version_app.frontend_url is not None + page.goto(node_version_app.frontend_url) + expect(page.get_by_role("heading")).to_have_text( + f"Node Version check {get_latest_node_version()}" + ) diff --git a/tests/units/__init__.py b/tests/units/__init__.py new file mode 100644 index 000000000..d0196603c --- /dev/null +++ b/tests/units/__init__.py @@ -0,0 +1,5 @@ +"""Root directory for tests.""" + +import os + +from reflex import constants diff --git a/tests/compiler/__init__.py b/tests/units/compiler/__init__.py similarity index 100% rename from tests/compiler/__init__.py rename to tests/units/compiler/__init__.py diff --git a/tests/compiler/test_compiler.py b/tests/units/compiler/test_compiler.py similarity index 100% rename from tests/compiler/test_compiler.py rename to tests/units/compiler/test_compiler.py diff --git a/tests/compiler/test_compiler_utils.py b/tests/units/compiler/test_compiler_utils.py similarity index 100% rename from tests/compiler/test_compiler_utils.py rename to tests/units/compiler/test_compiler_utils.py diff --git a/tests/components/__init__.py b/tests/units/components/__init__.py similarity index 100% rename from tests/components/__init__.py rename to tests/units/components/__init__.py diff --git a/tests/components/base/test_bare.py b/tests/units/components/base/test_bare.py similarity index 100% rename from tests/components/base/test_bare.py rename to tests/units/components/base/test_bare.py diff --git a/tests/components/base/test_link.py b/tests/units/components/base/test_link.py similarity index 100% rename from tests/components/base/test_link.py rename to tests/units/components/base/test_link.py diff --git a/tests/components/base/test_script.py b/tests/units/components/base/test_script.py similarity index 100% rename from tests/components/base/test_script.py rename to tests/units/components/base/test_script.py diff --git a/tests/components/core/__init__.py b/tests/units/components/core/__init__.py similarity index 100% rename from tests/components/core/__init__.py rename to tests/units/components/core/__init__.py diff --git a/tests/components/core/test_banner.py b/tests/units/components/core/test_banner.py similarity index 100% rename from tests/components/core/test_banner.py rename to tests/units/components/core/test_banner.py diff --git a/tests/components/core/test_colors.py b/tests/units/components/core/test_colors.py similarity index 100% rename from tests/components/core/test_colors.py rename to tests/units/components/core/test_colors.py diff --git a/tests/components/core/test_cond.py b/tests/units/components/core/test_cond.py similarity index 100% rename from tests/components/core/test_cond.py rename to tests/units/components/core/test_cond.py diff --git a/tests/components/core/test_debounce.py b/tests/units/components/core/test_debounce.py similarity index 100% rename from tests/components/core/test_debounce.py rename to tests/units/components/core/test_debounce.py diff --git a/tests/components/core/test_foreach.py b/tests/units/components/core/test_foreach.py similarity index 100% rename from tests/components/core/test_foreach.py rename to tests/units/components/core/test_foreach.py diff --git a/tests/components/core/test_html.py b/tests/units/components/core/test_html.py similarity index 100% rename from tests/components/core/test_html.py rename to tests/units/components/core/test_html.py diff --git a/tests/components/core/test_match.py b/tests/units/components/core/test_match.py similarity index 100% rename from tests/components/core/test_match.py rename to tests/units/components/core/test_match.py diff --git a/tests/components/core/test_responsive.py b/tests/units/components/core/test_responsive.py similarity index 100% rename from tests/components/core/test_responsive.py rename to tests/units/components/core/test_responsive.py diff --git a/tests/components/core/test_upload.py b/tests/units/components/core/test_upload.py similarity index 100% rename from tests/components/core/test_upload.py rename to tests/units/components/core/test_upload.py diff --git a/tests/components/datadisplay/__init__.py b/tests/units/components/datadisplay/__init__.py similarity index 100% rename from tests/components/datadisplay/__init__.py rename to tests/units/components/datadisplay/__init__.py diff --git a/tests/components/datadisplay/conftest.py b/tests/units/components/datadisplay/conftest.py similarity index 100% rename from tests/components/datadisplay/conftest.py rename to tests/units/components/datadisplay/conftest.py diff --git a/tests/components/datadisplay/test_code.py b/tests/units/components/datadisplay/test_code.py similarity index 100% rename from tests/components/datadisplay/test_code.py rename to tests/units/components/datadisplay/test_code.py diff --git a/tests/components/datadisplay/test_dataeditor.py b/tests/units/components/datadisplay/test_dataeditor.py similarity index 100% rename from tests/components/datadisplay/test_dataeditor.py rename to tests/units/components/datadisplay/test_dataeditor.py diff --git a/tests/components/datadisplay/test_datatable.py b/tests/units/components/datadisplay/test_datatable.py similarity index 100% rename from tests/components/datadisplay/test_datatable.py rename to tests/units/components/datadisplay/test_datatable.py diff --git a/tests/components/el/test_html.py b/tests/units/components/el/test_html.py similarity index 100% rename from tests/components/el/test_html.py rename to tests/units/components/el/test_html.py diff --git a/tests/components/forms/__init__.py b/tests/units/components/forms/__init__.py similarity index 100% rename from tests/components/forms/__init__.py rename to tests/units/components/forms/__init__.py diff --git a/tests/components/forms/test_form.py b/tests/units/components/forms/test_form.py similarity index 100% rename from tests/components/forms/test_form.py rename to tests/units/components/forms/test_form.py diff --git a/tests/components/forms/test_uploads.py b/tests/units/components/forms/test_uploads.py similarity index 100% rename from tests/components/forms/test_uploads.py rename to tests/units/components/forms/test_uploads.py diff --git a/tests/components/graphing/__init__.py b/tests/units/components/graphing/__init__.py similarity index 100% rename from tests/components/graphing/__init__.py rename to tests/units/components/graphing/__init__.py diff --git a/tests/components/graphing/test_plotly.py b/tests/units/components/graphing/test_plotly.py similarity index 100% rename from tests/components/graphing/test_plotly.py rename to tests/units/components/graphing/test_plotly.py diff --git a/tests/components/graphing/test_recharts.py b/tests/units/components/graphing/test_recharts.py similarity index 100% rename from tests/components/graphing/test_recharts.py rename to tests/units/components/graphing/test_recharts.py diff --git a/tests/components/layout/__init__.py b/tests/units/components/layout/__init__.py similarity index 100% rename from tests/components/layout/__init__.py rename to tests/units/components/layout/__init__.py diff --git a/tests/components/lucide/test_icon.py b/tests/units/components/lucide/test_icon.py similarity index 100% rename from tests/components/lucide/test_icon.py rename to tests/units/components/lucide/test_icon.py diff --git a/tests/components/media/__init__.py b/tests/units/components/media/__init__.py similarity index 100% rename from tests/components/media/__init__.py rename to tests/units/components/media/__init__.py diff --git a/tests/components/media/test_image.py b/tests/units/components/media/test_image.py similarity index 100% rename from tests/components/media/test_image.py rename to tests/units/components/media/test_image.py diff --git a/tests/components/radix/test_icon_button.py b/tests/units/components/radix/test_icon_button.py similarity index 100% rename from tests/components/radix/test_icon_button.py rename to tests/units/components/radix/test_icon_button.py diff --git a/tests/components/radix/test_layout.py b/tests/units/components/radix/test_layout.py similarity index 100% rename from tests/components/radix/test_layout.py rename to tests/units/components/radix/test_layout.py diff --git a/tests/components/recharts/test_cartesian.py b/tests/units/components/recharts/test_cartesian.py similarity index 100% rename from tests/components/recharts/test_cartesian.py rename to tests/units/components/recharts/test_cartesian.py diff --git a/tests/components/recharts/test_polar.py b/tests/units/components/recharts/test_polar.py similarity index 100% rename from tests/components/recharts/test_polar.py rename to tests/units/components/recharts/test_polar.py diff --git a/tests/components/test_component.py b/tests/units/components/test_component.py similarity index 100% rename from tests/components/test_component.py rename to tests/units/components/test_component.py diff --git a/tests/components/test_component_future_annotations.py b/tests/units/components/test_component_future_annotations.py similarity index 100% rename from tests/components/test_component_future_annotations.py rename to tests/units/components/test_component_future_annotations.py diff --git a/tests/components/test_component_state.py b/tests/units/components/test_component_state.py similarity index 100% rename from tests/components/test_component_state.py rename to tests/units/components/test_component_state.py diff --git a/tests/components/test_tag.py b/tests/units/components/test_tag.py similarity index 100% rename from tests/components/test_tag.py rename to tests/units/components/test_tag.py diff --git a/tests/components/typography/__init__.py b/tests/units/components/typography/__init__.py similarity index 100% rename from tests/components/typography/__init__.py rename to tests/units/components/typography/__init__.py diff --git a/tests/components/typography/test_markdown.py b/tests/units/components/typography/test_markdown.py similarity index 100% rename from tests/components/typography/test_markdown.py rename to tests/units/components/typography/test_markdown.py diff --git a/tests/conftest.py b/tests/units/conftest.py similarity index 100% rename from tests/conftest.py rename to tests/units/conftest.py diff --git a/tests/experimental/custom_script.js b/tests/units/experimental/custom_script.js similarity index 100% rename from tests/experimental/custom_script.js rename to tests/units/experimental/custom_script.js diff --git a/tests/experimental/test_assets.py b/tests/units/experimental/test_assets.py similarity index 100% rename from tests/experimental/test_assets.py rename to tests/units/experimental/test_assets.py diff --git a/tests/middleware/__init__.py b/tests/units/middleware/__init__.py similarity index 100% rename from tests/middleware/__init__.py rename to tests/units/middleware/__init__.py diff --git a/tests/middleware/conftest.py b/tests/units/middleware/conftest.py similarity index 100% rename from tests/middleware/conftest.py rename to tests/units/middleware/conftest.py diff --git a/tests/middleware/test_hydrate_middleware.py b/tests/units/middleware/test_hydrate_middleware.py similarity index 100% rename from tests/middleware/test_hydrate_middleware.py rename to tests/units/middleware/test_hydrate_middleware.py diff --git a/tests/states/__init__.py b/tests/units/states/__init__.py similarity index 100% rename from tests/states/__init__.py rename to tests/units/states/__init__.py diff --git a/tests/states/mutation.py b/tests/units/states/mutation.py similarity index 100% rename from tests/states/mutation.py rename to tests/units/states/mutation.py diff --git a/tests/states/upload.py b/tests/units/states/upload.py similarity index 100% rename from tests/states/upload.py rename to tests/units/states/upload.py diff --git a/tests/test_app.py b/tests/units/test_app.py similarity index 100% rename from tests/test_app.py rename to tests/units/test_app.py diff --git a/tests/test_attribute_access_type.py b/tests/units/test_attribute_access_type.py similarity index 100% rename from tests/test_attribute_access_type.py rename to tests/units/test_attribute_access_type.py diff --git a/tests/test_base.py b/tests/units/test_base.py similarity index 100% rename from tests/test_base.py rename to tests/units/test_base.py diff --git a/tests/test_config.py b/tests/units/test_config.py similarity index 100% rename from tests/test_config.py rename to tests/units/test_config.py diff --git a/tests/test_db_config.py b/tests/units/test_db_config.py similarity index 100% rename from tests/test_db_config.py rename to tests/units/test_db_config.py diff --git a/tests/test_event.py b/tests/units/test_event.py similarity index 100% rename from tests/test_event.py rename to tests/units/test_event.py diff --git a/tests/test_health_endpoint.py b/tests/units/test_health_endpoint.py similarity index 100% rename from tests/test_health_endpoint.py rename to tests/units/test_health_endpoint.py diff --git a/tests/test_model.py b/tests/units/test_model.py similarity index 100% rename from tests/test_model.py rename to tests/units/test_model.py diff --git a/tests/test_page.py b/tests/units/test_page.py similarity index 100% rename from tests/test_page.py rename to tests/units/test_page.py diff --git a/tests/test_prerequisites.py b/tests/units/test_prerequisites.py similarity index 100% rename from tests/test_prerequisites.py rename to tests/units/test_prerequisites.py diff --git a/tests/test_route.py b/tests/units/test_route.py similarity index 100% rename from tests/test_route.py rename to tests/units/test_route.py diff --git a/tests/test_sqlalchemy.py b/tests/units/test_sqlalchemy.py similarity index 100% rename from tests/test_sqlalchemy.py rename to tests/units/test_sqlalchemy.py diff --git a/tests/test_state.py b/tests/units/test_state.py similarity index 99% rename from tests/test_state.py rename to tests/units/test_state.py index 21584fff9..89aad1536 100644 --- a/tests/test_state.py +++ b/tests/units/test_state.py @@ -43,7 +43,7 @@ from reflex.testing import chdir from reflex.utils import format, prerequisites, types from reflex.utils.format import json_dumps from reflex.vars.base import ComputedVar, Var -from tests.states.mutation import MutableSQLAModel, MutableTestState +from tests.units.states.mutation import MutableSQLAModel, MutableTestState from .states import GenState @@ -440,26 +440,28 @@ def test_get_substates(): def test_get_name(): """Test getting the name of a state.""" - assert TestState.get_name() == "tests___test_state____test_state" - assert ChildState.get_name() == "tests___test_state____child_state" - assert ChildState2.get_name() == "tests___test_state____child_state2" - assert GrandchildState.get_name() == "tests___test_state____grandchild_state" + assert TestState.get_name() == "tests___units___test_state____test_state" + assert ChildState.get_name() == "tests___units___test_state____child_state" + assert ChildState2.get_name() == "tests___units___test_state____child_state2" + assert ( + GrandchildState.get_name() == "tests___units___test_state____grandchild_state" + ) def test_get_full_name(): """Test getting the full name.""" - assert TestState.get_full_name() == "tests___test_state____test_state" + assert TestState.get_full_name() == "tests___units___test_state____test_state" assert ( ChildState.get_full_name() - == "tests___test_state____test_state.tests___test_state____child_state" + == "tests___units___test_state____test_state.tests___units___test_state____child_state" ) assert ( ChildState2.get_full_name() - == "tests___test_state____test_state.tests___test_state____child_state2" + == "tests___units___test_state____test_state.tests___units___test_state____child_state2" ) assert ( GrandchildState.get_full_name() - == "tests___test_state____test_state.tests___test_state____child_state.tests___test_state____grandchild_state" + == "tests___units___test_state____test_state.tests___units___test_state____child_state.tests___units___test_state____grandchild_state" ) diff --git a/tests/test_state_tree.py b/tests/units/test_state_tree.py similarity index 100% rename from tests/test_state_tree.py rename to tests/units/test_state_tree.py diff --git a/tests/test_style.py b/tests/units/test_style.py similarity index 100% rename from tests/test_style.py rename to tests/units/test_style.py diff --git a/tests/test_telemetry.py b/tests/units/test_telemetry.py similarity index 100% rename from tests/test_telemetry.py rename to tests/units/test_telemetry.py diff --git a/tests/test_testing.py b/tests/units/test_testing.py similarity index 100% rename from tests/test_testing.py rename to tests/units/test_testing.py diff --git a/tests/test_var.py b/tests/units/test_var.py similarity index 100% rename from tests/test_var.py rename to tests/units/test_var.py diff --git a/tests/utils/__init__.py b/tests/units/utils/__init__.py similarity index 100% rename from tests/utils/__init__.py rename to tests/units/utils/__init__.py diff --git a/tests/utils/test_format.py b/tests/units/utils/test_format.py similarity index 99% rename from tests/utils/test_format.py rename to tests/units/utils/test_format.py index b108f9dc2..4ec5099f5 100644 --- a/tests/utils/test_format.py +++ b/tests/units/utils/test_format.py @@ -13,7 +13,7 @@ from reflex.utils import format from reflex.utils.serializers import serialize_figure from reflex.vars.base import LiteralVar, Var from reflex.vars.object import ObjectVar -from tests.test_state import ( +from tests.units.test_state import ( ChildState, ChildState2, ChildState3, diff --git a/tests/utils/test_imports.py b/tests/units/utils/test_imports.py similarity index 100% rename from tests/utils/test_imports.py rename to tests/units/utils/test_imports.py diff --git a/tests/utils/test_serializers.py b/tests/units/utils/test_serializers.py similarity index 100% rename from tests/utils/test_serializers.py rename to tests/units/utils/test_serializers.py diff --git a/tests/utils/test_types.py b/tests/units/utils/test_types.py similarity index 100% rename from tests/utils/test_types.py rename to tests/units/utils/test_types.py diff --git a/tests/utils/test_utils.py b/tests/units/utils/test_utils.py similarity index 100% rename from tests/utils/test_utils.py rename to tests/units/utils/test_utils.py diff --git a/tests/vars/test_base.py b/tests/units/vars/test_base.py similarity index 100% rename from tests/vars/test_base.py rename to tests/units/vars/test_base.py From 25016f5e27d21f5a1bd35dea48880d4ea0edccf2 Mon Sep 17 00:00:00 2001 From: Masen Furer Date: Thu, 26 Sep 2024 11:04:35 -0700 Subject: [PATCH 22/39] Update markdown component map to use new rx.code_block.theme enum (#3996) * Update markdown component map to use new rx.code_block.theme enum * change var to theme * give the types some attention instead of ignoring them --------- Co-authored-by: Khaleel Al-Adhami --- reflex/components/datadisplay/__init__.py | 1 - reflex/components/datadisplay/__init__.pyi | 1 - reflex/components/datadisplay/code.py | 220 +++++++-------------- reflex/components/datadisplay/code.pyi | 160 ++++++--------- reflex/components/markdown/markdown.py | 6 +- 5 files changed, 134 insertions(+), 254 deletions(-) diff --git a/reflex/components/datadisplay/__init__.py b/reflex/components/datadisplay/__init__.py index fe613ee52..1aff69676 100644 --- a/reflex/components/datadisplay/__init__.py +++ b/reflex/components/datadisplay/__init__.py @@ -8,7 +8,6 @@ _SUBMOD_ATTRS: dict[str, list[str]] = { "code": [ "CodeBlock", "code_block", - "LiteralCodeBlockTheme", "LiteralCodeLanguage", ], "dataeditor": ["data_editor", "data_editor_theme", "DataEditorTheme"], diff --git a/reflex/components/datadisplay/__init__.pyi b/reflex/components/datadisplay/__init__.pyi index 4882c83df..32b29d106 100644 --- a/reflex/components/datadisplay/__init__.pyi +++ b/reflex/components/datadisplay/__init__.pyi @@ -4,7 +4,6 @@ # ------------------------------------------------------ from .code import CodeBlock as CodeBlock -from .code import LiteralCodeBlockTheme as LiteralCodeBlockTheme from .code import LiteralCodeLanguage as LiteralCodeLanguage from .code import code_block as code_block from .dataeditor import DataEditorTheme as DataEditorTheme diff --git a/reflex/components/datadisplay/code.py b/reflex/components/datadisplay/code.py index 0b26e0c04..c18e44885 100644 --- a/reflex/components/datadisplay/code.py +++ b/reflex/components/datadisplay/code.py @@ -2,10 +2,8 @@ from __future__ import annotations -import enum -from typing import Any, Dict, Literal, Optional, Union - -from typing_extensions import get_args +import dataclasses +from typing import ClassVar, Dict, Literal, Optional, Union from reflex.components.component import Component, ComponentNamespace from reflex.components.core.cond import color_mode_cond @@ -19,55 +17,6 @@ from reflex.utils import console, format from reflex.utils.imports import ImportDict, ImportVar from reflex.vars.base import LiteralVar, Var, VarData -LiteralCodeBlockTheme = Literal[ - "a11y-dark", - "atom-dark", - "cb", - "coldark-cold", - "coldark-dark", - "coy", - "coy-without-shadows", - "darcula", - "dark", - "dracula", - "duotone-dark", - "duotone-earth", - "duotone-forest", - "duotone-light", - "duotone-sea", - "duotone-space", - "funky", - "ghcolors", - "gruvbox-dark", - "gruvbox-light", - "holi-theme", - "hopscotch", - "light", # not present in react-syntax-highlighter styles - "lucario", - "material-dark", - "material-light", - "material-oceanic", - "night-owl", - "nord", - "okaidia", - "one-dark", - "one-light", - "pojoaque", - "prism", - "shades-of-purple", - "solarized-dark-atom", - "solarizedlight", - "synthwave84", - "tomorrow", - "twilight", - "vs", - "vs-dark", - "vsc-dark-plus", - "xonokai", - "z-touch", -] - - LiteralCodeLanguage = Literal[ "abap", "abnf", @@ -351,18 +300,82 @@ LiteralCodeLanguage = Literal[ ] -def replace_quotes_with_camel_case(value: str) -> str: - """Replaces quotes in the given string with camel case format. +def construct_theme_var(theme: str) -> Var[Theme]: + """Construct a theme var. Args: - value (str): The string to be processed. + theme: The theme to construct. Returns: - str: The processed string with quotes replaced by camel case. + The constructed theme var. """ - for theme in get_args(LiteralCodeBlockTheme): - value = value.replace(f'"{theme}"', format.to_camel_case(theme)) - return value + return Var( + theme, + _var_data=VarData( + imports={ + f"react-syntax-highlighter/dist/cjs/styles/prism/{format.to_kebab_case(theme)}": [ + ImportVar(tag=theme, is_default=True, install=False) + ] + } + ), + ) + + +@dataclasses.dataclass(init=False) +class Theme: + """Themes for the CodeBlock component.""" + + a11y_dark: ClassVar[Var[Theme]] = construct_theme_var("a11yDark") + atom_dark: ClassVar[Var[Theme]] = construct_theme_var("atomDark") + cb: ClassVar[Var[Theme]] = construct_theme_var("cb") + coldark_cold: ClassVar[Var[Theme]] = construct_theme_var("coldarkCold") + coldark_dark: ClassVar[Var[Theme]] = construct_theme_var("coldarkDark") + coy: ClassVar[Var[Theme]] = construct_theme_var("coy") + coy_without_shadows: ClassVar[Var[Theme]] = construct_theme_var("coyWithoutShadows") + darcula: ClassVar[Var[Theme]] = construct_theme_var("darcula") + dark: ClassVar[Var[Theme]] = construct_theme_var("oneDark") + dracula: ClassVar[Var[Theme]] = construct_theme_var("dracula") + duotone_dark: ClassVar[Var[Theme]] = construct_theme_var("duotoneDark") + duotone_earth: ClassVar[Var[Theme]] = construct_theme_var("duotoneEarth") + duotone_forest: ClassVar[Var[Theme]] = construct_theme_var("duotoneForest") + duotone_light: ClassVar[Var[Theme]] = construct_theme_var("duotoneLight") + duotone_sea: ClassVar[Var[Theme]] = construct_theme_var("duotoneSea") + duotone_space: ClassVar[Var[Theme]] = construct_theme_var("duotoneSpace") + funky: ClassVar[Var[Theme]] = construct_theme_var("funky") + ghcolors: ClassVar[Var[Theme]] = construct_theme_var("ghcolors") + gruvbox_dark: ClassVar[Var[Theme]] = construct_theme_var("gruvboxDark") + gruvbox_light: ClassVar[Var[Theme]] = construct_theme_var("gruvboxLight") + holi_theme: ClassVar[Var[Theme]] = construct_theme_var("holiTheme") + hopscotch: ClassVar[Var[Theme]] = construct_theme_var("hopscotch") + light: ClassVar[Var[Theme]] = construct_theme_var("oneLight") + lucario: ClassVar[Var[Theme]] = construct_theme_var("lucario") + material_dark: ClassVar[Var[Theme]] = construct_theme_var("materialDark") + material_light: ClassVar[Var[Theme]] = construct_theme_var("materialLight") + material_oceanic: ClassVar[Var[Theme]] = construct_theme_var("materialOceanic") + night_owl: ClassVar[Var[Theme]] = construct_theme_var("nightOwl") + nord: ClassVar[Var[Theme]] = construct_theme_var("nord") + okaidia: ClassVar[Var[Theme]] = construct_theme_var("okaidia") + one_dark: ClassVar[Var[Theme]] = construct_theme_var("oneDark") + one_light: ClassVar[Var[Theme]] = construct_theme_var("oneLight") + pojoaque: ClassVar[Var[Theme]] = construct_theme_var("pojoaque") + prism: ClassVar[Var[Theme]] = construct_theme_var("prism") + shades_of_purple: ClassVar[Var[Theme]] = construct_theme_var("shadesOfPurple") + solarized_dark_atom: ClassVar[Var[Theme]] = construct_theme_var("solarizedDarkAtom") + solarizedlight: ClassVar[Var[Theme]] = construct_theme_var("solarizedlight") + synthwave84: ClassVar[Var[Theme]] = construct_theme_var("synthwave84") + tomorrow: ClassVar[Var[Theme]] = construct_theme_var("tomorrow") + twilight: ClassVar[Var[Theme]] = construct_theme_var("twilight") + vs: ClassVar[Var[Theme]] = construct_theme_var("vs") + vs_dark: ClassVar[Var[Theme]] = construct_theme_var("vsDark") + vsc_dark_plus: ClassVar[Var[Theme]] = construct_theme_var("vscDarkPlus") + xonokai: ClassVar[Var[Theme]] = construct_theme_var("xonokai") + z_touch: ClassVar[Var[Theme]] = construct_theme_var("zTouch") + + +for theme_name in dir(Theme): + if theme_name.startswith("_"): + continue + setattr(Theme, theme_name, getattr(Theme, theme_name)._replace(_var_type=Theme)) class CodeBlock(Component): @@ -375,7 +388,7 @@ class CodeBlock(Component): alias = "SyntaxHighlighter" # The theme to use ("light" or "dark"). - theme: Var[Any] = "one-light" # type: ignore + theme: Var[Union[Theme, str]] = Theme.one_light # The language to use. language: Var[LiteralCodeLanguage] = "python" # type: ignore @@ -523,91 +536,6 @@ class CodeBlock(Component): return out - @staticmethod - def convert_theme_name(theme) -> str: - """Convert theme names to appropriate names. - - Args: - theme: The theme name. - - Returns: - The right theme name. - """ - if theme in ["light", "dark"]: - return f"one-{theme}" - return theme - - -def construct_theme_var(theme: str) -> Var: - """Construct a theme var. - - Args: - theme: The theme to construct. - - Returns: - The constructed theme var. - """ - return Var( - theme, - _var_data=VarData( - imports={ - f"react-syntax-highlighter/dist/cjs/styles/prism/{format.to_kebab_case(theme)}": [ - ImportVar(tag=theme, is_default=True, install=False) - ] - } - ), - ) - - -class Theme(enum.Enum): - """Themes for the CodeBlock component.""" - - a11y_dark = construct_theme_var("a11yDark") - atom_dark = construct_theme_var("atomDark") - cb = construct_theme_var("cb") - coldark_cold = construct_theme_var("coldarkCold") - coldark_dark = construct_theme_var("coldarkDark") - coy = construct_theme_var("coy") - coy_without_shadows = construct_theme_var("coyWithoutShadows") - darcula = construct_theme_var("darcula") - dark = construct_theme_var("oneDark") - dracula = construct_theme_var("dracula") - duotone_dark = construct_theme_var("duotoneDark") - duotone_earth = construct_theme_var("duotoneEarth") - duotone_forest = construct_theme_var("duotoneForest") - duotone_light = construct_theme_var("duotoneLight") - duotone_sea = construct_theme_var("duotoneSea") - duotone_space = construct_theme_var("duotoneSpace") - funky = construct_theme_var("funky") - ghcolors = construct_theme_var("ghcolors") - gruvbox_dark = construct_theme_var("gruvboxDark") - gruvbox_light = construct_theme_var("gruvboxLight") - holi_theme = construct_theme_var("holiTheme") - hopscotch = construct_theme_var("hopscotch") - light = construct_theme_var("oneLight") - lucario = construct_theme_var("lucario") - material_dark = construct_theme_var("materialDark") - material_light = construct_theme_var("materialLight") - material_oceanic = construct_theme_var("materialOceanic") - night_owl = construct_theme_var("nightOwl") - nord = construct_theme_var("nord") - okaidia = construct_theme_var("okaidia") - one_dark = construct_theme_var("oneDark") - one_light = construct_theme_var("oneLight") - pojoaque = construct_theme_var("pojoaque") - prism = construct_theme_var("prism") - shades_of_purple = construct_theme_var("shadesOfPurple") - solarized_dark_atom = construct_theme_var("solarizedDarkAtom") - solarizedlight = construct_theme_var("solarizedlight") - synthwave84 = construct_theme_var("synthwave84") - tomorrow = construct_theme_var("tomorrow") - twilight = construct_theme_var("twilight") - vs = construct_theme_var("vs") - vs_dark = construct_theme_var("vsDark") - vsc_dark_plus = construct_theme_var("vscDarkPlus") - xonokai = construct_theme_var("xonokai") - z_touch = construct_theme_var("zTouch") - class CodeblockNamespace(ComponentNamespace): """Namespace for the CodeBlock component.""" diff --git a/reflex/components/datadisplay/code.pyi b/reflex/components/datadisplay/code.pyi index 7a074d1f9..621dce5d0 100644 --- a/reflex/components/datadisplay/code.pyi +++ b/reflex/components/datadisplay/code.pyi @@ -3,8 +3,8 @@ # ------------------- DO NOT EDIT ---------------------- # This file was generated by `reflex/utils/pyi_generator.py`! # ------------------------------------------------------ -import enum -from typing import Any, Callable, Dict, Literal, Optional, Union, overload +import dataclasses +from typing import Any, Callable, ClassVar, Dict, Literal, Optional, Union, overload from reflex.components.component import Component, ComponentNamespace from reflex.constants.colors import Color @@ -13,53 +13,6 @@ from reflex.style import Style from reflex.utils.imports import ImportDict from reflex.vars.base import Var -LiteralCodeBlockTheme = Literal[ - "a11y-dark", - "atom-dark", - "cb", - "coldark-cold", - "coldark-dark", - "coy", - "coy-without-shadows", - "darcula", - "dark", - "dracula", - "duotone-dark", - "duotone-earth", - "duotone-forest", - "duotone-light", - "duotone-sea", - "duotone-space", - "funky", - "ghcolors", - "gruvbox-dark", - "gruvbox-light", - "holi-theme", - "hopscotch", - "light", - "lucario", - "material-dark", - "material-light", - "material-oceanic", - "night-owl", - "nord", - "okaidia", - "one-dark", - "one-light", - "pojoaque", - "prism", - "shades-of-purple", - "solarized-dark-atom", - "solarizedlight", - "synthwave84", - "tomorrow", - "twilight", - "vs", - "vs-dark", - "vsc-dark-plus", - "xonokai", - "z-touch", -] LiteralCodeLanguage = Literal[ "abap", "abnf", @@ -342,7 +295,59 @@ LiteralCodeLanguage = Literal[ "zig", ] -def replace_quotes_with_camel_case(value: str) -> str: ... +def construct_theme_var(theme: str) -> Var[Theme]: ... +@dataclasses.dataclass(init=False) +class Theme: + a11y_dark: ClassVar[Var[Theme]] = construct_theme_var("a11yDark") + atom_dark: ClassVar[Var[Theme]] = construct_theme_var("atomDark") + cb: ClassVar[Var[Theme]] = construct_theme_var("cb") + coldark_cold: ClassVar[Var[Theme]] = construct_theme_var("coldarkCold") + coldark_dark: ClassVar[Var[Theme]] = construct_theme_var("coldarkDark") + coy: ClassVar[Var[Theme]] = construct_theme_var("coy") + coy_without_shadows: ClassVar[Var[Theme]] = construct_theme_var("coyWithoutShadows") + darcula: ClassVar[Var[Theme]] = construct_theme_var("darcula") + dark: ClassVar[Var[Theme]] = construct_theme_var("oneDark") + dracula: ClassVar[Var[Theme]] = construct_theme_var("dracula") + duotone_dark: ClassVar[Var[Theme]] = construct_theme_var("duotoneDark") + duotone_earth: ClassVar[Var[Theme]] = construct_theme_var("duotoneEarth") + duotone_forest: ClassVar[Var[Theme]] = construct_theme_var("duotoneForest") + duotone_light: ClassVar[Var[Theme]] = construct_theme_var("duotoneLight") + duotone_sea: ClassVar[Var[Theme]] = construct_theme_var("duotoneSea") + duotone_space: ClassVar[Var[Theme]] = construct_theme_var("duotoneSpace") + funky: ClassVar[Var[Theme]] = construct_theme_var("funky") + ghcolors: ClassVar[Var[Theme]] = construct_theme_var("ghcolors") + gruvbox_dark: ClassVar[Var[Theme]] = construct_theme_var("gruvboxDark") + gruvbox_light: ClassVar[Var[Theme]] = construct_theme_var("gruvboxLight") + holi_theme: ClassVar[Var[Theme]] = construct_theme_var("holiTheme") + hopscotch: ClassVar[Var[Theme]] = construct_theme_var("hopscotch") + light: ClassVar[Var[Theme]] = construct_theme_var("oneLight") + lucario: ClassVar[Var[Theme]] = construct_theme_var("lucario") + material_dark: ClassVar[Var[Theme]] = construct_theme_var("materialDark") + material_light: ClassVar[Var[Theme]] = construct_theme_var("materialLight") + material_oceanic: ClassVar[Var[Theme]] = construct_theme_var("materialOceanic") + night_owl: ClassVar[Var[Theme]] = construct_theme_var("nightOwl") + nord: ClassVar[Var[Theme]] = construct_theme_var("nord") + okaidia: ClassVar[Var[Theme]] = construct_theme_var("okaidia") + one_dark: ClassVar[Var[Theme]] = construct_theme_var("oneDark") + one_light: ClassVar[Var[Theme]] = construct_theme_var("oneLight") + pojoaque: ClassVar[Var[Theme]] = construct_theme_var("pojoaque") + prism: ClassVar[Var[Theme]] = construct_theme_var("prism") + shades_of_purple: ClassVar[Var[Theme]] = construct_theme_var("shadesOfPurple") + solarized_dark_atom: ClassVar[Var[Theme]] = construct_theme_var("solarizedDarkAtom") + solarizedlight: ClassVar[Var[Theme]] = construct_theme_var("solarizedlight") + synthwave84: ClassVar[Var[Theme]] = construct_theme_var("synthwave84") + tomorrow: ClassVar[Var[Theme]] = construct_theme_var("tomorrow") + twilight: ClassVar[Var[Theme]] = construct_theme_var("twilight") + vs: ClassVar[Var[Theme]] = construct_theme_var("vs") + vs_dark: ClassVar[Var[Theme]] = construct_theme_var("vsDark") + vsc_dark_plus: ClassVar[Var[Theme]] = construct_theme_var("vscDarkPlus") + xonokai: ClassVar[Var[Theme]] = construct_theme_var("xonokai") + z_touch: ClassVar[Var[Theme]] = construct_theme_var("zTouch") + +for theme_name in dir(Theme): + if theme_name.startswith("_"): + continue + setattr(Theme, theme_name, getattr(Theme, theme_name)._replace(_var_type=Theme)) class CodeBlock(Component): def add_imports(self) -> ImportDict: ... @@ -353,7 +358,7 @@ class CodeBlock(Component): *children, can_copy: Optional[bool] = False, copy_button: Optional[Union[Component, bool]] = None, - theme: Optional[Union[Any, Var[Any]]] = None, + theme: Optional[Union[Theme, Var[Union[Theme, str]], str]] = None, language: Optional[ Union[ Literal[ @@ -999,57 +1004,6 @@ class CodeBlock(Component): ... def add_style(self): ... - @staticmethod - def convert_theme_name(theme) -> str: ... - -def construct_theme_var(theme: str) -> Var: ... - -class Theme(enum.Enum): - a11y_dark = construct_theme_var("a11yDark") - atom_dark = construct_theme_var("atomDark") - cb = construct_theme_var("cb") - coldark_cold = construct_theme_var("coldarkCold") - coldark_dark = construct_theme_var("coldarkDark") - coy = construct_theme_var("coy") - coy_without_shadows = construct_theme_var("coyWithoutShadows") - darcula = construct_theme_var("darcula") - dark = construct_theme_var("oneDark") - dracula = construct_theme_var("dracula") - duotone_dark = construct_theme_var("duotoneDark") - duotone_earth = construct_theme_var("duotoneEarth") - duotone_forest = construct_theme_var("duotoneForest") - duotone_light = construct_theme_var("duotoneLight") - duotone_sea = construct_theme_var("duotoneSea") - duotone_space = construct_theme_var("duotoneSpace") - funky = construct_theme_var("funky") - ghcolors = construct_theme_var("ghcolors") - gruvbox_dark = construct_theme_var("gruvboxDark") - gruvbox_light = construct_theme_var("gruvboxLight") - holi_theme = construct_theme_var("holiTheme") - hopscotch = construct_theme_var("hopscotch") - light = construct_theme_var("oneLight") - lucario = construct_theme_var("lucario") - material_dark = construct_theme_var("materialDark") - material_light = construct_theme_var("materialLight") - material_oceanic = construct_theme_var("materialOceanic") - night_owl = construct_theme_var("nightOwl") - nord = construct_theme_var("nord") - okaidia = construct_theme_var("okaidia") - one_dark = construct_theme_var("oneDark") - one_light = construct_theme_var("oneLight") - pojoaque = construct_theme_var("pojoaque") - prism = construct_theme_var("prism") - shades_of_purple = construct_theme_var("shadesOfPurple") - solarized_dark_atom = construct_theme_var("solarizedDarkAtom") - solarizedlight = construct_theme_var("solarizedlight") - synthwave84 = construct_theme_var("synthwave84") - tomorrow = construct_theme_var("tomorrow") - twilight = construct_theme_var("twilight") - vs = construct_theme_var("vs") - vs_dark = construct_theme_var("vsDark") - vsc_dark_plus = construct_theme_var("vscDarkPlus") - xonokai = construct_theme_var("xonokai") - z_touch = construct_theme_var("zTouch") class CodeblockNamespace(ComponentNamespace): themes = Theme @@ -1059,7 +1013,7 @@ class CodeblockNamespace(ComponentNamespace): *children, can_copy: Optional[bool] = False, copy_button: Optional[Union[Component, bool]] = None, - theme: Optional[Union[Any, Var[Any]]] = None, + theme: Optional[Union[Theme, Var[Union[Theme, str]], str]] = None, language: Optional[ Union[ Literal[ diff --git a/reflex/components/markdown/markdown.py b/reflex/components/markdown/markdown.py index 1596f5ce2..6c288c071 100644 --- a/reflex/components/markdown/markdown.py +++ b/reflex/components/markdown/markdown.py @@ -147,7 +147,7 @@ class Markdown(Component): Returns: The imports for the markdown component. """ - from reflex.components.datadisplay.code import CodeBlock + from reflex.components.datadisplay.code import CodeBlock, Theme from reflex.components.radix.themes.typography.code import Code return [ @@ -173,8 +173,8 @@ class Markdown(Component): component(_MOCK_ARG)._get_all_imports() # type: ignore for component in self.component_map.values() ], - CodeBlock.create(theme="light")._get_imports(), # type: ignore, - Code.create()._get_imports(), # type: ignore, + CodeBlock.create(theme=Theme.light)._get_imports(), + Code.create()._get_imports(), ] def get_component(self, tag: str, **props) -> Component: From 130bcf96ca30dabdacbde15edaeac6f5fe499746 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Brand=C3=A9ho?= Date: Thu, 26 Sep 2024 11:56:59 -0700 Subject: [PATCH 23/39] default on_submit in form set to prevent_default (#4005) * default submit forms set to prevent_default * fix tests --- reflex/components/el/elements/forms.py | 5 ++++- tests/units/components/forms/test_form.py | 7 +++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/reflex/components/el/elements/forms.py b/reflex/components/el/elements/forms.py index 29fea357b..1963f8b37 100644 --- a/reflex/components/el/elements/forms.py +++ b/reflex/components/el/elements/forms.py @@ -10,7 +10,7 @@ from jinja2 import Environment from reflex.components.el.element import Element from reflex.components.tags.tag import Tag from reflex.constants import Dirs, EventTriggers -from reflex.event import EventChain, EventHandler +from reflex.event import EventChain, EventHandler, prevent_default from reflex.utils.imports import ImportDict from reflex.vars import VarData from reflex.vars.base import LiteralVar, Var @@ -148,6 +148,9 @@ class Form(BaseHTML): Returns: The form component. """ + if "on_submit" not in props: + props["on_submit"] = prevent_default + if "handle_submit_unique_name" in props: return super().create(*children, **props) diff --git a/tests/units/components/forms/test_form.py b/tests/units/components/forms/test_form.py index 3cbbab3b8..5f3ba2d37 100644 --- a/tests/units/components/forms/test_form.py +++ b/tests/units/components/forms/test_form.py @@ -1,5 +1,5 @@ from reflex.components.radix.primitives.form import Form -from reflex.event import EventChain +from reflex.event import EventChain, prevent_default from reflex.vars.base import Var @@ -15,7 +15,6 @@ def test_render_on_submit(): def test_render_no_on_submit(): - """A form without on_submit should not render a submit handler.""" + """A form without on_submit should render a prevent_default handler.""" f = Form.create() - for prop in f.render()["props"]: - assert "onSubmit" not in prop + assert f.event_triggers["on_submit"] == prevent_default From 54c7b5a261356b534683c1da40fa4dd88b31f6b0 Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Thu, 26 Sep 2024 13:47:05 -0700 Subject: [PATCH 24/39] disable prose by default for rx.html (#4001) * disable prose by default for rx.html * remove styled * put that on one line --- reflex/components/core/html.py | 2 +- tests/units/components/core/test_html.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/reflex/components/core/html.py b/reflex/components/core/html.py index a732e7828..cfe46e591 100644 --- a/reflex/components/core/html.py +++ b/reflex/components/core/html.py @@ -40,7 +40,7 @@ class Html(Div): given_class_name = props.pop("class_name", []) if isinstance(given_class_name, str): given_class_name = [given_class_name] - props["class_name"] = ["rx-Html", "prose", *given_class_name] + props["class_name"] = ["rx-Html", *given_class_name] # Create the component. return super().create(**props) diff --git a/tests/units/components/core/test_html.py b/tests/units/components/core/test_html.py index cc6530cb6..4847e1d5a 100644 --- a/tests/units/components/core/test_html.py +++ b/tests/units/components/core/test_html.py @@ -19,7 +19,7 @@ def test_html_create(): assert str(html.dangerouslySetInnerHTML) == '({ ["__html"] : "

Hello !

" })' # type: ignore assert ( str(html) - == '
Hello !

" })}/>' + == '
Hello !

" })}/>' ) @@ -37,5 +37,5 @@ def test_html_fstring_create(): ) assert ( str(html) - == f'
' # type: ignore + == f'
' # type: ignore ) From 60276cf1ff55fc5e6476e4d355d2bdb428f2acb5 Mon Sep 17 00:00:00 2001 From: LeoH Date: Thu, 26 Sep 2024 22:56:53 +0200 Subject: [PATCH 25/39] EventFnArgMismatch fix to support defaults args (#4004) * EventFnArgMismatch fix to support defaults args * fixing type hint and docstring raises * enforce stronger type checking * unwrap var annotations :( --------- Co-authored-by: Khaleel Al-Adhami --- reflex/event.py | 75 +++++++++++++++++++++++++++++---------- reflex/utils/types.py | 19 ++++++++-- tests/units/test_event.py | 2 +- 3 files changed, 74 insertions(+), 22 deletions(-) diff --git a/reflex/event.py b/reflex/event.py index ac0c713ab..d8f0a5f0f 100644 --- a/reflex/event.py +++ b/reflex/event.py @@ -18,10 +18,12 @@ from typing import ( get_type_hints, ) +from typing_extensions import get_args, get_origin + from reflex import constants from reflex.utils import format from reflex.utils.exceptions import EventFnArgMismatch, EventHandlerArgMismatch -from reflex.utils.types import ArgsSpec +from reflex.utils.types import ArgsSpec, GenericType from reflex.vars import VarData from reflex.vars.base import LiteralVar, Var from reflex.vars.function import FunctionStringVar, FunctionVar @@ -417,7 +419,7 @@ class FileUpload: on_upload_progress: Optional[Union[EventHandler, Callable]] = None @staticmethod - def on_upload_progress_args_spec(_prog: Dict[str, Union[int, float, bool]]): + def on_upload_progress_args_spec(_prog: Var[Dict[str, Union[int, float, bool]]]): """Args spec for on_upload_progress event handler. Returns: @@ -910,6 +912,20 @@ def call_event_handler( ) +def unwrap_var_annotation(annotation: GenericType): + """Unwrap a Var annotation or return it as is if it's not Var[X]. + + Args: + annotation: The annotation to unwrap. + + Returns: + The unwrapped annotation. + """ + if get_origin(annotation) is Var and (args := get_args(annotation)): + return args[0] + return annotation + + def parse_args_spec(arg_spec: ArgsSpec): """Parse the args provided in the ArgsSpec of an event trigger. @@ -921,20 +937,54 @@ def parse_args_spec(arg_spec: ArgsSpec): """ spec = inspect.getfullargspec(arg_spec) annotations = get_type_hints(arg_spec) + return arg_spec( *[ - Var(f"_{l_arg}").to(annotations.get(l_arg, FrontendEvent)) + Var(f"_{l_arg}").to( + unwrap_var_annotation(annotations.get(l_arg, FrontendEvent)) + ) for l_arg in spec.args ] ) +def check_fn_match_arg_spec(fn: Callable, arg_spec: ArgsSpec) -> List[Var]: + """Ensures that the function signature matches the passed argument specification + or raises an EventFnArgMismatch if they do not. + + Args: + fn: The function to be validated. + arg_spec: The argument specification for the event trigger. + + Returns: + The parsed arguments from the argument specification. + + Raises: + EventFnArgMismatch: Raised if the number of mandatory arguments do not match + """ + fn_args = inspect.getfullargspec(fn).args + fn_defaults_args = inspect.getfullargspec(fn).defaults + n_fn_args = len(fn_args) + n_fn_defaults_args = len(fn_defaults_args) if fn_defaults_args else 0 + if isinstance(fn, types.MethodType): + n_fn_args -= 1 # subtract 1 for bound self arg + parsed_args = parse_args_spec(arg_spec) + if not (n_fn_args - n_fn_defaults_args <= len(parsed_args) <= n_fn_args): + raise EventFnArgMismatch( + "The number of mandatory arguments accepted by " + f"{fn} ({n_fn_args - n_fn_defaults_args}) " + "does not match the arguments passed by the event trigger: " + f"{[str(v) for v in parsed_args]}\n" + "See https://reflex.dev/docs/events/event-arguments/" + ) + return parsed_args + + def call_event_fn(fn: Callable, arg_spec: ArgsSpec) -> list[EventSpec] | Var: """Call a function to a list of event specs. The function should return a single EventSpec, a list of EventSpecs, or a - single Var. The function signature must match the passed arg_spec or - EventFnArgsMismatch will be raised. + single Var. Args: fn: The function to call. @@ -944,7 +994,6 @@ def call_event_fn(fn: Callable, arg_spec: ArgsSpec) -> list[EventSpec] | Var: The event specs from calling the function or a Var. Raises: - EventFnArgMismatch: If the function signature doesn't match the arg spec. EventHandlerValueError: If the lambda returns an unusable value. """ # Import here to avoid circular imports. @@ -952,19 +1001,7 @@ def call_event_fn(fn: Callable, arg_spec: ArgsSpec) -> list[EventSpec] | Var: from reflex.utils.exceptions import EventHandlerValueError # Check that fn signature matches arg_spec - fn_args = inspect.getfullargspec(fn).args - n_fn_args = len(fn_args) - if isinstance(fn, types.MethodType): - n_fn_args -= 1 # subtract 1 for bound self arg - parsed_args = parse_args_spec(arg_spec) - if len(parsed_args) != n_fn_args: - raise EventFnArgMismatch( - "The number of arguments accepted by " - f"{fn} ({n_fn_args}) " - "does not match the arguments passed by the event trigger: " - f"{[str(v) for v in parsed_args]}\n" - "See https://reflex.dev/docs/events/event-arguments/" - ) + parsed_args = check_fn_match_arg_spec(fn, arg_spec) # Call the function with the parsed args. out = fn(*parsed_args) diff --git a/reflex/utils/types.py b/reflex/utils/types.py index 63238f67b..41e1ed49a 100644 --- a/reflex/utils/types.py +++ b/reflex/utils/types.py @@ -9,6 +9,7 @@ import sys import types from functools import cached_property, lru_cache, wraps from typing import ( + TYPE_CHECKING, Any, Callable, ClassVar, @@ -96,8 +97,22 @@ PrimitiveType = Union[int, float, bool, str, list, dict, set, tuple] StateVar = Union[PrimitiveType, Base, None] StateIterVar = Union[list, set, tuple] -# ArgsSpec = Callable[[Var], list[Var]] -ArgsSpec = Callable +if TYPE_CHECKING: + from reflex.vars.base import Var + + # ArgsSpec = Callable[[Var], list[Var]] + ArgsSpec = ( + Callable[[], List[Var]] + | Callable[[Var], List[Var]] + | Callable[[Var, Var], List[Var]] + | Callable[[Var, Var, Var], List[Var]] + | Callable[[Var, Var, Var, Var], List[Var]] + | Callable[[Var, Var, Var, Var, Var], List[Var]] + | Callable[[Var, Var, Var, Var, Var, Var], List[Var]] + | Callable[[Var, Var, Var, Var, Var, Var, Var], List[Var]] + ) +else: + ArgsSpec = Callable[..., List[Any]] PrimitiveToAnnotation = { diff --git a/tests/units/test_event.py b/tests/units/test_event.py index a152c3749..3996a6101 100644 --- a/tests/units/test_event.py +++ b/tests/units/test_event.py @@ -97,7 +97,7 @@ def test_call_event_handler_partial(): test_fn_with_args.__qualname__ = "test_fn_with_args" - def spec(a2: str) -> List[str]: + def spec(a2: Var[str]) -> List[Var[str]]: return [a2] handler = EventHandler(fn=test_fn_with_args) From 70bd88c682166c5ba3cef32ebe85f498a783549d Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Thu, 26 Sep 2024 13:59:17 -0700 Subject: [PATCH 26/39] serialize default value for disk state manager (#4008) --- reflex/state.py | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/reflex/state.py b/reflex/state.py index f0f3e1453..8ab6a90a2 100644 --- a/reflex/state.py +++ b/reflex/state.py @@ -2592,16 +2592,24 @@ def _serialize_type(type_: Any) -> str: return f"{type_.__module__}.{type_.__qualname__}" +def is_serializable(value: Any) -> bool: + """Check if a value is serializable. + + Args: + value: The value to check. + + Returns: + Whether the value is serializable. + """ + try: + return bool(dill.dumps(value)) + except Exception: + return False + + def state_to_schema( state: BaseState, -) -> List[ - Tuple[ - str, - str, - Any, - Union[bool, None], - ] -]: +) -> List[Tuple[str, str, Any, Union[bool, None], Any]]: """Convert a state to a schema. Args: @@ -2621,6 +2629,7 @@ def state_to_schema( if isinstance(model_field.required, bool) else None ), + (model_field.default if is_serializable(model_field.default) else None), ) for field_name, model_field in state.__fields__.items() ) From 0ab161c11967c9a85faa2a5a5898263276221877 Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Thu, 26 Sep 2024 16:00:28 -0700 Subject: [PATCH 27/39] remove format_state and override behavior for bare (#3979) * remove format_state and override behavior for bare * pass the test cases * only do one level of dicting dataclasses * remove dict and replace list with set * delete unnecessary serialize calls * remove serialize for mutable proxy * dang it darglint --- reflex/compiler/utils.py | 2 +- reflex/components/base/bare.py | 4 +- reflex/middleware/hydrate_middleware.py | 3 +- reflex/state.py | 21 +++------- reflex/utils/format.py | 44 +------------------- reflex/utils/serializers.py | 53 ++++-------------------- reflex/vars/base.py | 2 +- tests/integration/test_var_operations.py | 4 +- tests/units/test_app.py | 3 +- tests/units/utils/test_format.py | 3 +- tests/units/utils/test_serializers.py | 8 ++-- 11 files changed, 30 insertions(+), 117 deletions(-) diff --git a/reflex/compiler/utils.py b/reflex/compiler/utils.py index 1808f787a..443e1984f 100644 --- a/reflex/compiler/utils.py +++ b/reflex/compiler/utils.py @@ -155,7 +155,7 @@ def compile_state(state: Type[BaseState]) -> dict: initial_state = state(_reflex_internal_init=True).dict( initial=True, include_computed=False ) - return format.format_state(initial_state) + return initial_state def _compile_client_storage_field( diff --git a/reflex/components/base/bare.py b/reflex/components/base/bare.py index 970cdfc84..ada511ef2 100644 --- a/reflex/components/base/bare.py +++ b/reflex/components/base/bare.py @@ -7,7 +7,7 @@ from typing import Any, Iterator from reflex.components.component import Component from reflex.components.tags import Tag from reflex.components.tags.tagless import Tagless -from reflex.vars.base import Var +from reflex.vars import ArrayVar, BooleanVar, ObjectVar, Var class Bare(Component): @@ -33,6 +33,8 @@ class Bare(Component): def _render(self) -> Tag: if isinstance(self.contents, Var): + if isinstance(self.contents, (BooleanVar, ObjectVar, ArrayVar)): + return Tagless(contents=f"{{{str(self.contents.to_string())}}}") return Tagless(contents=f"{{{str(self.contents)}}}") return Tagless(contents=str(self.contents)) diff --git a/reflex/middleware/hydrate_middleware.py b/reflex/middleware/hydrate_middleware.py index 46b524cd7..2198b82c2 100644 --- a/reflex/middleware/hydrate_middleware.py +++ b/reflex/middleware/hydrate_middleware.py @@ -9,7 +9,6 @@ from reflex import constants from reflex.event import Event, get_hydrate_event from reflex.middleware.middleware import Middleware from reflex.state import BaseState, StateUpdate -from reflex.utils import format if TYPE_CHECKING: from reflex.app import App @@ -43,7 +42,7 @@ class HydrateMiddleware(Middleware): setattr(state, constants.CompileVars.IS_HYDRATED, False) # Get the initial state. - delta = format.format_state(state.dict()) + delta = state.dict() # since a full dict was captured, clean any dirtiness state._clean() diff --git a/reflex/state.py b/reflex/state.py index 8ab6a90a2..c16b37b69 100644 --- a/reflex/state.py +++ b/reflex/state.py @@ -73,7 +73,7 @@ from reflex.utils.exceptions import ( LockExpiredError, ) from reflex.utils.exec import is_testing_env -from reflex.utils.serializers import SerializedType, serialize, serializer +from reflex.utils.serializers import serializer from reflex.utils.types import override from reflex.vars import VarData @@ -1790,9 +1790,6 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow): for substate in self.dirty_substates.union(self._always_dirty_substates): delta.update(substates[substate].get_delta()) - # Format the delta. - delta = format.format_state(delta) - # Return the delta. return delta @@ -2433,7 +2430,7 @@ class StateUpdate: Returns: The state update as a JSON string. """ - return format.json_dumps(dataclasses.asdict(self)) + return format.json_dumps(self) class StateManager(Base, ABC): @@ -3660,22 +3657,16 @@ class MutableProxy(wrapt.ObjectProxy): @serializer -def serialize_mutable_proxy(mp: MutableProxy) -> SerializedType: - """Serialize the wrapped value of a MutableProxy. +def serialize_mutable_proxy(mp: MutableProxy): + """Return the wrapped value of a MutableProxy. Args: mp: The MutableProxy to serialize. Returns: - The serialized wrapped object. - - Raises: - ValueError: when the wrapped object is not serializable. + The wrapped object. """ - value = serialize(mp.__wrapped__) - if value is None: - raise ValueError(f"Cannot serialize {type(mp.__wrapped__)}") - return value + return mp.__wrapped__ class ImmutableMutableProxy(MutableProxy): diff --git a/reflex/utils/format.py b/reflex/utils/format.py index 86b4d96c9..ae985a871 100644 --- a/reflex/utils/format.py +++ b/reflex/utils/format.py @@ -9,7 +9,7 @@ import re from typing import TYPE_CHECKING, Any, Callable, List, Optional, Union from reflex import constants -from reflex.utils import exceptions, types +from reflex.utils import exceptions from reflex.utils.console import deprecate if TYPE_CHECKING: @@ -624,48 +624,6 @@ def format_query_params(router_data: dict[str, Any]) -> dict[str, str]: return {k.replace("-", "_"): v for k, v in params.items()} -def format_state(value: Any, key: Optional[str] = None) -> Any: - """Recursively format values in the given state. - - Args: - value: The state to format. - key: The key associated with the value (optional). - - Returns: - The formatted state. - - Raises: - TypeError: If the given value is not a valid state. - """ - from reflex.utils import serializers - - # Handle dicts. - if isinstance(value, dict): - return {k: format_state(v, k) for k, v in value.items()} - - # Handle lists, sets, typles. - if isinstance(value, types.StateIterBases): - return [format_state(v) for v in value] - - # Return state vars as is. - if isinstance(value, types.StateBases): - return value - - # Serialize the value. - serialized = serializers.serialize(value) - if serialized is not None: - return serialized - - if key is None: - raise TypeError( - f"No JSON serializer found for var {value} of type {type(value)}." - ) - else: - raise TypeError( - f"No JSON serializer found for State Var '{key}' of value {value} of type {type(value)}." - ) - - def format_state_name(state_name: str) -> str: """Format a state name, replacing dots with double underscore. diff --git a/reflex/utils/serializers.py b/reflex/utils/serializers.py index 42fb82916..614257181 100644 --- a/reflex/utils/serializers.py +++ b/reflex/utils/serializers.py @@ -12,7 +12,6 @@ from pathlib import Path from typing import ( Any, Callable, - Dict, List, Literal, Optional, @@ -126,7 +125,8 @@ def serialize( # If there is no serializer, return None. if serializer is None: if dataclasses.is_dataclass(value) and not isinstance(value, type): - return serialize(dataclasses.asdict(value)) + return {k.name: getattr(value, k.name) for k in dataclasses.fields(value)} + if get_type: return None, None return None @@ -214,32 +214,6 @@ def serialize_type(value: type) -> str: return value.__name__ -@serializer -def serialize_str(value: str) -> str: - """Serialize a string. - - Args: - value: The string to serialize. - - Returns: - The serialized string. - """ - return value - - -@serializer -def serialize_primitive(value: Union[bool, int, float, None]): - """Serialize a primitive type. - - Args: - value: The number/bool/None to serialize. - - Returns: - The serialized number/bool/None. - """ - return value - - @serializer def serialize_base(value: Base) -> dict: """Serialize a Base instance. @@ -250,33 +224,20 @@ def serialize_base(value: Base) -> dict: Returns: The serialized Base. """ - return {k: serialize(v) for k, v in value.dict().items() if not callable(v)} + return {k: v for k, v in value.dict().items() if not callable(v)} @serializer -def serialize_list(value: Union[List, Tuple, Set]) -> list: - """Serialize a list to a JSON string. +def serialize_set(value: Set) -> list: + """Serialize a set to a JSON serializable list. Args: - value: The list to serialize. + value: The set to serialize. Returns: The serialized list. """ - return [serialize(item) for item in value] - - -@serializer -def serialize_dict(prop: Dict[str, Any]) -> dict: - """Serialize a dictionary to a JSON string. - - Args: - prop: The dictionary to serialize. - - Returns: - The serialized dictionary. - """ - return {k: serialize(v) for k, v in prop.items()} + return list(value) @serializer(to=str) diff --git a/reflex/vars/base.py b/reflex/vars/base.py index 4faa38be7..afbc56a55 100644 --- a/reflex/vars/base.py +++ b/reflex/vars/base.py @@ -1141,7 +1141,7 @@ def serialize_literal(value: LiteralVar): Returns: The serialized Literal. """ - return serializers.serialize(value._var_value) + return value._var_value P = ParamSpec("P") diff --git a/tests/integration/test_var_operations.py b/tests/integration/test_var_operations.py index cae56e1a8..919a39f3b 100644 --- a/tests/integration/test_var_operations.py +++ b/tests/integration/test_var_operations.py @@ -793,8 +793,8 @@ def test_var_operations(driver, var_operations: AppHarness): ("foreach_list_ix", "1\n2"), ("foreach_list_nested", "1\n1\n2"), # rx.memo component with state - ("memo_comp", "1210"), - ("memo_comp_nested", "345"), + ("memo_comp", "[1,2]10"), + ("memo_comp_nested", "[3,4]5"), # foreach in a match ("foreach_in_match", "first\nsecond\nthird"), ] diff --git a/tests/units/test_app.py b/tests/units/test_app.py index 88655d7de..0c22c38e3 100644 --- a/tests/units/test_app.py +++ b/tests/units/test_app.py @@ -1,6 +1,5 @@ from __future__ import annotations -import dataclasses import functools import io import json @@ -1053,7 +1052,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": dataclasses.asdict(exp_router), + "router": exp_router, } }, events=[ diff --git a/tests/units/utils/test_format.py b/tests/units/utils/test_format.py index 4ec5099f5..042c3f323 100644 --- a/tests/units/utils/test_format.py +++ b/tests/units/utils/test_format.py @@ -1,6 +1,7 @@ from __future__ import annotations import datetime +import json from typing import Any, List import plotly.graph_objects as go @@ -621,7 +622,7 @@ def test_format_state(input, output): input: The state to format. output: The expected formatted state. """ - assert format.format_state(input) == output + assert json.loads(format.json_dumps(input)) == json.loads(format.json_dumps(output)) @pytest.mark.parametrize( diff --git a/tests/units/utils/test_serializers.py b/tests/units/utils/test_serializers.py index 97da98792..630187309 100644 --- a/tests/units/utils/test_serializers.py +++ b/tests/units/utils/test_serializers.py @@ -1,19 +1,21 @@ import datetime +import json from enum import Enum from pathlib import Path -from typing import Any, Dict, Type +from typing import Any, Type import pytest from reflex.base import Base from reflex.components.core.colors import Color from reflex.utils import serializers +from reflex.utils.format import json_dumps from reflex.vars.base import LiteralVar @pytest.mark.parametrize( "type_,expected", - [(str, True), (dict, True), (Dict[int, int], True), (Enum, True)], + [(Enum, True)], ) def test_has_serializer(type_: Type, expected: bool): """Test that has_serializer returns the correct value. @@ -198,7 +200,7 @@ def test_serialize(value: Any, expected: str): value: The value to serialize. expected: The expected result. """ - assert serializers.serialize(value) == expected + assert json.loads(json_dumps(value)) == json.loads(json_dumps(expected)) @pytest.mark.parametrize( From 299f842756fe4c6fb27d962d26d3152bf7f674c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Brand=C3=A9ho?= Date: Thu, 26 Sep 2024 16:12:12 -0700 Subject: [PATCH 28/39] add env var to enable using system node and bun (#4006) * add env var to enable using system node and bun * fix test to use env var --- .github/workflows/check_node_latest.yml | 3 +- reflex/constants/installer.py | 6 ++++ reflex/utils/path_ops.py | 37 ++++++++++++++++++++++++- reflex/utils/prerequisites.py | 4 ++- 4 files changed, 47 insertions(+), 3 deletions(-) diff --git a/.github/workflows/check_node_latest.yml b/.github/workflows/check_node_latest.yml index 945786c05..749b94efa 100644 --- a/.github/workflows/check_node_latest.yml +++ b/.github/workflows/check_node_latest.yml @@ -10,6 +10,7 @@ on: env: TELEMETRY_ENABLED: false + REFLEX_USE_SYSTEM_NODE: true jobs: check_latest_node: @@ -32,7 +33,7 @@ jobs: poetry run uv pip install pyvirtualdisplay pillow poetry run playwright install --with-deps - run: | - # poetry run pytest tests/test_node_version.py + poetry run pytest tests/test_node_version.py poetry run pytest tests/integration diff --git a/reflex/constants/installer.py b/reflex/constants/installer.py index e01e5ae69..01a11a37e 100644 --- a/reflex/constants/installer.py +++ b/reflex/constants/installer.py @@ -54,6 +54,9 @@ class Bun(SimpleNamespace): # Path of the bunfig file CONFIG_PATH = "bunfig.toml" + # The environment variable to use the system installed bun. + USE_SYSTEM_VAR = "REFLEX_USE_SYSTEM_BUN" + # FNM config. class Fnm(SimpleNamespace): @@ -96,6 +99,9 @@ class Node(SimpleNamespace): # The default path where npm is installed. NPM_PATH = os.path.join(BIN_PATH, "npm") + # The environment variable to use the system installed node. + USE_SYSTEM_VAR = "REFLEX_USE_SYSTEM_NODE" + class PackageJson(SimpleNamespace): """Constants used to build the package.json file.""" diff --git a/reflex/utils/path_ops.py b/reflex/utils/path_ops.py index 21065db99..00affd820 100644 --- a/reflex/utils/path_ops.py +++ b/reflex/utils/path_ops.py @@ -129,6 +129,41 @@ def which(program: str | Path) -> str | Path | None: return shutil.which(str(program)) +def use_system_install(var_name: str) -> bool: + """Check if the system install should be used. + + Args: + var_name: The name of the environment variable. + + Raises: + ValueError: If the variable name is invalid. + + Returns: + Whether the associated env var should use the system install. + """ + if not var_name.startswith("REFLEX_USE_SYSTEM_"): + raise ValueError("Invalid system install variable name.") + return os.getenv(var_name, "").lower() in ["true", "1", "yes"] + + +def use_system_node() -> bool: + """Check if the system node should be used. + + Returns: + Whether the system node should be used. + """ + return use_system_install(constants.Node.USE_SYSTEM_VAR) + + +def use_system_bun() -> bool: + """Check if the system bun should be used. + + Returns: + Whether the system bun should be used. + """ + return use_system_install(constants.Bun.USE_SYSTEM_VAR) + + def get_node_bin_path() -> str | None: """Get the node binary dir path. @@ -149,7 +184,7 @@ def get_node_path() -> str | None: The path to the node binary file. """ node_path = Path(constants.Node.PATH) - if not node_path.exists(): + if use_system_node() or not node_path.exists(): return str(which("node")) return str(node_path) diff --git a/reflex/utils/prerequisites.py b/reflex/utils/prerequisites.py index f86da0c53..f9eb9a790 100644 --- a/reflex/utils/prerequisites.py +++ b/reflex/utils/prerequisites.py @@ -143,7 +143,7 @@ def check_node_version() -> bool: # Compare the version numbers return ( current_version >= version.parse(constants.Node.MIN_VERSION) - if constants.IS_WINDOWS + if constants.IS_WINDOWS or path_ops.use_system_node() else current_version == version.parse(constants.Node.VERSION) ) return False @@ -1034,6 +1034,8 @@ def validate_bun(): # if a custom bun path is provided, make sure its valid # This is specific to non-FHS OS bun_path = get_config().bun_path + if path_ops.use_system_bun(): + bun_path = path_ops.which("bun") if bun_path != constants.Bun.DEFAULT_PATH: console.info(f"Using custom Bun path: {bun_path}") bun_version = get_bun_version() From ae0f3f820ed44a791be9325db2986a6016e6d74b Mon Sep 17 00:00:00 2001 From: Masen Furer Date: Thu, 26 Sep 2024 21:52:31 -0700 Subject: [PATCH 29/39] Handle bool cast for optional NumberVar (#4010) * Handle bool cast for optional NumberVar If the _var_type is optional, then also check that the value is not None * boolify the result of `and_operation` * flip order to be more semantically pure --------- Co-authored-by: Khaleel Al-Adhami --- reflex/vars/number.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/reflex/vars/number.py b/reflex/vars/number.py index 9a899f220..0aaa7a068 100644 --- a/reflex/vars/number.py +++ b/reflex/vars/number.py @@ -21,6 +21,7 @@ from typing import ( from reflex.constants.base import Dirs from reflex.utils.exceptions import PrimitiveUnserializableToJSON, VarTypeError from reflex.utils.imports import ImportDict, ImportVar +from reflex.utils.types import is_optional from .base import ( CustomVarOperationReturn, @@ -524,6 +525,8 @@ class NumberVar(Var[NUMBER_T]): Returns: The boolean value of the number. """ + if is_optional(self._var_type): + return boolify((self != None) & (self != 0)) # noqa: E711 return self != 0 def _is_strict_float(self) -> bool: From eea5dc1918b8be8d3ae4a190fc2d9328b81e64f3 Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Thu, 26 Sep 2024 21:54:03 -0700 Subject: [PATCH 30/39] change loglevel and fix granian on linux (#4012) * change loglevel and fix it on linux * run precommit * fix that as well --- reflex/config.py | 2 +- reflex/constants/base.py | 1 + reflex/reflex.py | 20 ++++++++++++++++++-- reflex/utils/exec.py | 11 ++++++++--- 4 files changed, 28 insertions(+), 6 deletions(-) diff --git a/reflex/config.py b/reflex/config.py index fadd82482..c237d7421 100644 --- a/reflex/config.py +++ b/reflex/config.py @@ -158,7 +158,7 @@ class Config(Base): app_name: str # The log level to use. - loglevel: constants.LogLevel = constants.LogLevel.INFO + loglevel: constants.LogLevel = constants.LogLevel.DEFAULT # The port to run the frontend on. NOTE: When running in dev mode, the next available port will be used if this is taken. frontend_port: int = constants.DefaultPorts.FRONTEND_PORT diff --git a/reflex/constants/base.py b/reflex/constants/base.py index df64a1006..225e8000b 100644 --- a/reflex/constants/base.py +++ b/reflex/constants/base.py @@ -173,6 +173,7 @@ class LogLevel(str, Enum): """The log levels.""" DEBUG = "debug" + DEFAULT = "default" INFO = "info" WARNING = "warning" ERROR = "error" diff --git a/reflex/reflex.py b/reflex/reflex.py index 07c1dff5b..44c0d40ff 100644 --- a/reflex/reflex.py +++ b/reflex/reflex.py @@ -15,6 +15,7 @@ from reflex_cli.utils import dependency from reflex import constants from reflex.config import get_config +from reflex.constants.base import LogLevel from reflex.custom_components.custom_components import custom_components_cli from reflex.state import reset_disk_state_manager from reflex.utils import console, redir, telemetry @@ -245,15 +246,30 @@ def _run( setup_frontend(Path.cwd()) commands.append((frontend_cmd, Path.cwd(), frontend_port, backend)) + # If no loglevel is specified, set the subprocesses loglevel to WARNING. + subprocesses_loglevel = ( + loglevel if loglevel != LogLevel.DEFAULT else LogLevel.WARNING + ) + # In prod mode, run the backend on a separate thread. if backend and env == constants.Env.PROD: - commands.append((backend_cmd, backend_host, backend_port, loglevel, frontend)) + commands.append( + ( + backend_cmd, + backend_host, + backend_port, + subprocesses_loglevel, + frontend, + ) + ) # Start the frontend and backend. with processes.run_concurrently_context(*commands): # In dev mode, run the backend on the main thread. if backend and env == constants.Env.DEV: - backend_cmd(backend_host, int(backend_port), loglevel, frontend) + backend_cmd( + backend_host, int(backend_port), subprocesses_loglevel, frontend + ) # The windows uvicorn bug workaround # https://github.com/reflex-dev/reflex/issues/2335 if constants.IS_WINDOWS and exec.frontend_process: diff --git a/reflex/utils/exec.py b/reflex/utils/exec.py index 82fb8d9b3..b6550fdde 100644 --- a/reflex/utils/exec.py +++ b/reflex/utils/exec.py @@ -16,6 +16,7 @@ import psutil from reflex import constants from reflex.config import get_config +from reflex.constants.base import LogLevel from reflex.utils import console, path_ops from reflex.utils.prerequisites import get_web_dir @@ -201,7 +202,11 @@ def get_granian_target(): Returns: The Granian target for the backend. """ - return get_app_module() + f".{constants.CompileVars.API}" + import reflex + + app_module_path = Path(reflex.__file__).parent / "app_module_for_backend.py" + + return f"{str(app_module_path)}:{constants.CompileVars.APP}.{constants.CompileVars.API}" def run_backend( @@ -233,7 +238,7 @@ def run_backend( run_uvicorn_backend(host, port, loglevel) -def run_uvicorn_backend(host, port, loglevel): +def run_uvicorn_backend(host, port, loglevel: LogLevel): """Run the backend in development mode using Uvicorn. Args: @@ -253,7 +258,7 @@ def run_uvicorn_backend(host, port, loglevel): ) -def run_granian_backend(host, port, loglevel): +def run_granian_backend(host, port, loglevel: LogLevel): """Run the backend in development mode using Granian. Args: From 9ca5d4a095731e75ab868edafd61fe78304fc5ee Mon Sep 17 00:00:00 2001 From: Masen Furer Date: Fri, 27 Sep 2024 12:04:43 -0700 Subject: [PATCH 31/39] Track backend-only vars that are declared without a default value (#4016) * Track backend-only vars that are declared without a default value Without this provision, declared backend vars can be accidentally shared among all states if a mutable value is assigned to the class attribute. * add test case for no default backend var --- reflex/state.py | 10 ++++++++++ tests/units/test_state.py | 8 +++++++- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/reflex/state.py b/reflex/state.py index c16b37b69..6af70db14 100644 --- a/reflex/state.py +++ b/reflex/state.py @@ -30,6 +30,7 @@ from typing import ( Type, Union, cast, + get_type_hints, ) import dill @@ -573,6 +574,15 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow): for name, value in cls.__dict__.items() if types.is_backend_base_variable(name, cls) } + # Add annotated backend vars that do not have a default value. + new_backend_vars.update( + { + name: Var("", _var_type=annotation_value).get_default_value() + for name, annotation_value in get_type_hints(cls).items() + if name not in new_backend_vars + and types.is_backend_base_variable(name, cls) + } + ) cls.backend_vars = { **cls.inherited_backend_vars, diff --git a/tests/units/test_state.py b/tests/units/test_state.py index 89aad1536..205162b9f 100644 --- a/tests/units/test_state.py +++ b/tests/units/test_state.py @@ -3216,6 +3216,7 @@ class MixinState(State, mixin=True): num: int = 0 _backend: int = 0 + _backend_no_default: dict @rx.var(cache=True) def computed(self) -> str: @@ -3243,11 +3244,16 @@ def test_mixin_state() -> None: """Test that a mixin state works correctly.""" assert "num" in UsesMixinState.base_vars assert "num" in UsesMixinState.vars - assert UsesMixinState.backend_vars == {"_backend": 0} + assert UsesMixinState.backend_vars == {"_backend": 0, "_backend_no_default": {}} assert "computed" in UsesMixinState.computed_vars assert "computed" in UsesMixinState.vars + assert ( + UsesMixinState(_reflex_internal_init=True)._backend_no_default # type: ignore + is not UsesMixinState.backend_vars["_backend_no_default"] + ) + def test_child_mixin_state() -> None: """Test that mixin vars are only applied to the highest state in the hierarchy.""" From 1b3422dab6d65728690e19784655553f6d72ee65 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Brand=C3=A9ho?= Date: Fri, 27 Sep 2024 16:17:30 -0700 Subject: [PATCH 32/39] improve lifespan typecheck and debug (#4014) * add lifespan debug statement * improve some of the logic for lifespan tasks * fix partial name with update_wrapper --- reflex/app_mixins/lifespan.py | 30 ++++++++++++++++++++++++------ reflex/utils/exceptions.py | 4 ++++ 2 files changed, 28 insertions(+), 6 deletions(-) diff --git a/reflex/app_mixins/lifespan.py b/reflex/app_mixins/lifespan.py index 2b5ed8b58..ef882a2ea 100644 --- a/reflex/app_mixins/lifespan.py +++ b/reflex/app_mixins/lifespan.py @@ -6,11 +6,13 @@ import asyncio import contextlib import functools import inspect -import sys from typing import Callable, Coroutine, Set, Union from fastapi import FastAPI +from reflex.utils import console +from reflex.utils.exceptions import InvalidLifespanTaskType + from .mixin import AppMixin @@ -26,6 +28,7 @@ class LifespanMixin(AppMixin): try: async with contextlib.AsyncExitStack() as stack: for task in self.lifespan_tasks: + run_msg = f"Started lifespan task: {task.__name__} as {{type}}" # type: ignore if isinstance(task, asyncio.Task): running_tasks.append(task) else: @@ -35,15 +38,19 @@ class LifespanMixin(AppMixin): _t = task() if isinstance(_t, contextlib._AsyncGeneratorContextManager): await stack.enter_async_context(_t) + console.debug(run_msg.format(type="asynccontextmanager")) elif isinstance(_t, Coroutine): - running_tasks.append(asyncio.create_task(_t)) + task_ = asyncio.create_task(_t) + task_.add_done_callback(lambda t: t.result()) + running_tasks.append(task_) + console.debug(run_msg.format(type="coroutine")) + else: + console.debug(run_msg.format(type="function")) yield finally: - cancel_kwargs = ( - {"msg": "lifespan_cleanup"} if sys.version_info >= (3, 9) else {} - ) for task in running_tasks: - task.cancel(**cancel_kwargs) + console.debug(f"Canceling lifespan task: {task}") + task.cancel(msg="lifespan_cleanup") def register_lifespan_task(self, task: Callable | asyncio.Task, **task_kwargs): """Register a task to run during the lifespan of the app. @@ -51,7 +58,18 @@ class LifespanMixin(AppMixin): Args: task: The task to register. task_kwargs: The kwargs of the task. + + Raises: + InvalidLifespanTaskType: If the task is a generator function. """ + if inspect.isgeneratorfunction(task) or inspect.isasyncgenfunction(task): + raise InvalidLifespanTaskType( + f"Task {task.__name__} of type generator must be decorated with contextlib.asynccontextmanager." + ) + if task_kwargs: + original_task = task task = functools.partial(task, **task_kwargs) # type: ignore + functools.update_wrapper(task, original_task) # type: ignore self.lifespan_tasks.add(task) # type: ignore + console.debug(f"Registered lifespan task: {task.__name__}") # type: ignore diff --git a/reflex/utils/exceptions.py b/reflex/utils/exceptions.py index 95d68c3b8..7c3532861 100644 --- a/reflex/utils/exceptions.py +++ b/reflex/utils/exceptions.py @@ -111,3 +111,7 @@ class GeneratedCodeHasNoFunctionDefs(ReflexError): class PrimitiveUnserializableToJSON(ReflexError, ValueError): """Raised when a primitive type is unserializable to JSON. Usually with NaN and Infinity.""" + + +class InvalidLifespanTaskType(ReflexError, TypeError): + """Raised when an invalid task type is registered as a lifespan task.""" From 62021b0b405432b73519902bc556b937c6387d4c Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Fri, 27 Sep 2024 16:58:20 -0700 Subject: [PATCH 33/39] implement _evaluate in state (#4018) * implement _evaluate in state * add warning * use typing_extension * add integration test --- reflex/state.py | 32 ++++++++++++++++++++ reflex/vars/base.py | 5 +-- tests/integration/test_dynamic_components.py | 15 +++++++++ 3 files changed, 50 insertions(+), 2 deletions(-) diff --git a/reflex/state.py b/reflex/state.py index 6af70db14..29ad84b3f 100644 --- a/reflex/state.py +++ b/reflex/state.py @@ -35,6 +35,7 @@ from typing import ( import dill from sqlalchemy.orm import DeclarativeBase +from typing_extensions import Self from reflex.config import get_config from reflex.vars.base import ( @@ -43,6 +44,7 @@ from reflex.vars.base import ( Var, computed_var, dispatch, + get_unique_variable_name, is_computed_var, ) @@ -695,6 +697,36 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow): and hasattr(value, "__code__") ) + @classmethod + def _evaluate(cls, f: Callable[[Self], Any]) -> Var: + """Evaluate a function to a ComputedVar. Experimental. + + Args: + f: The function to evaluate. + + Returns: + The ComputedVar. + """ + console.warn( + "The _evaluate method is experimental and may be removed in future versions." + ) + from reflex.components.base.fragment import fragment + from reflex.components.component import Component + + unique_var_name = get_unique_variable_name() + + @computed_var(_js_expr=unique_var_name, return_type=Component) + def computed_var_func(state: Self): + return fragment(f(state)) + + setattr(cls, unique_var_name, computed_var_func) + cls.computed_vars[unique_var_name] = computed_var_func + cls.vars[unique_var_name] = computed_var_func + cls._update_substate_inherited_vars({unique_var_name: computed_var_func}) + cls._always_dirty_computed_vars.add(unique_var_name) + + return getattr(cls, unique_var_name) + @classmethod def _mixins(cls) -> List[Type]: """Get the mixin classes of the state. diff --git a/reflex/vars/base.py b/reflex/vars/base.py index afbc56a55..2d78a14be 100644 --- a/reflex/vars/base.py +++ b/reflex/vars/base.py @@ -1559,8 +1559,9 @@ class ComputedVar(Var[RETURN_TYPE]): Raises: TypeError: If the computed var dependencies are not Var instances or var names. """ - hints = get_type_hints(fget) - hint = hints.get("return", Any) + hint = kwargs.pop("return_type", None) or get_type_hints(fget).get( + "return", Any + ) kwargs["_js_expr"] = kwargs.pop("_js_expr", fget.__name__) kwargs["_var_type"] = kwargs.pop("_var_type", hint) diff --git a/tests/integration/test_dynamic_components.py b/tests/integration/test_dynamic_components.py index 31080223f..5a4d99f9e 100644 --- a/tests/integration/test_dynamic_components.py +++ b/tests/integration/test_dynamic_components.py @@ -16,6 +16,8 @@ def DynamicComponents(): import reflex as rx class DynamicComponentsState(rx.State): + value: int = 10 + button: rx.Component = rx.button( "Click me", custom_attrs={ @@ -52,11 +54,20 @@ def DynamicComponents(): app = rx.App() + def factorial(n: int) -> int: + if n == 0: + return 1 + return n * factorial(n - 1) + @app.add_page def index(): return rx.vstack( DynamicComponentsState.client_token_component, DynamicComponentsState.button, + rx.text( + DynamicComponentsState._evaluate(lambda state: factorial(state.value)), + id="factorial", + ), ) @@ -150,3 +161,7 @@ def test_dynamic_components(driver, dynamic_components: AppHarness): dynamic_components.poll_for_content(button, exp_not_equal="Click me") == "Clicked" ) + + factorial = poll_for_result(lambda: driver.find_element(By.ID, "factorial")) + assert factorial + assert factorial.text == "3628800" From 23e979717f9d227a1afaaeab493560709efca79c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Brand=C3=A9ho?= Date: Fri, 27 Sep 2024 17:25:22 -0700 Subject: [PATCH 34/39] remove all runtime asserts (#4019) * remove all runtime asserts * Update reflex/testing.py Co-authored-by: Masen Furer --------- Co-authored-by: Masen Furer --- reflex/app.py | 14 +++++++++----- reflex/compiler/utils.py | 15 ++++++++++++--- reflex/components/base/meta.py | 8 +++++--- reflex/components/component.py | 6 +++++- reflex/components/core/cond.py | 8 ++++---- reflex/components/gridjs/datatable.py | 3 ++- reflex/components/markdown/markdown.py | 10 +++++++--- reflex/components/markdown/markdown.pyi | 3 +++ reflex/components/tags/iter_tag.py | 6 +++++- reflex/event.py | 6 +++++- reflex/experimental/assets.py | 4 +++- reflex/experimental/client_state.py | 6 +++++- reflex/experimental/misc.py | 8 +++++--- reflex/reflex.py | 3 ++- reflex/state.py | 8 +++++--- reflex/testing.py | 12 ++++++++---- reflex/utils/format.py | 4 +++- 17 files changed, 88 insertions(+), 36 deletions(-) diff --git a/reflex/app.py b/reflex/app.py index 63334997c..111dd9dfd 100644 --- a/reflex/app.py +++ b/reflex/app.py @@ -482,9 +482,8 @@ class App(MiddlewareMixin, LifespanMixin, Base): """ # If the route is not set, get it from the callable. if route is None: - assert isinstance( - component, Callable - ), "Route must be set if component is not a callable." + if not isinstance(component, Callable): + raise ValueError("Route must be set if component is not a callable.") # Format the route. route = format.format_route(component.__name__) else: @@ -1528,6 +1527,9 @@ class EventNamespace(AsyncNamespace): async def on_event(self, sid, data): """Event for receiving front-end websocket events. + Raises: + RuntimeError: If the Socket.IO is badly initialized. + Args: sid: The Socket.IO session id. data: The event data. @@ -1540,9 +1542,11 @@ class EventNamespace(AsyncNamespace): self.sid_to_token[sid] = event.token # Get the event environment. - assert self.app.sio is not None + if self.app.sio is None: + raise RuntimeError("Socket.IO is not initialized.") environ = self.app.sio.get_environ(sid, self.namespace) - assert environ is not None + if environ is None: + raise RuntimeError("Socket.IO environ is not initialized.") # Get the client headers. headers = { diff --git a/reflex/compiler/utils.py b/reflex/compiler/utils.py index 443e1984f..b10552554 100644 --- a/reflex/compiler/utils.py +++ b/reflex/compiler/utils.py @@ -44,6 +44,9 @@ def compile_import_statement(fields: list[ImportVar]) -> tuple[str, list[str]]: Args: fields: The set of fields to import from the library. + Raises: + ValueError: If there is more than one default import. + Returns: The libraries for default and rest. default: default library. When install "import def from library". @@ -54,7 +57,8 @@ def compile_import_statement(fields: list[ImportVar]) -> tuple[str, list[str]]: # Check for default imports. defaults = {field for field in fields_set if field.is_default} - assert len(defaults) < 2 + if len(defaults) >= 2: + raise ValueError("Only one default import is allowed.") # Get the default import, and the specific imports. default = next(iter({field.name for field in defaults}), "") @@ -92,6 +96,9 @@ def compile_imports(import_dict: ParsedImportDict) -> list[dict]: Args: import_dict: The import dict to compile. + Raises: + ValueError: If an import in the dict is invalid. + Returns: The list of import dict. """ @@ -106,8 +113,10 @@ def compile_imports(import_dict: ParsedImportDict) -> list[dict]: continue if not lib: - assert not default, "No default field allowed for empty library." - assert rest is not None and len(rest) > 0, "No fields to import." + if default: + raise ValueError("No default field allowed for empty library.") + if rest is None or len(rest) == 0: + raise ValueError("No fields to import.") for module in sorted(rest): import_dicts.append(get_import_dict(module)) continue diff --git a/reflex/components/base/meta.py b/reflex/components/base/meta.py index 55cb42f0a..526233c8b 100644 --- a/reflex/components/base/meta.py +++ b/reflex/components/base/meta.py @@ -16,13 +16,15 @@ class Title(Component): def render(self) -> dict: """Render the title component. + Raises: + ValueError: If the title is not a single string. + Returns: The rendered title component. """ # Make sure the title is a single string. - assert len(self.children) == 1 and isinstance( - self.children[0], Bare - ), "Title must be a single string." + if len(self.children) != 1 or not isinstance(self.children[0], Bare): + raise ValueError("Title must be a single string.") return super().render() diff --git a/reflex/components/component.py b/reflex/components/component.py index 7ee9b0d3a..9bdd12f0e 100644 --- a/reflex/components/component.py +++ b/reflex/components/component.py @@ -1744,10 +1744,14 @@ class CustomComponent(Component): Args: seen: The tags of the components that have already been seen. + Raises: + ValueError: If the tag is not set. + Returns: The set of custom components. """ - assert self.tag is not None, "The tag must be set." + if self.tag is None: + raise ValueError("The tag must be set.") # Store the seen components in a set to avoid infinite recursion. if seen is None: diff --git a/reflex/components/core/cond.py b/reflex/components/core/cond.py index d1f53be23..1590875d3 100644 --- a/reflex/components/core/cond.py +++ b/reflex/components/core/cond.py @@ -138,13 +138,13 @@ def cond(condition: Any, c1: Any, c2: Any = None) -> Component | Var: """ # Convert the condition to a Var. cond_var = LiteralVar.create(condition) - assert cond_var is not None, "The condition must be set." + if cond_var is None: + raise ValueError("The condition must be set.") # If the first component is a component, create a Cond component. if isinstance(c1, BaseComponent): - assert c2 is None or isinstance( - c2, BaseComponent - ), "Both arguments must be components." + if c2 is not None and not isinstance(c2, BaseComponent): + raise ValueError("Both arguments must be components.") return Cond.create(cond_var, c1, c2) # Otherwise, create a conditional Var. diff --git a/reflex/components/gridjs/datatable.py b/reflex/components/gridjs/datatable.py index aaccda9d2..34ca62605 100644 --- a/reflex/components/gridjs/datatable.py +++ b/reflex/components/gridjs/datatable.py @@ -124,7 +124,8 @@ class DataTable(Gridjs): if types.is_dataframe(type(self.data)): # If given a pandas df break up the data and columns data = serialize(self.data) - assert isinstance(data, dict), "Serialized dataframe should be a dict." + if not isinstance(data, dict): + raise ValueError("Serialized dataframe should be a dict.") self.columns = LiteralVar.create(data["columns"]) self.data = LiteralVar.create(data["data"]) diff --git a/reflex/components/markdown/markdown.py b/reflex/components/markdown/markdown.py index 6c288c071..1665144fd 100644 --- a/reflex/components/markdown/markdown.py +++ b/reflex/components/markdown/markdown.py @@ -95,12 +95,16 @@ class Markdown(Component): *children: The children of the component. **props: The properties of the component. + Raises: + ValueError: If the children are not valid. + Returns: The markdown component. """ - assert ( - len(children) == 1 and types._isinstance(children[0], Union[str, Var]) - ), "Markdown component must have exactly one child containing the markdown source." + if len(children) != 1 or not types._isinstance(children[0], Union[str, Var]): + raise ValueError( + "Markdown component must have exactly one child containing the markdown source." + ) # Update the base component map with the custom component map. component_map = {**get_base_component_map(), **props.pop("component_map", {})} diff --git a/reflex/components/markdown/markdown.pyi b/reflex/components/markdown/markdown.pyi index 611770a55..d82756f44 100644 --- a/reflex/components/markdown/markdown.pyi +++ b/reflex/components/markdown/markdown.pyi @@ -93,6 +93,9 @@ class Markdown(Component): custom_attrs: custom attribute **props: The properties of the component. + Raises: + ValueError: If the children are not valid. + Returns: The markdown component. """ diff --git a/reflex/components/tags/iter_tag.py b/reflex/components/tags/iter_tag.py index e998fc41a..86e5a57fc 100644 --- a/reflex/components/tags/iter_tag.py +++ b/reflex/components/tags/iter_tag.py @@ -114,6 +114,9 @@ class IterTag(Tag): def render_component(self) -> Component: """Render the component. + Raises: + ValueError: If the render function takes more than 2 arguments. + Returns: The rendered component. """ @@ -132,7 +135,8 @@ class IterTag(Tag): component = self.render_fn(arg) else: # If the render function takes the index as an argument. - assert len(args) == 2 + if len(args) != 2: + raise ValueError("The render function must take 2 arguments.") component = self.render_fn(arg, index) # Nested foreach components or cond must be wrapped in fragments. diff --git a/reflex/event.py b/reflex/event.py index d8f0a5f0f..95358ace1 100644 --- a/reflex/event.py +++ b/reflex/event.py @@ -1062,6 +1062,9 @@ def fix_events( token: The user token. router_data: The optional router data to set in the event. + Raises: + ValueError: If the event type is not what was expected. + Returns: The fixed events. """ @@ -1085,7 +1088,8 @@ def fix_events( # Otherwise, create an event from the event spec. if isinstance(e, EventHandler): e = e() - assert isinstance(e, EventSpec), f"Unexpected event type, {type(e)}." + if not isinstance(e, EventSpec): + raise ValueError(f"Unexpected event type, {type(e)}.") name = format.format_event_handler(e.handler) payload = {k._js_expr: v._decode() for k, v in e.args} # type: ignore diff --git a/reflex/experimental/assets.py b/reflex/experimental/assets.py index c18ac1e84..dcf386d8d 100644 --- a/reflex/experimental/assets.py +++ b/reflex/experimental/assets.py @@ -24,6 +24,7 @@ def asset(relative_filename: str, subfolder: Optional[str] = None) -> str: Raises: FileNotFoundError: If the file does not exist. + ValueError: If the module is None. Returns: The relative URL to the copied asset. @@ -31,7 +32,8 @@ def asset(relative_filename: str, subfolder: Optional[str] = None) -> str: # Determine the file by which the asset is exposed. calling_file = inspect.stack()[1].filename module = inspect.getmodule(inspect.stack()[1][0]) - assert module is not None + if module is None: + raise ValueError("Module is None") caller_module_path = module.__name__.replace(".", "/") subfolder = f"{caller_module_path}/{subfolder}" if subfolder else caller_module_path diff --git a/reflex/experimental/client_state.py b/reflex/experimental/client_state.py index 438ca0a23..c7b2260a1 100644 --- a/reflex/experimental/client_state.py +++ b/reflex/experimental/client_state.py @@ -91,12 +91,16 @@ class ClientStateVar(Var): default: The default value of the variable. global_ref: Whether the state should be accessible in any Component and on the backend. + Raises: + ValueError: If the var_name is not a string. + Returns: ClientStateVar """ if var_name is None: var_name = get_unique_variable_name() - assert isinstance(var_name, str), "var_name must be a string." + if not isinstance(var_name, str): + raise ValueError("var_name must be a string.") if default is NoValue: default_var = Var(_js_expr="") elif not isinstance(default, Var): diff --git a/reflex/experimental/misc.py b/reflex/experimental/misc.py index 716d081b8..e3d237153 100644 --- a/reflex/experimental/misc.py +++ b/reflex/experimental/misc.py @@ -12,10 +12,12 @@ async def run_in_thread(func) -> Any: Args: func (callable): The non-async function to run. + Raises: + ValueError: If the function is an async function. + Returns: Any: The return value of the function. """ - assert not asyncio.coroutines.iscoroutinefunction( - func - ), "func must be a non-async function" + if asyncio.coroutines.iscoroutinefunction(func): + raise ValueError("func must be a non-async function") return await asyncio.get_event_loop().run_in_executor(None, func) diff --git a/reflex/reflex.py b/reflex/reflex.py index 44c0d40ff..43ebe2eb4 100644 --- a/reflex/reflex.py +++ b/reflex/reflex.py @@ -230,7 +230,8 @@ def _run( exec.run_frontend_prod, exec.run_backend_prod, ) - assert setup_frontend and frontend_cmd and backend_cmd, "Invalid env" + if not setup_frontend or not frontend_cmd or not backend_cmd: + raise ValueError("Invalid env") # Post a telemetry event. telemetry.send(f"run-{env.value}") diff --git a/reflex/state.py b/reflex/state.py index 29ad84b3f..cda36a0a9 100644 --- a/reflex/state.py +++ b/reflex/state.py @@ -870,6 +870,9 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow): def get_parent_state(cls) -> Type[BaseState] | None: """Get the parent state. + Raises: + ValueError: If more than one parent state is found. + Returns: The parent state. """ @@ -878,9 +881,8 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow): for base in cls.__bases__ if issubclass(base, BaseState) and base is not BaseState and not base._mixin ] - assert ( - len(parent_states) < 2 - ), f"Only one parent state is allowed {parent_states}." + if len(parent_states) >= 2: + raise ValueError(f"Only one parent state is allowed {parent_states}.") return parent_states[0] if len(parent_states) == 1 else None # type: ignore @classmethod diff --git a/reflex/testing.py b/reflex/testing.py index 503db6c2f..bdbd3dc94 100644 --- a/reflex/testing.py +++ b/reflex/testing.py @@ -340,6 +340,9 @@ class AppHarness: This is necessary when the backend is restarted and the state manager is a StateManagerRedis instance. + + Raises: + RuntimeError: when the state manager cannot be reset """ if ( self.app_instance is not None @@ -354,7 +357,8 @@ class AppHarness: self.app_instance._state_manager = StateManagerRedis.create( state=self.app_instance.state, ) - assert isinstance(self.app_instance.state_manager, StateManagerRedis) + if not isinstance(self.app_instance.state_manager, StateManagerRedis): + raise RuntimeError("Failed to reset state manager.") def _start_frontend(self): # Set up the frontend. @@ -787,13 +791,13 @@ class AppHarness: Raises: RuntimeError: when the app hasn't started running TimeoutError: when the timeout expires before any states are seen + ValueError: when the state_manager is not a memory state manager """ if self.app_instance is None: raise RuntimeError("App is not running.") state_manager = self.app_instance.state_manager - assert isinstance( - state_manager, (StateManagerMemory, StateManagerDisk) - ), "Only works with memory state manager" + if not isinstance(state_manager, (StateManagerMemory, StateManagerDisk)): + raise ValueError("Only works with memory or disk state manager") if not self._poll_for( target=lambda: state_manager.states, timeout=timeout, diff --git a/reflex/utils/format.py b/reflex/utils/format.py index ae985a871..4029bd275 100644 --- a/reflex/utils/format.py +++ b/reflex/utils/format.py @@ -345,6 +345,7 @@ def format_prop( Raises: exceptions.InvalidStylePropError: If the style prop value is not a valid type. TypeError: If the prop is not valid. + ValueError: If the prop is not a string. """ # import here to avoid circular import. from reflex.event import EventChain @@ -391,7 +392,8 @@ def format_prop( raise TypeError(f"Could not format prop: {prop} of type {type(prop)}") from e # Wrap the variable in braces. - assert isinstance(prop, str), "The prop must be a string." + if not isinstance(prop, str): + raise ValueError(f"Invalid prop: {prop}. Expected a string.") return wrap(prop, "{", check_first=False) From 9719f5d57e2feb17fb0e378241e6b8b18622f04f Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Sun, 29 Sep 2024 12:08:56 -0700 Subject: [PATCH 35/39] use literal var instead of serialize for toast props (#4027) --- reflex/components/sonner/toast.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/reflex/components/sonner/toast.py b/reflex/components/sonner/toast.py index c797d7f84..b29b71875 100644 --- a/reflex/components/sonner/toast.py +++ b/reflex/components/sonner/toast.py @@ -17,7 +17,7 @@ from reflex.event import ( from reflex.style import Style, resolved_color_mode from reflex.utils import format from reflex.utils.imports import ImportVar -from reflex.utils.serializers import serialize, serializer +from reflex.utils.serializers import serializer from reflex.vars import VarData from reflex.vars.base import LiteralVar, Var @@ -281,8 +281,8 @@ class Toaster(Component): if message == "" and ("title" not in props or "description" not in props): raise ValueError("Toast message or title or description must be provided.") if props: - args = serialize(ToastProps(**props)) # type: ignore - toast = f"{toast_command}(`{message}`, {args})" + args = LiteralVar.create(ToastProps(**props)) + toast = f"{toast_command}(`{message}`, {str(args)})" else: toast = f"{toast_command}(`{message}`)" From bd71c8e6c9d65202445e8eb629cc83adc6d53b53 Mon Sep 17 00:00:00 2001 From: ChinoUkaegbu <77782533+ChinoUkaegbu@users.noreply.github.com> Date: Tue, 1 Oct 2024 17:24:26 +0100 Subject: [PATCH 36/39] feat: Add support for missing SVGs (#3962) --- reflex/components/el/elements/media.py | 89 +++++ reflex/components/el/elements/media.pyi | 480 ++++++++++++++++++++++++ tests/components/el/test_svg.py | 74 ++++ 3 files changed, 643 insertions(+) create mode 100644 tests/components/el/test_svg.py diff --git a/reflex/components/el/elements/media.py b/reflex/components/el/elements/media.py index 705233532..9935902ad 100644 --- a/reflex/components/el/elements/media.py +++ b/reflex/components/el/elements/media.py @@ -317,6 +317,42 @@ class Svg(BaseHTML): xmlns: Var[str] +class Text(BaseHTML): + """The SVG text component.""" + + tag = "text" + # The x coordinate of the starting point of the text baseline. + x: Var[Union[str, int]] + # The y coordinate of the starting point of the text baseline. + y: Var[Union[str, int]] + # Shifts the text position horizontally from a previous text element. + dx: Var[Union[str, int]] + # Shifts the text position vertically from a previous text element. + dy: Var[Union[str, int]] + # Rotates orientation of each individual glyph. + rotate: Var[Union[str, int]] + # How the text is stretched or compressed to fit the width defined by the text_length attribute. + length_adjust: Var[str] + # A width that the text should be scaled to fit. + text_length: Var[Union[str, int]] + + +class Line(BaseHTML): + """The SVG line component.""" + + tag = "line" + # The x-axis coordinate of the line starting point. + x1: Var[Union[str, int]] + # The x-axis coordinate of the the line ending point. + x2: Var[Union[str, int]] + # The y-axis coordinate of the line starting point. + y1: Var[Union[str, int]] + # The y-axis coordinate of the the line ending point. + y2: Var[Union[str, int]] + # The total path length, in user units. + path_length: Var[int] + + class Circle(BaseHTML): """The SVG circle component.""" @@ -331,6 +367,22 @@ class Circle(BaseHTML): path_length: Var[int] +class Ellipse(BaseHTML): + """The SVG ellipse component.""" + + tag = "ellipse" + # The x position of the center of the ellipse. + cx: Var[Union[str, int]] + # The y position of the center of the ellipse. + cy: Var[Union[str, int]] + # The radius of the ellipse on the x axis. + rx: Var[Union[str, int]] + # The radius of the ellipse on the y axis. + ry: Var[Union[str, int]] + # The total length for the ellipse's circumference, in user units. + path_length: Var[int] + + class Rect(BaseHTML): """The SVG rect component.""" @@ -394,6 +446,39 @@ class LinearGradient(BaseHTML): y2: Var[Union[str, int, bool]] +class RadialGradient(BaseHTML): + """Display the radialGradient element.""" + + tag = "radialGradient" + + # The x coordinate of the end circle of the radial gradient. + cx: Var[Union[str, int, bool]] + + # The y coordinate of the end circle of the radial gradient. + cy: Var[Union[str, int, bool]] + + # The radius of the start circle of the radial gradient. + fr: Var[Union[str, int, bool]] + + # The x coordinate of the start circle of the radial gradient. + fx: Var[Union[str, int, bool]] + + # The y coordinate of the start circle of the radial gradient. + fy: Var[Union[str, int, bool]] + + # Units for the gradient. + gradient_units: Var[Union[str, bool]] + + # Transform applied to the gradient. + gradient_transform: Var[Union[str, bool]] + + # The radius of the end circle of the radial gradient. + r: Var[Union[str, int, bool]] + + # Method used to spread the gradient. + spread_method: Var[Union[str, bool]] + + class Stop(BaseHTML): """Display the stop element.""" @@ -421,12 +506,16 @@ class Path(BaseHTML): class SVG(ComponentNamespace): """SVG component namespace.""" + text = staticmethod(Text.create) + line = staticmethod(Line.create) circle = staticmethod(Circle.create) + ellipse = staticmethod(Ellipse.create) rect = staticmethod(Rect.create) polygon = staticmethod(Polygon.create) path = staticmethod(Path.create) stop = staticmethod(Stop.create) linear_gradient = staticmethod(LinearGradient.create) + radial_gradient = staticmethod(RadialGradient.create) defs = staticmethod(Defs.create) __call__ = staticmethod(Svg.create) diff --git a/reflex/components/el/elements/media.pyi b/reflex/components/el/elements/media.pyi index 6d05fe69f..336d6fbb9 100644 --- a/reflex/components/el/elements/media.pyi +++ b/reflex/components/el/elements/media.pyi @@ -1550,6 +1550,242 @@ class Svg(BaseHTML): """ ... +class Text(BaseHTML): + @overload + @classmethod + def create( # type: ignore + cls, + *children, + x: Optional[Union[Var[Union[int, str]], int, str]] = None, + y: Optional[Union[Var[Union[int, str]], int, str]] = None, + dx: Optional[Union[Var[Union[int, str]], int, str]] = None, + dy: Optional[Union[Var[Union[int, str]], int, str]] = None, + rotate: Optional[Union[Var[Union[int, str]], int, str]] = None, + length_adjust: Optional[Union[Var[str], str]] = None, + text_length: Optional[Union[Var[Union[int, str]], int, str]] = None, + access_key: Optional[Union[Var[Union[bool, int, str]], bool, int, str]] = None, + auto_capitalize: Optional[ + Union[Var[Union[bool, int, str]], bool, int, str] + ] = None, + content_editable: Optional[ + Union[Var[Union[bool, int, str]], bool, int, str] + ] = None, + context_menu: Optional[ + Union[Var[Union[bool, int, str]], bool, int, str] + ] = None, + dir: Optional[Union[Var[Union[bool, int, str]], bool, int, str]] = None, + draggable: Optional[Union[Var[Union[bool, int, str]], bool, int, str]] = None, + enter_key_hint: Optional[ + Union[Var[Union[bool, int, str]], bool, int, str] + ] = None, + hidden: Optional[Union[Var[Union[bool, int, str]], bool, int, str]] = None, + input_mode: Optional[Union[Var[Union[bool, int, str]], bool, int, str]] = None, + item_prop: Optional[Union[Var[Union[bool, int, str]], bool, int, str]] = None, + lang: Optional[Union[Var[Union[bool, int, str]], bool, int, str]] = None, + role: Optional[Union[Var[Union[bool, int, str]], bool, int, str]] = None, + slot: Optional[Union[Var[Union[bool, int, str]], bool, int, str]] = None, + spell_check: Optional[Union[Var[Union[bool, int, str]], bool, int, str]] = None, + tab_index: Optional[Union[Var[Union[bool, int, str]], bool, int, str]] = None, + title: Optional[Union[Var[Union[bool, int, str]], bool, int, str]] = None, + style: Optional[Style] = None, + key: Optional[Any] = None, + id: Optional[Any] = None, + class_name: Optional[Any] = None, + autofocus: Optional[bool] = None, + custom_attrs: Optional[Dict[str, Union[Var, str]]] = None, + on_blur: Optional[Union[EventHandler, EventSpec, list, Callable, Var]] = None, + on_click: Optional[Union[EventHandler, EventSpec, list, Callable, Var]] = None, + on_context_menu: Optional[ + Union[EventHandler, EventSpec, list, Callable, Var] + ] = None, + on_double_click: Optional[ + Union[EventHandler, EventSpec, list, Callable, Var] + ] = None, + on_focus: Optional[Union[EventHandler, EventSpec, list, Callable, Var]] = None, + on_mount: Optional[Union[EventHandler, EventSpec, list, Callable, Var]] = None, + on_mouse_down: Optional[ + Union[EventHandler, EventSpec, list, Callable, Var] + ] = None, + on_mouse_enter: Optional[ + Union[EventHandler, EventSpec, list, Callable, Var] + ] = None, + on_mouse_leave: Optional[ + Union[EventHandler, EventSpec, list, Callable, Var] + ] = None, + on_mouse_move: Optional[ + Union[EventHandler, EventSpec, list, Callable, Var] + ] = None, + on_mouse_out: Optional[ + Union[EventHandler, EventSpec, list, Callable, Var] + ] = None, + on_mouse_over: Optional[ + Union[EventHandler, EventSpec, list, Callable, Var] + ] = None, + on_mouse_up: Optional[ + Union[EventHandler, EventSpec, list, Callable, Var] + ] = None, + on_scroll: Optional[Union[EventHandler, EventSpec, list, Callable, Var]] = None, + on_unmount: Optional[ + Union[EventHandler, EventSpec, list, Callable, Var] + ] = None, + **props, + ) -> "Text": + """Create the component. + + Args: + *children: The children of the component. + x: The x coordinate of the starting point of the text baseline. + y: The y coordinate of the starting point of the text baseline. + dx: Shifts the text position horizontally from a previous text element. + dy: Shifts the text position vertically from a previous text element. + rotate: Rotates orientation of each individual glyph. + length_adjust: How the text is stretched or compressed to fit the width defined by the text_length attribute. + text_length: A width that the text should be scaled to fit. + access_key: Provides a hint for generating a keyboard shortcut for the current element. + auto_capitalize: Controls whether and how text input is automatically capitalized as it is entered/edited by the user. + content_editable: Indicates whether the element's content is editable. + context_menu: Defines the ID of a element which will serve as the element's context menu. + dir: Defines the text direction. Allowed values are ltr (Left-To-Right) or rtl (Right-To-Left) + draggable: Defines whether the element can be dragged. + enter_key_hint: Hints what media types the media element is able to play. + hidden: Defines whether the element is hidden. + input_mode: Defines the type of the element. + item_prop: Defines the name of the element for metadata purposes. + lang: Defines the language used in the element. + role: Defines the role of the element. + slot: Assigns a slot in a shadow DOM shadow tree to an element. + spell_check: Defines whether the element may be checked for spelling errors. + tab_index: Defines the position of the current element in the tabbing order. + title: Defines a tooltip for the element. + style: The style of the component. + key: A unique key for the component. + id: The id for the component. + class_name: The class name for the component. + autofocus: Whether the component should take the focus once the page is loaded + custom_attrs: custom attribute + **props: The props of the component. + + Returns: + The component. + """ + ... + +class Line(BaseHTML): + @overload + @classmethod + def create( # type: ignore + cls, + *children, + x1: Optional[Union[Var[Union[int, str]], int, str]] = None, + x2: Optional[Union[Var[Union[int, str]], int, str]] = None, + y1: Optional[Union[Var[Union[int, str]], int, str]] = None, + y2: Optional[Union[Var[Union[int, str]], int, str]] = None, + path_length: Optional[Union[Var[int], int]] = None, + access_key: Optional[Union[Var[Union[bool, int, str]], bool, int, str]] = None, + auto_capitalize: Optional[ + Union[Var[Union[bool, int, str]], bool, int, str] + ] = None, + content_editable: Optional[ + Union[Var[Union[bool, int, str]], bool, int, str] + ] = None, + context_menu: Optional[ + Union[Var[Union[bool, int, str]], bool, int, str] + ] = None, + dir: Optional[Union[Var[Union[bool, int, str]], bool, int, str]] = None, + draggable: Optional[Union[Var[Union[bool, int, str]], bool, int, str]] = None, + enter_key_hint: Optional[ + Union[Var[Union[bool, int, str]], bool, int, str] + ] = None, + hidden: Optional[Union[Var[Union[bool, int, str]], bool, int, str]] = None, + input_mode: Optional[Union[Var[Union[bool, int, str]], bool, int, str]] = None, + item_prop: Optional[Union[Var[Union[bool, int, str]], bool, int, str]] = None, + lang: Optional[Union[Var[Union[bool, int, str]], bool, int, str]] = None, + role: Optional[Union[Var[Union[bool, int, str]], bool, int, str]] = None, + slot: Optional[Union[Var[Union[bool, int, str]], bool, int, str]] = None, + spell_check: Optional[Union[Var[Union[bool, int, str]], bool, int, str]] = None, + tab_index: Optional[Union[Var[Union[bool, int, str]], bool, int, str]] = None, + title: Optional[Union[Var[Union[bool, int, str]], bool, int, str]] = None, + style: Optional[Style] = None, + key: Optional[Any] = None, + id: Optional[Any] = None, + class_name: Optional[Any] = None, + autofocus: Optional[bool] = None, + custom_attrs: Optional[Dict[str, Union[Var, str]]] = None, + on_blur: Optional[Union[EventHandler, EventSpec, list, Callable, Var]] = None, + on_click: Optional[Union[EventHandler, EventSpec, list, Callable, Var]] = None, + on_context_menu: Optional[ + Union[EventHandler, EventSpec, list, Callable, Var] + ] = None, + on_double_click: Optional[ + Union[EventHandler, EventSpec, list, Callable, Var] + ] = None, + on_focus: Optional[Union[EventHandler, EventSpec, list, Callable, Var]] = None, + on_mount: Optional[Union[EventHandler, EventSpec, list, Callable, Var]] = None, + on_mouse_down: Optional[ + Union[EventHandler, EventSpec, list, Callable, Var] + ] = None, + on_mouse_enter: Optional[ + Union[EventHandler, EventSpec, list, Callable, Var] + ] = None, + on_mouse_leave: Optional[ + Union[EventHandler, EventSpec, list, Callable, Var] + ] = None, + on_mouse_move: Optional[ + Union[EventHandler, EventSpec, list, Callable, Var] + ] = None, + on_mouse_out: Optional[ + Union[EventHandler, EventSpec, list, Callable, Var] + ] = None, + on_mouse_over: Optional[ + Union[EventHandler, EventSpec, list, Callable, Var] + ] = None, + on_mouse_up: Optional[ + Union[EventHandler, EventSpec, list, Callable, Var] + ] = None, + on_scroll: Optional[Union[EventHandler, EventSpec, list, Callable, Var]] = None, + on_unmount: Optional[ + Union[EventHandler, EventSpec, list, Callable, Var] + ] = None, + **props, + ) -> "Line": + """Create the component. + + Args: + *children: The children of the component. + x1: The x-axis coordinate of the line starting point. + x2: The x-axis coordinate of the the line ending point. + y1: The y-axis coordinate of the line starting point. + y2: The y-axis coordinate of the the line ending point. + path_length: The total path length, in user units. + access_key: Provides a hint for generating a keyboard shortcut for the current element. + auto_capitalize: Controls whether and how text input is automatically capitalized as it is entered/edited by the user. + content_editable: Indicates whether the element's content is editable. + context_menu: Defines the ID of a element which will serve as the element's context menu. + dir: Defines the text direction. Allowed values are ltr (Left-To-Right) or rtl (Right-To-Left) + draggable: Defines whether the element can be dragged. + enter_key_hint: Hints what media types the media element is able to play. + hidden: Defines whether the element is hidden. + input_mode: Defines the type of the element. + item_prop: Defines the name of the element for metadata purposes. + lang: Defines the language used in the element. + role: Defines the role of the element. + slot: Assigns a slot in a shadow DOM shadow tree to an element. + spell_check: Defines whether the element may be checked for spelling errors. + tab_index: Defines the position of the current element in the tabbing order. + title: Defines a tooltip for the element. + style: The style of the component. + key: A unique key for the component. + id: The id for the component. + class_name: The class name for the component. + autofocus: Whether the component should take the focus once the page is loaded + custom_attrs: custom attribute + **props: The props of the component. + + Returns: + The component. + """ + ... + class Circle(BaseHTML): @overload @classmethod @@ -1664,6 +1900,122 @@ class Circle(BaseHTML): """ ... +class Ellipse(BaseHTML): + @overload + @classmethod + def create( # type: ignore + cls, + *children, + cx: Optional[Union[Var[Union[int, str]], int, str]] = None, + cy: Optional[Union[Var[Union[int, str]], int, str]] = None, + rx: Optional[Union[Var[Union[int, str]], int, str]] = None, + ry: Optional[Union[Var[Union[int, str]], int, str]] = None, + path_length: Optional[Union[Var[int], int]] = None, + access_key: Optional[Union[Var[Union[bool, int, str]], bool, int, str]] = None, + auto_capitalize: Optional[ + Union[Var[Union[bool, int, str]], bool, int, str] + ] = None, + content_editable: Optional[ + Union[Var[Union[bool, int, str]], bool, int, str] + ] = None, + context_menu: Optional[ + Union[Var[Union[bool, int, str]], bool, int, str] + ] = None, + dir: Optional[Union[Var[Union[bool, int, str]], bool, int, str]] = None, + draggable: Optional[Union[Var[Union[bool, int, str]], bool, int, str]] = None, + enter_key_hint: Optional[ + Union[Var[Union[bool, int, str]], bool, int, str] + ] = None, + hidden: Optional[Union[Var[Union[bool, int, str]], bool, int, str]] = None, + input_mode: Optional[Union[Var[Union[bool, int, str]], bool, int, str]] = None, + item_prop: Optional[Union[Var[Union[bool, int, str]], bool, int, str]] = None, + lang: Optional[Union[Var[Union[bool, int, str]], bool, int, str]] = None, + role: Optional[Union[Var[Union[bool, int, str]], bool, int, str]] = None, + slot: Optional[Union[Var[Union[bool, int, str]], bool, int, str]] = None, + spell_check: Optional[Union[Var[Union[bool, int, str]], bool, int, str]] = None, + tab_index: Optional[Union[Var[Union[bool, int, str]], bool, int, str]] = None, + title: Optional[Union[Var[Union[bool, int, str]], bool, int, str]] = None, + style: Optional[Style] = None, + key: Optional[Any] = None, + id: Optional[Any] = None, + class_name: Optional[Any] = None, + autofocus: Optional[bool] = None, + custom_attrs: Optional[Dict[str, Union[Var, str]]] = None, + on_blur: Optional[Union[EventHandler, EventSpec, list, Callable, Var]] = None, + on_click: Optional[Union[EventHandler, EventSpec, list, Callable, Var]] = None, + on_context_menu: Optional[ + Union[EventHandler, EventSpec, list, Callable, Var] + ] = None, + on_double_click: Optional[ + Union[EventHandler, EventSpec, list, Callable, Var] + ] = None, + on_focus: Optional[Union[EventHandler, EventSpec, list, Callable, Var]] = None, + on_mount: Optional[Union[EventHandler, EventSpec, list, Callable, Var]] = None, + on_mouse_down: Optional[ + Union[EventHandler, EventSpec, list, Callable, Var] + ] = None, + on_mouse_enter: Optional[ + Union[EventHandler, EventSpec, list, Callable, Var] + ] = None, + on_mouse_leave: Optional[ + Union[EventHandler, EventSpec, list, Callable, Var] + ] = None, + on_mouse_move: Optional[ + Union[EventHandler, EventSpec, list, Callable, Var] + ] = None, + on_mouse_out: Optional[ + Union[EventHandler, EventSpec, list, Callable, Var] + ] = None, + on_mouse_over: Optional[ + Union[EventHandler, EventSpec, list, Callable, Var] + ] = None, + on_mouse_up: Optional[ + Union[EventHandler, EventSpec, list, Callable, Var] + ] = None, + on_scroll: Optional[Union[EventHandler, EventSpec, list, Callable, Var]] = None, + on_unmount: Optional[ + Union[EventHandler, EventSpec, list, Callable, Var] + ] = None, + **props, + ) -> "Ellipse": + """Create the component. + + Args: + *children: The children of the component. + cx: The x position of the center of the ellipse. + cy: The y position of the center of the ellipse. + rx: The radius of the ellipse on the x axis. + ry: The radius of the ellipse on the y axis. + path_length: The total length for the ellipse's circumference, in user units. + access_key: Provides a hint for generating a keyboard shortcut for the current element. + auto_capitalize: Controls whether and how text input is automatically capitalized as it is entered/edited by the user. + content_editable: Indicates whether the element's content is editable. + context_menu: Defines the ID of a element which will serve as the element's context menu. + dir: Defines the text direction. Allowed values are ltr (Left-To-Right) or rtl (Right-To-Left) + draggable: Defines whether the element can be dragged. + enter_key_hint: Hints what media types the media element is able to play. + hidden: Defines whether the element is hidden. + input_mode: Defines the type of the element. + item_prop: Defines the name of the element for metadata purposes. + lang: Defines the language used in the element. + role: Defines the role of the element. + slot: Assigns a slot in a shadow DOM shadow tree to an element. + spell_check: Defines whether the element may be checked for spelling errors. + tab_index: Defines the position of the current element in the tabbing order. + title: Defines a tooltip for the element. + style: The style of the component. + key: A unique key for the component. + id: The id for the component. + class_name: The class name for the component. + autofocus: Whether the component should take the focus once the page is loaded + custom_attrs: custom attribute + **props: The props of the component. + + Returns: + The component. + """ + ... + class Rect(BaseHTML): @overload @classmethod @@ -2120,6 +2472,130 @@ class LinearGradient(BaseHTML): """ ... +class RadialGradient(BaseHTML): + @overload + @classmethod + def create( # type: ignore + cls, + *children, + cx: Optional[Union[Var[Union[bool, int, str]], bool, int, str]] = None, + cy: Optional[Union[Var[Union[bool, int, str]], bool, int, str]] = None, + fr: Optional[Union[Var[Union[bool, int, str]], bool, int, str]] = None, + fx: Optional[Union[Var[Union[bool, int, str]], bool, int, str]] = None, + fy: Optional[Union[Var[Union[bool, int, str]], bool, int, str]] = None, + gradient_units: Optional[Union[Var[Union[bool, str]], bool, str]] = None, + gradient_transform: Optional[Union[Var[Union[bool, str]], bool, str]] = None, + r: Optional[Union[Var[Union[bool, int, str]], bool, int, str]] = None, + spread_method: Optional[Union[Var[Union[bool, str]], bool, str]] = None, + access_key: Optional[Union[Var[Union[bool, int, str]], bool, int, str]] = None, + auto_capitalize: Optional[ + Union[Var[Union[bool, int, str]], bool, int, str] + ] = None, + content_editable: Optional[ + Union[Var[Union[bool, int, str]], bool, int, str] + ] = None, + context_menu: Optional[ + Union[Var[Union[bool, int, str]], bool, int, str] + ] = None, + dir: Optional[Union[Var[Union[bool, int, str]], bool, int, str]] = None, + draggable: Optional[Union[Var[Union[bool, int, str]], bool, int, str]] = None, + enter_key_hint: Optional[ + Union[Var[Union[bool, int, str]], bool, int, str] + ] = None, + hidden: Optional[Union[Var[Union[bool, int, str]], bool, int, str]] = None, + input_mode: Optional[Union[Var[Union[bool, int, str]], bool, int, str]] = None, + item_prop: Optional[Union[Var[Union[bool, int, str]], bool, int, str]] = None, + lang: Optional[Union[Var[Union[bool, int, str]], bool, int, str]] = None, + role: Optional[Union[Var[Union[bool, int, str]], bool, int, str]] = None, + slot: Optional[Union[Var[Union[bool, int, str]], bool, int, str]] = None, + spell_check: Optional[Union[Var[Union[bool, int, str]], bool, int, str]] = None, + tab_index: Optional[Union[Var[Union[bool, int, str]], bool, int, str]] = None, + title: Optional[Union[Var[Union[bool, int, str]], bool, int, str]] = None, + style: Optional[Style] = None, + key: Optional[Any] = None, + id: Optional[Any] = None, + class_name: Optional[Any] = None, + autofocus: Optional[bool] = None, + custom_attrs: Optional[Dict[str, Union[Var, str]]] = None, + on_blur: Optional[Union[EventHandler, EventSpec, list, Callable, Var]] = None, + on_click: Optional[Union[EventHandler, EventSpec, list, Callable, Var]] = None, + on_context_menu: Optional[ + Union[EventHandler, EventSpec, list, Callable, Var] + ] = None, + on_double_click: Optional[ + Union[EventHandler, EventSpec, list, Callable, Var] + ] = None, + on_focus: Optional[Union[EventHandler, EventSpec, list, Callable, Var]] = None, + on_mount: Optional[Union[EventHandler, EventSpec, list, Callable, Var]] = None, + on_mouse_down: Optional[ + Union[EventHandler, EventSpec, list, Callable, Var] + ] = None, + on_mouse_enter: Optional[ + Union[EventHandler, EventSpec, list, Callable, Var] + ] = None, + on_mouse_leave: Optional[ + Union[EventHandler, EventSpec, list, Callable, Var] + ] = None, + on_mouse_move: Optional[ + Union[EventHandler, EventSpec, list, Callable, Var] + ] = None, + on_mouse_out: Optional[ + Union[EventHandler, EventSpec, list, Callable, Var] + ] = None, + on_mouse_over: Optional[ + Union[EventHandler, EventSpec, list, Callable, Var] + ] = None, + on_mouse_up: Optional[ + Union[EventHandler, EventSpec, list, Callable, Var] + ] = None, + on_scroll: Optional[Union[EventHandler, EventSpec, list, Callable, Var]] = None, + on_unmount: Optional[ + Union[EventHandler, EventSpec, list, Callable, Var] + ] = None, + **props, + ) -> "RadialGradient": + """Create the component. + + Args: + *children: The children of the component. + cx: The x coordinate of the end circle of the radial gradient. + cy: The y coordinate of the end circle of the radial gradient. + fr: The radius of the start circle of the radial gradient. + fx: The x coordinate of the start circle of the radial gradient. + fy: The y coordinate of the start circle of the radial gradient. + gradient_units: Units for the gradient. + gradient_transform: Transform applied to the gradient. + r: The radius of the end circle of the radial gradient. + spread_method: Method used to spread the gradient. + access_key: Provides a hint for generating a keyboard shortcut for the current element. + auto_capitalize: Controls whether and how text input is automatically capitalized as it is entered/edited by the user. + content_editable: Indicates whether the element's content is editable. + context_menu: Defines the ID of a element which will serve as the element's context menu. + dir: Defines the text direction. Allowed values are ltr (Left-To-Right) or rtl (Right-To-Left) + draggable: Defines whether the element can be dragged. + enter_key_hint: Hints what media types the media element is able to play. + hidden: Defines whether the element is hidden. + input_mode: Defines the type of the element. + item_prop: Defines the name of the element for metadata purposes. + lang: Defines the language used in the element. + role: Defines the role of the element. + slot: Assigns a slot in a shadow DOM shadow tree to an element. + spell_check: Defines whether the element may be checked for spelling errors. + tab_index: Defines the position of the current element in the tabbing order. + title: Defines a tooltip for the element. + style: The style of the component. + key: A unique key for the component. + id: The id for the component. + class_name: The class name for the component. + autofocus: Whether the component should take the focus once the page is loaded + custom_attrs: custom attribute + **props: The props of the component. + + Returns: + The component. + """ + ... + class Stop(BaseHTML): @overload @classmethod @@ -2345,12 +2821,16 @@ class Path(BaseHTML): ... class SVG(ComponentNamespace): + text = staticmethod(Text.create) + line = staticmethod(Line.create) circle = staticmethod(Circle.create) + ellipse = staticmethod(Ellipse.create) rect = staticmethod(Rect.create) polygon = staticmethod(Polygon.create) path = staticmethod(Path.create) stop = staticmethod(Stop.create) linear_gradient = staticmethod(LinearGradient.create) + radial_gradient = staticmethod(RadialGradient.create) defs = staticmethod(Defs.create) @staticmethod diff --git a/tests/components/el/test_svg.py b/tests/components/el/test_svg.py new file mode 100644 index 000000000..29aaa96dd --- /dev/null +++ b/tests/components/el/test_svg.py @@ -0,0 +1,74 @@ +from reflex.components.el.elements.media import ( + Circle, + Defs, + Ellipse, + Line, + LinearGradient, + Path, + Polygon, + RadialGradient, + Rect, + Stop, + Svg, + Text, +) + + +def test_circle(): + circle = Circle.create().render() + assert circle["name"] == "circle" + + +def test_defs(): + defs = Defs.create().render() + assert defs["name"] == "defs" + + +def test_ellipse(): + ellipse = Ellipse.create().render() + assert ellipse["name"] == "ellipse" + + +def test_line(): + line = Line.create().render() + assert line["name"] == "line" + + +def test_linear_gradient(): + linear_gradient = LinearGradient.create().render() + assert linear_gradient["name"] == "linearGradient" + + +def test_path(): + path = Path.create().render() + assert path["name"] == "path" + + +def test_polygon(): + polygon = Polygon.create().render() + assert polygon["name"] == "polygon" + + +def test_radial_gradient(): + radial_gradient = RadialGradient.create().render() + assert radial_gradient["name"] == "radialGradient" + + +def test_rect(): + rect = Rect.create().render() + assert rect["name"] == "rect" + + +def test_svg(): + svg = Svg.create().render() + assert svg["name"] == "svg" + + +def test_text(): + text = Text.create().render() + assert text["name"] == "text" + + +def test_stop(): + stop = Stop.create().render() + assert stop["name"] == "stop" From 9c3cc0cfa61cdcc2841b62a5e16112023e15578e Mon Sep 17 00:00:00 2001 From: Masen Furer Date: Tue, 1 Oct 2024 12:34:28 -0700 Subject: [PATCH 37/39] bump version to 0.6.2dev1 (#4025) For further development against the version that will become 0.6.2 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 335fc440e..08c4fbdbc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "reflex" -version = "0.6.1dev1" +version = "0.6.2dev1" description = "Web apps in pure Python." license = "Apache-2.0" authors = [ From e96b4bf42eb84c19925dc2f84aae1f195bfee49c Mon Sep 17 00:00:00 2001 From: Simon Young <40179067+Kastier1@users.noreply.github.com> Date: Tue, 1 Oct 2024 14:32:05 -0700 Subject: [PATCH 38/39] a friendly little helper (#4021) * a friendly little helper * addressing comments * update comment --------- Co-authored-by: simon --- reflex/model.py | 5 ++--- reflex/utils/compat.py | 18 ++++++++++++++++++ 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/reflex/model.py b/reflex/model.py index fefb1f9e9..0e8d62e90 100644 --- a/reflex/model.py +++ b/reflex/model.py @@ -22,7 +22,7 @@ from reflex import constants from reflex.base import Base from reflex.config import get_config from reflex.utils import console -from reflex.utils.compat import sqlmodel +from reflex.utils.compat import sqlmodel, sqlmodel_field_has_primary_key def get_engine(url: str | None = None) -> sqlalchemy.engine.Engine: @@ -166,8 +166,7 @@ class Model(Base, sqlmodel.SQLModel): # pyright: ignore [reportGeneralTypeIssue non_default_primary_key_fields = [ field_name for field_name, field in cls.__fields__.items() - if field_name != "id" - and getattr(field.field_info, "primary_key", None) is True + if field_name != "id" and sqlmodel_field_has_primary_key(field) ] if non_default_primary_key_fields: cls.__fields__.pop("id", None) diff --git a/reflex/utils/compat.py b/reflex/utils/compat.py index ef5fcd3e1..27c4753db 100644 --- a/reflex/utils/compat.py +++ b/reflex/utils/compat.py @@ -69,3 +69,21 @@ def pydantic_v1_patch(): with pydantic_v1_patch(): import sqlmodel as sqlmodel + + +def sqlmodel_field_has_primary_key(field) -> bool: + """Determines if a field is a priamary. + + Args: + field: a rx.model field + + Returns: + If field is a primary key (Bool) + """ + if getattr(field.field_info, "primary_key", None) is True: + return True + if getattr(field.field_info, "sa_column", None) is None: + return False + if getattr(field.field_info.sa_column, "primary_key", None) is True: + return True + return False From c08720ed1a8b7b0d28d1b9d65531af71a5164dd3 Mon Sep 17 00:00:00 2001 From: Masen Furer Date: Tue, 1 Oct 2024 15:23:35 -0700 Subject: [PATCH 39/39] Use an equality check instead of startswith (#4024) --- reflex/components/dynamic.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/reflex/components/dynamic.py b/reflex/components/dynamic.py index ad044d54f..390b6e688 100644 --- a/reflex/components/dynamic.py +++ b/reflex/components/dynamic.py @@ -2,6 +2,7 @@ from reflex import constants from reflex.utils import imports +from reflex.utils.format import format_library_name from reflex.utils.serializers import serializer from reflex.vars import Var, get_unique_variable_name from reflex.vars.base import VarData, transform @@ -64,11 +65,12 @@ def load_dynamic_serializer(): imports = {} for lib, names in component._get_all_imports().items(): + formatted_lib_name = format_library_name(lib) if ( not lib.startswith((".", "/")) and not lib.startswith("http") and all( - not lib.startswith(lib_in_window) + formatted_lib_name != lib_in_window for lib_in_window in libs_in_window ) ):