diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index a5483337ce..4ba472338f 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -2,7 +2,7 @@ name: Bug report about: Create a report to help us improve title: '' -labels: '' +labels: bug assignees: '' --- diff --git a/.github/actions/setup_build_env/action.yml b/.github/actions/setup_build_env/action.yml index 425f2d2332..560b53749d 100644 --- a/.github/actions/setup_build_env/action.yml +++ b/.github/actions/setup_build_env/action.yml @@ -106,3 +106,8 @@ runs: run: | source ${{ inputs.create-venv-at-path }}/*/activate poetry install --only-root --no-interaction + + - name: Install uv + shell: bash + run: | + poetry run pip install uv diff --git a/.github/workflows/benchmarks.yml b/.github/workflows/benchmarks.yml index 5de54a5287..617519f751 100644 --- a/.github/workflows/benchmarks.yml +++ b/.github/workflows/benchmarks.yml @@ -54,7 +54,7 @@ jobs: - name: Install Requirements for reflex-web working-directory: ./reflex-web - run: poetry run pip install -r requirements.txt + run: poetry run uv pip install -r requirements.txt - name: Init Website for reflex-web working-directory: ./reflex-web run: poetry run reflex init @@ -117,7 +117,7 @@ jobs: run-poetry-install: true create-venv-at-path: .venv - name: Install additional dependencies for DB access - run: poetry run pip install psycopg2-binary + run: poetry run uv pip install psycopg2-binary - name: Run benchmark tests env: APP_HARNESS_HEADLESS: 1 @@ -149,7 +149,7 @@ jobs: run-poetry-install: true create-venv-at-path: .venv - name: Install additional dependencies for DB access - run: poetry run pip install psycopg2-binary + run: poetry run uv pip install psycopg2-binary - name: Build reflex run: | poetry build @@ -192,8 +192,13 @@ jobs: source .venv/*/activate poetry install --without dev --no-interaction --no-root + - name: Install uv + shell: bash + run: | + poetry run pip install uv + - name: Install additional dependencies for DB access - run: poetry run pip install psycopg2-binary + run: poetry run uv pip install psycopg2-binary - if: ${{ env.DATABASE_URL }} name: calculate and upload size diff --git a/.github/workflows/dependency-review.yml b/.github/workflows/dependency-review.yml new file mode 100644 index 0000000000..eb6eac00ce --- /dev/null +++ b/.github/workflows/dependency-review.yml @@ -0,0 +1,16 @@ +name: 'Dependency Review' +on: [pull_request] + +permissions: + contents: read + +jobs: + dependency-review: + runs-on: ubuntu-latest + steps: + - name: 'Checkout Repository' + uses: actions/checkout@v4 + - name: 'Dependency Review' + uses: actions/dependency-review-action@v4 + with: + allow-licenses: Apache-2.0, BSD-2-Clause, BSD-3-Clause, HPND, ISC, MIT, MPL-2.0, PSF-2.0, Unlicense diff --git a/.github/workflows/integration_app_harness.yml b/.github/workflows/integration_app_harness.yml index 6b113cd795..e92fdb6d02 100644 --- a/.github/workflows/integration_app_harness.yml +++ b/.github/workflows/integration_app_harness.yml @@ -45,7 +45,7 @@ jobs: python-version: ${{ matrix.python-version }} run-poetry-install: true create-venv-at-path: .venv - - run: poetry run pip install pyvirtualdisplay pillow + - run: poetry run uv pip install pyvirtualdisplay pillow - name: Run app harness tests env: SCREENSHOT_DIR: /tmp/screenshots diff --git a/.github/workflows/integration_tests.yml b/.github/workflows/integration_tests.yml index b1b622cb2c..b831075046 100644 --- a/.github/workflows/integration_tests.yml +++ b/.github/workflows/integration_tests.yml @@ -35,6 +35,8 @@ env: jobs: example-counter: + env: + OUTPUT_FILE: import_benchmark.json timeout-minutes: 30 strategy: # Prioritize getting more information out of the workflow (even if something fails) @@ -74,9 +76,9 @@ jobs: - name: Install requirements for counter example working-directory: ./reflex-examples/counter run: | - poetry run pip install -r requirements.txt + poetry run uv pip install -r requirements.txt - name: Install additional dependencies for DB access - run: poetry run pip install psycopg2-binary + run: poetry run uv pip install psycopg2-binary - name: Check export --backend-only before init for counter example working-directory: ./reflex-examples/counter run: | @@ -98,13 +100,31 @@ jobs: npm -v poetry run bash scripts/integration.sh ./reflex-examples/counter dev - name: Measure and upload .web size - if: ${{ env.DATABASE_URL }} + if: ${{ env.DATABASE_URL && github.event.pull_request.merged == true }} run: poetry run python scripts/benchmarks/benchmark_reflex_size.py --os "${{ matrix.os }}" --python-version "${{ matrix.python-version }}" --commit-sha "${{ github.sha }}" --pr-id "${{ github.event.pull_request.id }}" --db-url "${{ env.DATABASE_URL }}" --branch-name "${{ github.head_ref || github.ref_name }}" --measurement-type "counter-app-dot-web" --path ./reflex-examples/counter/.web + - name: Install hyperfine + if: github.event.pull_request.merged == true + run: cargo install --locked hyperfine + - name: Benchmark imports + if: github.event.pull_request.merged == true + working-directory: ./reflex-examples/counter + run: hyperfine --warmup 3 "export POETRY_VIRTUALENVS_PATH=../../.venv; poetry run python counter/counter.py" --show-output --export-json "${{ env.OUTPUT_FILE }}" --shell bash + - name: Upload Benchmarks + if : ${{ env.DATABASE_URL && github.event.pull_request.merged == true }} + run: + poetry run python scripts/benchmarks/benchmark_imports.py --os "${{ matrix.os }}" + --python-version "${{ matrix.python-version }}" --commit-sha "${{ github.sha }}" + --benchmark-json "./reflex-examples/counter/${{ env.OUTPUT_FILE }}" + --db-url "${{ env.DATABASE_URL }}" --branch-name "${{ github.head_ref || github.ref_name }}" + --event-type "${{ github.event_name }}" --actor "${{ github.actor }}" --pr-id "${{ github.event.pull_request.id }}" + + + reflex-web: strategy: @@ -134,9 +154,9 @@ jobs: - name: Install Requirements for reflex-web working-directory: ./reflex-web - run: poetry run pip install -r requirements.txt + run: poetry run uv pip install -r requirements.txt - name: Install additional dependencies for DB access - run: poetry run pip install psycopg2-binary + run: poetry run uv pip install psycopg2-binary - name: Init Website for reflex-web working-directory: ./reflex-web run: poetry run reflex init @@ -146,7 +166,7 @@ jobs: npm -v poetry run bash scripts/integration.sh ./reflex-web prod - name: Measure and upload .web size - if: ${{ env.DATABASE_URL }} + if: ${{ env.DATABASE_URL && github.event.pull_request.merged == true }} run: poetry run python scripts/benchmarks/benchmark_reflex_size.py --os "${{ matrix.os }}" --python-version "${{ matrix.python-version }}" --commit-sha "${{ github.sha }}" diff --git a/.github/workflows/integration_tests_wsl.yml b/.github/workflows/integration_tests_wsl.yml index 87f6118983..6750fcd467 100644 --- a/.github/workflows/integration_tests_wsl.yml +++ b/.github/workflows/integration_tests_wsl.yml @@ -56,11 +56,16 @@ jobs: run: | poetry install + - name: Install uv + shell: wsl-bash {0} + run: | + poetry run pip install uv + - name: Install requirements for counter example working-directory: ./reflex-examples/counter shell: wsl-bash {0} run: | - poetry run pip install -r requirements.txt + poetry run uv pip install -r requirements.txt - name: Check export --backend-only before init for counter example working-directory: ./reflex-examples/counter shell: wsl-bash {0} diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml index 452399c455..9e6e42a38b 100644 --- a/.github/workflows/pre-commit.yml +++ b/.github/workflows/pre-commit.yml @@ -28,7 +28,7 @@ jobs: create-venv-at-path: .venv # TODO pre-commit related stuff can be cached too (not a bottleneck yet) - run: | - poetry run pip install pre-commit + poetry run uv pip install pre-commit poetry run pre-commit run --all-files env: SKIP: update-pyi-files diff --git a/.github/workflows/unit_tests.yml b/.github/workflows/unit_tests.yml index 3d3e091ddc..f49a9b2797 100644 --- a/.github/workflows/unit_tests.yml +++ b/.github/workflows/unit_tests.yml @@ -77,9 +77,9 @@ jobs: export REDIS_URL=redis://localhost:6379 poetry run pytest tests --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 v2 + - name: Run unit tests w/ pydantic v1 run: | export PYTHONUNBUFFERED=1 - poetry run pip install "pydantic>2" + poetry run uv pip install "pydantic~=1.10" poetry run pytest tests --cov --no-cov-on-fail --cov-report= - run: poetry run coverage html diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c2f4a10796..ff669c6253 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -96,6 +96,15 @@ pre-commit install That's it you can now submit your PR. Thanks for contributing to Reflex! +## Editing Templates + +To edit the templates in Reflex you can do so in two way. + +Change to the basic `blank` template can be done in the `reflex/.templates/apps/blank` directory. + +Others templates can be edited in their own repository. For example the `sidebar` template can be found in the [`reflex-sidebar`](https://github.com/reflex-dev/sidebar-template) repository. + + ## Other Notes For some pull requests when adding new components you will have to generate a pyi file for the new component. This is done by running the following command in the `reflex` directory. diff --git a/integration/conftest.py b/integration/conftest.py index c3d57a0ae1..212ac9981d 100644 --- a/integration/conftest.py +++ b/integration/conftest.py @@ -51,7 +51,7 @@ def pytest_exception_interact(node, call, report): safe_filename = re.sub( r"(?u)[^-\w.]", "_", - str(node.nodeid).strip().replace(" ", "_").replace(":", "_"), + str(node.nodeid).strip().replace(" ", "_").replace(":", "_").replace(".py", ""), ) try: diff --git a/integration/init-test/in_docker_test_script.sh b/integration/init-test/in_docker_test_script.sh index 4fb836fd1c..4e898ac999 100755 --- a/integration/init-test/in_docker_test_script.sh +++ b/integration/init-test/in_docker_test_script.sh @@ -26,6 +26,7 @@ function do_export () { echo "Preparing test project dir" python3 -m venv ~/venv source ~/venv/bin/activate +pip install -U pip echo "Installing reflex from local repo code" pip install /reflex-repo diff --git a/integration/test_component_state.py b/integration/test_component_state.py index d2c10c7665..e903a1b742 100644 --- a/integration/test_component_state.py +++ b/integration/test_component_state.py @@ -1,4 +1,5 @@ """Test that per-component state scaffold works and operates independently.""" + from typing import Generator import pytest diff --git a/integration/test_state_inheritance.py b/integration/test_state_inheritance.py index 3f7d8cb2d1..86ab625e10 100644 --- a/integration/test_state_inheritance.py +++ b/integration/test_state_inheritance.py @@ -1,4 +1,5 @@ """Test state inheritance.""" + from __future__ import annotations from contextlib import suppress @@ -44,17 +45,15 @@ def StateInheritance(): """Test that state inheritance works as expected.""" import reflex as rx - class ChildMixin: - # mixin basevars only work with pydantic/rx.Base models - # child_mixin: str = "child_mixin" + class ChildMixin(rx.State, mixin=True): + child_mixin: str = "child_mixin" @rx.var def computed_child_mixin(self) -> str: return "computed_child_mixin" - class Mixin(ChildMixin): - # mixin basevars only work with pydantic/rx.Base models - # mixin: str = "mixin" + class Mixin(ChildMixin, mixin=True): + mixin: str = "mixin" @rx.var def computed_mixin(self) -> str: @@ -63,7 +62,7 @@ def computed_mixin(self) -> str: def on_click_mixin(self): return rx.call_script("alert('clicked')") - class OtherMixin(rx.Base): + class OtherMixin(rx.State, mixin=True): other_mixin: str = "other_mixin" other_mixin_clicks: int = 0 @@ -77,7 +76,7 @@ def on_click_other_mixin(self): f"{self.__class__.__name__}.clicked.{self.other_mixin_clicks}" ) - class Base1(rx.State, Mixin): + class Base1(Mixin, rx.State): _base1: str = "_base1" base1: str = "base1" @@ -121,14 +120,15 @@ def computed_backend_vars_child3(self) -> str: def index() -> rx.Component: return rx.vstack( - rx.chakra.input( + rx.input( id="token", value=Base1.router.session.client_token, is_read_only=True ), - # Base 1 + # Base 1 (Mixin, ChildMixin) rx.heading(Base1.computed_mixin, id="base1-computed_mixin"), rx.heading(Base1.computed_basevar, id="base1-computed_basevar"), - rx.heading(Base1.computed_child_mixin, id="base1-child-mixin"), + rx.heading(Base1.computed_child_mixin, id="base1-computed-child-mixin"), rx.heading(Base1.base1, id="base1-base1"), + rx.heading(Base1.child_mixin, id="base1-child-mixin"), rx.button( "Base1.on_click_mixin", on_click=Base1.on_click_mixin, # type: ignore @@ -137,31 +137,33 @@ def index() -> rx.Component: rx.heading( Base1.computed_backend_vars_base1, id="base1-computed_backend_vars" ), - # Base 2 + # Base 2 (no mixins) rx.heading(Base2.computed_basevar, id="base2-computed_basevar"), rx.heading(Base2.base2, id="base2-base2"), rx.heading( Base2.computed_backend_vars_base2, id="base2-computed_backend_vars" ), - # Child 1 + # Child 1 (Mixin, ChildMixin, OtherMixin) rx.heading(Child1.computed_basevar, id="child1-computed_basevar"), rx.heading(Child1.computed_mixin, id="child1-computed_mixin"), rx.heading(Child1.computed_other_mixin, id="child1-other-mixin"), - rx.heading(Child1.computed_child_mixin, id="child1-child-mixin"), + rx.heading(Child1.computed_child_mixin, id="child1-computed-child-mixin"), rx.heading(Child1.base1, id="child1-base1"), rx.heading(Child1.other_mixin, id="child1-other_mixin"), + rx.heading(Child1.child_mixin, id="child1-child-mixin"), rx.button( "Child1.on_click_other_mixin", on_click=Child1.on_click_other_mixin, # type: ignore id="child1-other-mixin-btn", ), - # Child 2 + # Child 2 (Mixin, ChildMixin, OtherMixin) rx.heading(Child2.computed_basevar, id="child2-computed_basevar"), rx.heading(Child2.computed_mixin, id="child2-computed_mixin"), rx.heading(Child2.computed_other_mixin, id="child2-other-mixin"), - rx.heading(Child2.computed_child_mixin, id="child2-child-mixin"), + rx.heading(Child2.computed_child_mixin, id="child2-computed-child-mixin"), rx.heading(Child2.base2, id="child2-base2"), rx.heading(Child2.other_mixin, id="child2-other_mixin"), + rx.heading(Child2.child_mixin, id="child2-child-mixin"), rx.button( "Child2.on_click_mixin", on_click=Child2.on_click_mixin, # type: ignore @@ -172,15 +174,16 @@ def index() -> rx.Component: on_click=Child2.on_click_other_mixin, # type: ignore id="child2-other-mixin-btn", ), - # Child 3 + # Child 3 (Mixin, ChildMixin, OtherMixin) rx.heading(Child3.computed_basevar, id="child3-computed_basevar"), rx.heading(Child3.computed_mixin, id="child3-computed_mixin"), rx.heading(Child3.computed_other_mixin, id="child3-other-mixin"), rx.heading(Child3.computed_childvar, id="child3-computed_childvar"), - rx.heading(Child3.computed_child_mixin, id="child3-child-mixin"), + rx.heading(Child3.computed_child_mixin, id="child3-computed-child-mixin"), rx.heading(Child3.child3, id="child3-child3"), rx.heading(Child3.base2, id="child3-base2"), rx.heading(Child3.other_mixin, id="child3-other_mixin"), + rx.heading(Child3.child_mixin, id="child3-child-mixin"), rx.button( "Child3.on_click_mixin", on_click=Child3.on_click_mixin, # type: ignore @@ -281,7 +284,9 @@ def test_state_inheritance( base1_computed_basevar = driver.find_element(By.ID, "base1-computed_basevar") assert base1_computed_basevar.text == "computed_basevar1" - base1_computed_child_mixin = driver.find_element(By.ID, "base1-child-mixin") + base1_computed_child_mixin = driver.find_element( + By.ID, "base1-computed-child-mixin" + ) assert base1_computed_child_mixin.text == "computed_child_mixin" base1_base1 = driver.find_element(By.ID, "base1-base1") @@ -292,6 +297,9 @@ def test_state_inheritance( ) assert base1_computed_backend_vars.text == "_base1" + base1_child_mixin = driver.find_element(By.ID, "base1-child-mixin") + assert base1_child_mixin.text == "child_mixin" + # Base 2 base2_computed_basevar = driver.find_element(By.ID, "base2-computed_basevar") assert base2_computed_basevar.text == "computed_basevar2" @@ -314,7 +322,9 @@ def test_state_inheritance( child1_computed_other_mixin = driver.find_element(By.ID, "child1-other-mixin") assert child1_computed_other_mixin.text == "other_mixin" - child1_computed_child_mixin = driver.find_element(By.ID, "child1-child-mixin") + child1_computed_child_mixin = driver.find_element( + By.ID, "child1-computed-child-mixin" + ) assert child1_computed_child_mixin.text == "computed_child_mixin" child1_base1 = driver.find_element(By.ID, "child1-base1") @@ -323,6 +333,9 @@ def test_state_inheritance( child1_other_mixin = driver.find_element(By.ID, "child1-other_mixin") assert child1_other_mixin.text == "other_mixin" + child1_child_mixin = driver.find_element(By.ID, "child1-child-mixin") + assert child1_child_mixin.text == "child_mixin" + # Child 2 child2_computed_basevar = driver.find_element(By.ID, "child2-computed_basevar") assert child2_computed_basevar.text == "computed_basevar2" @@ -333,7 +346,9 @@ def test_state_inheritance( child2_computed_other_mixin = driver.find_element(By.ID, "child2-other-mixin") assert child2_computed_other_mixin.text == "other_mixin" - child2_computed_child_mixin = driver.find_element(By.ID, "child2-child-mixin") + child2_computed_child_mixin = driver.find_element( + By.ID, "child2-computed-child-mixin" + ) assert child2_computed_child_mixin.text == "computed_child_mixin" child2_base2 = driver.find_element(By.ID, "child2-base2") @@ -342,6 +357,9 @@ def test_state_inheritance( child2_other_mixin = driver.find_element(By.ID, "child2-other_mixin") assert child2_other_mixin.text == "other_mixin" + child2_child_mixin = driver.find_element(By.ID, "child2-child-mixin") + assert child2_child_mixin.text == "child_mixin" + # Child 3 child3_computed_basevar = driver.find_element(By.ID, "child3-computed_basevar") assert child3_computed_basevar.text == "computed_basevar2" @@ -355,7 +373,9 @@ def test_state_inheritance( child3_computed_childvar = driver.find_element(By.ID, "child3-computed_childvar") assert child3_computed_childvar.text == "computed_childvar" - child3_computed_child_mixin = driver.find_element(By.ID, "child3-child-mixin") + child3_computed_child_mixin = driver.find_element( + By.ID, "child3-computed-child-mixin" + ) assert child3_computed_child_mixin.text == "computed_child_mixin" child3_child3 = driver.find_element(By.ID, "child3-child3") @@ -367,6 +387,9 @@ def test_state_inheritance( child3_other_mixin = driver.find_element(By.ID, "child3-other_mixin") assert child3_other_mixin.text == "other_mixin" + child3_child_mixin = driver.find_element(By.ID, "child3-child-mixin") + assert child3_child_mixin.text == "child_mixin" + child3_computed_backend_vars = driver.find_element( By.ID, "child3-computed_backend_vars" ) @@ -413,13 +436,13 @@ def test_state_inheritance( child2_other_mixin, exp_not_equal="Child2.clicked.1" ) child2_computed_mixin_value = state_inheritance.poll_for_content( - child2_computed_other_mixin, exp_not_equal="other_mixin" + child2_computed_other_mixin, exp_not_equal="Child2.clicked.1" ) child3_other_mixin_value = state_inheritance.poll_for_content( - child3_other_mixin, exp_not_equal="other_mixin" + child3_other_mixin, exp_not_equal="Child2.clicked.1" ) child3_computed_mixin_value = state_inheritance.poll_for_content( - child3_computed_other_mixin, exp_not_equal="other_mixin" + child3_computed_other_mixin, exp_not_equal="Child2.clicked.1" ) assert child2_other_mixin_value == "Child2.clicked.2" assert child2_computed_mixin_value == "Child2.clicked.2" diff --git a/poetry.lock b/poetry.lock index 1a7a3771a3..6ab08de7de 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.7.1 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" @@ -21,6 +21,20 @@ typing-extensions = ">=4" [package.extras] tz = ["backports.zoneinfo"] +[[package]] +name = "annotated-types" +version = "0.6.0" +description = "Reusable constraint types to use with typing.Annotated" +optional = false +python-versions = ">=3.8" +files = [ + {file = "annotated_types-0.6.0-py3-none-any.whl", hash = "sha256:0641064de18ba7a25dee8f96403ebc39113d0cb953a01429249d5c7564666a43"}, + {file = "annotated_types-0.6.0.tar.gz", hash = "sha256:563339e807e53ffd9c267e99fc6d9ea23eb8443c08f112651963e24e22f84a5d"}, +] + +[package.dependencies] +typing-extensions = {version = ">=4.0.0", markers = "python_version < \"3.9\""} + [[package]] name = "anyio" version = "4.3.0" @@ -84,6 +98,21 @@ tests = ["attrs[tests-no-zope]", "zope-interface"] tests-mypy = ["mypy (>=1.6)", "pytest-mypy-plugins"] tests-no-zope = ["attrs[tests-mypy]", "cloudpickle", "hypothesis", "pympler", "pytest (>=4.3.0)", "pytest-xdist[psutil]"] +[[package]] +name = "backports-tarfile" +version = "1.1.1" +description = "Backport of CPython tarfile module" +optional = false +python-versions = ">=3.8" +files = [ + {file = "backports.tarfile-1.1.1-py3-none-any.whl", hash = "sha256:73e0179647803d3726d82e76089d01d8549ceca9bace469953fcb4d97cf2d417"}, + {file = "backports_tarfile-1.1.1.tar.gz", hash = "sha256:9c2ef9696cb73374f7164e17fc761389393ca76777036f5aad42e8b93fcd8009"}, +] + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9.3)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +testing = ["jaraco.test", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)"] + [[package]] name = "bidict" version = "0.23.1" @@ -367,63 +396,63 @@ files = [ [[package]] name = "coverage" -version = "7.4.4" +version = "7.5.1" description = "Code coverage measurement for Python" optional = false python-versions = ">=3.8" files = [ - {file = "coverage-7.4.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e0be5efd5127542ef31f165de269f77560d6cdef525fffa446de6f7e9186cfb2"}, - {file = "coverage-7.4.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ccd341521be3d1b3daeb41960ae94a5e87abe2f46f17224ba5d6f2b8398016cf"}, - {file = "coverage-7.4.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:09fa497a8ab37784fbb20ab699c246053ac294d13fc7eb40ec007a5043ec91f8"}, - {file = "coverage-7.4.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b1a93009cb80730c9bca5d6d4665494b725b6e8e157c1cb7f2db5b4b122ea562"}, - {file = "coverage-7.4.4-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:690db6517f09336559dc0b5f55342df62370a48f5469fabf502db2c6d1cffcd2"}, - {file = "coverage-7.4.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:09c3255458533cb76ef55da8cc49ffab9e33f083739c8bd4f58e79fecfe288f7"}, - {file = "coverage-7.4.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:8ce1415194b4a6bd0cdcc3a1dfbf58b63f910dcb7330fe15bdff542c56949f87"}, - {file = "coverage-7.4.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b91cbc4b195444e7e258ba27ac33769c41b94967919f10037e6355e998af255c"}, - {file = "coverage-7.4.4-cp310-cp310-win32.whl", hash = "sha256:598825b51b81c808cb6f078dcb972f96af96b078faa47af7dfcdf282835baa8d"}, - {file = "coverage-7.4.4-cp310-cp310-win_amd64.whl", hash = "sha256:09ef9199ed6653989ebbcaacc9b62b514bb63ea2f90256e71fea3ed74bd8ff6f"}, - {file = "coverage-7.4.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0f9f50e7ef2a71e2fae92774c99170eb8304e3fdf9c8c3c7ae9bab3e7229c5cf"}, - {file = "coverage-7.4.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:623512f8ba53c422fcfb2ce68362c97945095b864cda94a92edbaf5994201083"}, - {file = "coverage-7.4.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0513b9508b93da4e1716744ef6ebc507aff016ba115ffe8ecff744d1322a7b63"}, - {file = "coverage-7.4.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40209e141059b9370a2657c9b15607815359ab3ef9918f0196b6fccce8d3230f"}, - {file = "coverage-7.4.4-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8a2b2b78c78293782fd3767d53e6474582f62443d0504b1554370bde86cc8227"}, - {file = "coverage-7.4.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:73bfb9c09951125d06ee473bed216e2c3742f530fc5acc1383883125de76d9cd"}, - {file = "coverage-7.4.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:1f384c3cc76aeedce208643697fb3e8437604b512255de6d18dae3f27655a384"}, - {file = "coverage-7.4.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:54eb8d1bf7cacfbf2a3186019bcf01d11c666bd495ed18717162f7eb1e9dd00b"}, - {file = "coverage-7.4.4-cp311-cp311-win32.whl", hash = "sha256:cac99918c7bba15302a2d81f0312c08054a3359eaa1929c7e4b26ebe41e9b286"}, - {file = "coverage-7.4.4-cp311-cp311-win_amd64.whl", hash = "sha256:b14706df8b2de49869ae03a5ccbc211f4041750cd4a66f698df89d44f4bd30ec"}, - {file = "coverage-7.4.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:201bef2eea65e0e9c56343115ba3814e896afe6d36ffd37bab783261db430f76"}, - {file = "coverage-7.4.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:41c9c5f3de16b903b610d09650e5e27adbfa7f500302718c9ffd1c12cf9d6818"}, - {file = "coverage-7.4.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d898fe162d26929b5960e4e138651f7427048e72c853607f2b200909794ed978"}, - {file = "coverage-7.4.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3ea79bb50e805cd6ac058dfa3b5c8f6c040cb87fe83de10845857f5535d1db70"}, - {file = "coverage-7.4.4-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce4b94265ca988c3f8e479e741693d143026632672e3ff924f25fab50518dd51"}, - {file = "coverage-7.4.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:00838a35b882694afda09f85e469c96367daa3f3f2b097d846a7216993d37f4c"}, - {file = "coverage-7.4.4-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:fdfafb32984684eb03c2d83e1e51f64f0906b11e64482df3c5db936ce3839d48"}, - {file = "coverage-7.4.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:69eb372f7e2ece89f14751fbcbe470295d73ed41ecd37ca36ed2eb47512a6ab9"}, - {file = "coverage-7.4.4-cp312-cp312-win32.whl", hash = "sha256:137eb07173141545e07403cca94ab625cc1cc6bc4c1e97b6e3846270e7e1fea0"}, - {file = "coverage-7.4.4-cp312-cp312-win_amd64.whl", hash = "sha256:d71eec7d83298f1af3326ce0ff1d0ea83c7cb98f72b577097f9083b20bdaf05e"}, - {file = "coverage-7.4.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d5ae728ff3b5401cc320d792866987e7e7e880e6ebd24433b70a33b643bb0384"}, - {file = "coverage-7.4.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:cc4f1358cb0c78edef3ed237ef2c86056206bb8d9140e73b6b89fbcfcbdd40e1"}, - {file = "coverage-7.4.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8130a2aa2acb8788e0b56938786c33c7c98562697bf9f4c7d6e8e5e3a0501e4a"}, - {file = "coverage-7.4.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cf271892d13e43bc2b51e6908ec9a6a5094a4df1d8af0bfc360088ee6c684409"}, - {file = "coverage-7.4.4-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a4cdc86d54b5da0df6d3d3a2f0b710949286094c3a6700c21e9015932b81447e"}, - {file = "coverage-7.4.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:ae71e7ddb7a413dd60052e90528f2f65270aad4b509563af6d03d53e979feafd"}, - {file = "coverage-7.4.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:38dd60d7bf242c4ed5b38e094baf6401faa114fc09e9e6632374388a404f98e7"}, - {file = "coverage-7.4.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:aa5b1c1bfc28384f1f53b69a023d789f72b2e0ab1b3787aae16992a7ca21056c"}, - {file = "coverage-7.4.4-cp38-cp38-win32.whl", hash = "sha256:dfa8fe35a0bb90382837b238fff375de15f0dcdb9ae68ff85f7a63649c98527e"}, - {file = "coverage-7.4.4-cp38-cp38-win_amd64.whl", hash = "sha256:b2991665420a803495e0b90a79233c1433d6ed77ef282e8e152a324bbbc5e0c8"}, - {file = "coverage-7.4.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3b799445b9f7ee8bf299cfaed6f5b226c0037b74886a4e11515e569b36fe310d"}, - {file = "coverage-7.4.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b4d33f418f46362995f1e9d4f3a35a1b6322cb959c31d88ae56b0298e1c22357"}, - {file = "coverage-7.4.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aadacf9a2f407a4688d700e4ebab33a7e2e408f2ca04dbf4aef17585389eff3e"}, - {file = "coverage-7.4.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7c95949560050d04d46b919301826525597f07b33beba6187d04fa64d47ac82e"}, - {file = "coverage-7.4.4-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ff7687ca3d7028d8a5f0ebae95a6e4827c5616b31a4ee1192bdfde697db110d4"}, - {file = "coverage-7.4.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:5fc1de20b2d4a061b3df27ab9b7c7111e9a710f10dc2b84d33a4ab25065994ec"}, - {file = "coverage-7.4.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:c74880fc64d4958159fbd537a091d2a585448a8f8508bf248d72112723974cbd"}, - {file = "coverage-7.4.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:742a76a12aa45b44d236815d282b03cfb1de3b4323f3e4ec933acfae08e54ade"}, - {file = "coverage-7.4.4-cp39-cp39-win32.whl", hash = "sha256:d89d7b2974cae412400e88f35d86af72208e1ede1a541954af5d944a8ba46c57"}, - {file = "coverage-7.4.4-cp39-cp39-win_amd64.whl", hash = "sha256:9ca28a302acb19b6af89e90f33ee3e1906961f94b54ea37de6737b7ca9d8827c"}, - {file = "coverage-7.4.4-pp38.pp39.pp310-none-any.whl", hash = "sha256:b2c5edc4ac10a7ef6605a966c58929ec6c1bd0917fb8c15cb3363f65aa40e677"}, - {file = "coverage-7.4.4.tar.gz", hash = "sha256:c901df83d097649e257e803be22592aedfd5182f07b3cc87d640bbb9afd50f49"}, + {file = "coverage-7.5.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c0884920835a033b78d1c73b6d3bbcda8161a900f38a488829a83982925f6c2e"}, + {file = "coverage-7.5.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:39afcd3d4339329c5f58de48a52f6e4e50f6578dd6099961cf22228feb25f38f"}, + {file = "coverage-7.5.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4a7b0ceee8147444347da6a66be737c9d78f3353b0681715b668b72e79203e4a"}, + {file = "coverage-7.5.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a9ca3f2fae0088c3c71d743d85404cec8df9be818a005ea065495bedc33da35"}, + {file = "coverage-7.5.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5fd215c0c7d7aab005221608a3c2b46f58c0285a819565887ee0b718c052aa4e"}, + {file = "coverage-7.5.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:4bf0655ab60d754491004a5efd7f9cccefcc1081a74c9ef2da4735d6ee4a6223"}, + {file = "coverage-7.5.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:61c4bf1ba021817de12b813338c9be9f0ad5b1e781b9b340a6d29fc13e7c1b5e"}, + {file = "coverage-7.5.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:db66fc317a046556a96b453a58eced5024af4582a8dbdc0c23ca4dbc0d5b3146"}, + {file = "coverage-7.5.1-cp310-cp310-win32.whl", hash = "sha256:b016ea6b959d3b9556cb401c55a37547135a587db0115635a443b2ce8f1c7228"}, + {file = "coverage-7.5.1-cp310-cp310-win_amd64.whl", hash = "sha256:df4e745a81c110e7446b1cc8131bf986157770fa405fe90e15e850aaf7619bc8"}, + {file = "coverage-7.5.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:796a79f63eca8814ca3317a1ea443645c9ff0d18b188de470ed7ccd45ae79428"}, + {file = "coverage-7.5.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4fc84a37bfd98db31beae3c2748811a3fa72bf2007ff7902f68746d9757f3746"}, + {file = "coverage-7.5.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6175d1a0559986c6ee3f7fccfc4a90ecd12ba0a383dcc2da30c2b9918d67d8a3"}, + {file = "coverage-7.5.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1fc81d5878cd6274ce971e0a3a18a8803c3fe25457165314271cf78e3aae3aa2"}, + {file = "coverage-7.5.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:556cf1a7cbc8028cb60e1ff0be806be2eded2daf8129b8811c63e2b9a6c43bca"}, + {file = "coverage-7.5.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:9981706d300c18d8b220995ad22627647be11a4276721c10911e0e9fa44c83e8"}, + {file = "coverage-7.5.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:d7fed867ee50edf1a0b4a11e8e5d0895150e572af1cd6d315d557758bfa9c057"}, + {file = "coverage-7.5.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:ef48e2707fb320c8f139424a596f5b69955a85b178f15af261bab871873bb987"}, + {file = "coverage-7.5.1-cp311-cp311-win32.whl", hash = "sha256:9314d5678dcc665330df5b69c1e726a0e49b27df0461c08ca12674bcc19ef136"}, + {file = "coverage-7.5.1-cp311-cp311-win_amd64.whl", hash = "sha256:5fa567e99765fe98f4e7d7394ce623e794d7cabb170f2ca2ac5a4174437e90dd"}, + {file = "coverage-7.5.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b6cf3764c030e5338e7f61f95bd21147963cf6aa16e09d2f74f1fa52013c1206"}, + {file = "coverage-7.5.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2ec92012fefebee89a6b9c79bc39051a6cb3891d562b9270ab10ecfdadbc0c34"}, + {file = "coverage-7.5.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:16db7f26000a07efcf6aea00316f6ac57e7d9a96501e990a36f40c965ec7a95d"}, + {file = "coverage-7.5.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:beccf7b8a10b09c4ae543582c1319c6df47d78fd732f854ac68d518ee1fb97fa"}, + {file = "coverage-7.5.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8748731ad392d736cc9ccac03c9845b13bb07d020a33423fa5b3a36521ac6e4e"}, + {file = "coverage-7.5.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7352b9161b33fd0b643ccd1f21f3a3908daaddf414f1c6cb9d3a2fd618bf2572"}, + {file = "coverage-7.5.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:7a588d39e0925f6a2bff87154752481273cdb1736270642aeb3635cb9b4cad07"}, + {file = "coverage-7.5.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:68f962d9b72ce69ea8621f57551b2fa9c70509af757ee3b8105d4f51b92b41a7"}, + {file = "coverage-7.5.1-cp312-cp312-win32.whl", hash = "sha256:f152cbf5b88aaeb836127d920dd0f5e7edff5a66f10c079157306c4343d86c19"}, + {file = "coverage-7.5.1-cp312-cp312-win_amd64.whl", hash = "sha256:5a5740d1fb60ddf268a3811bcd353de34eb56dc24e8f52a7f05ee513b2d4f596"}, + {file = "coverage-7.5.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:e2213def81a50519d7cc56ed643c9e93e0247f5bbe0d1247d15fa520814a7cd7"}, + {file = "coverage-7.5.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:5037f8fcc2a95b1f0e80585bd9d1ec31068a9bcb157d9750a172836e98bc7a90"}, + {file = "coverage-7.5.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c3721c2c9e4c4953a41a26c14f4cef64330392a6d2d675c8b1db3b645e31f0e"}, + {file = "coverage-7.5.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ca498687ca46a62ae590253fba634a1fe9836bc56f626852fb2720f334c9e4e5"}, + {file = "coverage-7.5.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0cdcbc320b14c3e5877ee79e649677cb7d89ef588852e9583e6b24c2e5072661"}, + {file = "coverage-7.5.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:57e0204b5b745594e5bc14b9b50006da722827f0b8c776949f1135677e88d0b8"}, + {file = "coverage-7.5.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:8fe7502616b67b234482c3ce276ff26f39ffe88adca2acf0261df4b8454668b4"}, + {file = "coverage-7.5.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:9e78295f4144f9dacfed4f92935fbe1780021247c2fabf73a819b17f0ccfff8d"}, + {file = "coverage-7.5.1-cp38-cp38-win32.whl", hash = "sha256:1434e088b41594baa71188a17533083eabf5609e8e72f16ce8c186001e6b8c41"}, + {file = "coverage-7.5.1-cp38-cp38-win_amd64.whl", hash = "sha256:0646599e9b139988b63704d704af8e8df7fa4cbc4a1f33df69d97f36cb0a38de"}, + {file = "coverage-7.5.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4cc37def103a2725bc672f84bd939a6fe4522310503207aae4d56351644682f1"}, + {file = "coverage-7.5.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:fc0b4d8bfeabd25ea75e94632f5b6e047eef8adaed0c2161ada1e922e7f7cece"}, + {file = "coverage-7.5.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0d0a0f5e06881ecedfe6f3dd2f56dcb057b6dbeb3327fd32d4b12854df36bf26"}, + {file = "coverage-7.5.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9735317685ba6ec7e3754798c8871c2f49aa5e687cc794a0b1d284b2389d1bd5"}, + {file = "coverage-7.5.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d21918e9ef11edf36764b93101e2ae8cc82aa5efdc7c5a4e9c6c35a48496d601"}, + {file = "coverage-7.5.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:c3e757949f268364b96ca894b4c342b41dc6f8f8b66c37878aacef5930db61be"}, + {file = "coverage-7.5.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:79afb6197e2f7f60c4824dd4b2d4c2ec5801ceb6ba9ce5d2c3080e5660d51a4f"}, + {file = "coverage-7.5.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d1d0d98d95dd18fe29dc66808e1accf59f037d5716f86a501fc0256455219668"}, + {file = "coverage-7.5.1-cp39-cp39-win32.whl", hash = "sha256:1cc0fe9b0b3a8364093c53b0b4c0c2dd4bb23acbec4c9240b5f284095ccf7981"}, + {file = "coverage-7.5.1-cp39-cp39-win_amd64.whl", hash = "sha256:dde0070c40ea8bb3641e811c1cfbf18e265d024deff6de52c5950677a8fb1e0f"}, + {file = "coverage-7.5.1-pp38.pp39.pp310-none-any.whl", hash = "sha256:6537e7c10cc47c595828b8a8be04c72144725c383c4702703ff4e42e44577312"}, + {file = "coverage-7.5.1.tar.gz", hash = "sha256:54de9ef3a9da981f7af93eafde4ede199e0846cd819eb27c88e2b712aae9708c"}, ] [package.dependencies] @@ -434,43 +463,43 @@ toml = ["tomli"] [[package]] name = "cryptography" -version = "42.0.5" +version = "42.0.7" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." optional = false python-versions = ">=3.7" files = [ - {file = "cryptography-42.0.5-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:a30596bae9403a342c978fb47d9b0ee277699fa53bbafad14706af51fe543d16"}, - {file = "cryptography-42.0.5-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:b7ffe927ee6531c78f81aa17e684e2ff617daeba7f189f911065b2ea2d526dec"}, - {file = "cryptography-42.0.5-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2424ff4c4ac7f6b8177b53c17ed5d8fa74ae5955656867f5a8affaca36a27abb"}, - {file = "cryptography-42.0.5-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:329906dcc7b20ff3cad13c069a78124ed8247adcac44b10bea1130e36caae0b4"}, - {file = "cryptography-42.0.5-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:b03c2ae5d2f0fc05f9a2c0c997e1bc18c8229f392234e8a0194f202169ccd278"}, - {file = "cryptography-42.0.5-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:f8837fe1d6ac4a8052a9a8ddab256bc006242696f03368a4009be7ee3075cdb7"}, - {file = "cryptography-42.0.5-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:0270572b8bd2c833c3981724b8ee9747b3ec96f699a9665470018594301439ee"}, - {file = "cryptography-42.0.5-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:b8cac287fafc4ad485b8a9b67d0ee80c66bf3574f655d3b97ef2e1082360faf1"}, - {file = "cryptography-42.0.5-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:16a48c23a62a2f4a285699dba2e4ff2d1cff3115b9df052cdd976a18856d8e3d"}, - {file = "cryptography-42.0.5-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:2bce03af1ce5a5567ab89bd90d11e7bbdff56b8af3acbbec1faded8f44cb06da"}, - {file = "cryptography-42.0.5-cp37-abi3-win32.whl", hash = "sha256:b6cd2203306b63e41acdf39aa93b86fb566049aeb6dc489b70e34bcd07adca74"}, - {file = "cryptography-42.0.5-cp37-abi3-win_amd64.whl", hash = "sha256:98d8dc6d012b82287f2c3d26ce1d2dd130ec200c8679b6213b3c73c08b2b7940"}, - {file = "cryptography-42.0.5-cp39-abi3-macosx_10_12_universal2.whl", hash = "sha256:5e6275c09d2badf57aea3afa80d975444f4be8d3bc58f7f80d2a484c6f9485c8"}, - {file = "cryptography-42.0.5-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e4985a790f921508f36f81831817cbc03b102d643b5fcb81cd33df3fa291a1a1"}, - {file = "cryptography-42.0.5-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7cde5f38e614f55e28d831754e8a3bacf9ace5d1566235e39d91b35502d6936e"}, - {file = "cryptography-42.0.5-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:7367d7b2eca6513681127ebad53b2582911d1736dc2ffc19f2c3ae49997496bc"}, - {file = "cryptography-42.0.5-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:cd2030f6650c089aeb304cf093f3244d34745ce0cfcc39f20c6fbfe030102e2a"}, - {file = "cryptography-42.0.5-cp39-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:a2913c5375154b6ef2e91c10b5720ea6e21007412f6437504ffea2109b5a33d7"}, - {file = "cryptography-42.0.5-cp39-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:c41fb5e6a5fe9ebcd58ca3abfeb51dffb5d83d6775405305bfa8715b76521922"}, - {file = "cryptography-42.0.5-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:3eaafe47ec0d0ffcc9349e1708be2aaea4c6dd4978d76bf6eb0cb2c13636c6fc"}, - {file = "cryptography-42.0.5-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:1b95b98b0d2af784078fa69f637135e3c317091b615cd0905f8b8a087e86fa30"}, - {file = "cryptography-42.0.5-cp39-abi3-win32.whl", hash = "sha256:1f71c10d1e88467126f0efd484bd44bca5e14c664ec2ede64c32f20875c0d413"}, - {file = "cryptography-42.0.5-cp39-abi3-win_amd64.whl", hash = "sha256:a011a644f6d7d03736214d38832e030d8268bcff4a41f728e6030325fea3e400"}, - {file = "cryptography-42.0.5-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:9481ffe3cf013b71b2428b905c4f7a9a4f76ec03065b05ff499bb5682a8d9ad8"}, - {file = "cryptography-42.0.5-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:ba334e6e4b1d92442b75ddacc615c5476d4ad55cc29b15d590cc6b86efa487e2"}, - {file = "cryptography-42.0.5-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:ba3e4a42397c25b7ff88cdec6e2a16c2be18720f317506ee25210f6d31925f9c"}, - {file = "cryptography-42.0.5-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:111a0d8553afcf8eb02a4fea6ca4f59d48ddb34497aa8706a6cf536f1a5ec576"}, - {file = "cryptography-42.0.5-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:cd65d75953847815962c84a4654a84850b2bb4aed3f26fadcc1c13892e1e29f6"}, - {file = "cryptography-42.0.5-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:e807b3188f9eb0eaa7bbb579b462c5ace579f1cedb28107ce8b48a9f7ad3679e"}, - {file = "cryptography-42.0.5-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:f12764b8fffc7a123f641d7d049d382b73f96a34117e0b637b80643169cec8ac"}, - {file = "cryptography-42.0.5-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:37dd623507659e08be98eec89323469e8c7b4c1407c85112634ae3dbdb926fdd"}, - {file = "cryptography-42.0.5.tar.gz", hash = "sha256:6fe07eec95dfd477eb9530aef5bead34fec819b3aaf6c5bd6d20565da607bfe1"}, + {file = "cryptography-42.0.7-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:a987f840718078212fdf4504d0fd4c6effe34a7e4740378e59d47696e8dfb477"}, + {file = "cryptography-42.0.7-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:bd13b5e9b543532453de08bcdc3cc7cebec6f9883e886fd20a92f26940fd3e7a"}, + {file = "cryptography-42.0.7-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a79165431551042cc9d1d90e6145d5d0d3ab0f2d66326c201d9b0e7f5bf43604"}, + {file = "cryptography-42.0.7-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a47787a5e3649008a1102d3df55424e86606c9bae6fb77ac59afe06d234605f8"}, + {file = "cryptography-42.0.7-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:02c0eee2d7133bdbbc5e24441258d5d2244beb31da5ed19fbb80315f4bbbff55"}, + {file = "cryptography-42.0.7-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:5e44507bf8d14b36b8389b226665d597bc0f18ea035d75b4e53c7b1ea84583cc"}, + {file = "cryptography-42.0.7-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:7f8b25fa616d8b846aef64b15c606bb0828dbc35faf90566eb139aa9cff67af2"}, + {file = "cryptography-42.0.7-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:93a3209f6bb2b33e725ed08ee0991b92976dfdcf4e8b38646540674fc7508e13"}, + {file = "cryptography-42.0.7-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:e6b8f1881dac458c34778d0a424ae5769de30544fc678eac51c1c8bb2183e9da"}, + {file = "cryptography-42.0.7-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:3de9a45d3b2b7d8088c3fbf1ed4395dfeff79d07842217b38df14ef09ce1d8d7"}, + {file = "cryptography-42.0.7-cp37-abi3-win32.whl", hash = "sha256:789caea816c6704f63f6241a519bfa347f72fbd67ba28d04636b7c6b7da94b0b"}, + {file = "cryptography-42.0.7-cp37-abi3-win_amd64.whl", hash = "sha256:8cb8ce7c3347fcf9446f201dc30e2d5a3c898d009126010cbd1f443f28b52678"}, + {file = "cryptography-42.0.7-cp39-abi3-macosx_10_12_universal2.whl", hash = "sha256:a3a5ac8b56fe37f3125e5b72b61dcde43283e5370827f5233893d461b7360cd4"}, + {file = "cryptography-42.0.7-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:779245e13b9a6638df14641d029add5dc17edbef6ec915688f3acb9e720a5858"}, + {file = "cryptography-42.0.7-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0d563795db98b4cd57742a78a288cdbdc9daedac29f2239793071fe114f13785"}, + {file = "cryptography-42.0.7-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:31adb7d06fe4383226c3e963471f6837742889b3c4caa55aac20ad951bc8ffda"}, + {file = "cryptography-42.0.7-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:efd0bf5205240182e0f13bcaea41be4fdf5c22c5129fc7ced4a0282ac86998c9"}, + {file = "cryptography-42.0.7-cp39-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:a9bc127cdc4ecf87a5ea22a2556cab6c7eda2923f84e4f3cc588e8470ce4e42e"}, + {file = "cryptography-42.0.7-cp39-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:3577d029bc3f4827dd5bf8bf7710cac13527b470bbf1820a3f394adb38ed7d5f"}, + {file = "cryptography-42.0.7-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:2e47577f9b18723fa294b0ea9a17d5e53a227867a0a4904a1a076d1646d45ca1"}, + {file = "cryptography-42.0.7-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:1a58839984d9cb34c855197043eaae2c187d930ca6d644612843b4fe8513c886"}, + {file = "cryptography-42.0.7-cp39-abi3-win32.whl", hash = "sha256:e6b79d0adb01aae87e8a44c2b64bc3f3fe59515280e00fb6d57a7267a2583cda"}, + {file = "cryptography-42.0.7-cp39-abi3-win_amd64.whl", hash = "sha256:16268d46086bb8ad5bf0a2b5544d8a9ed87a0e33f5e77dd3c3301e63d941a83b"}, + {file = "cryptography-42.0.7-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:2954fccea107026512b15afb4aa664a5640cd0af630e2ee3962f2602693f0c82"}, + {file = "cryptography-42.0.7-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:362e7197754c231797ec45ee081f3088a27a47c6c01eff2ac83f60f85a50fe60"}, + {file = "cryptography-42.0.7-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:4f698edacf9c9e0371112792558d2f705b5645076cc0aaae02f816a0171770fd"}, + {file = "cryptography-42.0.7-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:5482e789294854c28237bba77c4c83be698be740e31a3ae5e879ee5444166582"}, + {file = "cryptography-42.0.7-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:e9b2a6309f14c0497f348d08a065d52f3020656f675819fc405fb63bbcd26562"}, + {file = "cryptography-42.0.7-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:d8e3098721b84392ee45af2dd554c947c32cc52f862b6a3ae982dbb90f577f14"}, + {file = "cryptography-42.0.7-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:c65f96dad14f8528a447414125e1fc8feb2ad5a272b8f68477abbcc1ea7d94b9"}, + {file = "cryptography-42.0.7-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:36017400817987670037fbb0324d71489b6ead6231c9604f8fc1f7d008087c68"}, + {file = "cryptography-42.0.7.tar.gz", hash = "sha256:ecbfbc00bf55888edda9868a4cf927205de8499e7fabe6c050322298382953f2"}, ] [package.dependencies] @@ -534,36 +563,23 @@ files = [ {file = "distro-1.9.0.tar.gz", hash = "sha256:2fa77c6fd8940f116ee1d6b94a2f90b13b5ea8d019b98bc8bafdcabcdd9bdbed"}, ] -[[package]] -name = "docopt" -version = "0.6.2" -description = "Pythonic argument parser, that will make you smile" -optional = false -python-versions = "*" -files = [ - {file = "docopt-0.6.2.tar.gz", hash = "sha256:49b3a825280bd66b3aa83585ef59c4a8c82f2c8a522dbe754a8bc8d08c85c491"}, -] - [[package]] name = "docutils" version = "0.20.1" description = "Docutils -- Python Documentation Utilities" optional = false -python-versions = ">=3.7" -files = [ - {file = "docutils-0.20.1-py3-none-any.whl", hash = "sha256:96f387a2c5562db4476f09f13bbab2192e764cac08ebbf3a34a95d9b1e4a59d6"}, - {file = "docutils-0.20.1.tar.gz", hash = "sha256:f08a4e276c3a1583a86dce3e34aba3fe04d02bba2dd51ed16106244e8a923e3b"}, -] +python-versions = "*" +files = [] [[package]] name = "exceptiongroup" -version = "1.2.0" +version = "1.2.1" description = "Backport of PEP 654 (exception groups)" optional = false python-versions = ">=3.7" files = [ - {file = "exceptiongroup-1.2.0-py3-none-any.whl", hash = "sha256:4bfd3996ac73b41e9b9628b04e079f193850720ea5945fc96a08633c66912f14"}, - {file = "exceptiongroup-1.2.0.tar.gz", hash = "sha256:91f5c769735f051a4290d52edd0858999b57e5876e9f85937691bd4c9fa3ed68"}, + {file = "exceptiongroup-1.2.1-py3-none-any.whl", hash = "sha256:5258b9ed329c5bbdd31a309f53cbfb0b155341807f6ff7606a1e801a891b29ad"}, + {file = "exceptiongroup-1.2.1.tar.gz", hash = "sha256:a4785e48b045528f5bfe627b6ad554ff32def154f42372786903b7abcfe1aa16"}, ] [package.extras] @@ -571,32 +587,32 @@ test = ["pytest (>=6)"] [[package]] name = "fastapi" -version = "0.110.0" +version = "0.110.3" description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production" optional = false python-versions = ">=3.8" files = [ - {file = "fastapi-0.110.0-py3-none-any.whl", hash = "sha256:87a1f6fb632a218222c5984be540055346a8f5d8a68e8f6fb647b1dc9934de4b"}, - {file = "fastapi-0.110.0.tar.gz", hash = "sha256:266775f0dcc95af9d3ef39bad55cff525329a931d5fd51930aadd4f428bf7ff3"}, + {file = "fastapi-0.110.3-py3-none-any.whl", hash = "sha256:fd7600612f755e4050beb74001310b5a7e1796d149c2ee363124abdfa0289d32"}, + {file = "fastapi-0.110.3.tar.gz", hash = "sha256:555700b0159379e94fdbfc6bb66a0f1c43f4cf7060f25239af3d84b63a656626"}, ] [package.dependencies] pydantic = ">=1.7.4,<1.8 || >1.8,<1.8.1 || >1.8.1,<2.0.0 || >2.0.0,<2.0.1 || >2.0.1,<2.1.0 || >2.1.0,<3.0.0" -starlette = ">=0.36.3,<0.37.0" +starlette = ">=0.37.2,<0.38.0" typing-extensions = ">=4.8.0" [package.extras] -all = ["email-validator (>=2.0.0)", "httpx (>=0.23.0)", "itsdangerous (>=1.1.0)", "jinja2 (>=2.11.2)", "orjson (>=3.2.1)", "pydantic-extra-types (>=2.0.0)", "pydantic-settings (>=2.0.0)", "python-multipart (>=0.0.7)", "pyyaml (>=5.3.1)", "ujson (>=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0)", "uvicorn[standard] (>=0.12.0)"] +all = ["email_validator (>=2.0.0)", "httpx (>=0.23.0)", "itsdangerous (>=1.1.0)", "jinja2 (>=2.11.2)", "orjson (>=3.2.1)", "pydantic-extra-types (>=2.0.0)", "pydantic-settings (>=2.0.0)", "python-multipart (>=0.0.7)", "pyyaml (>=5.3.1)", "ujson (>=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0)", "uvicorn[standard] (>=0.12.0)"] [[package]] name = "filelock" -version = "3.13.3" +version = "3.14.0" description = "A platform independent file lock." optional = false python-versions = ">=3.8" files = [ - {file = "filelock-3.13.3-py3-none-any.whl", hash = "sha256:5ffa845303983e7a0b7ae17636509bc97997d58afeafa72fb141a17b152284cb"}, - {file = "filelock-3.13.3.tar.gz", hash = "sha256:a79895a25bbefdf55d1a2a0a80968f7dbb28edcd6d4234a0afb3f37ecde4b546"}, + {file = "filelock-3.14.0-py3-none-any.whl", hash = "sha256:43339835842f110ca7ae60f1e1c160714c5a6afd15a2873419ab185334975c0f"}, + {file = "filelock-3.14.0.tar.gz", hash = "sha256:6ea72da3be9b8c82afd3edcf99f2fffbb5076335a5ae4d03248bb5b6c3eae78a"}, ] [package.extras] @@ -677,22 +693,23 @@ test = ["objgraph", "psutil"] [[package]] name = "gunicorn" -version = "21.2.0" +version = "22.0.0" description = "WSGI HTTP Server for UNIX" optional = false -python-versions = ">=3.5" +python-versions = ">=3.7" files = [ - {file = "gunicorn-21.2.0-py3-none-any.whl", hash = "sha256:3213aa5e8c24949e792bcacfc176fef362e7aac80b76c56f6b5122bf350722f0"}, - {file = "gunicorn-21.2.0.tar.gz", hash = "sha256:88ec8bff1d634f98e61b9f65bc4bf3cd918a90806c6f5c48bc5603849ec81033"}, + {file = "gunicorn-22.0.0-py3-none-any.whl", hash = "sha256:350679f91b24062c86e386e198a15438d53a7a8207235a78ba1b53df4c4378d9"}, + {file = "gunicorn-22.0.0.tar.gz", hash = "sha256:4a0b436239ff76fb33f11c07a16482c521a7e09c1ce3cc293c2330afe01bec63"}, ] [package.dependencies] packaging = "*" [package.extras] -eventlet = ["eventlet (>=0.24.1)"] +eventlet = ["eventlet (>=0.24.1,!=0.36.0)"] gevent = ["gevent (>=1.4.0)"] setproctitle = ["setproctitle"] +testing = ["coverage", "eventlet", "gevent", "pytest", "pytest-cov"] tornado = ["tornado (>=0.2)"] [[package]] @@ -753,13 +770,13 @@ socks = ["socksio (==1.*)"] [[package]] name = "identify" -version = "2.5.35" +version = "2.5.36" description = "File identification library for Python" optional = false python-versions = ">=3.8" files = [ - {file = "identify-2.5.35-py2.py3-none-any.whl", hash = "sha256:c4de0081837b211594f8e877a6b4fad7ca32bbfc1a9307fdd61c28bfe923f13e"}, - {file = "identify-2.5.35.tar.gz", hash = "sha256:10a7ca245cfcd756a554a7288159f72ff105ad233c7c4b9c6f0f4d108f5f6791"}, + {file = "identify-2.5.36-py2.py3-none-any.whl", hash = "sha256:37d93f380f4de590500d9dba7db359d0d3da95ffe7f9de1753faa159e71e7dfa"}, + {file = "identify-2.5.36.tar.gz", hash = "sha256:e5e00f54165f9047fbebeb4a560f9acfb8af4c88232be60a488e9b68d122745d"}, ] [package.extras] @@ -767,13 +784,13 @@ license = ["ukkonen"] [[package]] name = "idna" -version = "3.6" +version = "3.7" description = "Internationalized Domain Names in Applications (IDNA)" optional = false python-versions = ">=3.5" files = [ - {file = "idna-3.6-py3-none-any.whl", hash = "sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f"}, - {file = "idna-3.6.tar.gz", hash = "sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca"}, + {file = "idna-3.7-py3-none-any.whl", hash = "sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0"}, + {file = "idna-3.7.tar.gz", hash = "sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc"}, ] [[package]] @@ -844,28 +861,31 @@ testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-ena [[package]] name = "jaraco-context" -version = "4.3.0" -description = "Context managers by jaraco" +version = "5.3.0" +description = "Useful decorators and context managers" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "jaraco.context-4.3.0-py3-none-any.whl", hash = "sha256:5d9e95ca0faa78943ed66f6bc658dd637430f16125d86988e77844c741ff2f11"}, - {file = "jaraco.context-4.3.0.tar.gz", hash = "sha256:4dad2404540b936a20acedec53355bdaea223acb88fd329fa6de9261c941566e"}, + {file = "jaraco.context-5.3.0-py3-none-any.whl", hash = "sha256:3e16388f7da43d384a1a7cd3452e72e14732ac9fe459678773a3608a812bf266"}, + {file = "jaraco.context-5.3.0.tar.gz", hash = "sha256:c2f67165ce1f9be20f32f650f25d8edfc1646a8aeee48ae06fb35f90763576d2"}, ] +[package.dependencies] +"backports.tarfile" = {version = "*", markers = "python_version < \"3.12\""} + [package.extras] -docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] -testing = ["flake8 (<5)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"] +docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +testing = ["portend", "pytest (>=6,!=8.1.1)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy", "pytest-ruff (>=0.2.1)"] [[package]] name = "jaraco-functools" -version = "4.0.0" +version = "4.0.1" description = "Functools like those found in stdlib" optional = false python-versions = ">=3.8" files = [ - {file = "jaraco.functools-4.0.0-py3-none-any.whl", hash = "sha256:daf276ddf234bea897ef14f43c4e1bf9eefeac7b7a82a4dd69228ac20acff68d"}, - {file = "jaraco.functools-4.0.0.tar.gz", hash = "sha256:c279cb24c93d694ef7270f970d499cab4d3813f4e08273f95398651a634f0925"}, + {file = "jaraco.functools-4.0.1-py3-none-any.whl", hash = "sha256:3b24ccb921d6b593bdceb56ce14799204f473976e2a9d4b15b04d0f2c2326664"}, + {file = "jaraco_functools-4.0.1.tar.gz", hash = "sha256:d33fa765374c0611b52f8b3a795f8900869aa88c84769d4d1746cd68fb28c3e8"}, ] [package.dependencies] @@ -873,7 +893,7 @@ more-itertools = "*" [package.extras] docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-lint"] -testing = ["jaraco.classes", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-ruff"] +testing = ["jaraco.classes", "pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy", "pytest-ruff (>=0.2.1)"] [[package]] name = "jeepney" @@ -892,13 +912,13 @@ trio = ["async_generator", "trio"] [[package]] name = "jinja2" -version = "3.1.3" +version = "3.1.4" description = "A very fast and expressive template engine." optional = false python-versions = ">=3.7" files = [ - {file = "Jinja2-3.1.3-py3-none-any.whl", hash = "sha256:7d6d50dd97d52cbc355597bd845fabfbac3f551e1f99619e39a35ce8c370b5fa"}, - {file = "Jinja2-3.1.3.tar.gz", hash = "sha256:ac8bd6544d4bb2c9792bf3a159e80bba8fda7f07e81bc3aed565432d5925ba90"}, + {file = "jinja2-3.1.4-py3-none-any.whl", hash = "sha256:bc5dd2abb727a5319567b7a813e6a2e7318c39f4f487cfe6c89c6f9c7d25197d"}, + {file = "jinja2-3.1.4.tar.gz", hash = "sha256:4a3aee7acbbe7303aede8e9648d13b8bf88a429282aa6122a993f0ac800cb369"}, ] [package.dependencies] @@ -909,13 +929,13 @@ i18n = ["Babel (>=2.7)"] [[package]] name = "keyring" -version = "25.0.0" +version = "25.2.0" description = "Store and access your passwords safely." optional = false python-versions = ">=3.8" files = [ - {file = "keyring-25.0.0-py3-none-any.whl", hash = "sha256:9a15cd280338920388e8c1787cb8792b9755dabb3e7c61af5ac1f8cd437cefde"}, - {file = "keyring-25.0.0.tar.gz", hash = "sha256:fc024ed53c7ea090e30723e6bd82f58a39dc25d9a6797d866203ecd0ee6306cb"}, + {file = "keyring-25.2.0-py3-none-any.whl", hash = "sha256:19f17d40335444aab84b19a0d16a77ec0758a9c384e3446ae2ed8bd6d53b67a5"}, + {file = "keyring-25.2.0.tar.gz", hash = "sha256:7045f367268ce42dba44745050164b431e46f6e92f99ef2937dfadaef368d8cf"}, ] [package.dependencies] @@ -931,17 +951,17 @@ SecretStorage = {version = ">=3.2", markers = "sys_platform == \"linux\""} [package.extras] completion = ["shtab (>=1.1.0)"] docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] -testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy", "pytest-ruff (>=0.2.1)"] +testing = ["pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy", "pytest-ruff (>=0.2.1)"] [[package]] name = "mako" -version = "1.3.2" +version = "1.3.3" description = "A super-fast templating language that borrows the best ideas from the existing templating languages." optional = false python-versions = ">=3.8" files = [ - {file = "Mako-1.3.2-py3-none-any.whl", hash = "sha256:32a99d70754dfce237019d17ffe4a282d2d3351b9c476e90d8a60e63f133b80c"}, - {file = "Mako-1.3.2.tar.gz", hash = "sha256:2a0c8ad7f6274271b3bb7467dd37cf9cc6dab4bc19cb69a4ef10669402de698e"}, + {file = "Mako-1.3.3-py3-none-any.whl", hash = "sha256:5324b88089a8978bf76d1629774fcc2f1c07b82acdf00f4c5dd8ceadfffc4b40"}, + {file = "Mako-1.3.3.tar.gz", hash = "sha256:e16c01d9ab9c11f7290eef1cfefc093fb5a45ee4a3da09e2fec2e4d1bae54e73"}, ] [package.dependencies] @@ -1270,47 +1290,47 @@ test = ["hypothesis (>=5.5.3)", "pytest (>=6.0)", "pytest-xdist (>=1.31)"] [[package]] name = "pandas" -version = "2.2.1" +version = "2.2.2" description = "Powerful data structures for data analysis, time series, and statistics" optional = false python-versions = ">=3.9" files = [ - {file = "pandas-2.2.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:8df8612be9cd1c7797c93e1c5df861b2ddda0b48b08f2c3eaa0702cf88fb5f88"}, - {file = "pandas-2.2.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0f573ab277252ed9aaf38240f3b54cfc90fff8e5cab70411ee1d03f5d51f3944"}, - {file = "pandas-2.2.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f02a3a6c83df4026e55b63c1f06476c9aa3ed6af3d89b4f04ea656ccdaaaa359"}, - {file = "pandas-2.2.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c38ce92cb22a4bea4e3929429aa1067a454dcc9c335799af93ba9be21b6beb51"}, - {file = "pandas-2.2.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:c2ce852e1cf2509a69e98358e8458775f89599566ac3775e70419b98615f4b06"}, - {file = "pandas-2.2.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:53680dc9b2519cbf609c62db3ed7c0b499077c7fefda564e330286e619ff0dd9"}, - {file = "pandas-2.2.1-cp310-cp310-win_amd64.whl", hash = "sha256:94e714a1cca63e4f5939cdce5f29ba8d415d85166be3441165edd427dc9f6bc0"}, - {file = "pandas-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f821213d48f4ab353d20ebc24e4faf94ba40d76680642fb7ce2ea31a3ad94f9b"}, - {file = "pandas-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c70e00c2d894cb230e5c15e4b1e1e6b2b478e09cf27cc593a11ef955b9ecc81a"}, - {file = "pandas-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e97fbb5387c69209f134893abc788a6486dbf2f9e511070ca05eed4b930b1b02"}, - {file = "pandas-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:101d0eb9c5361aa0146f500773395a03839a5e6ecde4d4b6ced88b7e5a1a6403"}, - {file = "pandas-2.2.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:7d2ed41c319c9fb4fd454fe25372028dfa417aacb9790f68171b2e3f06eae8cd"}, - {file = "pandas-2.2.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:af5d3c00557d657c8773ef9ee702c61dd13b9d7426794c9dfeb1dc4a0bf0ebc7"}, - {file = "pandas-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:06cf591dbaefb6da9de8472535b185cba556d0ce2e6ed28e21d919704fef1a9e"}, - {file = "pandas-2.2.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:88ecb5c01bb9ca927ebc4098136038519aa5d66b44671861ffab754cae75102c"}, - {file = "pandas-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:04f6ec3baec203c13e3f8b139fb0f9f86cd8c0b94603ae3ae8ce9a422e9f5bee"}, - {file = "pandas-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a935a90a76c44fe170d01e90a3594beef9e9a6220021acfb26053d01426f7dc2"}, - {file = "pandas-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c391f594aae2fd9f679d419e9a4d5ba4bce5bb13f6a989195656e7dc4b95c8f0"}, - {file = "pandas-2.2.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:9d1265545f579edf3f8f0cb6f89f234f5e44ba725a34d86535b1a1d38decbccc"}, - {file = "pandas-2.2.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:11940e9e3056576ac3244baef2fedade891977bcc1cb7e5cc8f8cc7d603edc89"}, - {file = "pandas-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:4acf681325ee1c7f950d058b05a820441075b0dd9a2adf5c4835b9bc056bf4fb"}, - {file = "pandas-2.2.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9bd8a40f47080825af4317d0340c656744f2bfdb6819f818e6ba3cd24c0e1397"}, - {file = "pandas-2.2.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:df0c37ebd19e11d089ceba66eba59a168242fc6b7155cba4ffffa6eccdfb8f16"}, - {file = "pandas-2.2.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:739cc70eaf17d57608639e74d63387b0d8594ce02f69e7a0b046f117974b3019"}, - {file = "pandas-2.2.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f9d3558d263073ed95e46f4650becff0c5e1ffe0fc3a015de3c79283dfbdb3df"}, - {file = "pandas-2.2.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:4aa1d8707812a658debf03824016bf5ea0d516afdea29b7dc14cf687bc4d4ec6"}, - {file = "pandas-2.2.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:76f27a809cda87e07f192f001d11adc2b930e93a2b0c4a236fde5429527423be"}, - {file = "pandas-2.2.1-cp39-cp39-win_amd64.whl", hash = "sha256:1ba21b1d5c0e43416218db63037dbe1a01fc101dc6e6024bcad08123e48004ab"}, - {file = "pandas-2.2.1.tar.gz", hash = "sha256:0ab90f87093c13f3e8fa45b48ba9f39181046e8f3317d3aadb2fffbb1b978572"}, + {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"}, ] [package.dependencies] numpy = [ - {version = ">=1.26.0,<2", markers = "python_version >= \"3.12\""}, - {version = ">=1.23.2,<2", markers = "python_version == \"3.11\""}, - {version = ">=1.22.4,<2", markers = "python_version < \"3.11\""}, + {version = ">=1.26.0", markers = "python_version >= \"3.12\""}, + {version = ">=1.23.2", markers = "python_version == \"3.11\""}, + {version = ">=1.22.4", markers = "python_version < \"3.11\""}, ] python-dateutil = ">=2.8.2" pytz = ">=2020.1" @@ -1467,21 +1487,6 @@ pip = ">=23.1.2" graphviz = ["graphviz (>=0.20.1)"] test = ["covdefaults (>=2.3)", "diff-cover (>=8.0.1)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)", "virtualenv (>=20.25,<21)"] -[[package]] -name = "pipreqs" -version = "0.4.13" -description = "Pip requirements.txt generator based on imports in project" -optional = false -python-versions = ">=3.7" -files = [ - {file = "pipreqs-0.4.13-py2.py3-none-any.whl", hash = "sha256:e522b9ed54aa3e8b7978ff251ab7a9af2f75d2cd8de4c102e881b666a79a308e"}, - {file = "pipreqs-0.4.13.tar.gz", hash = "sha256:a17f167880b6921be37533ce4c81ddc6e22b465c107aad557db43b1add56a99b"}, -] - -[package.dependencies] -docopt = "*" -yarg = "*" - [[package]] name = "pkginfo" version = "1.10.0" @@ -1498,28 +1503,29 @@ testing = ["pytest", "pytest-cov", "wheel"] [[package]] name = "platformdirs" -version = "3.11.0" -description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +version = "4.2.1" +description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "platformdirs-3.11.0-py3-none-any.whl", hash = "sha256:e9d171d00af68be50e9202731309c4e658fd8bc76f55c11c7dd760d023bda68e"}, - {file = "platformdirs-3.11.0.tar.gz", hash = "sha256:cf8ee52a3afdb965072dcc652433e0c7e3e40cf5ea1477cd4b3b1d2eb75495b3"}, + {file = "platformdirs-4.2.1-py3-none-any.whl", hash = "sha256:17d5a1161b3fd67b390023cb2d3b026bbd40abde6fdb052dfbd3a29c3ba22ee1"}, + {file = "platformdirs-4.2.1.tar.gz", hash = "sha256:031cd18d4ec63ec53e82dceaac0417d218a6863f7745dfcc9efe7793b7039bdf"}, ] [package.extras] -docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.1)", "sphinx-autodoc-typehints (>=1.24)"] -test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4)", "pytest-cov (>=4.1)", "pytest-mock (>=3.11.1)"] +docs = ["furo (>=2023.9.10)", "proselint (>=0.13)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.25.2)"] +test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)"] +type = ["mypy (>=1.8)"] [[package]] name = "plotly" -version = "5.20.0" +version = "5.22.0" description = "An open-source, interactive data visualization library for Python" optional = false python-versions = ">=3.8" files = [ - {file = "plotly-5.20.0-py3-none-any.whl", hash = "sha256:837a9c8aa90f2c0a2f0d747b82544d014dc2a2bdde967b5bb1da25b53932d1a9"}, - {file = "plotly-5.20.0.tar.gz", hash = "sha256:bf901c805d22032cfa534b2ff7c5aa6b0659e037f19ec1e0cca7f585918b5c89"}, + {file = "plotly-5.22.0-py3-none-any.whl", hash = "sha256:68fc1901f098daeb233cc3dd44ec9dc31fb3ca4f4e53189344199c43496ed006"}, + {file = "plotly-5.22.0.tar.gz", hash = "sha256:859fdadbd86b5770ae2466e542b761b247d1c6b49daed765b95bb8c7063e7469"}, ] [package.dependencies] @@ -1528,13 +1534,13 @@ tenacity = ">=6.2.0" [[package]] name = "pluggy" -version = "1.4.0" +version = "1.5.0" description = "plugin and hook calling mechanisms for python" optional = false python-versions = ">=3.8" files = [ - {file = "pluggy-1.4.0-py3-none-any.whl", hash = "sha256:7db9f7b503d67d1c5b95f59773ebb58a8c1c288129a88665838012cfb07b8981"}, - {file = "pluggy-1.4.0.tar.gz", hash = "sha256:8c85c2876142a764e5b7548e7d9a0e0ddb46f5185161049a79b7e974454223be"}, + {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, + {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, ] [package.extras] @@ -1611,85 +1617,139 @@ files = [ [[package]] name = "pydantic" -version = "1.10.14" -description = "Data validation and settings management using python type hints" +version = "2.7.1" +description = "Data validation using Python type hints" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "pydantic-1.10.14-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7f4fcec873f90537c382840f330b90f4715eebc2bc9925f04cb92de593eae054"}, - {file = "pydantic-1.10.14-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8e3a76f571970fcd3c43ad982daf936ae39b3e90b8a2e96c04113a369869dc87"}, - {file = "pydantic-1.10.14-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:82d886bd3c3fbeaa963692ef6b643159ccb4b4cefaf7ff1617720cbead04fd1d"}, - {file = "pydantic-1.10.14-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:798a3d05ee3b71967844a1164fd5bdb8c22c6d674f26274e78b9f29d81770c4e"}, - {file = "pydantic-1.10.14-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:23d47a4b57a38e8652bcab15a658fdb13c785b9ce217cc3a729504ab4e1d6bc9"}, - {file = "pydantic-1.10.14-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f9f674b5c3bebc2eba401de64f29948ae1e646ba2735f884d1594c5f675d6f2a"}, - {file = "pydantic-1.10.14-cp310-cp310-win_amd64.whl", hash = "sha256:24a7679fab2e0eeedb5a8924fc4a694b3bcaac7d305aeeac72dd7d4e05ecbebf"}, - {file = "pydantic-1.10.14-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9d578ac4bf7fdf10ce14caba6f734c178379bd35c486c6deb6f49006e1ba78a7"}, - {file = "pydantic-1.10.14-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:fa7790e94c60f809c95602a26d906eba01a0abee9cc24150e4ce2189352deb1b"}, - {file = "pydantic-1.10.14-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aad4e10efa5474ed1a611b6d7f0d130f4aafadceb73c11d9e72823e8f508e663"}, - {file = "pydantic-1.10.14-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1245f4f61f467cb3dfeced2b119afef3db386aec3d24a22a1de08c65038b255f"}, - {file = "pydantic-1.10.14-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:21efacc678a11114c765eb52ec0db62edffa89e9a562a94cbf8fa10b5db5c046"}, - {file = "pydantic-1.10.14-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:412ab4a3f6dbd2bf18aefa9f79c7cca23744846b31f1d6555c2ee2b05a2e14ca"}, - {file = "pydantic-1.10.14-cp311-cp311-win_amd64.whl", hash = "sha256:e897c9f35281f7889873a3e6d6b69aa1447ceb024e8495a5f0d02ecd17742a7f"}, - {file = "pydantic-1.10.14-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:d604be0f0b44d473e54fdcb12302495fe0467c56509a2f80483476f3ba92b33c"}, - {file = "pydantic-1.10.14-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a42c7d17706911199798d4c464b352e640cab4351efe69c2267823d619a937e5"}, - {file = "pydantic-1.10.14-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:596f12a1085e38dbda5cbb874d0973303e34227b400b6414782bf205cc14940c"}, - {file = "pydantic-1.10.14-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:bfb113860e9288d0886e3b9e49d9cf4a9d48b441f52ded7d96db7819028514cc"}, - {file = "pydantic-1.10.14-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:bc3ed06ab13660b565eed80887fcfbc0070f0aa0691fbb351657041d3e874efe"}, - {file = "pydantic-1.10.14-cp37-cp37m-win_amd64.whl", hash = "sha256:ad8c2bc677ae5f6dbd3cf92f2c7dc613507eafe8f71719727cbc0a7dec9a8c01"}, - {file = "pydantic-1.10.14-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:c37c28449752bb1f47975d22ef2882d70513c546f8f37201e0fec3a97b816eee"}, - {file = "pydantic-1.10.14-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:49a46a0994dd551ec051986806122767cf144b9702e31d47f6d493c336462597"}, - {file = "pydantic-1.10.14-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:53e3819bd20a42470d6dd0fe7fc1c121c92247bca104ce608e609b59bc7a77ee"}, - {file = "pydantic-1.10.14-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0fbb503bbbbab0c588ed3cd21975a1d0d4163b87e360fec17a792f7d8c4ff29f"}, - {file = "pydantic-1.10.14-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:336709883c15c050b9c55a63d6c7ff09be883dbc17805d2b063395dd9d9d0022"}, - {file = "pydantic-1.10.14-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:4ae57b4d8e3312d486e2498d42aed3ece7b51848336964e43abbf9671584e67f"}, - {file = "pydantic-1.10.14-cp38-cp38-win_amd64.whl", hash = "sha256:dba49d52500c35cfec0b28aa8b3ea5c37c9df183ffc7210b10ff2a415c125c4a"}, - {file = "pydantic-1.10.14-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c66609e138c31cba607d8e2a7b6a5dc38979a06c900815495b2d90ce6ded35b4"}, - {file = "pydantic-1.10.14-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d986e115e0b39604b9eee3507987368ff8148222da213cd38c359f6f57b3b347"}, - {file = "pydantic-1.10.14-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:646b2b12df4295b4c3148850c85bff29ef6d0d9621a8d091e98094871a62e5c7"}, - {file = "pydantic-1.10.14-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:282613a5969c47c83a8710cc8bfd1e70c9223feb76566f74683af889faadc0ea"}, - {file = "pydantic-1.10.14-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:466669501d08ad8eb3c4fecd991c5e793c4e0bbd62299d05111d4f827cded64f"}, - {file = "pydantic-1.10.14-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:13e86a19dca96373dcf3190fcb8797d40a6f12f154a244a8d1e8e03b8f280593"}, - {file = "pydantic-1.10.14-cp39-cp39-win_amd64.whl", hash = "sha256:08b6ec0917c30861e3fe71a93be1648a2aa4f62f866142ba21670b24444d7fd8"}, - {file = "pydantic-1.10.14-py3-none-any.whl", hash = "sha256:8ee853cd12ac2ddbf0ecbac1c289f95882b2d4482258048079d13be700aa114c"}, - {file = "pydantic-1.10.14.tar.gz", hash = "sha256:46f17b832fe27de7850896f3afee50ea682220dd218f7e9c88d436788419dca6"}, + {file = "pydantic-2.7.1-py3-none-any.whl", hash = "sha256:e029badca45266732a9a79898a15ae2e8b14840b1eabbb25844be28f0b33f3d5"}, + {file = "pydantic-2.7.1.tar.gz", hash = "sha256:e9dbb5eada8abe4d9ae5f46b9939aead650cd2b68f249bb3a8139dbe125803cc"}, ] [package.dependencies] -typing-extensions = ">=4.2.0" +annotated-types = ">=0.4.0" +pydantic-core = "2.18.2" +typing-extensions = ">=4.6.1" [package.extras] -dotenv = ["python-dotenv (>=0.10.4)"] -email = ["email-validator (>=1.0.3)"] +email = ["email-validator (>=2.0.0)"] + +[[package]] +name = "pydantic-core" +version = "2.18.2" +description = "Core functionality for Pydantic validation and serialization" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pydantic_core-2.18.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:9e08e867b306f525802df7cd16c44ff5ebbe747ff0ca6cf3fde7f36c05a59a81"}, + {file = "pydantic_core-2.18.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f0a21cbaa69900cbe1a2e7cad2aa74ac3cf21b10c3efb0fa0b80305274c0e8a2"}, + {file = "pydantic_core-2.18.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0680b1f1f11fda801397de52c36ce38ef1c1dc841a0927a94f226dea29c3ae3d"}, + {file = "pydantic_core-2.18.2-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:95b9d5e72481d3780ba3442eac863eae92ae43a5f3adb5b4d0a1de89d42bb250"}, + {file = "pydantic_core-2.18.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c4fcf5cd9c4b655ad666ca332b9a081112cd7a58a8b5a6ca7a3104bc950f2038"}, + {file = "pydantic_core-2.18.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b5155ff768083cb1d62f3e143b49a8a3432e6789a3abee8acd005c3c7af1c74"}, + {file = "pydantic_core-2.18.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:553ef617b6836fc7e4df130bb851e32fe357ce36336d897fd6646d6058d980af"}, + {file = "pydantic_core-2.18.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b89ed9eb7d616ef5714e5590e6cf7f23b02d0d539767d33561e3675d6f9e3857"}, + {file = "pydantic_core-2.18.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:75f7e9488238e920ab6204399ded280dc4c307d034f3924cd7f90a38b1829563"}, + {file = "pydantic_core-2.18.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:ef26c9e94a8c04a1b2924149a9cb081836913818e55681722d7f29af88fe7b38"}, + {file = "pydantic_core-2.18.2-cp310-none-win32.whl", hash = "sha256:182245ff6b0039e82b6bb585ed55a64d7c81c560715d1bad0cbad6dfa07b4027"}, + {file = "pydantic_core-2.18.2-cp310-none-win_amd64.whl", hash = "sha256:e23ec367a948b6d812301afc1b13f8094ab7b2c280af66ef450efc357d2ae543"}, + {file = "pydantic_core-2.18.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:219da3f096d50a157f33645a1cf31c0ad1fe829a92181dd1311022f986e5fbe3"}, + {file = "pydantic_core-2.18.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:cc1cfd88a64e012b74e94cd00bbe0f9c6df57049c97f02bb07d39e9c852e19a4"}, + {file = "pydantic_core-2.18.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:05b7133a6e6aeb8df37d6f413f7705a37ab4031597f64ab56384c94d98fa0e90"}, + {file = "pydantic_core-2.18.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:224c421235f6102e8737032483f43c1a8cfb1d2f45740c44166219599358c2cd"}, + {file = "pydantic_core-2.18.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b14d82cdb934e99dda6d9d60dc84a24379820176cc4a0d123f88df319ae9c150"}, + {file = "pydantic_core-2.18.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2728b01246a3bba6de144f9e3115b532ee44bd6cf39795194fb75491824a1413"}, + {file = "pydantic_core-2.18.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:470b94480bb5ee929f5acba6995251ada5e059a5ef3e0dfc63cca287283ebfa6"}, + {file = "pydantic_core-2.18.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:997abc4df705d1295a42f95b4eec4950a37ad8ae46d913caeee117b6b198811c"}, + {file = "pydantic_core-2.18.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:75250dbc5290e3f1a0f4618db35e51a165186f9034eff158f3d490b3fed9f8a0"}, + {file = "pydantic_core-2.18.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:4456f2dca97c425231d7315737d45239b2b51a50dc2b6f0c2bb181fce6207664"}, + {file = "pydantic_core-2.18.2-cp311-none-win32.whl", hash = "sha256:269322dcc3d8bdb69f054681edff86276b2ff972447863cf34c8b860f5188e2e"}, + {file = "pydantic_core-2.18.2-cp311-none-win_amd64.whl", hash = "sha256:800d60565aec896f25bc3cfa56d2277d52d5182af08162f7954f938c06dc4ee3"}, + {file = "pydantic_core-2.18.2-cp311-none-win_arm64.whl", hash = "sha256:1404c69d6a676245199767ba4f633cce5f4ad4181f9d0ccb0577e1f66cf4c46d"}, + {file = "pydantic_core-2.18.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:fb2bd7be70c0fe4dfd32c951bc813d9fe6ebcbfdd15a07527796c8204bd36242"}, + {file = "pydantic_core-2.18.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6132dd3bd52838acddca05a72aafb6eab6536aa145e923bb50f45e78b7251043"}, + {file = "pydantic_core-2.18.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7d904828195733c183d20a54230c0df0eb46ec746ea1a666730787353e87182"}, + {file = "pydantic_core-2.18.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c9bd70772c720142be1020eac55f8143a34ec9f82d75a8e7a07852023e46617f"}, + {file = "pydantic_core-2.18.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2b8ed04b3582771764538f7ee7001b02e1170223cf9b75dff0bc698fadb00cf3"}, + {file = "pydantic_core-2.18.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e6dac87ddb34aaec85f873d737e9d06a3555a1cc1a8e0c44b7f8d5daeb89d86f"}, + {file = "pydantic_core-2.18.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7ca4ae5a27ad7a4ee5170aebce1574b375de390bc01284f87b18d43a3984df72"}, + {file = "pydantic_core-2.18.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:886eec03591b7cf058467a70a87733b35f44707bd86cf64a615584fd72488b7c"}, + {file = "pydantic_core-2.18.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ca7b0c1f1c983e064caa85f3792dd2fe3526b3505378874afa84baf662e12241"}, + {file = "pydantic_core-2.18.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4b4356d3538c3649337df4074e81b85f0616b79731fe22dd11b99499b2ebbdf3"}, + {file = "pydantic_core-2.18.2-cp312-none-win32.whl", hash = "sha256:8b172601454f2d7701121bbec3425dd71efcb787a027edf49724c9cefc14c038"}, + {file = "pydantic_core-2.18.2-cp312-none-win_amd64.whl", hash = "sha256:b1bd7e47b1558ea872bd16c8502c414f9e90dcf12f1395129d7bb42a09a95438"}, + {file = "pydantic_core-2.18.2-cp312-none-win_arm64.whl", hash = "sha256:98758d627ff397e752bc339272c14c98199c613f922d4a384ddc07526c86a2ec"}, + {file = "pydantic_core-2.18.2-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:9fdad8e35f278b2c3eb77cbdc5c0a49dada440657bf738d6905ce106dc1de439"}, + {file = "pydantic_core-2.18.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:1d90c3265ae107f91a4f279f4d6f6f1d4907ac76c6868b27dc7fb33688cfb347"}, + {file = "pydantic_core-2.18.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:390193c770399861d8df9670fb0d1874f330c79caaca4642332df7c682bf6b91"}, + {file = "pydantic_core-2.18.2-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:82d5d4d78e4448683cb467897fe24e2b74bb7b973a541ea1dcfec1d3cbce39fb"}, + {file = "pydantic_core-2.18.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4774f3184d2ef3e14e8693194f661dea5a4d6ca4e3dc8e39786d33a94865cefd"}, + {file = "pydantic_core-2.18.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d4d938ec0adf5167cb335acb25a4ee69a8107e4984f8fbd2e897021d9e4ca21b"}, + {file = "pydantic_core-2.18.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e0e8b1be28239fc64a88a8189d1df7fad8be8c1ae47fcc33e43d4be15f99cc70"}, + {file = "pydantic_core-2.18.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:868649da93e5a3d5eacc2b5b3b9235c98ccdbfd443832f31e075f54419e1b96b"}, + {file = "pydantic_core-2.18.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:78363590ef93d5d226ba21a90a03ea89a20738ee5b7da83d771d283fd8a56761"}, + {file = "pydantic_core-2.18.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:852e966fbd035a6468fc0a3496589b45e2208ec7ca95c26470a54daed82a0788"}, + {file = "pydantic_core-2.18.2-cp38-none-win32.whl", hash = "sha256:6a46e22a707e7ad4484ac9ee9f290f9d501df45954184e23fc29408dfad61350"}, + {file = "pydantic_core-2.18.2-cp38-none-win_amd64.whl", hash = "sha256:d91cb5ea8b11607cc757675051f61b3d93f15eca3cefb3e6c704a5d6e8440f4e"}, + {file = "pydantic_core-2.18.2-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:ae0a8a797a5e56c053610fa7be147993fe50960fa43609ff2a9552b0e07013e8"}, + {file = "pydantic_core-2.18.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:042473b6280246b1dbf530559246f6842b56119c2926d1e52b631bdc46075f2a"}, + {file = "pydantic_core-2.18.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a388a77e629b9ec814c1b1e6b3b595fe521d2cdc625fcca26fbc2d44c816804"}, + {file = "pydantic_core-2.18.2-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e25add29b8f3b233ae90ccef2d902d0ae0432eb0d45370fe315d1a5cf231004b"}, + {file = "pydantic_core-2.18.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f459a5ce8434614dfd39bbebf1041952ae01da6bed9855008cb33b875cb024c0"}, + {file = "pydantic_core-2.18.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eff2de745698eb46eeb51193a9f41d67d834d50e424aef27df2fcdee1b153845"}, + {file = "pydantic_core-2.18.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a8309f67285bdfe65c372ea3722b7a5642680f3dba538566340a9d36e920b5f0"}, + {file = "pydantic_core-2.18.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f93a8a2e3938ff656a7c1bc57193b1319960ac015b6e87d76c76bf14fe0244b4"}, + {file = "pydantic_core-2.18.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:22057013c8c1e272eb8d0eebc796701167d8377441ec894a8fed1af64a0bf399"}, + {file = "pydantic_core-2.18.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:cfeecd1ac6cc1fb2692c3d5110781c965aabd4ec5d32799773ca7b1456ac636b"}, + {file = "pydantic_core-2.18.2-cp39-none-win32.whl", hash = "sha256:0d69b4c2f6bb3e130dba60d34c0845ba31b69babdd3f78f7c0c8fae5021a253e"}, + {file = "pydantic_core-2.18.2-cp39-none-win_amd64.whl", hash = "sha256:d9319e499827271b09b4e411905b24a426b8fb69464dfa1696258f53a3334641"}, + {file = "pydantic_core-2.18.2-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:a1874c6dd4113308bd0eb568418e6114b252afe44319ead2b4081e9b9521fe75"}, + {file = "pydantic_core-2.18.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:ccdd111c03bfd3666bd2472b674c6899550e09e9f298954cfc896ab92b5b0e6d"}, + {file = "pydantic_core-2.18.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e18609ceaa6eed63753037fc06ebb16041d17d28199ae5aba0052c51449650a9"}, + {file = "pydantic_core-2.18.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e5c584d357c4e2baf0ff7baf44f4994be121e16a2c88918a5817331fc7599d7"}, + {file = "pydantic_core-2.18.2-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:43f0f463cf89ace478de71a318b1b4f05ebc456a9b9300d027b4b57c1a2064fb"}, + {file = "pydantic_core-2.18.2-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:e1b395e58b10b73b07b7cf740d728dd4ff9365ac46c18751bf8b3d8cca8f625a"}, + {file = "pydantic_core-2.18.2-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:0098300eebb1c837271d3d1a2cd2911e7c11b396eac9661655ee524a7f10587b"}, + {file = "pydantic_core-2.18.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:36789b70d613fbac0a25bb07ab3d9dba4d2e38af609c020cf4d888d165ee0bf3"}, + {file = "pydantic_core-2.18.2-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:3f9a801e7c8f1ef8718da265bba008fa121243dfe37c1cea17840b0944dfd72c"}, + {file = "pydantic_core-2.18.2-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:3a6515ebc6e69d85502b4951d89131ca4e036078ea35533bb76327f8424531ce"}, + {file = "pydantic_core-2.18.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:20aca1e2298c56ececfd8ed159ae4dde2df0781988c97ef77d5c16ff4bd5b400"}, + {file = "pydantic_core-2.18.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:223ee893d77a310a0391dca6df00f70bbc2f36a71a895cecd9a0e762dc37b349"}, + {file = "pydantic_core-2.18.2-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2334ce8c673ee93a1d6a65bd90327588387ba073c17e61bf19b4fd97d688d63c"}, + {file = "pydantic_core-2.18.2-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:cbca948f2d14b09d20268cda7b0367723d79063f26c4ffc523af9042cad95592"}, + {file = "pydantic_core-2.18.2-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:b3ef08e20ec49e02d5c6717a91bb5af9b20f1805583cb0adfe9ba2c6b505b5ae"}, + {file = "pydantic_core-2.18.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:c6fdc8627910eed0c01aed6a390a252fe3ea6d472ee70fdde56273f198938374"}, + {file = "pydantic_core-2.18.2.tar.gz", hash = "sha256:2e29d20810dfc3043ee13ac7d9e25105799817683348823f305ab3f349b9386e"}, +] + +[package.dependencies] +typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" [[package]] name = "pygments" -version = "2.17.2" +version = "2.18.0" description = "Pygments is a syntax highlighting package written in Python." optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "pygments-2.17.2-py3-none-any.whl", hash = "sha256:b27c2826c47d0f3219f29554824c30c5e8945175d888647acd804ddd04af846c"}, - {file = "pygments-2.17.2.tar.gz", hash = "sha256:da46cec9fd2de5be3a8a784f434e4c4ab670b4ff54d605c4c2717e9d49c4c367"}, + {file = "pygments-2.18.0-py3-none-any.whl", hash = "sha256:b8e6aca0523f3ab76fee51799c488e38782ac06eafcf95e7ba832985c8e7b13a"}, + {file = "pygments-2.18.0.tar.gz", hash = "sha256:786ff802f32e91311bff3889f6e9a86e81505fe99f2735bb6d60ae0c5004f199"}, ] [package.extras] -plugins = ["importlib-metadata"] windows-terminal = ["colorama (>=0.4.6)"] [[package]] name = "pyproject-hooks" -version = "1.0.0" +version = "1.1.0" description = "Wrappers to call pyproject.toml-based build backend hooks." optional = false python-versions = ">=3.7" files = [ - {file = "pyproject_hooks-1.0.0-py3-none-any.whl", hash = "sha256:283c11acd6b928d2f6a7c73fa0d01cb2bdc5f07c57a2eeb6e83d5e56b97976f8"}, - {file = "pyproject_hooks-1.0.0.tar.gz", hash = "sha256:f271b298b97f5955d53fb12b72c1fb1948c22c1a6b70b315c54cedaca0264ef5"}, + {file = "pyproject_hooks-1.1.0-py3-none-any.whl", hash = "sha256:7ceeefe9aec63a1064c18d939bdc3adf2d8aa1988a510afec15151578b232aa2"}, + {file = "pyproject_hooks-1.1.0.tar.gz", hash = "sha256:4b37730834edbd6bd37f26ece6b44802fb1c1ee2ece0e54ddff8bfc06db86965"}, ] -[package.dependencies] -tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} - [[package]] name = "pyright" version = "1.1.334" @@ -1744,13 +1804,13 @@ testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "no [[package]] name = "pytest-asyncio" -version = "0.21.1" +version = "0.21.2" description = "Pytest support for asyncio" optional = false python-versions = ">=3.7" files = [ - {file = "pytest-asyncio-0.21.1.tar.gz", hash = "sha256:40a7eae6dded22c7b604986855ea48400ab15b069ae38116e8c01238e9eeb64d"}, - {file = "pytest_asyncio-0.21.1-py3-none-any.whl", hash = "sha256:8666c1c8ac02631d7c51ba282e0c69a8a452b211ffedf2599099845da5c5c37b"}, + {file = "pytest_asyncio-0.21.2-py3-none-any.whl", hash = "sha256:ab664c88bb7998f711d8039cacd4884da6430886ae8bbd4eded552ed2004f16b"}, + {file = "pytest_asyncio-0.21.2.tar.gz", hash = "sha256:d67738fc232b94b326b9d060750beb16e0074210b98dd8b58a5239fa2a154f45"}, ] [package.dependencies] @@ -1916,7 +1976,6 @@ files = [ {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"}, {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"}, {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"}, - {file = "PyYAML-6.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290"}, {file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"}, {file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"}, {file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"}, @@ -1924,16 +1983,8 @@ files = [ {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"}, {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"}, {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"}, - {file = "PyYAML-6.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b"}, {file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"}, {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, - {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, - {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, - {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef"}, - {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, - {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, - {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, - {file = "PyYAML-6.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df"}, {file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"}, {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"}, {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"}, @@ -1950,7 +2001,6 @@ files = [ {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"}, {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"}, {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"}, - {file = "PyYAML-6.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6"}, {file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"}, {file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"}, {file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"}, @@ -1958,7 +2008,6 @@ files = [ {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"}, {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"}, {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"}, - {file = "PyYAML-6.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5"}, {file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"}, {file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"}, {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, @@ -1985,13 +2034,13 @@ md = ["cmarkgfm (>=0.8.0)"] [[package]] name = "redis" -version = "5.0.3" +version = "5.0.4" description = "Python client for Redis database and key-value store" optional = false python-versions = ">=3.7" files = [ - {file = "redis-5.0.3-py3-none-any.whl", hash = "sha256:5da9b8fe9e1254293756c16c008e8620b3d15fcc6dde6babde9541850e72a32d"}, - {file = "redis-5.0.3.tar.gz", hash = "sha256:4973bae7444c0fbed64a06b87446f79361cb7e4ec1538c022d696ed7a5015580"}, + {file = "redis-5.0.4-py3-none-any.whl", hash = "sha256:7adc2835c7a9b5033b7ad8f8918d09b7344188228809c98df07af226d39dec91"}, + {file = "redis-5.0.4.tar.gz", hash = "sha256:ec31f2ed9675cc54c21ba854cfe0462e6faf1d83c8ce5944709db8a4700b9c61"}, ] [package.dependencies] @@ -2003,24 +2052,23 @@ ocsp = ["cryptography (>=36.0.1)", "pyopenssl (==20.0.1)", "requests (>=2.26.0)" [[package]] name = "reflex-hosting-cli" -version = "0.1.9" +version = "0.1.13" description = "Reflex Hosting CLI" optional = false -python-versions = ">=3.8,<4.0" +python-versions = "<4.0,>=3.8" files = [ - {file = "reflex_hosting_cli-0.1.9-py3-none-any.whl", hash = "sha256:826c09f4f5acbda9c995e82b68e3e7b5c44ac63827bc5c8c53cbf527188ea2e0"}, - {file = "reflex_hosting_cli-0.1.9.tar.gz", hash = "sha256:6055a8baedd068cc6da9097ae76b38978734d2a4a4d05f223f9575484322b2a2"}, + {file = "reflex_hosting_cli-0.1.13-py3-none-any.whl", hash = "sha256:5bfec7f3d7ce4bbd703989f086494e586a641ef37c9e60e60fb82bdfb07ff227"}, + {file = "reflex_hosting_cli-0.1.13.tar.gz", hash = "sha256:c5d6afdcfeb74cee046a374ddbd59116ab8ed797dae688fcc744dabae24dc571"}, ] [package.dependencies] charset-normalizer = ">=3.3.2,<4.0.0" -httpx = ">=0.25.1" -pipdeptree = ">=2.13.1,<3.0.0" -pipreqs = ">=0.4.13,<0.5.0" -platformdirs = ">=3.10.0,<4.0.0" -pydantic = ">=1.10.2,<2.0.0" +httpx = ">=0.25.1,<1.0" +pipdeptree = ">=2.13.1,<2.17.0" +platformdirs = ">=3.10.0,<5.0" +pydantic = ">=1.10.2,<3.0" python-dateutil = ">=2.8.1" -rich = ">=13.0.0,<14.0.0" +rich = ">=13.0.0,<14.0" tabulate = ">=0.9.0,<0.10.0" typer = ">=0.4.2,<1" websockets = ">=10.4" @@ -2136,13 +2184,13 @@ jeepney = ">=0.6" [[package]] name = "selenium" -version = "4.19.0" +version = "4.20.0" description = "" optional = false python-versions = ">=3.8" files = [ - {file = "selenium-4.19.0-py3-none-any.whl", hash = "sha256:5b4f49240d61e687a73f7968ae2517d403882aae3550eae2a229c745e619f1d9"}, - {file = "selenium-4.19.0.tar.gz", hash = "sha256:d9dfd6d0b021d71d0a48b865fe7746490ba82b81e9c87b212360006629eb1853"}, + {file = "selenium-4.20.0-py3-none-any.whl", hash = "sha256:b1d0c33b38ca27d0499183e48e1dd09ff26973481f5d3ef2983073813ae6588d"}, + {file = "selenium-4.20.0.tar.gz", hash = "sha256:0bd564ee166980d419a8aaf4ac00289bc152afcf2eadca5efe8c8e36711853fd"}, ] [package.dependencies] @@ -2154,18 +2202,18 @@ urllib3 = {version = ">=1.26,<3", extras = ["socks"]} [[package]] name = "setuptools" -version = "69.2.0" +version = "69.5.1" description = "Easily download, build, install, upgrade, and uninstall Python packages" optional = false python-versions = ">=3.8" files = [ - {file = "setuptools-69.2.0-py3-none-any.whl", hash = "sha256:c21c49fb1042386df081cb5d86759792ab89efca84cf114889191cd09aacc80c"}, - {file = "setuptools-69.2.0.tar.gz", hash = "sha256:0ff4183f8f42cd8fa3acea16c45205521a4ef28f73c6391d8a25e92893134f2e"}, + {file = "setuptools-69.5.1-py3-none-any.whl", hash = "sha256:c636ac361bc47580504644275c9ad802c50415c7522212252c033bd15f301f32"}, + {file = "setuptools-69.5.1.tar.gz", hash = "sha256:6c1fccdac05a97e598fb0ae3bbed5904ccb317337a51139dcd51453611bbb987"}, ] [package.extras] -docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"] -testing = ["build[virtualenv]", "filelock (>=3.4.0)", "importlib-metadata", "ini2toml[lite] (>=0.9)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "mypy (==1.9)", "packaging (>=23.2)", "pip (>=19.1)", "pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-home (>=0.5)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff (>=0.2.1)", "pytest-timeout", "pytest-xdist (>=3)", "tomli", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] +docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"] +testing = ["build[virtualenv]", "filelock (>=3.4.0)", "importlib-metadata", "ini2toml[lite] (>=0.9)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "mypy (==1.9)", "packaging (>=23.2)", "pip (>=19.1)", "pytest (>=6,!=8.1.1)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-home (>=0.5)", "pytest-mypy", "pytest-perf", "pytest-ruff (>=0.2.1)", "pytest-timeout", "pytest-xdist (>=3)", "tomli", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] testing-integration = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "packaging (>=23.2)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] [[package]] @@ -2231,60 +2279,60 @@ files = [ [[package]] name = "sqlalchemy" -version = "2.0.29" +version = "2.0.30" description = "Database Abstraction Library" optional = false python-versions = ">=3.7" files = [ - {file = "SQLAlchemy-2.0.29-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4c142852ae192e9fe5aad5c350ea6befe9db14370b34047e1f0f7cf99e63c63b"}, - {file = "SQLAlchemy-2.0.29-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:99a1e69d4e26f71e750e9ad6fdc8614fbddb67cfe2173a3628a2566034e223c7"}, - {file = "SQLAlchemy-2.0.29-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5ef3fbccb4058355053c51b82fd3501a6e13dd808c8d8cd2561e610c5456013c"}, - {file = "SQLAlchemy-2.0.29-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9d6753305936eddc8ed190e006b7bb33a8f50b9854823485eed3a886857ab8d1"}, - {file = "SQLAlchemy-2.0.29-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:0f3ca96af060a5250a8ad5a63699180bc780c2edf8abf96c58af175921df847a"}, - {file = "SQLAlchemy-2.0.29-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c4520047006b1d3f0d89e0532978c0688219857eb2fee7c48052560ae76aca1e"}, - {file = "SQLAlchemy-2.0.29-cp310-cp310-win32.whl", hash = "sha256:b2a0e3cf0caac2085ff172c3faacd1e00c376e6884b5bc4dd5b6b84623e29e4f"}, - {file = "SQLAlchemy-2.0.29-cp310-cp310-win_amd64.whl", hash = "sha256:01d10638a37460616708062a40c7b55f73e4d35eaa146781c683e0fa7f6c43fb"}, - {file = "SQLAlchemy-2.0.29-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:308ef9cb41d099099fffc9d35781638986870b29f744382904bf9c7dadd08513"}, - {file = "SQLAlchemy-2.0.29-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:296195df68326a48385e7a96e877bc19aa210e485fa381c5246bc0234c36c78e"}, - {file = "SQLAlchemy-2.0.29-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a13b917b4ffe5a0a31b83d051d60477819ddf18276852ea68037a144a506efb9"}, - {file = "SQLAlchemy-2.0.29-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f6d971255d9ddbd3189e2e79d743ff4845c07f0633adfd1de3f63d930dbe673"}, - {file = "SQLAlchemy-2.0.29-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:61405ea2d563407d316c63a7b5271ae5d274a2a9fbcd01b0aa5503635699fa1e"}, - {file = "SQLAlchemy-2.0.29-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:de7202ffe4d4a8c1e3cde1c03e01c1a3772c92858837e8f3879b497158e4cb44"}, - {file = "SQLAlchemy-2.0.29-cp311-cp311-win32.whl", hash = "sha256:b5d7ed79df55a731749ce65ec20d666d82b185fa4898430b17cb90c892741520"}, - {file = "SQLAlchemy-2.0.29-cp311-cp311-win_amd64.whl", hash = "sha256:205f5a2b39d7c380cbc3b5dcc8f2762fb5bcb716838e2d26ccbc54330775b003"}, - {file = "SQLAlchemy-2.0.29-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:d96710d834a6fb31e21381c6d7b76ec729bd08c75a25a5184b1089141356171f"}, - {file = "SQLAlchemy-2.0.29-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:52de4736404e53c5c6a91ef2698c01e52333988ebdc218f14c833237a0804f1b"}, - {file = "SQLAlchemy-2.0.29-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c7b02525ede2a164c5fa5014915ba3591730f2cc831f5be9ff3b7fd3e30958e"}, - {file = "SQLAlchemy-2.0.29-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0dfefdb3e54cd15f5d56fd5ae32f1da2d95d78319c1f6dfb9bcd0eb15d603d5d"}, - {file = "SQLAlchemy-2.0.29-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:a88913000da9205b13f6f195f0813b6ffd8a0c0c2bd58d499e00a30eb508870c"}, - {file = "SQLAlchemy-2.0.29-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:fecd5089c4be1bcc37c35e9aa678938d2888845a134dd016de457b942cf5a758"}, - {file = "SQLAlchemy-2.0.29-cp312-cp312-win32.whl", hash = "sha256:8197d6f7a3d2b468861ebb4c9f998b9df9e358d6e1cf9c2a01061cb9b6cf4e41"}, - {file = "SQLAlchemy-2.0.29-cp312-cp312-win_amd64.whl", hash = "sha256:9b19836ccca0d321e237560e475fd99c3d8655d03da80c845c4da20dda31b6e1"}, - {file = "SQLAlchemy-2.0.29-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:87a1d53a5382cdbbf4b7619f107cc862c1b0a4feb29000922db72e5a66a5ffc0"}, - {file = "SQLAlchemy-2.0.29-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2a0732dffe32333211801b28339d2a0babc1971bc90a983e3035e7b0d6f06b93"}, - {file = "SQLAlchemy-2.0.29-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90453597a753322d6aa770c5935887ab1fc49cc4c4fdd436901308383d698b4b"}, - {file = "SQLAlchemy-2.0.29-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:ea311d4ee9a8fa67f139c088ae9f905fcf0277d6cd75c310a21a88bf85e130f5"}, - {file = "SQLAlchemy-2.0.29-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:5f20cb0a63a3e0ec4e169aa8890e32b949c8145983afa13a708bc4b0a1f30e03"}, - {file = "SQLAlchemy-2.0.29-cp37-cp37m-win32.whl", hash = "sha256:e5bbe55e8552019c6463709b39634a5fc55e080d0827e2a3a11e18eb73f5cdbd"}, - {file = "SQLAlchemy-2.0.29-cp37-cp37m-win_amd64.whl", hash = "sha256:c2f9c762a2735600654c654bf48dad388b888f8ce387b095806480e6e4ff6907"}, - {file = "SQLAlchemy-2.0.29-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:7e614d7a25a43a9f54fcce4675c12761b248547f3d41b195e8010ca7297c369c"}, - {file = "SQLAlchemy-2.0.29-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:471fcb39c6adf37f820350c28aac4a7df9d3940c6548b624a642852e727ea586"}, - {file = "SQLAlchemy-2.0.29-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:988569c8732f54ad3234cf9c561364221a9e943b78dc7a4aaf35ccc2265f1930"}, - {file = "SQLAlchemy-2.0.29-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dddaae9b81c88083e6437de95c41e86823d150f4ee94bf24e158a4526cbead01"}, - {file = "SQLAlchemy-2.0.29-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:334184d1ab8f4c87f9652b048af3f7abea1c809dfe526fb0435348a6fef3d380"}, - {file = "SQLAlchemy-2.0.29-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:38b624e5cf02a69b113c8047cf7f66b5dfe4a2ca07ff8b8716da4f1b3ae81567"}, - {file = "SQLAlchemy-2.0.29-cp38-cp38-win32.whl", hash = "sha256:bab41acf151cd68bc2b466deae5deeb9e8ae9c50ad113444151ad965d5bf685b"}, - {file = "SQLAlchemy-2.0.29-cp38-cp38-win_amd64.whl", hash = "sha256:52c8011088305476691b8750c60e03b87910a123cfd9ad48576d6414b6ec2a1d"}, - {file = "SQLAlchemy-2.0.29-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3071ad498896907a5ef756206b9dc750f8e57352113c19272bdfdc429c7bd7de"}, - {file = "SQLAlchemy-2.0.29-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:dba622396a3170974f81bad49aacebd243455ec3cc70615aeaef9e9613b5bca5"}, - {file = "SQLAlchemy-2.0.29-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7b184e3de58009cc0bf32e20f137f1ec75a32470f5fede06c58f6c355ed42a72"}, - {file = "SQLAlchemy-2.0.29-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c37f1050feb91f3d6c32f864d8e114ff5545a4a7afe56778d76a9aec62638ba"}, - {file = "SQLAlchemy-2.0.29-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:bda7ce59b06d0f09afe22c56714c65c957b1068dee3d5e74d743edec7daba552"}, - {file = "SQLAlchemy-2.0.29-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:25664e18bef6dc45015b08f99c63952a53a0a61f61f2e48a9e70cec27e55f699"}, - {file = "SQLAlchemy-2.0.29-cp39-cp39-win32.whl", hash = "sha256:77d29cb6c34b14af8a484e831ab530c0f7188f8efed1c6a833a2c674bf3c26ec"}, - {file = "SQLAlchemy-2.0.29-cp39-cp39-win_amd64.whl", hash = "sha256:04c487305ab035a9548f573763915189fc0fe0824d9ba28433196f8436f1449c"}, - {file = "SQLAlchemy-2.0.29-py3-none-any.whl", hash = "sha256:dc4ee2d4ee43251905f88637d5281a8d52e916a021384ec10758826f5cbae305"}, - {file = "SQLAlchemy-2.0.29.tar.gz", hash = "sha256:bd9566b8e58cabd700bc367b60e90d9349cd16f0984973f98a9a09f9c64e86f0"}, + {file = "SQLAlchemy-2.0.30-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:3b48154678e76445c7ded1896715ce05319f74b1e73cf82d4f8b59b46e9c0ddc"}, + {file = "SQLAlchemy-2.0.30-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2753743c2afd061bb95a61a51bbb6a1a11ac1c44292fad898f10c9839a7f75b2"}, + {file = "SQLAlchemy-2.0.30-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a7bfc726d167f425d4c16269a9a10fe8630ff6d14b683d588044dcef2d0f6be7"}, + {file = "SQLAlchemy-2.0.30-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c4f61ada6979223013d9ab83a3ed003ded6959eae37d0d685db2c147e9143797"}, + {file = "SQLAlchemy-2.0.30-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:3a365eda439b7a00732638f11072907c1bc8e351c7665e7e5da91b169af794af"}, + {file = "SQLAlchemy-2.0.30-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:bba002a9447b291548e8d66fd8c96a6a7ed4f2def0bb155f4f0a1309fd2735d5"}, + {file = "SQLAlchemy-2.0.30-cp310-cp310-win32.whl", hash = "sha256:0138c5c16be3600923fa2169532205d18891b28afa817cb49b50e08f62198bb8"}, + {file = "SQLAlchemy-2.0.30-cp310-cp310-win_amd64.whl", hash = "sha256:99650e9f4cf3ad0d409fed3eec4f071fadd032e9a5edc7270cd646a26446feeb"}, + {file = "SQLAlchemy-2.0.30-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:955991a09f0992c68a499791a753523f50f71a6885531568404fa0f231832aa0"}, + {file = "SQLAlchemy-2.0.30-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f69e4c756ee2686767eb80f94c0125c8b0a0b87ede03eacc5c8ae3b54b99dc46"}, + {file = "SQLAlchemy-2.0.30-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69c9db1ce00e59e8dd09d7bae852a9add716efdc070a3e2068377e6ff0d6fdaa"}, + {file = "SQLAlchemy-2.0.30-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a1429a4b0f709f19ff3b0cf13675b2b9bfa8a7e79990003207a011c0db880a13"}, + {file = "SQLAlchemy-2.0.30-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:efedba7e13aa9a6c8407c48facfdfa108a5a4128e35f4c68f20c3407e4376aa9"}, + {file = "SQLAlchemy-2.0.30-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:16863e2b132b761891d6c49f0a0f70030e0bcac4fd208117f6b7e053e68668d0"}, + {file = "SQLAlchemy-2.0.30-cp311-cp311-win32.whl", hash = "sha256:2ecabd9ccaa6e914e3dbb2aa46b76dede7eadc8cbf1b8083c94d936bcd5ffb49"}, + {file = "SQLAlchemy-2.0.30-cp311-cp311-win_amd64.whl", hash = "sha256:0b3f4c438e37d22b83e640f825ef0f37b95db9aa2d68203f2c9549375d0b2260"}, + {file = "SQLAlchemy-2.0.30-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:5a79d65395ac5e6b0c2890935bad892eabb911c4aa8e8015067ddb37eea3d56c"}, + {file = "SQLAlchemy-2.0.30-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9a5baf9267b752390252889f0c802ea13b52dfee5e369527da229189b8bd592e"}, + {file = "SQLAlchemy-2.0.30-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3cb5a646930c5123f8461f6468901573f334c2c63c795b9af350063a736d0134"}, + {file = "SQLAlchemy-2.0.30-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:296230899df0b77dec4eb799bcea6fbe39a43707ce7bb166519c97b583cfcab3"}, + {file = "SQLAlchemy-2.0.30-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c62d401223f468eb4da32627bffc0c78ed516b03bb8a34a58be54d618b74d472"}, + {file = "SQLAlchemy-2.0.30-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:3b69e934f0f2b677ec111b4d83f92dc1a3210a779f69bf905273192cf4ed433e"}, + {file = "SQLAlchemy-2.0.30-cp312-cp312-win32.whl", hash = "sha256:77d2edb1f54aff37e3318f611637171e8ec71472f1fdc7348b41dcb226f93d90"}, + {file = "SQLAlchemy-2.0.30-cp312-cp312-win_amd64.whl", hash = "sha256:b6c7ec2b1f4969fc19b65b7059ed00497e25f54069407a8701091beb69e591a5"}, + {file = "SQLAlchemy-2.0.30-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:5a8e3b0a7e09e94be7510d1661339d6b52daf202ed2f5b1f9f48ea34ee6f2d57"}, + {file = "SQLAlchemy-2.0.30-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b60203c63e8f984df92035610c5fb76d941254cf5d19751faab7d33b21e5ddc0"}, + {file = "SQLAlchemy-2.0.30-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f1dc3eabd8c0232ee8387fbe03e0a62220a6f089e278b1f0aaf5e2d6210741ad"}, + {file = "SQLAlchemy-2.0.30-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:40ad017c672c00b9b663fcfcd5f0864a0a97828e2ee7ab0c140dc84058d194cf"}, + {file = "SQLAlchemy-2.0.30-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:e42203d8d20dc704604862977b1470a122e4892791fe3ed165f041e4bf447a1b"}, + {file = "SQLAlchemy-2.0.30-cp37-cp37m-win32.whl", hash = "sha256:2a4f4da89c74435f2bc61878cd08f3646b699e7d2eba97144030d1be44e27584"}, + {file = "SQLAlchemy-2.0.30-cp37-cp37m-win_amd64.whl", hash = "sha256:b6bf767d14b77f6a18b6982cbbf29d71bede087edae495d11ab358280f304d8e"}, + {file = "SQLAlchemy-2.0.30-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:bc0c53579650a891f9b83fa3cecd4e00218e071d0ba00c4890f5be0c34887ed3"}, + {file = "SQLAlchemy-2.0.30-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:311710f9a2ee235f1403537b10c7687214bb1f2b9ebb52702c5aa4a77f0b3af7"}, + {file = "SQLAlchemy-2.0.30-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:408f8b0e2c04677e9c93f40eef3ab22f550fecb3011b187f66a096395ff3d9fd"}, + {file = "SQLAlchemy-2.0.30-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:37a4b4fb0dd4d2669070fb05b8b8824afd0af57587393015baee1cf9890242d9"}, + {file = "SQLAlchemy-2.0.30-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:a943d297126c9230719c27fcbbeab57ecd5d15b0bd6bfd26e91bfcfe64220621"}, + {file = "SQLAlchemy-2.0.30-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:0a089e218654e740a41388893e090d2e2c22c29028c9d1353feb38638820bbeb"}, + {file = "SQLAlchemy-2.0.30-cp38-cp38-win32.whl", hash = "sha256:fa561138a64f949f3e889eb9ab8c58e1504ab351d6cf55259dc4c248eaa19da6"}, + {file = "SQLAlchemy-2.0.30-cp38-cp38-win_amd64.whl", hash = "sha256:7d74336c65705b986d12a7e337ba27ab2b9d819993851b140efdf029248e818e"}, + {file = "SQLAlchemy-2.0.30-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ae8c62fe2480dd61c532ccafdbce9b29dacc126fe8be0d9a927ca3e699b9491a"}, + {file = "SQLAlchemy-2.0.30-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:2383146973a15435e4717f94c7509982770e3e54974c71f76500a0136f22810b"}, + {file = "SQLAlchemy-2.0.30-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8409de825f2c3b62ab15788635ccaec0c881c3f12a8af2b12ae4910a0a9aeef6"}, + {file = "SQLAlchemy-2.0.30-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0094c5dc698a5f78d3d1539853e8ecec02516b62b8223c970c86d44e7a80f6c7"}, + {file = "SQLAlchemy-2.0.30-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:edc16a50f5e1b7a06a2dcc1f2205b0b961074c123ed17ebda726f376a5ab0953"}, + {file = "SQLAlchemy-2.0.30-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:f7703c2010355dd28f53deb644a05fc30f796bd8598b43f0ba678878780b6e4c"}, + {file = "SQLAlchemy-2.0.30-cp39-cp39-win32.whl", hash = "sha256:1f9a727312ff6ad5248a4367358e2cf7e625e98b1028b1d7ab7b806b7d757513"}, + {file = "SQLAlchemy-2.0.30-cp39-cp39-win_amd64.whl", hash = "sha256:a0ef36b28534f2a5771191be6edb44cc2673c7b2edf6deac6562400288664221"}, + {file = "SQLAlchemy-2.0.30-py3-none-any.whl", hash = "sha256:7108d569d3990c71e26a42f60474b4c02c8586c4681af5fd67e51a044fdea86a"}, + {file = "SQLAlchemy-2.0.30.tar.gz", hash = "sha256:2b1708916730f4830bc69d6f49d37f7698b5bd7530aca7f04f785f8849e95255"}, ] [package.dependencies] @@ -2318,13 +2366,13 @@ sqlcipher = ["sqlcipher3_binary"] [[package]] name = "sqlmodel" -version = "0.0.16" +version = "0.0.18" description = "SQLModel, SQL databases in Python, designed for simplicity, compatibility, and robustness." optional = false -python-versions = ">=3.7,<4.0" +python-versions = ">=3.7" files = [ - {file = "sqlmodel-0.0.16-py3-none-any.whl", hash = "sha256:b972f5d319580d6c37ecc417881f6ec4d1ad3ed3583d0ac0ed43234a28bf605a"}, - {file = "sqlmodel-0.0.16.tar.gz", hash = "sha256:966656f18a8e9a2d159eb215b07fb0cf5222acfae3362707ca611848a8a06bd1"}, + {file = "sqlmodel-0.0.18-py3-none-any.whl", hash = "sha256:d70fdf8fe595e30a918660cf4537b9c5fc2fffdbfcba851a0135de73c3ebcbb7"}, + {file = "sqlmodel-0.0.18.tar.gz", hash = "sha256:2e520efe03810ef2c268a1004cfc5ef8f8a936312232f38d6c8e62c11af2cac3"}, ] [package.dependencies] @@ -2333,13 +2381,13 @@ SQLAlchemy = ">=2.0.0,<2.1.0" [[package]] name = "starlette" -version = "0.36.3" +version = "0.37.2" description = "The little ASGI library that shines." optional = false python-versions = ">=3.8" files = [ - {file = "starlette-0.36.3-py3-none-any.whl", hash = "sha256:13d429aa93a61dc40bf503e8c801db1f1bca3dc706b10ef2434a36123568f044"}, - {file = "starlette-0.36.3.tar.gz", hash = "sha256:90a671733cfb35771d8cc605e0b679d23b992f8dcfad48cc60b38cb29aeb7080"}, + {file = "starlette-0.37.2-py3-none-any.whl", hash = "sha256:6fe59f29268538e5d0d182f2791a479a0c64638e6935d1c6989e63fb2699c6ee"}, + {file = "starlette-0.37.2.tar.gz", hash = "sha256:9af890290133b79fc3db55474ade20f6220a364a0402e0b556e7cd5e1e093823"}, ] [package.dependencies] @@ -2388,17 +2436,18 @@ widechars = ["wcwidth"] [[package]] name = "tenacity" -version = "8.2.3" +version = "8.3.0" description = "Retry code until it succeeds" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "tenacity-8.2.3-py3-none-any.whl", hash = "sha256:ce510e327a630c9e1beaf17d42e6ffacc88185044ad85cf74c0a8887c6a0f88c"}, - {file = "tenacity-8.2.3.tar.gz", hash = "sha256:5398ef0d78e63f40007c1fb4c0bff96e1911394d2fa8d194f77619c05ff6cc8a"}, + {file = "tenacity-8.3.0-py3-none-any.whl", hash = "sha256:3649f6443dbc0d9b01b9d8020a9c4ec7a1ff5f6f3c6c8a036ef371f573fe9185"}, + {file = "tenacity-8.3.0.tar.gz", hash = "sha256:953d4e6ad24357bceffbc9707bc74349aca9d245f68eb65419cf0c249a1949a2"}, ] [package.extras] -doc = ["reno", "sphinx", "tornado (>=4.5)"] +doc = ["reno", "sphinx"] +test = ["pytest", "tornado (>=4.5)", "typeguard"] [[package]] name = "toml" @@ -2493,63 +2542,30 @@ urllib3 = ">=1.26.0" [[package]] name = "typer" -version = "0.12.0" +version = "0.12.3" description = "Typer, build great CLIs. Easy to code. Based on Python type hints." optional = false python-versions = ">=3.7" files = [ - {file = "typer-0.12.0-py3-none-any.whl", hash = "sha256:0441a0bb8962fb4383b8537ada9f7eb2d0deda0caa2cfe7387cc221290f617e4"}, - {file = "typer-0.12.0.tar.gz", hash = "sha256:900fe786ce2d0ea44653d3c8ee4594a22a496a3104370ded770c992c5e3c542d"}, -] - -[package.dependencies] -typer-cli = "0.12.0" -typer-slim = {version = "0.12.0", extras = ["standard"]} - -[[package]] -name = "typer-cli" -version = "0.12.0" -description = "Typer, build great CLIs. Easy to code. Based on Python type hints." -optional = false -python-versions = ">=3.7" -files = [ - {file = "typer_cli-0.12.0-py3-none-any.whl", hash = "sha256:7b7e2dd49f59974bb5a869747045d5444b17bffb851e006cd424f602d3578104"}, - {file = "typer_cli-0.12.0.tar.gz", hash = "sha256:603ed3d5a278827bd497e4dc73a39bb714b230371c8724090b0de2abdcdd9f6e"}, -] - -[package.dependencies] -typer-slim = {version = "0.12.0", extras = ["standard"]} - -[[package]] -name = "typer-slim" -version = "0.12.0" -description = "Typer, build great CLIs. Easy to code. Based on Python type hints." -optional = false -python-versions = ">=3.7" -files = [ - {file = "typer_slim-0.12.0-py3-none-any.whl", hash = "sha256:ddd7042b29a32140528caa415750bcae54113ba0c32270ca11a6f64069ddadf9"}, - {file = "typer_slim-0.12.0.tar.gz", hash = "sha256:3e8a3f17286b173d76dca0fd4e02651c9a2ce1467b3754876b1ac4bd72572beb"}, + {file = "typer-0.12.3-py3-none-any.whl", hash = "sha256:070d7ca53f785acbccba8e7d28b08dcd88f79f1fbda035ade0aecec71ca5c914"}, + {file = "typer-0.12.3.tar.gz", hash = "sha256:49e73131481d804288ef62598d97a1ceef3058905aa536a1134f90891ba35482"}, ] [package.dependencies] click = ">=8.0.0" -rich = {version = ">=10.11.0", optional = true, markers = "extra == \"standard\""} -shellingham = {version = ">=1.3.0", optional = true, markers = "extra == \"standard\""} +rich = ">=10.11.0" +shellingham = ">=1.3.0" typing-extensions = ">=3.7.4.3" -[package.extras] -all = ["rich (>=10.11.0)", "shellingham (>=1.3.0)"] -standard = ["rich (>=10.11.0)", "shellingham (>=1.3.0)"] - [[package]] name = "typing-extensions" -version = "4.10.0" +version = "4.11.0" description = "Backported and Experimental Type Hints for Python 3.8+" optional = false python-versions = ">=3.8" files = [ - {file = "typing_extensions-4.10.0-py3-none-any.whl", hash = "sha256:69b1a937c3a517342112fb4c6df7e72fc39a38e7891a5730ed4985b5214b5475"}, - {file = "typing_extensions-4.10.0.tar.gz", hash = "sha256:b0abd7c89e8fb96f98db18d86106ff1d90ab692004eb746cf6eda2682f91b3cb"}, + {file = "typing_extensions-4.11.0-py3-none-any.whl", hash = "sha256:c1f94d72897edaf4ce775bb7558d5b79d8126906a14ea5ed1635921406c0387a"}, + {file = "typing_extensions-4.11.0.tar.gz", hash = "sha256:83f085bd5ca59c80295fc2a82ab5dac679cbe02b9f33f7d83af68e241bea51b0"}, ] [[package]] @@ -2621,13 +2637,13 @@ standard = ["colorama (>=0.4)", "httptools (>=0.5.0)", "python-dotenv (>=0.13)", [[package]] name = "virtualenv" -version = "20.25.1" +version = "20.26.1" description = "Virtual Python Environment builder" optional = false python-versions = ">=3.7" files = [ - {file = "virtualenv-20.25.1-py3-none-any.whl", hash = "sha256:961c026ac520bac5f69acb8ea063e8a4f071bcc9457b9c1f28f6b085c511583a"}, - {file = "virtualenv-20.25.1.tar.gz", hash = "sha256:e08e13ecdca7a0bd53798f356d5831434afa5b07b93f0abdf0797b7a06ffe197"}, + {file = "virtualenv-20.26.1-py3-none-any.whl", hash = "sha256:7aa9982a728ae5892558bff6a2839c00b9ed145523ece2274fad6f414690ae75"}, + {file = "virtualenv-20.26.1.tar.gz", hash = "sha256:604bfdceaeece392802e6ae48e69cec49168b9c5f4a44e483963f9242eb0e78b"}, ] [package.dependencies] @@ -2636,7 +2652,7 @@ filelock = ">=3.12.2,<4" platformdirs = ">=3.9.1,<5" [package.extras] -docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"] +docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2,!=7.3)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"] test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8)", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10)"] [[package]] @@ -2955,20 +2971,6 @@ files = [ [package.dependencies] h11 = ">=0.9.0,<1" -[[package]] -name = "yarg" -version = "0.1.9" -description = "A semi hard Cornish cheese, also queries PyPI (PyPI client)" -optional = false -python-versions = "*" -files = [ - {file = "yarg-0.1.9-py2.py3-none-any.whl", hash = "sha256:4f9cebdc00fac946c9bf2783d634e538a71c7d280a4d806d45fd4dc0ef441492"}, - {file = "yarg-0.1.9.tar.gz", hash = "sha256:55695bf4d1e3e7f756496c36a69ba32c40d18f821e38f61d028f6049e5e15911"}, -] - -[package.dependencies] -requests = "*" - [[package]] name = "zipp" version = "3.18.1" @@ -2987,4 +2989,4 @@ testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "p [metadata] lock-version = "2.0" python-versions = "^3.8" -content-hash = "ca7c9f3bbefac3f6e2cd06bc64f5c0d745000ede8680a08e4e8dfc68dacc7c07" +content-hash = "a5abcbe48c3d9d39752223c5abb7cebadf0ad444b8d6af400a7c2c769543bb13" diff --git a/pyproject.toml b/pyproject.toml index 143638836e..fb3eab724f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "reflex" -version = "0.4.9" +version = "0.5.0" description = "Web apps in pure Python." license = "Apache-2.0" authors = [ @@ -29,7 +29,7 @@ packages = [ python = "^3.8" dill = ">=0.3.8,<0.4" fastapi = ">=0.96.0,<1.0" -gunicorn = ">=20.1.0,<22.0" +gunicorn = ">=20.1.0,<23.0" jinja2 = ">=3.1.2,<4.0" psutil = ">=5.9.4,<6.0" pydantic = ">=1.10.2,<3.0" @@ -106,3 +106,4 @@ lint.ignore = ["B008", "D203", "D205", "D213", "D401", "D406", "D407", "E501", " "tests/*.py" = ["D100", "D103", "D104", "B018"] "reflex/.templates/*.py" = ["D100", "D103", "D104"] "*.pyi" = ["ALL"] +"*/blank.py" = ["I001"] diff --git a/reflex/.templates/apps/blank/code/blank.py b/reflex/.templates/apps/blank/code/blank.py index 3ab5aca8d6..c0de6e44fe 100644 --- a/reflex/.templates/apps/blank/code/blank.py +++ b/reflex/.templates/apps/blank/code/blank.py @@ -1,34 +1,37 @@ """Welcome to Reflex! This file outlines the steps to create a basic app.""" -from rxconfig import config - import reflex as rx -docs_url = "https://reflex.dev/docs/getting-started/introduction/" -filename = f"{config.app_name}/{config.app_name}.py" +from rxconfig import config class State(rx.State): """The app state.""" + ... + def index() -> rx.Component: - return rx.center( - rx.theme_panel(), + # Welcome Page (Index) + return rx.container( + rx.color_mode.button(position="top-right"), rx.vstack( rx.heading("Welcome to Reflex!", size="9"), - rx.text("Get started by editing ", rx.code(filename)), - rx.button( - "Check out our docs!", - on_click=lambda: rx.redirect(docs_url), - size="4", + rx.text( + "Get started by editing ", + rx.code(f"{config.app_name}/{config.app_name}.py"), + size="5", + ), + rx.link( + rx.button("Check out our docs!"), + href="https://reflex.dev/docs/getting-started/introduction/", + is_external=True, ), - rx.logo(), - align="center", - spacing="7", - font_size="2em", + spacing="5", + justify="center", + min_height="85vh", ), - height="100vh", + rx.logo(), ) diff --git a/reflex/.templates/apps/demo/code/demo.py b/reflex/.templates/apps/demo/code/demo.py index 7031f09d03..2774ec5794 100644 --- a/reflex/.templates/apps/demo/code/demo.py +++ b/reflex/.templates/apps/demo/code/demo.py @@ -44,7 +44,7 @@ def template(main_content: Callable[[], rx.Component]) -> rx.Component: ), rx.chakra.menu_item( rx.chakra.link( - "Contact", href="mailto:founders@=reflex.dev", width="100%" + "Contact", href="mailto:founders@reflex.dev", width="100%" ) ), ), diff --git a/reflex/.templates/jinja/web/tailwind.config.js.jinja2 b/reflex/.templates/jinja/web/tailwind.config.js.jinja2 index ac170f442c..00ae98e0c6 100644 --- a/reflex/.templates/jinja/web/tailwind.config.js.jinja2 +++ b/reflex/.templates/jinja/web/tailwind.config.js.jinja2 @@ -17,4 +17,16 @@ module.exports = { {% if darkMode is defined %} darkMode: {{darkMode|json_dumps}}, {% endif %} + {% if corePlugins is defined %} + corePlugins: {{corePlugins|json_dumps}}, + {% endif %} + {% if important is defined %} + important: {{important|json_dumps}}, + {% endif %} + {% if prefix is defined %} + prefix: {{prefix|json_dumps}}, + {% endif %} + {% if separator is defined %} + separator: {{separator|json_dumps}}, + {% endif %} }; diff --git a/reflex/__init__.py b/reflex/__init__.py index f5c7537891..b67208ec96 100644 --- a/reflex/__init__.py +++ b/reflex/__init__.py @@ -59,6 +59,7 @@ "code", "container", "context_menu", + "data_list", "dialog", "divider", "drawer", @@ -79,6 +80,7 @@ "scroll_area", "section", "select", + "skeleton", "slider", "spacer", "spinner", @@ -111,7 +113,8 @@ "ordered_list", "moment", "logo", - "toast", + # Toast is in experimental namespace initially + # "toast", ] _MAPPING = { diff --git a/reflex/__init__.pyi b/reflex/__init__.pyi index 835666e1c4..f919220a8d 100644 --- a/reflex/__init__.pyi +++ b/reflex/__init__.pyi @@ -46,6 +46,7 @@ from reflex.components import checkbox as checkbox from reflex.components import code as code from reflex.components import container as container from reflex.components import context_menu as context_menu +from reflex.components import data_list as data_list from reflex.components import dialog as dialog from reflex.components import divider as divider from reflex.components import drawer as drawer @@ -66,6 +67,7 @@ from reflex.components import radio as radio from reflex.components import scroll_area as scroll_area from reflex.components import section as section from reflex.components import select as select +from reflex.components import skeleton as skeleton from reflex.components import slider as slider from reflex.components import spacer as spacer from reflex.components import spinner as spinner @@ -97,7 +99,6 @@ from reflex.components import unordered_list as unordered_list from reflex.components import ordered_list as ordered_list from reflex.components import moment as moment from reflex.components import logo as logo -from reflex.components import toast as toast from reflex.components.component import Component as Component from reflex.components.component import NoSSRComponent as NoSSRComponent from reflex.components.component import memo as memo diff --git a/reflex/app.py b/reflex/app.py index e7cb8f027f..4d99d6949e 100644 --- a/reflex/app.py +++ b/reflex/app.py @@ -403,9 +403,12 @@ def _generate_component(component: Component | ComponentCallable) -> Component: The generated component. Raises: + VarOperationTypeError: When an invalid component var related function is passed. TypeError: When an invalid component function is passed. exceptions.MatchTypeError: If the return types of match cases in rx.match are different. """ + from reflex.utils.exceptions import VarOperationTypeError + try: return component if isinstance(component, Component) else component() except exceptions.MatchTypeError: @@ -413,7 +416,7 @@ def _generate_component(component: Component | ComponentCallable) -> Component: except TypeError as e: message = str(e) if "BaseVar" in message or "ComputedVar" in message: - raise TypeError( + raise VarOperationTypeError( "You may be trying to use an invalid Python function on a state var. " "When referencing a var inside your render code, only limited var operations are supported. " "See the var operation docs here: https://reflex.dev/docs/vars/var-operations/" @@ -555,11 +558,13 @@ def _check_routes_conflict(self, new_route: str): Based on conflicts that NextJS would throw if not intercepted. Raises: - ValueError: exception showing which conflict exist with the route to be added + RouteValueError: exception showing which conflict exist with the route to be added Args: new_route: the route being newly added. """ + from reflex.utils.exceptions import RouteValueError + if "[" not in new_route: return @@ -576,7 +581,7 @@ def _check_routes_conflict(self, new_route: str): ): 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 - raise ValueError( + raise RouteValueError( f"You cannot use different slug names for the same dynamic path in {route} and {new_route} ('{r}' != '{nr}')" ) elif rw not in segments and r != nr: @@ -755,8 +760,10 @@ def _compile(self, export: bool = False): export: Whether to compile the app for export. Raises: - RuntimeError: When any page uses state, but no rx.State subclass is defined. + ReflexRuntimeError: When any page uses state, but no rx.State subclass is defined. """ + from reflex.utils.exceptions import ReflexRuntimeError + # Render a default 404 page if the user didn't supply one if constants.Page404.SLUG not in self.pages: self.add_custom_404_page() @@ -837,7 +844,7 @@ def _compile(self, export: bool = False): # Catch "static" apps (that do not define a rx.State subclass) which are trying to access rx.State. if code_uses_state_contexts(stateful_components_code) and self.state is None: - raise RuntimeError( + raise ReflexRuntimeError( "To access rx.State in frontend components, at least one " "subclass of rx.State must be defined in the app." ) @@ -1060,50 +1067,60 @@ async def process( headers: The client headers. client_ip: The client_ip. + Raises: + ReflexError: If a reflex specific error occurs during processing the event. + Yields: The state updates after processing the event. """ - # Add request data to the state. - router_data = event.router_data - router_data.update( - { - constants.RouteVar.QUERY: format.format_query_params(event.router_data), - constants.RouteVar.CLIENT_TOKEN: event.token, - constants.RouteVar.SESSION_ID: sid, - constants.RouteVar.HEADERS: headers, - constants.RouteVar.CLIENT_IP: client_ip, - } - ) - # Get the state for the session exclusively. - async with app.state_manager.modify_state(event.substate_token) as state: - # re-assign only when the value is different - if state.router_data != router_data: - # assignment will recurse into substates and force recalculation of - # dependent ComputedVar (dynamic route variables) - state.router_data = router_data - state.router = RouterData(router_data) - - # Preprocess the event. - update = await app._preprocess(state, event) - - # If there was an update, yield it. - if update is not None: - yield update - - # Only process the event if there is no update. - else: - if app._process_background(state, event) is not None: - # `final=True` allows the frontend send more events immediately. - yield StateUpdate(final=True) - return + from reflex.utils import telemetry + from reflex.utils.exceptions import ReflexError + + try: + # Add request data to the state. + router_data = event.router_data + router_data.update( + { + constants.RouteVar.QUERY: format.format_query_params(event.router_data), + constants.RouteVar.CLIENT_TOKEN: event.token, + constants.RouteVar.SESSION_ID: sid, + constants.RouteVar.HEADERS: headers, + constants.RouteVar.CLIENT_IP: client_ip, + } + ) + # Get the state for the session exclusively. + async with app.state_manager.modify_state(event.substate_token) as state: + # re-assign only when the value is different + if state.router_data != router_data: + # assignment will recurse into substates and force recalculation of + # dependent ComputedVar (dynamic route variables) + state.router_data = router_data + state.router = RouterData(router_data) + + # Preprocess the event. + update = await app._preprocess(state, event) + + # If there was an update, yield it. + if update is not None: + yield update - # Process the event synchronously. - async for update in state._process(event): - # Postprocess the event. - update = await app._postprocess(state, event, update) + # Only process the event if there is no update. + else: + if app._process_background(state, event) is not None: + # `final=True` allows the frontend send more events immediately. + yield StateUpdate(final=True) + return - # Yield the update. - yield update + # Process the event synchronously. + async for update in state._process(event): + # Postprocess the event. + update = await app._postprocess(state, event, update) + + # Yield the update. + yield update + except ReflexError as ex: + telemetry.send("error", context="backend", detail=str(ex)) + raise async def ping() -> str: @@ -1137,10 +1154,12 @@ async def upload_file(request: Request, files: List[UploadFile]): emitted by the upload handler. Raises: - ValueError: if there are no args with supported annotation. - TypeError: if a background task is used as the handler. + UploadValueError: if there are no args with supported annotation. + UploadTypeError: if a background task is used as the handler. HTTPException: when the request does not include token / handler headers. """ + from reflex.utils.exceptions import UploadTypeError, UploadValueError + token = request.headers.get("reflex-client-token") handler = request.headers.get("reflex-event-handler") @@ -1166,7 +1185,7 @@ async def upload_file(request: Request, files: List[UploadFile]): # check if there exists any handler args with annotation, List[UploadFile] if isinstance(func, EventHandler): if func.is_background: - raise TypeError( + raise UploadTypeError( f"@rx.background is not supported for upload handler `{handler}`.", ) func = func.fn @@ -1181,7 +1200,7 @@ async def upload_file(request: Request, files: List[UploadFile]): break if not handler_upload_param: - raise ValueError( + raise UploadValueError( f"`{handler}` handler should have a parameter annotated as " "List[rx.UploadFile]" ) diff --git a/reflex/base.py b/reflex/base.py index b87d5dd5f2..d15a7bedc0 100644 --- a/reflex/base.py +++ b/reflex/base.py @@ -1,23 +1,19 @@ """Define the base Reflex class.""" + from __future__ import annotations import os from typing import TYPE_CHECKING, Any, List, Type try: - # TODO The type checking guard can be removed once - # reflex-hosting-cli tools are compatible with pydantic v2 - - if not TYPE_CHECKING: - import pydantic.v1 as pydantic - from pydantic.v1 import BaseModel - from pydantic.v1.fields import ModelField - else: - raise ModuleNotFoundError + import pydantic.v1 as pydantic + from pydantic.v1 import BaseModel + from pydantic.v1.fields import ModelField except ModuleNotFoundError: - import pydantic - from pydantic import BaseModel - from pydantic.fields import ModelField + if not TYPE_CHECKING: + import pydantic + from pydantic import BaseModel + from pydantic.fields import ModelField # type: ignore from reflex import constants @@ -31,15 +27,17 @@ def validate_field_name(bases: List[Type["BaseModel"]], field_name: str) -> None field_name: name of attribute Raises: - NameError: If state var field shadows another in its parent state + VarNameError: If state var field shadows another in its parent state """ + from reflex.utils.exceptions import VarNameError + reload = os.getenv(constants.RELOAD_CONFIG) == "True" for base in bases: try: if not reload and getattr(base, field_name, None): pass except TypeError as te: - raise NameError( + raise VarNameError( f'State var "{field_name}" in {base} has been shadowed by a substate var; ' f'use a different field name instead".' ) from te @@ -50,7 +48,7 @@ def validate_field_name(bases: List[Type["BaseModel"]], field_name: str) -> None pydantic.main.validate_field_name = validate_field_name # type: ignore -class Base(pydantic.BaseModel): +class Base(pydantic.BaseModel): # pyright: ignore [reportUnboundVariable] """The base class subclassed by all Reflex classes. This class wraps Pydantic and provides common methods such as @@ -75,7 +73,10 @@ def json(self) -> str: """ from reflex.utils.serializers import serialize - return self.__config__.json_dumps(self.dict(), default=serialize) + return self.__config__.json_dumps( # type: ignore + self.dict(), + default=serialize, + ) def set(self, **kwargs): """Set multiple fields and return the object. @@ -114,7 +115,7 @@ def add_field(cls, var: Any, default_value: Any): value=default_value, annotation=var._var_type, class_validators=None, - config=cls.__config__, + config=cls.__config__, # type: ignore ) cls.__fields__.update({var._var_name: new_field}) diff --git a/reflex/compiler/utils.py b/reflex/compiler/utils.py index 733d76a370..9f389d8118 100644 --- a/reflex/compiler/utils.py +++ b/reflex/compiler/utils.py @@ -3,19 +3,13 @@ from __future__ import annotations import os -from typing import TYPE_CHECKING, Any, Callable, Dict, Optional, Type, Union +from typing import Any, Callable, Dict, Optional, Type, Union from urllib.parse import urlparse try: - # TODO The type checking guard can be removed once - # reflex-hosting-cli tools are compatible with pydantic v2 - - if not TYPE_CHECKING: - from pydantic.v1.fields import ModelField - else: - raise ModuleNotFoundError + from pydantic.v1.fields import ModelField except ModuleNotFoundError: - from pydantic.fields import ModelField + from pydantic.fields import ModelField # type: ignore from reflex import constants from reflex.components.base import ( diff --git a/reflex/components/component.py b/reflex/components/component.py index 592e04c3c4..b039ebe0d3 100644 --- a/reflex/components/component.py +++ b/reflex/components/component.py @@ -374,7 +374,7 @@ def __init__(self, *args, **kwargs): raise ValueError( f"The {(comp_name := type(self).__name__)} does not take in an `{key}` event trigger. If {comp_name}" f" is a third party component make sure to add `{key}` to the component's event triggers. " - f"visit https://reflex.dev/docs/wrapping-react/logic/#event-triggers for more info." + f"visit https://reflex.dev/docs/wrapping-react/guide/#event-triggers for more info." ) if key in triggers: # Event triggers are bound to event chains. @@ -731,6 +731,7 @@ def create(cls, *children, **props) -> Component: # Import here to avoid circular imports. from reflex.components.base.bare import Bare from reflex.components.base.fragment import Fragment + from reflex.utils.exceptions import ComponentTypeError # Translate deprecated props to new names. new_prop_names = [ @@ -757,7 +758,7 @@ def validate_children(children): validate_children(child) # Make sure the child is a valid type. if not types._isinstance(child, ComponentChild): - raise TypeError( + raise ComponentTypeError( "Children of Reflex components must be other components, " "state vars, or primitive Python types. " f"Got child {child} of type {type(child)}.", @@ -781,7 +782,7 @@ def validate_children(children): return cls(children=children, **props) - def add_style(self) -> Style | None: + def add_style(self) -> dict[str, Any] | None: """Add style to the component. Downstream components can override this method to return a style dict @@ -801,20 +802,16 @@ def _add_style(self) -> Style: The style to add. """ styles = [] - vars = [] # Walk the MRO to call all `add_style` methods. for base in self._iter_parent_classes_with_method("add_style"): s = base.add_style(self) # type: ignore if s is not None: styles.append(s) - vars.append(s._var_data) _style = Style() for s in reversed(styles): _style.update(s) - - _style._var_data = VarData.merge(*vars) return _style def _get_component_style(self, styles: ComponentStyle) -> Style | None: diff --git a/reflex/components/core/debounce.py b/reflex/components/core/debounce.py index 7b3fd60180..88d1e1f943 100644 --- a/reflex/components/core/debounce.py +++ b/reflex/components/core/debounce.py @@ -119,6 +119,8 @@ def create(cls, *children: Component, **props: Any) -> Component: component = super().create(**props) component._get_style = child._get_style component.event_triggers.update(child.event_triggers) + component.children = child.children + component._rename_props = child._rename_props return component def get_event_triggers(self) -> dict[str, Any]: diff --git a/reflex/components/core/foreach.py b/reflex/components/core/foreach.py index 8bca0a3dbc..88f2886a8a 100644 --- a/reflex/components/core/foreach.py +++ b/reflex/components/core/foreach.py @@ -2,16 +2,24 @@ from __future__ import annotations import inspect -from hashlib import md5 from typing import Any, Callable, Iterable from reflex.components.base.fragment import Fragment from reflex.components.component import Component from reflex.components.tags import IterTag from reflex.constants import MemoizationMode +from reflex.utils import console from reflex.vars import Var +class ForeachVarError(TypeError): + """Raised when the iterable type is Any.""" + + +class ForeachRenderError(TypeError): + """Raised when there is an error with the foreach render function.""" + + class Foreach(Component): """A component that takes in an iterable and a render function and renders a list of components.""" @@ -24,56 +32,84 @@ class Foreach(Component): render_fn: Callable = Fragment.create @classmethod - def create(cls, iterable: Var[Iterable], render_fn: Callable, **props) -> Foreach: + def create( + cls, + iterable: Var[Iterable] | Iterable, + render_fn: Callable, + **props, + ) -> Foreach: """Create a foreach component. Args: iterable: The iterable to create components from. render_fn: A function from the render args to the component. - **props: The attributes to pass to each child component. + **props: The attributes to pass to each child component (deprecated). Returns: The foreach component. Raises: - TypeError: If the iterable is of type Any. + ForeachVarError: If the iterable is of type Any. """ - iterable = Var.create(iterable) # type: ignore + if props: + console.deprecate( + feature_name="Passing props to rx.foreach", + reason="it does not have the intended effect and may be confusing", + deprecation_version="0.5.0", + removal_version="0.6.0", + ) + iterable = Var.create_safe(iterable) if iterable._var_type == Any: - raise TypeError( - f"Could not foreach over var of type Any. (If you are trying to foreach over a state var, add a type annotation to the var.)" + raise ForeachVarError( + f"Could not foreach over var `{iterable._var_full_name}` of type Any. " + "(If you are trying to foreach over a state var, add a type annotation to the var). " + "See https://reflex.dev/docs/library/layout/foreach/" ) component = cls( iterable=iterable, render_fn=render_fn, - **props, ) - # Keep a ref to a rendered component to determine correct imports. - component.children = [ - component._render(props=dict(index_var_name="i")).render_component() - ] + # Keep a ref to a rendered component to determine correct imports/hooks/styles. + component.children = [component._render().render_component()] return component - def _render(self, props: dict[str, Any] | None = None) -> IterTag: - props = {} if props is None else props.copy() + def _render(self) -> IterTag: + props = {} - # Determine the arg var name based on the params accepted by render_fn. render_sig = inspect.signature(self.render_fn) params = list(render_sig.parameters.values()) + + # Validate the render function signature. + if len(params) == 0 or len(params) > 2: + raise ForeachRenderError( + "Expected 1 or 2 parameters in foreach render function, got " + f"{[p.name for p in params]}. See https://reflex.dev/docs/library/layout/foreach/" + ) + if len(params) >= 1: - props.setdefault("arg_var_name", params[0].name) + # Determine the arg var name based on the params accepted by render_fn. + props["arg_var_name"] = params[0].name - if len(params) >= 2: + if len(params) == 2: # Determine the index var name based on the params accepted by render_fn. - props.setdefault("index_var_name", params[1].name) - elif "index_var_name" not in props: - # Otherwise, use a deterministic index, based on the rendered code. - code_hash = md5(str(self.children[0].render()).encode("utf-8")).hexdigest() - props.setdefault("index_var_name", f"index_{code_hash}") + props["index_var_name"] = params[1].name + else: + # Otherwise, use a deterministic index, based on the render function bytecode. + code_hash = ( + hash(self.render_fn.__code__) + .to_bytes( + length=8, + byteorder="big", + signed=True, + ) + .hex() + ) + props["index_var_name"] = f"index_{code_hash}" return IterTag( iterable=self.iterable, render_fn=self.render_fn, + children=self.children, **props, ) @@ -84,19 +120,9 @@ def render(self): The dictionary for template of component. """ tag = self._render() - component = tag.render_component() return dict( - tag.add_props( - **self.event_triggers, - key=self.key, - sx=self.style, - id=self.id, - class_name=self.class_name, - ).set( - children=[component.render()], - props=tag.format_props(), - ), + tag, iterable_state=tag.iterable._var_full_name, arg_name=tag.arg_var_name, arg_index=tag.get_index_var_arg(), diff --git a/reflex/components/el/elements/media.py b/reflex/components/el/elements/media.py index 2865ca66a1..88c35e4cc5 100644 --- a/reflex/components/el/elements/media.py +++ b/reflex/components/el/elements/media.py @@ -1,6 +1,7 @@ """Element classes. This is an auto-generated file. Do not edit. See ../generate.py.""" from typing import Any, Union +from reflex import Component from reflex.vars import Var as Var from .base import BaseHTML @@ -116,6 +117,24 @@ class Img(BaseHTML): # The name of the map to use with the image use_map: Var[Union[str, int, bool]] + @classmethod + def create(cls, *children, **props) -> Component: + """Override create method to apply source attribute to value if user fails to pass in attribute. + + Args: + *children: The children of the component. + **props: The props of the component. + + Returns: + The component. + + """ + return ( + super().create(src=children[0], **props) + if children + else super().create(*children, **props) + ) + class Map(BaseHTML): """Display the map element.""" diff --git a/reflex/components/el/elements/media.pyi b/reflex/components/el/elements/media.pyi index d1aa409780..53e2a341f4 100644 --- a/reflex/components/el/elements/media.pyi +++ b/reflex/components/el/elements/media.pyi @@ -8,6 +8,7 @@ from reflex.vars import Var, BaseVar, ComputedVar from reflex.event import EventChain, EventHandler, EventSpec from reflex.style import Style from typing import Any, Union +from reflex import Component from reflex.vars import Var as Var from .base import BaseHTML @@ -470,7 +471,7 @@ class Img(BaseHTML): ] = None, **props ) -> "Img": - """Create the component. + """Override create method to apply source attribute to value if user fails to pass in attribute. Args: *children: The children of the component. @@ -512,6 +513,7 @@ class Img(BaseHTML): Returns: The component. + """ ... diff --git a/reflex/components/props.py b/reflex/components/props.py new file mode 100644 index 0000000000..92ee8a955b --- /dev/null +++ b/reflex/components/props.py @@ -0,0 +1,30 @@ +"""A class that holds props to be passed or applied to a component.""" +from __future__ import annotations + +from reflex.base import Base +from reflex.utils import format +from reflex.utils.serializers import serialize + + +class PropsBase(Base): + """Base for a class containing props that can be serialized as a JS object.""" + + def json(self) -> str: + """Convert the object to a json-like string. + + Vars will be unwrapped so they can represent actual JS var names and functions. + + Keys will be converted to camelCase. + + Returns: + The object as a Javascript Object literal. + """ + return format.unwrap_vars( + self.__config__.json_dumps( + { + format.to_camel_case(key): value + for key, value in self.dict().items() + }, + default=serialize, + ) + ) diff --git a/reflex/components/radix/primitives/accordion.py b/reflex/components/radix/primitives/accordion.py index 529c9efcb8..9cadfb35d7 100644 --- a/reflex/components/radix/primitives/accordion.py +++ b/reflex/components/radix/primitives/accordion.py @@ -6,9 +6,10 @@ from reflex.components.component import Component, ComponentNamespace from reflex.components.core.colors import color +from reflex.components.core.cond import cond from reflex.components.lucide.icon import Icon from reflex.components.radix.primitives.base import RadixPrimitiveComponent -from reflex.components.radix.themes.base import LiteralAccentColor +from reflex.components.radix.themes.base import LiteralAccentColor, LiteralRadius from reflex.style import Style from reflex.utils import imports from reflex.vars import Var, get_uuid_string_var @@ -19,6 +20,32 @@ LiteralAccordionVariant = Literal["classic", "soft", "surface", "outline", "ghost"] DEFAULT_ANIMATION_DURATION = 250 +DEFAULT_ANIMATION_EASING = "cubic-bezier(0.87, 0, 0.13, 1)" + + +def _inherited_variant_selector( + variant: Var[LiteralAccordionVariant] | LiteralAccordionVariant, + *selectors: str, +) -> str: + """Create a multi CSS selector for targeting variant against the given selectors. + + Args: + variant: The variant to target. + selectors: The selectors to apply the variant to (default &) + + Returns: + A CSS selector that is more specific on elements that directly set the variant. + """ + if not selectors: + selectors = ("&",) + # Prefer the `data-variant` that is set directly on the selector, + # but also inherit the `data-variant` from any parent element. + return ", ".join( + [ + f"{selector}[data-variant='{variant}'], *:where([data-variant='{variant}']) {selector}" + for selector in selectors + ] + ) class AccordionComponent(RadixPrimitiveComponent): @@ -30,14 +57,14 @@ class AccordionComponent(RadixPrimitiveComponent): color_scheme: Var[LiteralAccentColor] # The variant of the component. - variant: Var[LiteralAccordionVariant] = Var.create_safe("classic") + variant: Var[LiteralAccordionVariant] def add_style(self) -> Style | None: """Add style to the component.""" if self.color_scheme is not None: self.custom_attrs["data-accent-color"] = self.color_scheme - - self.custom_attrs["data-variant"] = self.variant + if self.variant is not None: + self.custom_attrs["data-variant"] = self.variant def _exclude_props(self) -> list[str]: return ["color_scheme", "variant"] @@ -71,28 +98,27 @@ class AccordionRoot(AccordionComponent): # The orientation of the accordion. orientation: Var[LiteralAccordionOrientation] - # The variant of the accordion. - variant: Var[LiteralAccordionVariant] = Var.create_safe("classic") + # The radius of the accordion corners. + radius: Var[LiteralRadius] - _valid_children: List[str] = ["AccordionItem"] + # The time in milliseconds to animate open and close + duration: Var[int] = Var.create_safe(DEFAULT_ANIMATION_DURATION) - @classmethod - def create(cls, *children, **props) -> Component: - """Create the Accordion root component. + # The easing function to use for the animation. + easing: Var[str] = Var.create_safe(DEFAULT_ANIMATION_EASING) - Args: - *children: The children of the component. - **props: The properties of the component. + # Whether to show divider lines between items. + show_dividers: Var[bool] - Returns: - The Accordion root Component. - """ - for child in children: - if isinstance(child, AccordionItem): - child.color_scheme = props.get("color_scheme") # type: ignore - child.variant = props.get("variant") # type: ignore + _valid_children: List[str] = ["AccordionItem"] - return super().create(*children, **props) + def _exclude_props(self) -> list[str]: + return super()._exclude_props() + [ + "radius", + "duration", + "easing", + "show_dividers", + ] def get_event_triggers(self) -> Dict[str, Any]: """Get the events triggers signatures for the component. @@ -111,30 +137,42 @@ def add_style(self): Returns: The style of the component. """ - return Style( - { - "border_radius": "6px", - "box_shadow": f"0 2px 10px {color('black', 1, alpha=True)}", - "&[data-variant='classic']": { - "background_color": color("accent", 9), - "box_shadow": f"0 2px 10px {color('black', 4, alpha=True)}", - }, - "&[data-variant='soft']": { - "background_color": color("accent", 3), - }, - "&[data-variant='outline']": { - "border": f"1px solid {color('accent', 6)}", - }, - "&[data-variant='surface']": { - "border": f"1px solid {color('accent', 6)}", - "background_color": color("accent", 3), - }, - "&[data-variant='ghost']": { - "background_color": "none", - "box_shadow": "None", - }, - } - ) + if self.radius is not None: + self.custom_attrs["data-radius"] = self.radius + if self.variant is None: + # The default variant is classic + self.custom_attrs["data-variant"] = "classic" + + style = { + "border_radius": "var(--radius-4)", + "box_shadow": f"0 2px 10px {color('black', 1, alpha=True)}", + "&[data-variant='classic']": { + "background_color": color("accent", 9), + "box_shadow": f"0 2px 10px {color('black', 4, alpha=True)}", + }, + "&[data-variant='soft']": { + "background_color": color("accent", 3), + }, + "&[data-variant='outline']": { + "border": f"1px solid {color('accent', 6)}", + }, + "&[data-variant='surface']": { + "border": f"1px solid {color('accent', 6)}", + "background_color": "var(--accent-surface)", + }, + "&[data-variant='ghost']": { + "background_color": "none", + "box_shadow": "None", + }, + "--animation-duration": f"{self.duration}ms", + "--animation-easing": self.easing, + } + if self.show_dividers is not None: + style["--divider-px"] = cond(self.show_dividers, "1px", "0") + else: + style["&[data-variant='outline']"]["--divider-px"] = "1px" + style["&[data-variant='surface']"]["--divider-px"] = "1px" + return Style(style) class AccordionItem(AccordionComponent): @@ -185,23 +223,28 @@ def create( ): cls_name = f"{cls_name} AccordionItem" + color_scheme = props.get("color_scheme") + variant = props.get("variant") + if (header is not None) and (content is not None): children = [ AccordionHeader.create( AccordionTrigger.create( header, AccordionIcon.create( - color_scheme=props.get("color_scheme"), - variant=props.get("variant"), + color_scheme=color_scheme, + variant=variant, ), - color_scheme=props.get("color_scheme"), - variant=props.get("variant"), + color_scheme=color_scheme, + variant=variant, ), - color_scheme=props.get("color_scheme"), - variant=props.get("variant"), + color_scheme=color_scheme, + variant=variant, ), AccordionContent.create( - content, color_scheme=props.get("color_scheme") + content, + color_scheme=color_scheme, + variant=variant, ), ] @@ -213,29 +256,35 @@ def add_style(self) -> Style | None: Returns: The style of the component. """ - for child in self.children: - if isinstance(child, (AccordionHeader, AccordionContent)): - child.color_scheme = self.color_scheme - child.variant = self.variant - + divider_style = f"var(--divider-px) solid {color('gray', 6, alpha=True)}" return Style( { "overflow": "hidden", "width": "100%", "margin_top": "1px", + "border_top": divider_style, "&:first-child": { "margin_top": 0, - "border_top_left_radius": "4px", - "border_top_right_radius": "4px", + "border_top": 0, + "border_top_left_radius": "var(--radius-4)", + "border_top_right_radius": "var(--radius-4)", }, "&:last-child": { - "border_bottom_left_radius": "4px", - "border_bottom_right_radius": "4px", + "border_bottom_left_radius": "var(--radius-4)", + "border_bottom_right_radius": "var(--radius-4)", }, "&:focus-within": { "position": "relative", "z_index": 1, }, + _inherited_variant_selector("ghost", "&:first-child"): { + "border_radius": 0, + "border_top": divider_style, + }, + _inherited_variant_selector("ghost", "&:last-child"): { + "border_radius": 0, + "border_bottom": divider_style, + }, } ) @@ -271,17 +320,9 @@ def add_style(self) -> Style | None: Returns: The style of the component. """ - for child in self.children: - if isinstance(child, AccordionTrigger): - child.color_scheme = self.color_scheme - child.variant = self.variant - return Style({"display": "flex"}) -cubic_bezier = "cubic-bezier(0.87, 0, 0.13, 1)" - - class AccordionTrigger(AccordionComponent): """An accordion component.""" @@ -313,24 +354,18 @@ def add_style(self) -> Style | None: Returns: The style of the component. """ - for child in self.children: - if isinstance(child, AccordionIcon): - child.color_scheme = self.color_scheme - child.variant = self.variant - return Style( { "color": color("accent", 11), + "font_size": "1.1em", "line_height": 1, - "font_size": "15px", "justify_content": "space-between", "align_items": "center", "flex": 1, "display": "flex", - "padding": "0 20px", - "height": "45px", - "font_family": "inherit", + "padding": "var(--space-3) var(--space-4)", "width": "100%", + "box_shadow": f"0 var(--divider-px) 0 {color('gray', 6, alpha=True)}", "&[data-state='open'] > .AccordionChevron": { "transform": "rotate(180deg)", }, @@ -338,17 +373,15 @@ def add_style(self) -> Style | None: "background_color": color("accent", 4), }, "& > .AccordionChevron": { - "transition": f"transform {DEFAULT_ANIMATION_DURATION}ms {cubic_bezier}", + "transition": f"transform var(--animation-duration) var(--animation-easing)", }, - "&[data-variant='classic']": { - "color": color("accent", 12), - "box_shadow": color("accent", 11), + _inherited_variant_selector("classic"): { + "color": "var(--accent-contrast)", "&:hover": { "background_color": color("accent", 10), }, "& > .AccordionChevron": { - "color": color("accent", 12), - "transition": f"transform {DEFAULT_ANIMATION_DURATION}ms {cubic_bezier}", + "color": "var(--accent-contrast)", }, }, } @@ -444,30 +477,31 @@ def add_style(self) -> Style | None: The style of the component. """ slideDown = Var.create( - f"${{slideDown}} {DEFAULT_ANIMATION_DURATION}ms {cubic_bezier}", + f"${{slideDown}} var(--animation-duration) var(--animation-easing)", _var_is_string=True, ) slideUp = Var.create( - f"${{slideUp}} {DEFAULT_ANIMATION_DURATION}ms {cubic_bezier}", + f"${{slideUp}} var(--animation-duration) var(--animation-easing)", _var_is_string=True, ) return Style( { "overflow": "hidden", - "font_size": "10px", "color": color("accent", 11), - "background_color": color("accent", 3), - "padding": "0 15px", + "padding_x": "var(--space-4)", + # Apply before and after content to avoid height animation jank. + "&:before, &:after": { + "content": "' '", + "display": "block", + "height": "var(--space-3)", + }, "&[data-state='open']": {"animation": slideDown}, "&[data-state='closed']": {"animation": slideUp}, - "&[data-variant='classic']": { - "color": color("accent", 12), - "background_color": color("accent", 9), + _inherited_variant_selector("classic"): { + "color": "var(--accent-contrast)", }, - "&[data-variant='outline']": {"background_color": "transparent"}, - "&[data-variant='ghost']": {"background_color": "transparent"}, } ) diff --git a/reflex/components/radix/primitives/accordion.pyi b/reflex/components/radix/primitives/accordion.pyi index 20bc8dfe0d..e7ae3ff190 100644 --- a/reflex/components/radix/primitives/accordion.pyi +++ b/reflex/components/radix/primitives/accordion.pyi @@ -10,9 +10,10 @@ from reflex.style import Style from typing import Any, Dict, List, Literal, Optional, Union from reflex.components.component import Component, ComponentNamespace from reflex.components.core.colors import color +from reflex.components.core.cond import cond from reflex.components.lucide.icon import Icon from reflex.components.radix.primitives.base import RadixPrimitiveComponent -from reflex.components.radix.themes.base import LiteralAccentColor +from reflex.components.radix.themes.base import LiteralAccentColor, LiteralRadius from reflex.style import Style from reflex.utils import imports from reflex.vars import Var, get_uuid_string_var @@ -22,6 +23,7 @@ LiteralAccordionDir = Literal["ltr", "rtl"] LiteralAccordionOrientation = Literal["vertical", "horizontal"] LiteralAccordionVariant = Literal["classic", "soft", "surface", "outline", "ghost"] DEFAULT_ANIMATION_DURATION = 250 +DEFAULT_ANIMATION_EASING = "cubic-bezier(0.87, 0, 0.13, 1)" class AccordionComponent(RadixPrimitiveComponent): def add_style(self) -> Style | None: ... @@ -173,6 +175,8 @@ class AccordionComponent(RadixPrimitiveComponent): ... class AccordionRoot(AccordionComponent): + def get_event_triggers(self) -> Dict[str, Any]: ... + def add_style(self): ... @overload @classmethod def create( # type: ignore @@ -196,12 +200,15 @@ class AccordionRoot(AccordionComponent): Literal["vertical", "horizontal"], ] ] = None, - variant: Optional[ + radius: Optional[ Union[ - Var[Literal["classic", "soft", "surface", "outline", "ghost"]], - Literal["classic", "soft", "surface", "outline", "ghost"], + Var[Literal["none", "small", "medium", "large", "full"]], + Literal["none", "small", "medium", "large", "full"], ] ] = None, + duration: Optional[Union[Var[int], int]] = None, + easing: Optional[Union[Var[str], str]] = None, + show_dividers: Optional[Union[Var[bool], bool]] = None, color_scheme: Optional[ Union[ Var[ @@ -264,6 +271,12 @@ class AccordionRoot(AccordionComponent): ], ] ] = None, + variant: Optional[ + Union[ + Var[Literal["classic", "soft", "surface", "outline", "ghost"]], + Literal["classic", "soft", "surface", "outline", "ghost"], + ] + ] = None, as_child: Optional[Union[Var[bool], bool]] = None, style: Optional[Style] = None, key: Optional[Any] = None, @@ -321,7 +334,7 @@ class AccordionRoot(AccordionComponent): ] = None, **props ) -> "AccordionRoot": - """Create the Accordion root component. + """Create the component. Args: *children: The children of the component. @@ -332,8 +345,12 @@ class AccordionRoot(AccordionComponent): disabled: Whether or not the accordion is disabled. dir: The reading direction of the accordion when applicable. orientation: The orientation of the accordion. - variant: The variant of the component. + radius: The radius of the accordion corners. + duration: The time in milliseconds to animate open and close + easing: The easing function to use for the animation. + show_dividers: Whether to show divider lines between items. color_scheme: The color scheme of the component. + variant: The variant of the component. as_child: Change the default rendered element for the one passed as a child. style: The style of the component. key: A unique key for the component. @@ -341,14 +358,12 @@ class AccordionRoot(AccordionComponent): class_name: The class name for the component. autofocus: Whether the component should take the focus once the page is loaded custom_attrs: custom attribute - **props: The properties of the component. + **props: The props of the component. Returns: - The Accordion root Component. + The component. """ ... - def get_event_triggers(self) -> Dict[str, Any]: ... - def add_style(self): ... class AccordionItem(AccordionComponent): @overload @@ -656,8 +671,6 @@ class AccordionHeader(AccordionComponent): ... def add_style(self) -> Style | None: ... -cubic_bezier = "cubic-bezier(0.87, 0, 0.13, 1)" - class AccordionTrigger(AccordionComponent): @overload @classmethod diff --git a/reflex/components/radix/primitives/form.py b/reflex/components/radix/primitives/form.py index 3e9e38f38a..3369673dac 100644 --- a/reflex/components/radix/primitives/form.py +++ b/reflex/components/radix/primitives/form.py @@ -44,7 +44,7 @@ def add_style(self) -> Style | None: Returns: The style of the component. """ - return Style({"width": "260px"}) + return Style({"width": "100%"}) class FormField(FormComponent): diff --git a/reflex/components/radix/themes/color_mode.py b/reflex/components/radix/themes/color_mode.py index 8a29d3b870..2329918f4c 100644 --- a/reflex/components/radix/themes/color_mode.py +++ b/reflex/components/radix/themes/color_mode.py @@ -23,7 +23,8 @@ from reflex.components.component import BaseComponent from reflex.components.core.cond import Cond, color_mode_cond, cond from reflex.components.lucide.icon import Icon -from reflex.style import color_mode, toggle_color_mode +from reflex.components.radix.themes.components.switch import Switch +from reflex.style import LIGHT_COLOR_MODE, color_mode, toggle_color_mode from reflex.utils import console from reflex.vars import BaseVar, Var @@ -143,11 +144,34 @@ def create( ) +class ColorModeSwitch(Switch): + """Switch for toggling light / dark mode via toggle_color_mode.""" + + @classmethod + def create(cls, *children, **props): + """Create a switch component bound to color_mode. + + Args: + *children: The children of the component. + **props: The props to pass to the component. + + Returns: + The switch component. + """ + return Switch.create( + *children, + checked=color_mode != LIGHT_COLOR_MODE, + on_change=toggle_color_mode, + **props, + ) + + class ColorModeNamespace(BaseVar): """Namespace for color mode components.""" icon = staticmethod(ColorModeIcon.create) button = staticmethod(ColorModeIconButton.create) + switch = staticmethod(ColorModeSwitch.create) color_mode_var_and_namespace = ColorModeNamespace(**dataclasses.asdict(color_mode)) diff --git a/reflex/components/radix/themes/color_mode.pyi b/reflex/components/radix/themes/color_mode.pyi index 1c61743666..e4a2cbcacd 100644 --- a/reflex/components/radix/themes/color_mode.pyi +++ b/reflex/components/radix/themes/color_mode.pyi @@ -12,7 +12,8 @@ from typing import Literal, get_args from reflex.components.component import BaseComponent from reflex.components.core.cond import Cond, color_mode_cond, cond from reflex.components.lucide.icon import Icon -from reflex.style import color_mode, toggle_color_mode +from reflex.components.radix.themes.components.switch import Switch +from reflex.style import LIGHT_COLOR_MODE, color_mode, toggle_color_mode from reflex.utils import console from reflex.vars import BaseVar, Var from .components.icon_button import IconButton @@ -362,8 +363,184 @@ class ColorModeIconButton(IconButton): """ ... +class ColorModeSwitch(Switch): + @overload + @classmethod + def create( # type: ignore + cls, + *children, + as_child: Optional[Union[Var[bool], bool]] = None, + default_checked: Optional[Union[Var[bool], bool]] = None, + checked: Optional[Union[Var[bool], bool]] = None, + disabled: Optional[Union[Var[bool], bool]] = None, + required: Optional[Union[Var[bool], bool]] = None, + name: Optional[Union[Var[str], str]] = None, + value: Optional[Union[Var[str], str]] = None, + size: Optional[ + Union[Var[Literal["1", "2", "3"]], Literal["1", "2", "3"]] + ] = None, + variant: Optional[ + Union[ + Var[Literal["classic", "surface", "soft"]], + Literal["classic", "surface", "soft"], + ] + ] = None, + color_scheme: Optional[ + Union[ + Var[ + Literal[ + "tomato", + "red", + "ruby", + "crimson", + "pink", + "plum", + "purple", + "violet", + "iris", + "indigo", + "blue", + "cyan", + "teal", + "jade", + "green", + "grass", + "brown", + "orange", + "sky", + "mint", + "lime", + "yellow", + "amber", + "gold", + "bronze", + "gray", + ] + ], + Literal[ + "tomato", + "red", + "ruby", + "crimson", + "pink", + "plum", + "purple", + "violet", + "iris", + "indigo", + "blue", + "cyan", + "teal", + "jade", + "green", + "grass", + "brown", + "orange", + "sky", + "mint", + "lime", + "yellow", + "amber", + "gold", + "bronze", + "gray", + ], + ] + ] = None, + high_contrast: Optional[Union[Var[bool], bool]] = None, + radius: Optional[ + Union[ + Var[Literal["none", "small", "full"]], Literal["none", "small", "full"] + ] + ] = 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, function, BaseVar] + ] = None, + on_change: Optional[ + Union[EventHandler, EventSpec, list, function, BaseVar] + ] = None, + on_click: Optional[ + Union[EventHandler, EventSpec, list, function, BaseVar] + ] = None, + on_context_menu: Optional[ + Union[EventHandler, EventSpec, list, function, BaseVar] + ] = None, + on_double_click: Optional[ + Union[EventHandler, EventSpec, list, function, BaseVar] + ] = None, + on_focus: Optional[ + Union[EventHandler, EventSpec, list, function, BaseVar] + ] = None, + on_mount: Optional[ + Union[EventHandler, EventSpec, list, function, BaseVar] + ] = None, + on_mouse_down: Optional[ + Union[EventHandler, EventSpec, list, function, BaseVar] + ] = None, + on_mouse_enter: Optional[ + Union[EventHandler, EventSpec, list, function, BaseVar] + ] = None, + on_mouse_leave: Optional[ + Union[EventHandler, EventSpec, list, function, BaseVar] + ] = None, + on_mouse_move: Optional[ + Union[EventHandler, EventSpec, list, function, BaseVar] + ] = None, + on_mouse_out: Optional[ + Union[EventHandler, EventSpec, list, function, BaseVar] + ] = None, + on_mouse_over: Optional[ + Union[EventHandler, EventSpec, list, function, BaseVar] + ] = None, + on_mouse_up: Optional[ + Union[EventHandler, EventSpec, list, function, BaseVar] + ] = None, + on_scroll: Optional[ + Union[EventHandler, EventSpec, list, function, BaseVar] + ] = None, + on_unmount: Optional[ + Union[EventHandler, EventSpec, list, function, BaseVar] + ] = None, + **props + ) -> "ColorModeSwitch": + """Create a switch component bound to color_mode. + + Args: + *children: The children of the component. + as_child: Change the default rendered element for the one passed as a child, merging their props and behavior. + default_checked: Whether the switch is checked by default + checked: Whether the switch is checked + disabled: If true, prevent the user from interacting with the switch + required: If true, the user must interact with the switch to submit the form + name: The name of the switch (when submitting a form) + value: The value associated with the "on" position + size: Switch size "1" - "4" + variant: Variant of switch: "classic" | "surface" | "soft" + color_scheme: Override theme color for switch + high_contrast: Whether to render the switch with higher contrast color against background + radius: Override theme radius for switch: "none" | "small" | "full" + 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 to pass to the component. + + Returns: + The switch component. + """ + ... + class ColorModeNamespace(BaseVar): icon = staticmethod(ColorModeIcon.create) button = staticmethod(ColorModeIconButton.create) + switch = staticmethod(ColorModeSwitch.create) color_mode_var_and_namespace = ColorModeNamespace(**dataclasses.asdict(color_mode)) diff --git a/reflex/components/radix/themes/components/__init__.py b/reflex/components/radix/themes/components/__init__.py index 0559e98423..7df1f77079 100644 --- a/reflex/components/radix/themes/components/__init__.py +++ b/reflex/components/radix/themes/components/__init__.py @@ -62,7 +62,8 @@ "inset", "menu", "popover", - "progress", + # progress is in experimental namespace until https://github.com/radix-ui/themes/pull/492 + # "progress", "radio", "radio_cards", "radio_group", diff --git a/reflex/components/radix/themes/components/icon_button.py b/reflex/components/radix/themes/components/icon_button.py index cd21501dbd..452e376401 100644 --- a/reflex/components/radix/themes/components/icon_button.py +++ b/reflex/components/radix/themes/components/icon_button.py @@ -70,16 +70,19 @@ def create(cls, *children, **props) -> Component: "IconButton requires a child icon. Pass a string as the first child or a rx.icon." ) if "size" in props: - RADIX_TO_LUCIDE_SIZE = {"1": "12px", "2": "24px", "3": "36px", "4": "48px"} + RADIX_TO_LUCIDE_SIZE = {"1": 12, "2": 24, "3": 36, "4": 48} if isinstance(props["size"], str): children[0].size = RADIX_TO_LUCIDE_SIZE[props["size"]] else: - children[0].size = Match.create( + size_map_var = Match.create( props["size"], *[(size, px) for size, px in RADIX_TO_LUCIDE_SIZE.items()], - "12px", + 12, ) + if not isinstance(size_map_var, Var): + raise ValueError(f"Match did not return a Var: {size_map_var}") + children[0].size = size_map_var return super().create(*children, **props) def add_style(self): diff --git a/reflex/components/radix/themes/components/progress.py b/reflex/components/radix/themes/components/progress.py index 4121ed12bc..bcd64edb85 100644 --- a/reflex/components/radix/themes/components/progress.py +++ b/reflex/components/radix/themes/components/progress.py @@ -2,6 +2,7 @@ from typing import Literal +from reflex.components.component import Component from reflex.vars import Var from ..base import LiteralAccentColor, RadixThemesComponent @@ -12,9 +13,12 @@ class Progress(RadixThemesComponent): tag = "Progress" - # The value of the progress bar: "0" to "100" + # The value of the progress bar: 0 to max (default 100) value: Var[int] + # The maximum progress value. + max: Var[int] + # The size of the progress bar: "1" | "2" | "3" size: Var[Literal["1", "2", "3"]] @@ -33,5 +37,19 @@ class Progress(RadixThemesComponent): # The duration of the progress bar animation. Once the duration times out, the progress bar will start an indeterminate animation. duration: Var[str] + @classmethod + def create(cls, *children, **props) -> Component: + """Create a Progress component. + + Args: + *children: The children of the component. + **props: The properties of the component. + + Returns: + The Progress Component. + """ + props.setdefault("width", "100%") + return super().create(*children, **props) + progress = Progress.create diff --git a/reflex/components/radix/themes/components/progress.pyi b/reflex/components/radix/themes/components/progress.pyi index 286c7918e1..e72b832732 100644 --- a/reflex/components/radix/themes/components/progress.pyi +++ b/reflex/components/radix/themes/components/progress.pyi @@ -8,6 +8,7 @@ from reflex.vars import Var, BaseVar, ComputedVar from reflex.event import EventChain, EventHandler, EventSpec from reflex.style import Style from typing import Literal +from reflex.components.component import Component from reflex.vars import Var from ..base import LiteralAccentColor, RadixThemesComponent @@ -18,6 +19,7 @@ class Progress(RadixThemesComponent): cls, *children, value: Optional[Union[Var[int], int]] = None, + max: Optional[Union[Var[int], int]] = None, size: Optional[ Union[Var[Literal["1", "2", "3"]], Literal["1", "2", "3"]] ] = None, @@ -150,14 +152,12 @@ class Progress(RadixThemesComponent): ] = None, **props ) -> "Progress": - """Create a new component instance. - - Will prepend "RadixThemes" to the component tag to avoid conflicts with - other UI libraries for common names, like Text and Button. + """Create a Progress component. Args: - *children: Child components. - value: The value of the progress bar: "0" to "100" + *children: The children of the component. + value: The value of the progress bar: 0 to max (default 100) + max: The maximum progress value. size: The size of the progress bar: "1" | "2" | "3" variant: The variant of the progress bar: "classic" | "surface" | "soft" color_scheme: The color theme of the progress bar @@ -170,10 +170,10 @@ class Progress(RadixThemesComponent): 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: Component properties. + **props: The properties of the component. Returns: - A new component instance. + The Progress Component. """ ... diff --git a/reflex/components/radix/themes/components/text_field.py b/reflex/components/radix/themes/components/text_field.py index 148bddcffb..970b950e3e 100644 --- a/reflex/components/radix/themes/components/text_field.py +++ b/reflex/components/radix/themes/components/text_field.py @@ -1,11 +1,15 @@ """Interactive components provided by @radix-ui/themes.""" +from __future__ import annotations from typing import Any, Dict, Literal, Union from reflex.components import el +from reflex.components.base.fragment import Fragment from reflex.components.component import Component, ComponentNamespace from reflex.components.core.debounce import DebounceInput from reflex.constants import EventTriggers +from reflex.style import Style, format_as_emotion +from reflex.utils import console from reflex.vars import Var from ..base import ( @@ -79,10 +83,85 @@ def create(cls, *children, **props) -> Component: Returns: The component. """ + component = super().create(*children, **props) if props.get("value") is not None and props.get("on_change"): # create a debounced input if the user requests full control to avoid typing jank - return DebounceInput.create(super().create(*children, **props)) - return super().create(*children, **props) + return DebounceInput.create(component) + return component + + @classmethod + def create_root_deprecated(cls, *children, **props) -> Component: + """Create a Fragment component (wrapper for deprecated name). + + Copy the attributes that were previously defined on TextFieldRoot in 0.4.9 to + any child input elements (via custom_attrs). + + Args: + *children: The children of the component. + **props: The properties of the component. + + Returns: + The component. + """ + console.deprecate( + feature_name="rx.input.root", + reason="use rx.input without the .root suffix", + deprecation_version="0.5.0", + removal_version="0.6.0", + ) + inputs = [ + child + for child in children + if isinstance(child, (TextFieldRoot, DebounceInput)) + ] + if not inputs: + # Old-style where no explicit child input was provided + return cls.create(*children, **props) + slots = [child for child in children if isinstance(child, TextFieldSlot)] + carry_props = { + prop: props.pop(prop) + for prop in ["size", "variant", "color_scheme", "radius"] + if prop in props + } + template = cls.create(**props) + for child in inputs: + child.children.extend(slots) + custom_attrs = child.custom_attrs + custom_attrs.update( + { + prop: value + for prop, value in carry_props.items() + if prop not in custom_attrs and getattr(child, prop) is None + } + ) + style = Style(template.style) + style.update(child.style) + child._get_style = lambda style=style: { + "css": Var.create(format_as_emotion(style)) + } + for trigger in template.event_triggers: + if trigger not in child.event_triggers: + child.event_triggers[trigger] = template.event_triggers[trigger] + return Fragment.create(*inputs) + + @classmethod + def create_input_deprecated(cls, *children, **props) -> Component: + """Create a TextFieldRoot component (wrapper for deprecated name). + + Args: + *children: The children of the component. + **props: The properties of the component. + + Returns: + The component. + """ + console.deprecate( + feature_name="rx.input.input", + reason="use rx.input without the .input suffix", + deprecation_version="0.5.0", + removal_version="0.6.0", + ) + return cls.create(*children, **props) def get_event_triggers(self) -> Dict[str, Any]: """Get the event triggers that pass the component's value to the handler. @@ -112,7 +191,8 @@ class TextFieldSlot(RadixThemesComponent): class TextField(ComponentNamespace): """TextField components namespace.""" - root = staticmethod(TextFieldRoot.create) + root = staticmethod(TextFieldRoot.create_root_deprecated) + input = staticmethod(TextFieldRoot.create_input_deprecated) slot = staticmethod(TextFieldSlot.create) __call__ = staticmethod(TextFieldRoot.create) diff --git a/reflex/components/radix/themes/components/text_field.pyi b/reflex/components/radix/themes/components/text_field.pyi index 7811d2bbae..c14fb031af 100644 --- a/reflex/components/radix/themes/components/text_field.pyi +++ b/reflex/components/radix/themes/components/text_field.pyi @@ -9,9 +9,12 @@ from reflex.event import EventChain, EventHandler, EventSpec from reflex.style import Style from typing import Any, Dict, Literal, Union from reflex.components import el +from reflex.components.base.fragment import Fragment from reflex.components.component import Component, ComponentNamespace from reflex.components.core.debounce import DebounceInput from reflex.constants import EventTriggers +from reflex.style import Style, format_as_emotion +from reflex.utils import console from reflex.vars import Var from ..base import LiteralAccentColor, LiteralRadius, RadixThemesComponent @@ -263,6 +266,10 @@ class TextFieldRoot(el.Div, RadixThemesComponent): The component. """ ... + @classmethod + def create_root_deprecated(cls, *children, **props) -> Component: ... + @classmethod + def create_input_deprecated(cls, *children, **props) -> Component: ... def get_event_triggers(self) -> Dict[str, Any]: ... class TextFieldSlot(RadixThemesComponent): @@ -408,7 +415,8 @@ class TextFieldSlot(RadixThemesComponent): ... class TextField(ComponentNamespace): - root = staticmethod(TextFieldRoot.create) + root = staticmethod(TextFieldRoot.create_root_deprecated) + input = staticmethod(TextFieldRoot.create_input_deprecated) slot = staticmethod(TextFieldSlot.create) @staticmethod diff --git a/reflex/components/sonner/toast.py b/reflex/components/sonner/toast.py index 5781f1ee44..d820af2da5 100644 --- a/reflex/components/sonner/toast.py +++ b/reflex/components/sonner/toast.py @@ -2,16 +2,20 @@ from __future__ import annotations -from typing import Literal +from typing import Any, Literal, Optional from reflex.base import Base from reflex.components.component import Component, ComponentNamespace from reflex.components.lucide.icon import Icon -from reflex.event import EventSpec, call_script +from reflex.components.props import PropsBase +from reflex.event import ( + EventSpec, + call_script, +) from reflex.style import Style, color_mode from reflex.utils import format from reflex.utils.imports import ImportVar -from reflex.utils.serializers import serialize +from reflex.utils.serializers import serialize, serializer from reflex.vars import Var, VarData LiteralPosition = Literal[ @@ -27,46 +31,68 @@ toast_ref = Var.create_safe("refs['__toast']") -class PropsBase(Base): - """Base class for all props classes.""" +class ToastAction(Base): + """A toast action that render a button in the toast.""" - def json(self) -> str: - """Convert the object to a json string. + label: str + on_click: Any - Returns: - The object as a json string. - """ - from reflex.utils.serializers import serialize - return self.__config__.json_dumps( - {format.to_camel_case(key): value for key, value in self.dict().items()}, - default=serialize, +@serializer +def serialize_action(action: ToastAction) -> dict: + """Serialize a toast action. + + Args: + action: The toast action to serialize. + + Returns: + The serialized toast action with on_click formatted to queue the given event. + """ + return { + "label": action.label, + "onClick": format.format_queue_events(action.on_click), + } + + +def _toast_callback_signature(toast: Var) -> list[Var]: + """The signature for the toast callback, stripping out unserializable keys. + + Args: + toast: The toast variable. + + Returns: + A function call stripping non-serializable members of the toast object. + """ + return [ + Var.create_safe( + f"(() => {{let {{action, cancel, onDismiss, onAutoClose, ...rest}} = {toast}; return rest}})()" ) + ] class ToastProps(PropsBase): """Props for the toast component.""" # Toast's description, renders underneath the title. - description: str = "" + description: Optional[str] # Whether to show the close button. - close_button: bool = False + close_button: Optional[bool] # Dark toast in light mode and vice versa. - invert: bool = False + invert: Optional[bool] # Control the sensitivity of the toast for screen readers - important: bool = False + important: Optional[bool] # Time in milliseconds that should elapse before automatically closing the toast. - duration: int = 4000 + duration: Optional[int] # Position of the toast. - position: LiteralPosition = "bottom-right" + position: Optional[LiteralPosition] # If false, it'll prevent the user from dismissing the toast. - dismissible: bool = True + dismissible: Optional[bool] # TODO: fix serialization of icons for toast? (might not be possible yet) # Icon displayed in front of toast's text, aligned vertically. @@ -74,25 +100,63 @@ class ToastProps(PropsBase): # TODO: fix implementation for action / cancel buttons # Renders a primary button, clicking it will close the toast. - # action: str = "" + action: Optional[ToastAction] # Renders a secondary button, clicking it will close the toast. - # cancel: str = "" + cancel: Optional[ToastAction] # Custom id for the toast. - id: str = "" + id: Optional[str] # Removes the default styling, which allows for easier customization. - unstyled: bool = False + unstyled: Optional[bool] # Custom style for the toast. - style: Style = Style() + style: Optional[Style] + # XXX: These still do not seem to work # Custom style for the toast primary button. - # action_button_styles: Style = Style() + action_button_styles: Optional[Style] # Custom style for the toast secondary button. - # cancel_button_styles: Style = Style() + cancel_button_styles: Optional[Style] + + # The function gets called when either the close button is clicked, or the toast is swiped. + on_dismiss: Optional[Any] + + # Function that gets called when the toast disappears automatically after it's timeout (duration` prop). + on_auto_close: Optional[Any] + + def dict(self, *args, **kwargs) -> dict: + """Convert the object to a dictionary. + + Args: + *args: The arguments to pass to the base class. + **kwargs: The keyword arguments to pass to the base + + Returns: + The object as a dictionary with ToastAction fields intact. + """ + kwargs.setdefault("exclude_none", True) + d = super().dict(*args, **kwargs) + # Keep these fields as ToastAction so they can be serialized specially + if "action" in d: + d["action"] = self.action + if isinstance(self.action, dict): + d["action"] = ToastAction(**self.action) + if "cancel" in d: + d["cancel"] = self.cancel + if isinstance(self.cancel, dict): + d["cancel"] = ToastAction(**self.cancel) + if "on_dismiss" in d: + d["on_dismiss"] = format.format_queue_events( + self.on_dismiss, _toast_callback_signature + ) + if "on_auto_close" in d: + d["on_auto_close"] = format.format_queue_events( + self.on_auto_close, _toast_callback_signature + ) + return d class Toaster(Component): diff --git a/reflex/components/sonner/toast.pyi b/reflex/components/sonner/toast.pyi index 2bf937703c..5bd6cdeb41 100644 --- a/reflex/components/sonner/toast.pyi +++ b/reflex/components/sonner/toast.pyi @@ -7,15 +7,16 @@ from typing import Any, Dict, Literal, Optional, Union, overload from reflex.vars import Var, BaseVar, ComputedVar from reflex.event import EventChain, EventHandler, EventSpec from reflex.style import Style -from typing import Literal +from typing import Any, Literal, Optional from reflex.base import Base from reflex.components.component import Component, ComponentNamespace from reflex.components.lucide.icon import Icon +from reflex.components.props import PropsBase from reflex.event import EventSpec, call_script from reflex.style import Style, color_mode from reflex.utils import format from reflex.utils.imports import ImportVar -from reflex.utils.serializers import serialize +from reflex.utils.serializers import serialize, serializer from reflex.vars import Var, VarData LiteralPosition = Literal[ @@ -28,20 +29,32 @@ LiteralPosition = Literal[ ] toast_ref = Var.create_safe("refs['__toast']") -class PropsBase(Base): - def json(self) -> str: ... +class ToastAction(Base): + label: str + on_click: Any + +@serializer +def serialize_action(action: ToastAction) -> dict: ... class ToastProps(PropsBase): - description: str - close_button: bool - invert: bool - important: bool - duration: int - position: LiteralPosition - dismissible: bool - id: str - unstyled: bool - style: Style + description: Optional[str] + close_button: Optional[bool] + invert: Optional[bool] + important: Optional[bool] + duration: Optional[int] + position: Optional[LiteralPosition] + dismissible: Optional[bool] + action: Optional[ToastAction] + cancel: Optional[ToastAction] + id: Optional[str] + unstyled: Optional[bool] + style: Optional[Style] + action_button_styles: Optional[Style] + cancel_button_styles: Optional[Style] + on_dismiss: Optional[Any] + on_auto_close: Optional[Any] + + def dict(self, *args, **kwargs) -> dict: ... class Toaster(Component): @staticmethod diff --git a/reflex/config.py b/reflex/config.py index e00a9213b8..40c2ef8bf7 100644 --- a/reflex/config.py +++ b/reflex/config.py @@ -6,16 +6,10 @@ import os import sys import urllib.parse -from typing import TYPE_CHECKING, Any, Dict, List, Optional, Set +from typing import Any, Dict, List, Optional, Set try: - # TODO The type checking guard can be removed once - # reflex-hosting-cli tools are compatible with pydantic v2 - - if not TYPE_CHECKING: - import pydantic.v1 as pydantic - else: - raise ModuleNotFoundError + import pydantic.v1 as pydantic except ModuleNotFoundError: import pydantic @@ -257,8 +251,10 @@ def update_from_env(self) -> dict[str, Any]: The updated config values. Raises: - ValueError: If an environment variable is set to an invalid type. + EnvVarValueError: If an environment variable is set to an invalid type. """ + from reflex.utils.exceptions import EnvVarValueError + updated_values = {} # Iterate over the fields. for key, field in self.__fields__.items(): @@ -279,11 +275,11 @@ def update_from_env(self) -> dict[str, Any]: env_var = env_var.lower() in ["true", "1", "yes"] else: env_var = field.type_(env_var) - except ValueError: + except ValueError as ve: console.error( f"Could not convert {key.upper()}={env_var} to type {field.type_}" ) - raise + raise EnvVarValueError from ve # Set the value. updated_values[key] = env_var diff --git a/reflex/constants/installer.py b/reflex/constants/installer.py index 5bf9b365ea..3cca265e60 100644 --- a/reflex/constants/installer.py +++ b/reflex/constants/installer.py @@ -35,7 +35,7 @@ class Bun(SimpleNamespace): """Bun constants.""" # The Bun version. - VERSION = "1.1.6" + VERSION = "1.1.8" # Min Bun Version MIN_VERSION = "0.7.0" # The directory to store the bun. diff --git a/reflex/custom_components/custom_components.py b/reflex/custom_components/custom_components.py index 79ab26d93f..ec35fb5c4b 100644 --- a/reflex/custom_components/custom_components.py +++ b/reflex/custom_components/custom_components.py @@ -20,7 +20,6 @@ from reflex.config import get_config from reflex.constants import CustomComponents from reflex.utils import console -from reflex.utils.pyi_generator import PyiGenerator config = get_config() custom_components_cli = typer.Typer() @@ -438,6 +437,8 @@ def _run_commands_in_subprocess(cmds: list[str]) -> bool: def _make_pyi_files(): """Create pyi files for the custom component.""" + from reflex.utils.pyi_generator import PyiGenerator + package_name = _get_package_config()["project"]["name"] for dir, _, _ in os.walk(f"./{package_name}"): diff --git a/reflex/event.py b/reflex/event.py index ece1015820..3f1487c199 100644 --- a/reflex/event.py +++ b/reflex/event.py @@ -4,7 +4,6 @@ import inspect from base64 import b64encode -from types import FunctionType from typing import ( Any, Callable, @@ -180,8 +179,10 @@ def __call__(self, *args: Any) -> EventSpec: The event spec, containing both the function and args. Raises: - TypeError: If the arguments are invalid. + EventHandlerTypeError: If the arguments are invalid. """ + from reflex.utils.exceptions import EventHandlerTypeError + # Get the function args. fn_args = inspect.getfullargspec(self.fn).args[1:] fn_args = (Var.create_safe(arg) for arg in fn_args) @@ -197,7 +198,7 @@ def __call__(self, *args: Any) -> EventSpec: try: values.append(Var.create(arg, _var_is_string=isinstance(arg, str))) except TypeError as e: - raise TypeError( + 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)) @@ -256,8 +257,10 @@ def add_args(self, *args: Var) -> EventSpec: The event spec with the new arguments. Raises: - TypeError: If the arguments are invalid. + EventHandlerTypeError: If the arguments are invalid. """ + from reflex.utils.exceptions import EventHandlerTypeError + # Get the remaining unfilled function args. fn_args = inspect.getfullargspec(self.handler.fn).args[1 + len(self.args) :] fn_args = (Var.create_safe(arg) for arg in fn_args) @@ -268,7 +271,7 @@ def add_args(self, *args: Var) -> EventSpec: try: values.append(Var.create(arg, _var_is_string=isinstance(arg, str))) except TypeError as e: - raise TypeError( + 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)) @@ -312,10 +315,12 @@ def __call__(self, *args, **kwargs) -> EventSpec: The EventSpec returned from calling the function. Raises: - TypeError: If the CallableEventSpec has no associated function. + EventHandlerTypeError: If the CallableEventSpec has no associated function. """ + from reflex.utils.exceptions import EventHandlerTypeError + if self.fn is None: - raise TypeError("CallableEventSpec has no associated function.") + raise EventHandlerTypeError("CallableEventSpec has no associated function.") return self.fn(*args, **kwargs) @@ -700,7 +705,11 @@ def _callback_arg_spec(eval_result): def call_script( javascript_code: str, - callback: EventHandler | Callable | None = None, + callback: EventSpec + | EventHandler + | Callable + | List[EventSpec | EventHandler | Callable] + | None = None, ) -> EventSpec: """Create an event handler that executes arbitrary javascript code. @@ -710,21 +719,14 @@ def call_script( Returns: EventSpec: An event that will execute the client side javascript. - - Raises: - ValueError: If the callback is not a valid event handler. """ callback_kwargs = {} if callback is not None: - arg_name = parse_args_spec(_callback_arg_spec)[0]._var_name - if isinstance(callback, EventHandler): - event_spec = call_event_handler(callback, _callback_arg_spec) - elif isinstance(callback, FunctionType): - event_spec = call_event_fn(callback, _callback_arg_spec)[0] - else: - raise ValueError("Cannot use {callback!r} as a call_script callback.") callback_kwargs = { - "callback": f"({arg_name}) => queueEvents([{format.format_event(event_spec)}], {constants.CompileVars.SOCKET})" + "callback": format.format_queue_events( + callback, + args_spec=lambda result: [result], + ) } return server_side( "_call_script", @@ -834,10 +836,11 @@ def call_event_fn(fn: Callable, arg: Union[Var, ArgsSpec]) -> list[EventSpec]: The event specs from calling the function. Raises: - ValueError: If the lambda has an invalid signature. + EventHandlerValueError: If the lambda has an invalid signature. """ # Import here to avoid circular imports. from reflex.event import EventHandler, EventSpec + from reflex.utils.exceptions import EventHandlerValueError # Get the args of the lambda. args = inspect.getfullargspec(fn).args @@ -851,7 +854,7 @@ def call_event_fn(fn: Callable, arg: Union[Var, ArgsSpec]) -> list[EventSpec]: elif len(args) == 1: out = fn(arg) else: - raise ValueError(f"Lambda {fn} must have 0 or 1 arguments.") + raise EventHandlerValueError(f"Lambda {fn} must have 0 or 1 arguments.") # Convert the output to a list. if not isinstance(out, List): @@ -869,7 +872,9 @@ def call_event_fn(fn: Callable, arg: Union[Var, ArgsSpec]) -> list[EventSpec]: # Make sure the event spec is valid. if not isinstance(e, EventSpec): - raise ValueError(f"Lambda {fn} returned an invalid event spec: {e}.") + raise EventHandlerValueError( + f"Lambda {fn} returned an invalid event spec: {e}." + ) # Add the event spec to the chain. events.append(e) diff --git a/reflex/experimental/__init__.py b/reflex/experimental/__init__.py index eb9b2eb95d..29bda85453 100644 --- a/reflex/experimental/__init__.py +++ b/reflex/experimental/__init__.py @@ -2,6 +2,10 @@ from types import SimpleNamespace +from reflex.components.props import PropsBase +from reflex.components.radix.themes.components.progress import progress as progress +from reflex.components.sonner.toast import toast as toast + from ..utils.console import warn from . import hooks as hooks from .layout import layout as layout @@ -14,5 +18,8 @@ _x = SimpleNamespace( hooks=hooks, layout=layout, + progress=progress, + PropsBase=PropsBase, run_in_thread=run_in_thread, + toast=toast, ) diff --git a/reflex/model.py b/reflex/model.py index c6c4a2a158..088601cae4 100644 --- a/reflex/model.py +++ b/reflex/model.py @@ -133,7 +133,7 @@ def get_metadata(cls) -> sqlalchemy.MetaData: return metadata -class Model(Base, sqlmodel.SQLModel): +class Model(Base, sqlmodel.SQLModel): # pyright: ignore [reportGeneralTypeIssues] """Base class to define a table in the database.""" # The primary key for the table. diff --git a/reflex/state.py b/reflex/state.py index 5cbc0dc8c3..287f70073a 100644 --- a/reflex/state.py +++ b/reflex/state.py @@ -29,13 +29,7 @@ import dill try: - # TODO The type checking guard can be removed once - # reflex-hosting-cli tools are compatible with pydantic v2 - - if not TYPE_CHECKING: - import pydantic.v1 as pydantic - else: - raise ModuleNotFoundError + import pydantic.v1 as pydantic except ModuleNotFoundError: import pydantic @@ -45,6 +39,7 @@ from reflex import constants from reflex.base import Base from reflex.event import ( + BACKGROUND_TASK_MARKER, Event, EventHandler, EventSpec, @@ -284,11 +279,13 @@ def __call__(self, *args: Any) -> EventSpec: Raises: AttributeError: If the given Var name does not exist on the state. - ValueError: If the given Var name is not a str + EventHandlerValueError: If the given Var name is not a str """ + from reflex.utils.exceptions import EventHandlerValueError + if args: if not isinstance(args[0], str): - raise ValueError( + raise EventHandlerValueError( f"Var name must be passed as a string, got {args[0]!r}" ) # Check that the requested Var setter exists on the State at compile time. @@ -362,6 +359,9 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow): # Whether the state has ever been touched since instantiation. _was_touched: bool = False + # Whether this state class is a mixin and should not be instantiated. + _mixin: ClassVar[bool] = False + # A special event handler for setting base vars. setvar: ClassVar[EventHandler] @@ -385,10 +385,12 @@ def __init__( **kwargs: The kwargs to pass to the Pydantic init method. Raises: - RuntimeError: If the state is instantiated directly by end user. + ReflexRuntimeError: If the state is instantiated directly by end user. """ + from reflex.utils.exceptions import ReflexRuntimeError + if not _reflex_internal_init and not is_testing_env(): - raise RuntimeError( + raise ReflexRuntimeError( "State classes should not be instantiated directly in a Reflex app. " "See https://reflex.dev/docs/state/ for further information." ) @@ -429,23 +431,30 @@ def _get_computed_vars(cls) -> list[ComputedVar]: """ return [ v - for mixin in cls.__mro__ - if mixin is cls or not issubclass(mixin, (BaseState, ABC)) + for mixin in cls._mixins() + [cls] for v in mixin.__dict__.values() if isinstance(v, ComputedVar) ] @classmethod - def __init_subclass__(cls, **kwargs): + def __init_subclass__(cls, mixin: bool = False, **kwargs): """Do some magic for the subclass initialization. Args: + mixin: Whether the subclass is a mixin and should not be initialized. **kwargs: The kwargs to pass to the pydantic init_subclass method. Raises: - ValueError: If a substate class shadows another. + StateValueError: If a substate class shadows another. """ + from reflex.utils.exceptions import StateValueError + super().__init_subclass__(**kwargs) + + cls._mixin = mixin + if mixin: + return + # Event handlers should not shadow builtin state methods. cls._check_overridden_methods() # Computed vars should not shadow builtin state props. @@ -476,7 +485,7 @@ def __init_subclass__(cls, **kwargs): else: # During normal operation, subclasses cannot have the same name, even if they are # defined in different modules. - raise ValueError( + raise StateValueError( f"The substate class '{cls.__name__}' has been defined multiple times. " "Shadowing substate classes is not allowed." ) @@ -585,6 +594,8 @@ def _copy_fn(fn: Callable) -> Callable: closure=fn.__closure__, ) newfn.__annotations__ = fn.__annotations__ + if mark := getattr(fn, BACKGROUND_TASK_MARKER, None): + setattr(newfn, BACKGROUND_TASK_MARKER, mark) return newfn @staticmethod @@ -615,8 +626,11 @@ def _mixins(cls) -> List[Type]: return [ mixin for mixin in cls.__mro__ - if not issubclass(mixin, (BaseState, ABC)) - and mixin not in [pydantic.BaseModel, Base] + if ( + mixin not in [pydantic.BaseModel, Base, cls] + and issubclass(mixin, BaseState) + and mixin._mixin is True + ) ] @classmethod @@ -739,7 +753,7 @@ def get_parent_state(cls) -> Type[BaseState] | None: parent_states = [ base for base in cls.__bases__ - if types._issubclass(base, BaseState) and base is not BaseState + if issubclass(base, BaseState) and base is not BaseState and not base._mixin ] assert len(parent_states) < 2, "Only one parent state is allowed." return parent_states[0] if len(parent_states) == 1 else None # type: ignore @@ -832,10 +846,12 @@ def _init_var(cls, prop: BaseVar): prop: The variable to initialize Raises: - TypeError: if the variable has an incorrect type + VarTypeError: if the variable has an incorrect type """ + from reflex.utils.exceptions import VarTypeError + if not types.is_valid_var_type(prop._var_type): - raise TypeError( + raise VarTypeError( "State vars must be primitive Python types, " "Plotly figures, Pandas dataframes, " "or subclasses of rx.Base. " @@ -1457,6 +1473,9 @@ async def _process_event( Yields: StateUpdate object """ + from reflex.utils import telemetry + from reflex.utils.exceptions import ReflexError + # Get the function to process the event. fn = functools.partial(handler.fn, state) @@ -1492,9 +1511,11 @@ async def _process_event( yield state._as_state_update(handler, events, final=True) # If an error occurs, throw a window alert. - except Exception: + except Exception as ex: error = traceback.format_exc() print(error) + if isinstance(ex, ReflexError): + telemetry.send("error", context="backend", detail=str(ex)) yield state._as_state_update( handler, window_alert("An error occurred. See logs for details."), @@ -1691,10 +1712,12 @@ def dict( if initial: computed_vars = { # Include initial computed vars. - prop_name: cv._initial_value - if isinstance(cv, ComputedVar) - and not isinstance(cv._initial_value, types.Unset) - else self.get_value(getattr(self, prop_name)) + prop_name: ( + cv._initial_value + if isinstance(cv, ComputedVar) + and not isinstance(cv._initial_value, types.Unset) + else self.get_value(getattr(self, prop_name)) + ) for prop_name, cv in self.computed_vars.items() } elif include_computed: @@ -1821,7 +1844,7 @@ def on_load_internal(self) -> list[Event | EventSpec] | None: ] -class ComponentState(Base): +class ComponentState(State, mixin=True): """Base class to allow for the creation of a state instance per component. This allows for the bundling of UI and state logic into a single class, @@ -1863,6 +1886,18 @@ def get_component(cls, **props): # The number of components created from this class. _per_component_state_instance_count: ClassVar[int] = 0 + @classmethod + def __init_subclass__(cls, mixin: bool = False, **kwargs): + """Overwrite mixin default to True. + + Args: + mixin: Whether the subclass is a mixin and should not be initialized. + **kwargs: The kwargs to pass to the pydantic init_subclass method. + """ + if ComponentState in cls.__bases__: + mixin = True + super().__init_subclass__(mixin=mixin, **kwargs) + @classmethod def get_component(cls, *children, **props) -> "Component": """Get the component instance. diff --git a/reflex/style.py b/reflex/style.py index 0216eedd9c..21a601dd0a 100644 --- a/reflex/style.py +++ b/reflex/style.py @@ -180,12 +180,15 @@ def update(self, style_dict: dict | None, **kwargs): style_dict: The style dictionary. kwargs: Other key value pairs to apply to the dict update. """ - if kwargs: - style_dict = {**(style_dict or {}), **kwargs} if not isinstance(style_dict, Style): converted_dict = type(self)(style_dict) else: converted_dict = style_dict + if kwargs: + if converted_dict is None: + converted_dict = type(self)(kwargs) + else: + converted_dict.update(kwargs) # Combine our VarData with that of any Vars in the style_dict that was passed. self._var_data = VarData.merge(self._var_data, converted_dict._var_data) super().update(converted_dict) diff --git a/reflex/testing.py b/reflex/testing.py index 5613af3338..aa8cbb8935 100644 --- a/reflex/testing.py +++ b/reflex/testing.py @@ -26,6 +26,7 @@ AsyncIterator, Callable, Coroutine, + List, Optional, Type, TypeVar, @@ -513,12 +514,19 @@ def _poll_for_servers(self, timeout: TimeoutType = None) -> socket.socket: raise TimeoutError("Backend is not listening.") return backend.servers[0].sockets[0] - def frontend(self, driver_clz: Optional[Type["WebDriver"]] = None) -> "WebDriver": + def frontend( + self, + driver_clz: Optional[Type["WebDriver"]] = None, + driver_kwargs: dict[str, Any] | None = None, + driver_option_args: List[str] | None = None, + ) -> "WebDriver": """Get a selenium webdriver instance pointed at the app. Args: driver_clz: webdriver.Chrome (default), webdriver.Firefox, webdriver.Safari, webdriver.Edge, etc + driver_kwargs: additional keyword arguments to pass to the webdriver constructor + driver_option_args: additional arguments for the webdriver options Returns: Instance of the given webdriver navigated to the frontend url of the app. @@ -541,19 +549,30 @@ def frontend(self, driver_clz: Optional[Type["WebDriver"]] = None) -> "WebDriver requested_driver = os.environ.get("APP_HARNESS_DRIVER", "Chrome") driver_clz = getattr(webdriver, requested_driver) options = getattr(webdriver, f"{requested_driver}Options")() - if driver_clz is webdriver.Chrome and want_headless: + if driver_clz is webdriver.Chrome: options = webdriver.ChromeOptions() - options.add_argument("--headless=new") - elif driver_clz is webdriver.Firefox and want_headless: + options.add_argument("--class=AppHarness") + if want_headless: + options.add_argument("--headless=new") + elif driver_clz is webdriver.Firefox: options = webdriver.FirefoxOptions() - options.add_argument("-headless") - elif driver_clz is webdriver.Edge and want_headless: + if want_headless: + options.add_argument("-headless") + elif driver_clz is webdriver.Edge: options = webdriver.EdgeOptions() - options.add_argument("headless") - if options and (args := os.environ.get("APP_HARNESS_DRIVER_ARGS")): + if want_headless: + options.add_argument("headless") + if options is None: + raise RuntimeError(f"Could not determine options for {driver_clz}") + if args := os.environ.get("APP_HARNESS_DRIVER_ARGS"): for arg in args.split(","): options.add_argument(arg) - driver = driver_clz(options=options) # type: ignore + if driver_option_args is not None: + for arg in driver_option_args: + options.add_argument(arg) + if driver_kwargs is None: + driver_kwargs = {} + driver = driver_clz(options=options, **driver_kwargs) # type: ignore driver.get(self.frontend_url) self._frontends.append(driver) return driver diff --git a/reflex/utils/exceptions.py b/reflex/utils/exceptions.py index e4a9a6e6c9..aabaaef149 100644 --- a/reflex/utils/exceptions.py +++ b/reflex/utils/exceptions.py @@ -1,21 +1,77 @@ """Custom Exceptions.""" -class InvalidStylePropError(TypeError): - """Custom Type Error when style props have invalid values.""" +class ReflexError(Exception): + """Base exception for all Reflex exceptions.""" + + +class ReflexRuntimeError(ReflexError, RuntimeError): + """Custom RuntimeError for Reflex.""" + + +class UploadTypeError(ReflexError, TypeError): + """Custom TypeError for upload related errors.""" + + +class EnvVarValueError(ReflexError, ValueError): + """Custom ValueError raised when unable to convert env var to expected type.""" + + +class ComponentTypeError(ReflexError, TypeError): + """Custom TypeError for component related errors.""" + + +class EventHandlerTypeError(ReflexError, TypeError): + """Custom TypeError for event handler related errors.""" + + +class EventHandlerValueError(ReflexError, ValueError): + """Custom ValueError for event handler related errors.""" + + +class StateValueError(ReflexError, ValueError): + """Custom ValueError for state related errors.""" + - pass +class VarNameError(ReflexError, NameError): + """Custom NameError for when a state var has been shadowed by a substate var.""" -class ImmutableStateError(AttributeError): +class VarTypeError(ReflexError, TypeError): + """Custom TypeError for var related errors.""" + + +class VarValueError(ReflexError, ValueError): + """Custom ValueError for var related errors.""" + + +class VarAttributeError(ReflexError, AttributeError): + """Custom AttributeError for var related errors.""" + + +class UploadValueError(ReflexError, ValueError): + """Custom ValueError for upload related errors.""" + + +class RouteValueError(ReflexError, ValueError): + """Custom ValueError for route related errors.""" + + +class VarOperationTypeError(ReflexError, TypeError): + """Custom TypeError for when unsupported operations are performed on vars.""" + + +class InvalidStylePropError(ReflexError, TypeError): + """Custom Type Error when style props have invalid values.""" + + +class ImmutableStateError(ReflexError): """Raised when a background task attempts to modify state outside of context.""" -class LockExpiredError(Exception): +class LockExpiredError(ReflexError): """Raised when the state lock expires while an event is being processed.""" -class MatchTypeError(TypeError): +class MatchTypeError(ReflexError, TypeError): """Raised when the return types of match cases are different.""" - - pass diff --git a/reflex/utils/format.py b/reflex/utils/format.py index 70f6b5b251..5a0d1b9595 100644 --- a/reflex/utils/format.py +++ b/reflex/utils/format.py @@ -6,7 +6,7 @@ import json import os import re -from typing import TYPE_CHECKING, Any, List, Optional, Union +from typing import TYPE_CHECKING, Any, Callable, List, Optional, Union from reflex import constants from reflex.utils import exceptions, serializers, types @@ -15,7 +15,7 @@ if TYPE_CHECKING: from reflex.components.component import ComponentStyle - from reflex.event import EventChain, EventHandler, EventSpec + from reflex.event import ArgsSpec, EventChain, EventHandler, EventSpec WRAP_MAP = { "{": "}", @@ -590,6 +590,77 @@ def format_event_chain( ) +def format_queue_events( + events: EventSpec + | EventHandler + | Callable + | List[EventSpec | EventHandler | Callable] + | None = None, + args_spec: Optional[ArgsSpec] = None, +) -> Var[EventChain]: + """Format a list of event handler / event spec as a javascript callback. + + The resulting code can be passed to interfaces that expect a callback + function and when triggered it will directly call queueEvents. + + It is intended to be executed in the rx.call_script context, where some + existing API needs a callback to trigger a backend event handler. + + Args: + events: The events to queue. + args_spec: The argument spec for the callback. + + Returns: + The compiled javascript callback to queue the given events on the frontend. + """ + from reflex.event import ( + EventChain, + EventHandler, + EventSpec, + call_event_fn, + call_event_handler, + ) + + if not events: + return Var.create_safe( + "() => null", _var_is_string=False, _var_is_local=False + ).to(EventChain) + + # If no spec is provided, the function will take no arguments. + def _default_args_spec(): + return [] + + # Construct the arguments that the function accepts. + sig = inspect.signature(args_spec or _default_args_spec) # type: ignore + if sig.parameters: + arg_def = ",".join(f"_{p}" for p in sig.parameters) + arg_def = f"({arg_def})" + else: + arg_def = "()" + + payloads = [] + if not isinstance(events, list): + events = [events] + + # Process each event/spec/lambda (similar to Component._create_event_chain). + for spec in events: + specs: list[EventSpec] = [] + if isinstance(spec, (EventHandler, EventSpec)): + specs = [call_event_handler(spec, args_spec or _default_args_spec)] + elif isinstance(spec, type(lambda: None)): + specs = call_event_fn(spec, args_spec or _default_args_spec) + payloads.extend(format_event(s) for s in specs) + + # Return the final code snippet, expecting queueEvents, processEvent, and socket to be in scope. + # Typically this snippet will _only_ run from within an rx.call_script eval context. + return Var.create_safe( + f"{arg_def} => {{queueEvents([{','.join(payloads)}], {constants.CompileVars.SOCKET}); " + f"processEvent({constants.CompileVars.SOCKET})}}", + _var_is_string=False, + _var_is_local=False, + ).to(EventChain) + + def format_query_params(router_data: dict[str, Any]) -> dict[str, str]: """Convert back query params name to python-friendly case. diff --git a/reflex/utils/prerequisites.py b/reflex/utils/prerequisites.py index 2e59b99534..cd4739c445 100644 --- a/reflex/utils/prerequisites.py +++ b/reflex/utils/prerequisites.py @@ -181,7 +181,12 @@ def get_install_package_manager() -> str | None: Returns: The path to the package manager. """ - if constants.IS_WINDOWS and not is_windows_bun_supported(): + if ( + constants.IS_WINDOWS + and not is_windows_bun_supported() + or windows_check_onedrive_in_path() + or windows_npm_escape_hatch() + ): return get_package_manager() return get_config().bun_path @@ -199,6 +204,24 @@ def get_package_manager() -> str | None: return npm_path +def windows_check_onedrive_in_path() -> bool: + """For windows, check if oneDrive is present in the project dir path. + + Returns: + If oneDrive is in the path of the project directory. + """ + return "onedrive" in str(Path.cwd()).lower() + + +def windows_npm_escape_hatch() -> bool: + """For windows, if the user sets REFLEX_USE_NPM, use npm instead of bun. + + Returns: + If the user has set REFLEX_USE_NPM. + """ + return os.environ.get("REFLEX_USE_NPM", "").lower() in ["true", "1", "yes"] + + def get_app(reload: bool = False) -> ModuleType: """Get the app module based on the default config. @@ -210,28 +233,35 @@ def get_app(reload: bool = False) -> ModuleType: Raises: RuntimeError: If the app name is not set in the config. + exceptions.ReflexError: Reflex specific errors. """ - os.environ[constants.RELOAD_CONFIG] = str(reload) - config = get_config() - if not config.app_name: - raise RuntimeError( - "Cannot get the app module because `app_name` is not set in rxconfig! " - "If this error occurs in a reflex test case, ensure that `get_app` is mocked." - ) - module = config.module - sys.path.insert(0, os.getcwd()) - app = __import__(module, fromlist=(constants.CompileVars.APP,)) + from reflex.utils import exceptions, telemetry + + try: + os.environ[constants.RELOAD_CONFIG] = str(reload) + config = get_config() + if not config.app_name: + raise RuntimeError( + "Cannot get the app module because `app_name` is not set in rxconfig! " + "If this error occurs in a reflex test case, ensure that `get_app` is mocked." + ) + module = config.module + sys.path.insert(0, os.getcwd()) + app = __import__(module, fromlist=(constants.CompileVars.APP,)) - if reload: - from reflex.state import reload_state_module + if reload: + from reflex.state import reload_state_module - # Reset rx.State subclasses to avoid conflict when reloading. - reload_state_module(module=module) + # Reset rx.State subclasses to avoid conflict when reloading. + reload_state_module(module=module) - # Reload the app module. - importlib.reload(app) + # Reload the app module. + importlib.reload(app) - return app + return app + except exceptions.ReflexError as ex: + telemetry.send("error", context="frontend", detail=str(ex)) + raise def get_compiled_app(reload: bool = False, export: bool = False) -> ModuleType: @@ -737,10 +767,17 @@ def install_bun(): Raises: FileNotFoundError: If required packages are not found. """ - if constants.IS_WINDOWS and not is_windows_bun_supported(): - console.warn( - "Bun for Windows is currently only available for x86 64-bit Windows. Installation will fall back on npm." - ) + win_supported = is_windows_bun_supported() + one_drive_in_path = windows_check_onedrive_in_path() + if constants.IS_WINDOWS and not win_supported or one_drive_in_path: + if not win_supported: + console.warn( + "Bun for Windows is currently only available for x86 64-bit Windows. Installation will fall back on npm." + ) + if one_drive_in_path: + console.warn( + "Creating project directories in OneDrive is not recommended for bun usage on windows. This will fallback to npm." + ) # Skip if bun is already installed. if os.path.exists(get_config().bun_path) and get_bun_version() == version.parse( @@ -843,11 +880,13 @@ def install_frontend_packages(packages: set[str], config: Config): if not constants.IS_WINDOWS or constants.IS_WINDOWS and is_windows_bun_supported() + and not windows_check_onedrive_in_path() else None ) processes.run_process_with_fallback( [get_install_package_manager(), "install"], # type: ignore fallback=fallback_command, + analytics_enabled=True, show_status_message="Installing base frontend packages", cwd=constants.Dirs.WEB, shell=constants.IS_WINDOWS, @@ -863,6 +902,7 @@ def install_frontend_packages(packages: set[str], config: Config): *((config.tailwind or {}).get("plugins", [])), ], fallback=fallback_command, + analytics_enabled=True, show_status_message="Installing tailwind", cwd=constants.Dirs.WEB, shell=constants.IS_WINDOWS, @@ -873,6 +913,7 @@ def install_frontend_packages(packages: set[str], config: Config): processes.run_process_with_fallback( [get_install_package_manager(), "add", *packages], fallback=fallback_command, + analytics_enabled=True, show_status_message="Installing frontend packages from config and components", cwd=constants.Dirs.WEB, shell=constants.IS_WINDOWS, @@ -914,9 +955,26 @@ def needs_reinit(frontend: bool = True) -> bool: return True if constants.IS_WINDOWS: + import uvicorn + + uvi_ver = uvicorn.__version__ console.warn( """Windows Subsystem for Linux (WSL) is recommended for improving initial install times.""" ) + if sys.version_info >= (3, 12) and uvi_ver != "0.24.0.post1": + console.warn( + f"""On Python 3.12, `uvicorn==0.24.0.post1` is recommended for improved hot reload times. Found {uvi_ver} instead.""" + ) + + if sys.version_info < (3, 12) and uvi_ver != "0.20.0": + console.warn( + f"""On Python < 3.12, `uvicorn==0.20.0` is recommended for improved hot reload times. Found {uvi_ver} instead.""" + ) + + if windows_check_onedrive_in_path(): + console.warn( + "Creating project directories in OneDrive may lead to performance issues. For optimal performance, It is recommended to avoid using OneDrive for your reflex app." + ) # No need to reinitialize if the app is already initialized. return False diff --git a/reflex/utils/processes.py b/reflex/utils/processes.py index f63e1c709a..cadd875471 100644 --- a/reflex/utils/processes.py +++ b/reflex/utils/processes.py @@ -211,6 +211,7 @@ def stream_logs( process: subprocess.Popen, progress=None, suppress_errors: bool = False, + analytics_enabled: bool = False, ): """Stream the logs for a process. @@ -219,6 +220,7 @@ def stream_logs( process: The process. progress: The ongoing progress bar if one is being used. suppress_errors: If True, do not exit if errors are encountered (for fallback). + analytics_enabled: Whether analytics are enabled for this command. Yields: The lines of the process output. @@ -226,6 +228,8 @@ def stream_logs( Raises: Exit: If the process failed. """ + from reflex.utils import telemetry + # Store the tail of the logs. logs = collections.deque(maxlen=512) with process: @@ -246,6 +250,8 @@ def stream_logs( console.error(f"{message} failed with exit code {process.returncode}") for line in logs: console.error(line, end="") + if analytics_enabled: + telemetry.send("error", context=message) console.error("Run with [bold]--loglevel debug [/bold] for the full log.") raise typer.Exit(1) @@ -261,16 +267,27 @@ def show_logs(message: str, process: subprocess.Popen): pass -def show_status(message: str, process: subprocess.Popen, suppress_errors: bool = False): +def show_status( + message: str, + process: subprocess.Popen, + suppress_errors: bool = False, + analytics_enabled: bool = False, +): """Show the status of a process. Args: message: The initial message to display. process: The process. suppress_errors: If True, do not exit if errors are encountered (for fallback). + analytics_enabled: Whether analytics are enabled for this command. """ with console.status(message) as status: - for line in stream_logs(message, process, suppress_errors=suppress_errors): + for line in stream_logs( + message, + process, + suppress_errors=suppress_errors, + analytics_enabled=analytics_enabled, + ): status.update(f"{message} {line}") @@ -319,19 +336,31 @@ def get_command_with_loglevel(command: list[str]) -> list[str]: return command -def run_process_with_fallback(args, *, show_status_message, fallback=None, **kwargs): +def run_process_with_fallback( + args, + *, + show_status_message, + fallback=None, + analytics_enabled: bool = False, + **kwargs, +): """Run subprocess and retry using fallback command if initial command fails. Args: args: A string, or a sequence of program arguments. show_status_message: The status message to be displayed in the console. fallback: The fallback command to run. + analytics_enabled: Whether analytics are enabled for this command. kwargs: Kwargs to pass to new_process function. """ process = new_process(get_command_with_loglevel(args), **kwargs) if fallback is None: # No fallback given, or this _is_ the fallback command. - show_status(show_status_message, process) + show_status( + show_status_message, + process, + analytics_enabled=analytics_enabled, + ) else: # Suppress errors for initial command, because we will try to fallback show_status(show_status_message, process, suppress_errors=True) @@ -345,6 +374,7 @@ def run_process_with_fallback(args, *, show_status_message, fallback=None, **kwa fallback_args, show_status_message=show_status_message, fallback=None, + analytics_enabled=analytics_enabled, **kwargs, ) diff --git a/reflex/utils/telemetry.py b/reflex/utils/telemetry.py index e38c4fe793..f7398f01ae 100644 --- a/reflex/utils/telemetry.py +++ b/reflex/utils/telemetry.py @@ -126,6 +126,10 @@ def _prepare_event(event: str, **kwargs) -> dict: cpuinfo = get_cpu_info() + additional_keys = ["template", "context", "detail"] + additional_fields = { + key: value for key in additional_keys if (value := kwargs.get(key)) is not None + } return { "api_key": "phc_JoMo0fOyi0GQAooY3UyO9k0hebGkMyFJrrCw1Gt5SGb", "event": event, @@ -139,11 +143,7 @@ def _prepare_event(event: str, **kwargs) -> dict: "cpu_count": get_cpu_count(), "memory": get_memory(), "cpu_info": dict(cpuinfo) if cpuinfo else {}, - **( - {"template": template} - if (template := kwargs.get("template")) is not None - else {} - ), + **additional_fields, }, "timestamp": stamp, } diff --git a/reflex/utils/types.py b/reflex/utils/types.py index 8214e0765e..f75e20dcc4 100644 --- a/reflex/utils/types.py +++ b/reflex/utils/types.py @@ -8,7 +8,6 @@ import types from functools import wraps from typing import ( - TYPE_CHECKING, Any, Callable, Dict, @@ -28,15 +27,9 @@ import sqlalchemy try: - # TODO The type checking guard can be removed once - # reflex-hosting-cli tools are compatible with pydantic v2 - - if not TYPE_CHECKING: - from pydantic.v1.fields import ModelField - else: - raise ModuleNotFoundError + from pydantic.v1.fields import ModelField except ModuleNotFoundError: - from pydantic.fields import ModelField + from pydantic.fields import ModelField # type: ignore from sqlalchemy.ext.associationproxy import AssociationProxyInstance from sqlalchemy.ext.hybrid import hybrid_property diff --git a/reflex/vars.py b/reflex/vars.py index 244d131c07..cccef95eb2 100644 --- a/reflex/vars.py +++ b/reflex/vars.py @@ -35,6 +35,7 @@ from reflex import constants from reflex.base import Base from reflex.utils import console, format, imports, serializers, types +from reflex.utils.exceptions import VarAttributeError, VarTypeError, VarValueError # This module used to export ImportVar itself, so we still import it for export here from reflex.utils.imports import ImportDict, ImportVar @@ -358,7 +359,7 @@ def create( The var. Raises: - TypeError: If the value is JSON-unserializable. + VarTypeError: If the value is JSON-unserializable. """ # Check for none values. if value is None: @@ -376,7 +377,7 @@ def create( type_ = type(value) name = value if type_ in types.JSONType else serializers.serialize(value) if name is None: - raise TypeError( + raise VarTypeError( f"No JSON serializer found for var {value} of type {type_}." ) name = name if isinstance(name, str) else format.json_dumps(name) @@ -552,9 +553,9 @@ def __bool__(self) -> bool: """Raise exception if using Var in a boolean context. Raises: - TypeError: when attempting to bool-ify the Var. + VarTypeError: when attempting to bool-ify the Var. """ - raise TypeError( + raise VarTypeError( f"Cannot convert Var {self._var_full_name!r} to bool for use with `if`, `and`, `or`, and `not`. " "Instead use `rx.cond` and bitwise operators `&` (and), `|` (or), `~` (invert)." ) @@ -563,9 +564,9 @@ def __iter__(self) -> Any: """Raise exception if using Var in an iterable context. Raises: - TypeError: when attempting to iterate over the Var. + VarTypeError: when attempting to iterate over the Var. """ - raise TypeError( + raise VarTypeError( f"Cannot iterate over Var {self._var_full_name!r}. Instead use `rx.foreach`." ) @@ -594,7 +595,7 @@ def __getitem__(self, i: Any) -> Var: The indexed var. Raises: - TypeError: If the var is not indexable. + VarTypeError: If the var is not indexable. """ # Indexing is only supported for strings, lists, tuples, dicts, and dataframes. if not ( @@ -602,11 +603,11 @@ def __getitem__(self, i: Any) -> Var: or types.is_dataframe(self._var_type) ): if self._var_type == Any: - raise TypeError( + raise VarTypeError( "Could not index into var of type Any. (If you are trying to index into a state var, " "add the correct type annotation to the var.)" ) - raise TypeError( + raise VarTypeError( f"Var {self._var_name} of type {self._var_type} does not support indexing." ) @@ -625,7 +626,7 @@ def __getitem__(self, i: Any) -> Var: or isinstance(i, Var) and not i._var_type == int ): - raise TypeError("Index must be an integer or an integer var.") + raise VarTypeError("Index must be an integer or an integer var.") # Handle slices first. if isinstance(i, slice): @@ -668,7 +669,7 @@ def __getitem__(self, i: Any) -> Var: i._var_type, types.get_args(Union[int, str, float]) ) ): - raise TypeError( + raise VarTypeError( "Index must be one of the following types: int, str, int or str Var" ) # Get the type of the indexed var. @@ -697,7 +698,7 @@ def __getattribute__(self, name: str) -> Any: The var attribute. Raises: - AttributeError: If the attribute cannot be found, or if __getattr__ fallback should be used. + VarAttributeError: If the attribute cannot be found, or if __getattr__ fallback should be used. """ try: var_attribute = super().__getattribute__(name) @@ -708,10 +709,12 @@ def __getattribute__(self, name: str) -> Any: super().__getattribute__("_var_type"), name ) if type_ is not None: - raise AttributeError(f"{name} is being accessed through the Var.") + raise VarAttributeError( + f"{name} is being accessed through the Var." + ) # Return the attribute as-is. return var_attribute - except AttributeError: + except VarAttributeError: raise # fall back to __getattr__ anyway def __getattr__(self, name: str) -> Var: @@ -724,13 +727,13 @@ def __getattr__(self, name: str) -> Var: The var attribute. Raises: - AttributeError: If the var is wrongly annotated or can't find attribute. - TypeError: If an annotation to the var isn't provided. + VarAttributeError: If the var is wrongly annotated or can't find attribute. + VarTypeError: If an annotation to the var isn't provided. """ # Check if the attribute is one of the class fields. if not name.startswith("_"): if self._var_type == Any: - raise TypeError( + raise VarTypeError( f"You must provide an annotation for the state var `{self._var_full_name}`. Annotation cannot be `{self._var_type}`" ) from None is_optional = types.is_optional(self._var_type) @@ -744,16 +747,16 @@ def __getattr__(self, name: str) -> Var: ) if name in REPLACED_NAMES: - raise AttributeError( + raise VarAttributeError( f"Field {name!r} was renamed to {REPLACED_NAMES[name]!r}" ) - raise AttributeError( + raise VarAttributeError( f"The State var `{self._var_full_name}` has no attribute '{name}' or may have been annotated " f"wrongly." ) - raise AttributeError( + raise VarAttributeError( f"The State var has no attribute '{name}' or may have been annotated wrongly.", ) @@ -780,8 +783,8 @@ def operation( The operation result. Raises: - TypeError: If the operation between two operands is invalid. - ValueError: If flip is set to true and value of operand is not provided + VarTypeError: If the operation between two operands is invalid. + VarValueError: If flip is set to true and value of operand is not provided """ if isinstance(other, str): other = Var.create(json.dumps(other)) @@ -791,7 +794,7 @@ def operation( type_ = type_ or self._var_type if other is None and flip: - raise ValueError( + raise VarValueError( "flip_operands cannot be set to True if the value of 'other' operand is not provided" ) @@ -814,7 +817,7 @@ def get_operand_full_name(operand): types.get_base_class(right_operand._var_type), # type: ignore op, ): - raise TypeError( + raise VarTypeError( f"Unsupported Operand type(s) for {op}: `{left_operand._var_full_name}` of type {left_operand._var_type.__name__} and `{right_operand._var_full_name}` of type {right_operand._var_type.__name__}" # type: ignore ) @@ -935,10 +938,10 @@ def length(self) -> Var: A var with the absolute value. Raises: - TypeError: If the var is not a list. + VarTypeError: If the var is not a list. """ if not types._issubclass(self._var_type, List): - raise TypeError(f"Cannot get length of non-list var {self}.") + raise VarTypeError(f"Cannot get length of non-list var {self}.") return self._replace( _var_name=f"{self._var_name}.length", _var_type=int, @@ -1338,9 +1341,9 @@ def __contains__(self, _: Any) -> Var: """Override the 'in' operator to alert the user that it is not supported. Raises: - TypeError: the operation is not supported + VarTypeError: the operation is not supported """ - raise TypeError( + raise VarTypeError( "'in' operator not supported for Var types, use Var.contains() instead." ) @@ -1351,13 +1354,13 @@ def contains(self, other: Any) -> Var: other: The object to check. Raises: - TypeError: If the var is not a valid type: dict, list, tuple or str. + VarTypeError: If the var is not a valid type: dict, list, tuple or str. Returns: A var representing the contain check. """ if not (types._issubclass(self._var_type, Union[dict, list, tuple, str, set])): - raise TypeError( + raise VarTypeError( f"Var {self._var_full_name} of type {self._var_type} does not support contains check." ) method = ( @@ -1381,7 +1384,7 @@ def contains(self, other: Any) -> Var: if types._issubclass(self._var_type, str) and not types._issubclass( other._var_type, str ): - raise TypeError( + raise VarTypeError( f"'in ' requires string as left operand, not {other._var_type}" ) return self._replace( @@ -1395,13 +1398,13 @@ def reverse(self) -> Var: """Reverse a list var. Raises: - TypeError: If the var is not a list. + VarTypeError: If the var is not a list. Returns: A var with the reversed list. """ if not types._issubclass(self._var_type, list): - raise TypeError(f"Cannot reverse non-list var {self._var_full_name}.") + raise VarTypeError(f"Cannot reverse non-list var {self._var_full_name}.") return self._replace( _var_name=f"[...{self._var_full_name}].reverse()", @@ -1416,10 +1419,10 @@ def lower(self) -> Var: A var with the lowercase string. Raises: - TypeError: If the var is not a string. + VarTypeError: If the var is not a string. """ if not types._issubclass(self._var_type, str): - raise TypeError( + raise VarTypeError( f"Cannot convert non-string var {self._var_full_name} to lowercase." ) @@ -1436,10 +1439,10 @@ def upper(self) -> Var: A var with the uppercase string. Raises: - TypeError: If the var is not a string. + VarTypeError: If the var is not a string. """ if not types._issubclass(self._var_type, str): - raise TypeError( + raise VarTypeError( f"Cannot convert non-string var {self._var_full_name} to uppercase." ) @@ -1459,10 +1462,10 @@ def strip(self, other: str | Var[str] = " ") -> Var: A var with the stripped string. Raises: - TypeError: If the var is not a string. + VarTypeError: If the var is not a string. """ if not types._issubclass(self._var_type, str): - raise TypeError(f"Cannot strip non-string var {self._var_full_name}.") + raise VarTypeError(f"Cannot strip non-string var {self._var_full_name}.") other = Var.create_safe(json.dumps(other)) if isinstance(other, str) else other @@ -1482,10 +1485,10 @@ def split(self, other: str | Var[str] = " ") -> Var: A var with the list. Raises: - TypeError: If the var is not a string. + VarTypeError: If the var is not a string. """ if not types._issubclass(self._var_type, str): - raise TypeError(f"Cannot split non-string var {self._var_full_name}.") + raise VarTypeError(f"Cannot split non-string var {self._var_full_name}.") other = Var.create_safe(json.dumps(other)) if isinstance(other, str) else other @@ -1506,10 +1509,10 @@ def join(self, other: str | Var[str] | None = None) -> Var: A var with the string. Raises: - TypeError: If the var is not a list. + VarTypeError: If the var is not a list. """ if not types._issubclass(self._var_type, list): - raise TypeError(f"Cannot join non-list var {self._var_full_name}.") + raise VarTypeError(f"Cannot join non-list var {self._var_full_name}.") if other is None: other = Var.create_safe('""') @@ -1535,11 +1538,11 @@ def foreach(self, fn: Callable) -> Var: A var representing foreach operation. Raises: - TypeError: If the var is not a list. + VarTypeError: If the var is not a list. """ inner_types = get_args(self._var_type) if not inner_types: - raise TypeError( + raise VarTypeError( f"Cannot foreach over non-sequence var {self._var_full_name} of type {self._var_type}." ) arg = BaseVar( @@ -1576,25 +1579,27 @@ def range( A var representing range operation. Raises: - TypeError: If the var is not an int. + VarTypeError: If the var is not an int. """ if not isinstance(v1, Var): v1 = Var.create_safe(v1) if v1._var_type != int: - raise TypeError(f"Cannot get range on non-int var {v1._var_full_name}.") + raise VarTypeError(f"Cannot get range on non-int var {v1._var_full_name}.") if not isinstance(v2, Var): v2 = Var.create(v2) if v2 is None: v2 = Var.create_safe("undefined") elif v2._var_type != int: - raise TypeError(f"Cannot get range on non-int var {v2._var_full_name}.") + raise VarTypeError(f"Cannot get range on non-int var {v2._var_full_name}.") if not isinstance(step, Var): step = Var.create(step) if step is None: step = Var.create_safe(1) elif step._var_type != int: - raise TypeError(f"Cannot get range on non-int var {step._var_full_name}.") + raise VarTypeError( + f"Cannot get range on non-int var {step._var_full_name}." + ) return BaseVar( _var_name=f"Array.from(range({v1._var_full_name}, {v2._var_full_name}, {step._var_name}))", @@ -1929,7 +1934,7 @@ def _deps( A set of variable names accessed by the given obj. Raises: - ValueError: if the function references the get_state, parent_state, or substates attributes + VarValueError: if the function references the get_state, parent_state, or substates attributes (cannot track deps in a related state, only implicitly via parent state). """ d = set() @@ -1976,7 +1981,7 @@ def _deps( except Exception: ref_obj = None if instruction.argval in invalid_names: - raise ValueError( + raise VarValueError( f"Cached var {self._var_full_name} cannot access arbitrary state via `{instruction.argval}`." ) if callable(ref_obj): diff --git a/scripts/benchmarks/benchmark_imports.py b/scripts/benchmarks/benchmark_imports.py new file mode 100644 index 0000000000..6258434d69 --- /dev/null +++ b/scripts/benchmarks/benchmark_imports.py @@ -0,0 +1,160 @@ +"""Runs the benchmarks and inserts the results into the database.""" + +from __future__ import annotations + +import argparse +import json +import os +from datetime import datetime + +import psycopg2 + + +def extract_stats_from_json(json_file: str) -> dict: + """Extracts the stats from the JSON data and returns them as dictionaries. + + Args: + json_file: The JSON file to extract the stats data from. + + Returns: + dict: The stats for each test. + """ + with open(json_file, "r") as file: + json_data = json.load(file) + + # Load the JSON data if it is a string, otherwise assume it's already a dictionary + data = json.loads(json_data) if isinstance(json_data, str) else json_data + + result = data.get("results", [{}])[0] + return { + k: v + for k, v in result.items() + if k in ("mean", "stddev", "median", "min", "max") + } + + +def insert_benchmarking_data( + db_connection_url: str, + os_type_version: str, + python_version: str, + performance_data: dict, + commit_sha: str, + pr_title: str, + branch_name: str, + event_type: str, + actor: str, + pr_id: str, +): + """Insert the benchmarking data into the database. + + Args: + db_connection_url: The URL to connect to the database. + os_type_version: The OS type and version to insert. + python_version: The Python version to insert. + performance_data: The imports performance data to insert. + commit_sha: The commit SHA to insert. + pr_title: The PR title to insert. + branch_name: The name of the branch. + event_type: Type of github event(push, pull request, etc) + actor: Username of the user that triggered the run. + pr_id: Id of the PR. + """ + # Serialize the JSON data + simple_app_performance_json = json.dumps(performance_data) + # Get the current timestamp + current_timestamp = datetime.now() + + # Connect to the database and insert the data + with psycopg2.connect(db_connection_url) as conn, conn.cursor() as cursor: + insert_query = """ + INSERT INTO import_benchmarks (os, python_version, commit_sha, time, pr_title, branch_name, event_type, actor, performance, pr_id) + VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s); + """ + cursor.execute( + insert_query, + ( + os_type_version, + python_version, + commit_sha, + current_timestamp, + pr_title, + branch_name, + event_type, + actor, + simple_app_performance_json, + pr_id, + ), + ) + # Commit the transaction + conn.commit() + + +def main(): + """Runs the benchmarks and inserts the results.""" + # Get the commit SHA and JSON directory from the command line arguments + parser = argparse.ArgumentParser(description="Run benchmarks and process results.") + parser.add_argument( + "--os", help="The OS type and version to insert into the database." + ) + parser.add_argument( + "--python-version", help="The Python version to insert into the database." + ) + parser.add_argument( + "--commit-sha", help="The commit SHA to insert into the database." + ) + parser.add_argument( + "--benchmark-json", + help="The JSON file containing the benchmark results.", + ) + parser.add_argument( + "--db-url", + help="The URL to connect to the database.", + required=True, + ) + parser.add_argument( + "--pr-title", + help="The PR title to insert into the database.", + ) + parser.add_argument( + "--branch-name", + help="The current branch", + required=True, + ) + parser.add_argument( + "--event-type", + help="The github event type", + required=True, + ) + parser.add_argument( + "--actor", + help="Username of the user that triggered the run.", + required=True, + ) + parser.add_argument( + "--pr-id", + help="ID of the PR.", + required=True, + ) + args = parser.parse_args() + + # Get the PR title from env or the args. For the PR merge or push event, there is no PR title, leaving it empty. + pr_title = args.pr_title or os.getenv("PR_TITLE", "") + + cleaned_benchmark_results = extract_stats_from_json(args.benchmark_json) + # Insert the data into the database + insert_benchmarking_data( + db_connection_url=args.db_url, + os_type_version=args.os, + python_version=args.python_version, + performance_data=cleaned_benchmark_results, + commit_sha=args.commit_sha, + pr_title=pr_title, + branch_name=args.branch_name, + event_type=args.event_type, + actor=args.actor, + pr_id=args.pr_id, + ) + + +if __name__ == "__main__": + main() diff --git a/tests/components/core/test_foreach.py b/tests/components/core/test_foreach.py index 34e43e94d7..9691ed50e6 100644 --- a/tests/components/core/test_foreach.py +++ b/tests/components/core/test_foreach.py @@ -2,17 +2,11 @@ import pytest -from reflex.components import box, foreach, text -from reflex.components.core import Foreach +from reflex.components import box, el, foreach, text +from reflex.components.core.foreach import Foreach, ForeachRenderError, ForeachVarError from reflex.state import BaseState from reflex.vars import Var -try: - # When pydantic v2 is installed - from pydantic.v1 import ValidationError # type: ignore -except ImportError: - from pydantic import ValidationError - class ForEachState(BaseState): """A state for testing the ForEach component.""" @@ -84,12 +78,12 @@ def display_nested_color_with_shades_v2(color): def display_color_tuple(color): assert color._var_type == str - return box(text(color, "tuple")) + return box(text(color)) def display_colors_set(color): assert color._var_type == str - return box(text(color, "set")) + return box(text(color)) def display_nested_list_element(element: Var[str], index: Var[int]): @@ -100,7 +94,7 @@ def display_nested_list_element(element: Var[str], index: Var[int]): def display_color_index_tuple(color): assert color._var_type == Union[int, str] - return box(text(color, "index_tuple")) + return box(text(color)) seen_index_vars = set() @@ -215,24 +209,46 @@ def test_foreach_render(state_var, render_fn, render_dict): # Make sure the index vars are unique. arg_index = rend["arg_index"] + assert isinstance(arg_index, Var) assert arg_index._var_name not in seen_index_vars assert arg_index._var_type == int seen_index_vars.add(arg_index._var_name) def test_foreach_bad_annotations(): - """Test that the foreach component raises a TypeError if the iterable is of type Any.""" - with pytest.raises(TypeError): + """Test that the foreach component raises a ForeachVarError if the iterable is of type Any.""" + with pytest.raises(ForeachVarError): Foreach.create( - ForEachState.bad_annotation_list, # type: ignore + ForEachState.bad_annotation_list, lambda sublist: Foreach.create(sublist, lambda color: text(color)), ) def test_foreach_no_param_in_signature(): - """Test that the foreach component raises a TypeError if no parameters are passed.""" - with pytest.raises(ValidationError): + """Test that the foreach component raises a ForeachRenderError if no parameters are passed.""" + with pytest.raises(ForeachRenderError): Foreach.create( - ForEachState.colors_list, # type: ignore + ForEachState.colors_list, lambda: text("color"), ) + + +def test_foreach_too_many_params_in_signature(): + """Test that the foreach component raises a ForeachRenderError if too many parameters are passed.""" + with pytest.raises(ForeachRenderError): + Foreach.create( + ForEachState.colors_list, + lambda color, index, extra: text(color), + ) + + +def test_foreach_component_styles(): + """Test that the foreach component works with global component styles.""" + component = el.div( + foreach( + ForEachState.colors_list, + display_color, + ) + ) + component._add_style_recursive({box: {"color": "red"}}) + assert 'css={{"color": "red"}}' in str(component) diff --git a/tests/components/test_component.py b/tests/components/test_component.py index cfa8af7359..e4d7205d73 100644 --- a/tests/components/test_component.py +++ b/tests/components/test_component.py @@ -1989,3 +1989,73 @@ def add_hooks(self): assert len(imports["react"]) == 2 assert ImportVar(tag="useRef") in imports["react"] assert ImportVar(tag="useEffect") in imports["react"] + + +def test_add_style_embedded_vars(test_state: BaseState): + """Test that add_style works with embedded vars when returning a plain dict. + + Args: + test_state: A test state. + """ + v0 = Var.create_safe("parent")._replace( + merge_var_data=VarData(hooks={"useParent": None}), # type: ignore + ) + v1 = rx.color("plum", 10) + v2 = Var.create_safe("text")._replace( + merge_var_data=VarData(hooks={"useText": None}), # type: ignore + ) + + class ParentComponent(Component): + def add_style(self): + return Style( + { + "fake_parent": v0, + } + ) + + class StyledComponent(ParentComponent): + tag = "StyledComponent" + + def add_style(self): + return { + "color": v1, + "fake": v2, + "margin": f"{test_state.num}%", + } + + page = rx.vstack(StyledComponent.create()) + page._add_style_recursive(Style()) + + assert ( + "const test_state = useContext(StateContexts.test_state)" + in page._get_all_hooks_internal() + ) + assert "useText" in page._get_all_hooks_internal() + assert "useParent" in page._get_all_hooks_internal() + assert ( + str(page).count( + 'css={{"fakeParent": "parent", "color": "var(--plum-10)", "fake": "text", "margin": `${test_state.num}%`}}' + ) + == 1 + ) + + +def test_add_style_foreach(): + class StyledComponent(Component): + tag = "StyledComponent" + ix: Var[int] + + def add_style(self): + return Style({"color": "red"}) + + page = rx.vstack(rx.foreach(Var.range(3), lambda i: StyledComponent.create(i))) + page._add_style_recursive(Style()) + + # Expect only a single child of the foreach on the python side + assert len(page.children[0].children) == 1 + + # Expect the style to be added to the child of the foreach + assert 'css={{"color": "red"}}' in str(page.children[0].children[0]) + + # Expect only one instance of this CSS dict in the rendered page + assert str(page).count('css={{"color": "red"}}') == 1 diff --git a/tests/test_style.py b/tests/test_style.py index ccc7b65695..825d72a9d2 100644 --- a/tests/test_style.py +++ b/tests/test_style.py @@ -8,7 +8,7 @@ from reflex import style from reflex.components.component import evaluate_style_namespaces from reflex.style import Style -from reflex.vars import Var +from reflex.vars import Var, VarData test_style = [ ({"a": 1}, {"a": 1}), @@ -503,3 +503,25 @@ def test_evaluate_style_namespaces(): assert rx.text.__call__ not in style_dict style_dict = evaluate_style_namespaces(style_dict) # type: ignore assert rx.text.__call__ in style_dict + + +def test_style_update_with_var_data(): + """Test that .update with a Style containing VarData works.""" + red_var = Var.create_safe("red")._replace( + merge_var_data=VarData(hooks={"const red = true": None}), # type: ignore + ) + blue_var = Var.create_safe("blue", _var_is_local=False)._replace( + merge_var_data=VarData(hooks={"const blue = true": None}), # type: ignore + ) + + s1 = Style( + { + "color": red_var, + } + ) + s2 = Style() + s2.update(s1, background_color=f"{blue_var}ish") + assert s2 == {"color": "red", "backgroundColor": "`${blue}ish`"} + assert s2._var_data is not None + assert "const red = true" in s2._var_data.hooks + assert "const blue = true" in s2._var_data.hooks